web: harden drop-open and autosave after adversarial review

Review of the drop-open and autosave commits surfaced one blocker and
five majors; all are fixed here, and two claims in the autosave commit
message are corrected by this one: tab close does NOT clear a snapshot
(matches native .sv$ semantics — saving, restoring, discarding, exit,
and the capped boot scan do), and serialization runs on the main
thread between awaits, not off the paint path.

- The restore prompt can no longer appear with no entries (empty
  "Recover Drawing" modal on every boot once the autosave dir exists).
- Restore no longer consumes the snapshot before the open succeeds:
  the recovered drawing goes through the guarded drop queue, and the
  snapshot is deleted only after the tab opens; cancel or parse
  failure keeps the only copy.
- Saving during an autosave round defers its snapshot cleanup until
  the round finishes, so a stale round can no longer resurrect a
  snapshot newer than the save.
- The boot scan no longer deletes snapshots by mtime comparison
  (re-opening a drawing refreshes the recent copy's mtime, which
  silently destroyed wanted snapshots); GC is now a 16-entry cap with
  oldest-first eviction.
- OPFS write failures abort the writable stream (a leaked lock could
  stall the same-key writer and disable autosave for the session), and
  DOMException errors now carry their name/message.
- Empty (crash-window) snapshots are never offered; an unreadable one
  warns and stays discardable instead of looping forever.
- Drop-open polish: reads start inside the handler and complete in
  drop order (legacy WebKit safety, deterministic multi-file order),
  dragenter is canceled too, the install guard only latches on
  success, non-drawing drops don't steal propagation, a lock-poison
  panic can't cross into JS, and re-dropping an open drawing no
  longer risks clobbering an in-flight open (it re-queues).
- The model-space sheet floor is per-axis (a thin 8x400 mm profile
  keeps its length) and centers content on the enlarged page.

(cherry picked from commit 3edbc5b7c8)
This commit is contained in:
Afonsop authored and Hakan Seven committed 2026-10-12 12:02:04 +03:00
1 parent 12fd04790f
commit 3023cefea4
7 files changed
+360 -96

No files matched your search

+9 -2
View File
@@ -26,14 +26,21 @@ impl OpenCADStudio {
/// Queue the web crash-recovery prompt behind whatever startup modal is
/// already showing (donation, GPU warning…); the after-message hook in
/// `update` surfaces it as soon as the slot is free.
/// `update` surfaces it as soon as the slot is free. No-op when nothing
/// is pending: once the autosave OPFS directory merely exists (even with
/// no entries), queueing unconditionally would show an empty
/// "Recover Drawing" modal on every boot.
#[cfg(target_arch = "wasm32")]
pub(super) fn queue_autosave_restore_prompt(&mut self) {
if self.web_autosave_pending.is_empty() {
return;
}
if !self
.pending_startup_modals
.contains(&ModalKind::AutosaveRestore)
{
self.pending_startup_modals.push_back(ModalKind::AutosaveRestore);
self.pending_startup_modals
.push_back(ModalKind::AutosaveRestore);
}
self.show_next_startup_modal();
}