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.
The last high-severity web gap plus a small integration bundle.
In-place save: with the File System Access API (Chromium/Edge), Save
now works like desktop. Plain Save on a tab with a retained handle
writes straight through — no picker, no download, no "(1)" copies.
First save or format change opens showSaveFilePicker AT the confirm
gesture (the worker serialization that follows can outlast the
transient-activation window; retained handles need no activation to
write). Picker cancel cancels the save like desktop; picker or write
failure falls back to the legacy download with a note; Firefox/Safari
keep the download flow untouched (feature-detected). Handles are
keyed by tab id in a thread-local registry (Message must be Send —
JsValue cannot ride in messages or App state); closing a tab drops
its handle; Save As re-picks. Dirty clears only on successful
persistence, same race guard as before.
Image paste: Ctrl+V of a screenshot inserts it as an image reference
— the clipboard reader falls back to image/* items when neither the
entity clipboard nor text has anything (precedence preserved), the
bytes enter the image source registry as pasted-<timestamp>.<ext>,
and attach reuses the IMAGE command's model-creation path verbatim.
Browser print: beforeprint snapshots the WebGL canvas into a print-
only <img> (the capture_canvas technique), so the browser's own print
menu produces the drawing instead of a blank page.
Automation capture on web: the capture op no longer calls the
synchronous GPU-map screenshot (dead on wasm); it waits one presented
frame and reads via capture_canvas — the same workaround the save-
thumbnail path uses — delivering the PNG as a download.
Two reviewers, no blockers, three majors — all fixed.
- The remembered-tab focus after a Restore-all batch fired at the
message boundary, but the LAST restore's tab inserts one async round
later (its OPFS recent-cache write precedes the insertion) and the
insertion's own activation overwrote the selection. The focus flag
now also consumes at the insertion point, so the batch focus holds
in the common all-success case.
- The service worker cached ANY successful in-scope navigation under
the shell key — a direct navigation to a font or JSON would be
served as the app shell for every offline navigation until the next
online visit. Only the scope root caches as the shell now.
- Local macOS builds broke: sha256sum does not exist there (shasum
does) and BSD sed requires an explicit -i suffix. The wrapper picks
the available hasher and uses a portable in-place edit.
Also: '/app/' gets its own web app manifest (the previous commit's
'installable' claim was true only of the marketing site, whose
start_url sits OUTSIDE the service worker's scope — an installed app
opened offline showed an error page). The README now states the
honest offline timing (assets captured on the second visit — the
first visit's preloads outrun the installing worker) and the
long-lived-window update behavior.
Session recovery: more than one pending snapshot adds a 'Restore all'
button to the Recover Drawing modal — every snapshot goes through the
exact same guarded path as a single restore (deleted only after its
own open succeeds). The open-tab list and active tab persist to
localStorage on web (open/save/close) and the remembered tab is
re-selected once a restore batch settles; the modal notes how many
drawings the session had. Nothing is auto-opened.
Deep links: ?file=<url|name> opens a drawing at boot. http(s) URLs
must end in .dwg/.dxf and are fetched with the same streaming,
64 MB-capped, 8 s-deadline reader the remote images use, then go
through the guarded open queue; cross-origin opens announce the
origin first. Bare names resolve from the browser's recent storage,
with a hint when absent. Everything else (file:, javascript:, drive
letters, non-drawing tails) is refused with a console note.
Offline PWA: the app was installable (manifest) but downloaded its
55 MB bundle on every visit. A cache-first service worker now serves
the whole bundle from Cache Storage after one online visit —
navigations network-first so updates land, hashed assets
stale-while-revalidate, non-GET/cross-origin passthrough, old caches
cleaned on activate, and the waiting-worker update flow documented.
The build wrapper stamps the cache version from the bundle hash and
hard-fails if sw.js is missing or unstamped.
Also fixes a locale group mismatch on the DATALINK pick notice
(catalog said a display group that does not exist in the ftl
sources).
Upstream issues, fixed:
#1345 — hatches plotted at draw_depth 0.0: the plot path cloned models
from the hatch cache, which bakes the depth at seed time; the canvas
re-reads the live depth map every frame, which is why the editor was
right and every export (PDF and native printing — both route through
the exporter) was wrong, with hatch-vs-hatch ties broken by HashMap
order. The plot path now re-reads the depth map per entity, and the
exporter's tie-break ranks follow the editor's own pass order
(hatch < image < wire fill < wire < text < wipeout) instead of pinning
hatches between fills and wires. Pinned by content-stream operator-
order tests (back/front hatches, overlapping hatches, interleaved
wire-between-hatches) and a scene test that fails pre-fix.
#1348 — set_properties' optimistic-concurrency compare now matches
numbers by value (10.0 == 10) through json_values_match, compatible
with the shape of upstream PR #1350; nested objects/arrays recurse.
#1349 — web automation contract: control protocol 2 (v1 clients still
accepted) adds {op:'open', name, data_base64} routed through the
guarded open queue; window.ocsControl.ready/request promise wrapper
with an ocs-ready event (web/ocs-control.js); the startup donation
prompt is suppressed once a control request is seen (opt-in pre-boot
via localStorage); docs/automation documents the whole web contract.
Also: DATALINK CSV-to-table works on web (picker + bytes through the
session registry — was std::fs, silently dead); COLORSCHEME accepts
System (prefers-color-scheme on web, AppsUseLightTheme on Windows).
Decode accounting, made honest per the fix-of-fix review: into_rgba8
allocates the RGBA destination while the native buffer is alive for
every non-Rgba8 layout (L8 5, La8 6, Rgb8 7, L16 6, La16 8 B/px
peaks — the formula now says so), GIF bills the screen+frame
coexistence at 8 B/px, and TIFF drops the 3x multipliers both ways —
tiff 0.11 splits the given budget between frame and scratch (a total
ceiling), so the old double-billing refused honest 16-bit GeoTIFFs
~2.25x early while admitting hostile chunk transients at 3x. The
image-refusal-unknown-size locale key moves to the group the catalog
always claimed (file).