fix: close the adversarial findings on the save/paste/merge wave

Three reviewers over the in-place save, the paste/print/capture
bundle, and the upstream merge: two blockers, six majors — all fixed.

In-place save (blocker + 2 majors):
- A stale retained handle could receive bytes serialized for a
  DIFFERENT format (Save As .dwg->.dxf whose re-pick failed wrote DXF
  bytes through the .dwg handle) or silently save in place while the
  user was told a copy was downloading. The serialized format now
  travels to completion (tab-keyed pending map), the write requires a
  matching handle extension AND a live tab, and a failed re-pick
  drops the old handle before the download fallback.
- Discarding an unsaved tab leaked its handle, and a serialization
  still in flight could then overwrite the real file the user just
  declined to save. Discard drops the handle; a tab-gone completion
  downloads instead of writing.
- Overlapping saves had no guard (native has one): a wasm in-flight
  slot refuses a second save with the same localized message, cleared
  on every exit path.

Paste/print/capture (blocker + 2 majors):
- pasted_image_name built its timestamp from Date::new(0) — a
  CONSTANT name, so every paste overwrote every earlier paste's
  image (the registry is last-wins; the first entity then displayed
  the second paste's pixels). Real clock, milliseconds, and a
  collision-suffixed unique name now, with the pure logic tested.
- The beforeprint snapshot read the canvas outside the frame — blank
  on WebGL2 (no preserveDrawingBuffer). The print image is now kept
  WARM: refreshed on redraw frames (throttled to 2 s, skipped when
  hidden), so it is ready before the browser ever prints.
- A capture op in a hidden tab wedged the ENTIRE automation channel
  (frames never fire, the pending op blocked every settle). Intake
  now fails fast when hidden, and a 5 s watchdog expires a parked
  capture with a clear error.

Merge residue (3 majors):
- The 9 upstream XREF-bind tests lost in resolution are restored
  verbatim (the bind feature shipped untested).
- The XREF manager panel reported registry-loaded xrefs as NotFound
  on web — stat now consults the registry first (wasm), with a
  regression test.
- Snap: our endpoint map gained the closing-segment wraparound pair
  (survey-scale closed polylines kept cm-level precision on the last
  edge), and the 3D-snap gate no longer suppresses the 2D edge wire
  when the F4 system is disabled (solid edges were snappable by
  NEITHER system); the setting is mirrored to the scene with
  invalidation, and the test that encoded the old bug now pins the
  fallback.
This commit is contained in:
Afonsop committed 2026-09-21 15:23:37 +01:00
1 parent 55e3a6a9e2
commit da9504604e
15 files changed
+1071 -84

No files matched your search

+37
View File
@@ -798,6 +798,11 @@ pub(super) struct OpenCADStudio {
main_window: Option<window::Id>,
/// Hides drawing UI overlays for one thumbnail capture frame.
thumbnail_capture_clean: bool,
/// Web: when the print-only snapshot `<img>` was last refreshed (see
/// [`crate::sys::refresh_print_snapshot`]). `None` = never captured, so
/// the first presented frame fills it.
#[cfg(target_arch = "wasm32")]
print_snapshot_at: Option<Instant>,
#[cfg(not(target_arch = "wasm32"))]
pending_native_thumbnail_save: Option<PendingNativeThumbnailSave>,
#[cfg(target_arch = "wasm32")]
@@ -2099,6 +2104,12 @@ pub enum Message {
/// directly and never emits this.
#[cfg(target_arch = "wasm32")]
ControlCaptureFrame,
/// Web: a frame was presented, so the print-only snapshot `<img>` can be
/// refreshed from the still-readable canvas (see
/// [`crate::sys::refresh_print_snapshot`]). The handler throttles to one
/// capture every 2 s; native never emits this.
#[cfg(target_arch = "wasm32")]
PrintSnapshotFrame,
ControlToggle,
Tick(Instant),
/// Periodic drain of plugin-to-host requests that arrived outside a host
@@ -4264,6 +4275,8 @@ impl OpenCADStudio {
pending_click_snap: None,
main_window: None,
thumbnail_capture_clean: false,
#[cfg(target_arch = "wasm32")]
print_snapshot_at: None,
#[cfg(not(target_arch = "wasm32"))]
pending_native_thumbnail_save: None,
#[cfg(target_arch = "wasm32")]
@@ -4644,6 +4657,30 @@ impl OpenCADStudio {
app
}
/// Web: keep the print-only snapshot `<img>` always warm
/// ([`crate::sys::refresh_print_snapshot`]). Runs on every presented
/// frame but captures at most once every 2 s, and only while the page is
/// visible — a hidden tab presents no frames anyway, and skipping keeps a
/// backgrounded app from burning encode cycles it cannot need. Frames
/// only arrive while the window is actually redrawing, so an idle app
/// performs no captures at all; the printout may thus lag the canvas by
/// up to the throttle window.
#[cfg(target_arch = "wasm32")]
fn on_print_snapshot_frame(&mut self) {
const THROTTLE: std::time::Duration = std::time::Duration::from_secs(2);
if self
.print_snapshot_at
.is_some_and(|at| at.elapsed() < THROTTLE)
{
return;
}
if !crate::sys::page_visible() {
return;
}
self.print_snapshot_at = Some(Instant::now());
crate::sys::refresh_print_snapshot();
}
/// Install `cmd` as the active interactive command for tab `tab`.
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(crate) fn set_active_command(