readCanvases sorts file canvases newest-first by updatedAt. The
canvases route then merged them via Map.set in iteration order, which
overwrites earlier (newer) entries with later (older) ones. If an
agent wrote `result-v1.json` and `result-v2.json` with the same canvas
id, the user would see the OLDER payload — the exact opposite of the
intended last-write-wins behavior.
Fix: extract the merge into a pure helper (merge.ts) with explicit
first-write-wins semantics. Synthesized seeds the map first (preserves
the pass-9 reservation that core- prefix wins on collision); file
canvases skip ids already present, so the newest wins.
Adds 5 regression tests covering: empty input, synth + file mix,
duplicate-id newest-wins (the regression), synth-vs-file collision,
and final sort order.
Also adds a "Why no HTML canvas type?" section to the feature writeup
HTML — the most-asked design question, answered honestly so reviewers
don't have to ask. Covers the trust-hierarchy-inversion argument
(agents are lowest-trust + LLMs are prompt-injectable + same-origin
HTML = supervisor JS execution), the leaky-sanitizer argument, and
the legitimate sandboxed-iframe path for v0.5+.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>