fix(viewer): final-review fixes — save base from written bytes, honest poll errors, retryable first read

- write.rs: a save's new base is sha256 of the bytes written; the script's
  post-mv hash comes back as disk_hash, and a mismatch (another writer landed
  after us) shows "Changed on disk" instead of being adopted (ledger M2).
- write.rs: conflict:/gone:/read-only strings are constants with a pure
  saved_file() mapping and tests; app/src/viewer/ipcMessages.ts is the one TS
  copy and a cargo test checks it against the Rust originals.
- write.rs: the comment now says the in-place `cat >` fallback follows a
  planted symlink, and why that is accepted (runs as claude).
- poll.rs: a file deleted between `test -f` and `sha256sum` reads as gone.
- viewerState/EditorPane: poll_failed carries its message; only the
  "Start the project before" refusal reads as Container not running, anything
  else gets its own banner and leaves Save enabled.
- EditorPane: a failed first read shows Retry and is retried by the poll.
- spec §1: refused OSC 8 targets keep the refusal card (Task 9 ruling).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 21:52:43 -07:00
co-authored by Claude Opus 5.5
parent 398281c8b5
commit bf2291089a
12 changed files with 535 additions and 73 deletions
@@ -115,7 +115,10 @@ AppManifest spec, `2026-09-22-app-manifest-lockdown-design.md`).
`activate`/`hover`, including unparseable ones and `javascript:`; so the handler parses with
`new URL()` itself: `file:``onOpenFile(pathname decoded, no line)`, `http(s):` → the
existing `sanitizeRelayUrl``openUrlExternal` path unchanged, anything else → refused
(`console.warn`, no hover card). The hover card for a `file:` target shows the path and "Open
(`console.warn`, nothing opened). A refused target (`javascript:`, any other scheme,
unparseable text) keeps the existing refusal card ("This link will not be opened — it failed
the URL safety check"), which never echoes the target into the DOM; this is the ruled
behaviour (Task 9), not a new hover card. The hover card for a `file:` target shows the path and "Open
in viewer". The `WebLinksAddon` comment that describes the old `allowNonHttpProtocols`
behaviour is updated, not left stale.
- Activation uses the same click gating as web links (`opensOnClick`: no selection drag,
@@ -330,7 +333,8 @@ click, editable, CodeMirror 6, probe-roots resolution, 2 s polling, no autosave,
Focus/title/unminimize are done from Rust, so no `core:window:allow-set-*` grants.
5. **OSC 8 with `allowNonHttpProtocols: true` receives unparseable and `javascript:` targets
(§1).** The handler now parses and dispatches itself, and refuses everything but `file:` and
`http(s):` before drawing a hover card. The stale comment in the `WebLinksAddon` branch is
`http(s):` before drawing a file or web hover card; a refused target gets only the existing
refusal card, which never echoes the target. The stale comment in the `WebLinksAddon` branch is
updated as part of the change.
6. **Wrapped-row joining is re-implemented (§1).** `WebLinksAddon`'s `LinkComputer` is not
exported from the built package and `urlDetector.ts` never touches the buffer.