Final-wave cleanups from the whole-branch review (final-review.md Minor
1-5): spec §4 now says selective pruning, not "deletes the directory
every build"; spec §3.3 now describes the TypeScript-AST scan
(fail-closed Vite-order resolution, namespace imports as member access
only, the every-code-file boundary check) instead of the old
regex/chunk description; the viewer spec's historical "every command
is callable from every window" line gets a dated "closed by the
AppManifest lockdown" note; the lib.rs doc comment on
the_generated_app_manifest_matches_the_handler_list no longer claims
independence from the shared parser it actually reuses; and the vitest
command-name regex now allows digits, matching Rust's [a-z0-9_]+.
Also adds a cargo test backstop
(the_tauri_config_capability_check_runs_against_the_real_tree) that
runs build.rs's tauri-config capability check against the real
app/src-tauri tree on every `cargo test`, closing the gap where a new
tauri.<platform>.conf.json on an already-built tree only gets checked
by build.rs on a clean build.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Task 2 already closed the risk (build.rs declares a Tauri AppManifest and
gates every app command per window); this task brings the docs in line so
the threat model of record no longer claims app commands are ungated or
that any local window can call any app command.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Six tasks: shared command_census module with unit tests; the atomic
build.rs + grants + census-test commit with negative proofs; the
embedded-manifest read-back test; the vitest import-closure check; the
threat-model and CLAUDE.md rewrite; full verification and hand-off.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Five viewer commands (viewer_poll_file was added), both existing census
tests, the wrapper-import shape of the frontend, controller rulings on
CI scope / Box::leak / webviews, and a spike (§7) that reproduced every
mechanism claim against tauri-build 2.6.0 in a throwaway worktree. One
design change from the spike: stale autogenerated permissions are pruned
selectively rather than by remove_dir_all, because tauri-build emits
rerun-if-changed=permissions and wholesale regeneration would rebuild
the crate on every cargo invocation.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- 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>
- CLAUDE.md: document the viewer.html fallback trap (missing/broken Vite entry
silently serves index.html into the viewer window) and point at the Rust test
that pins it.
- Give the manual verification checklist a durable, tracked home: append it as
a markdown task list to the terminal-file-viewer design spec, including the
file-path hover key-hint wording check and the CRLF/BOM round-trip save check.
- default.json: state the app-command residual risk and the pending AppManifest
lockdown directly in the capability file's own description, not only in
CLAUDE.md, since this file is the reviewed threat model of record.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review the approved design against tauri 2.11.0, xterm 5.5.0, Vite 6.4.1
and the tree: record the verified multi-window, capability, CSP and exec
facts, choose the save mechanism (stage in /tmp, swap in as the container
user), make polling a sha256sum exec, store realpath-resolved targets, and
pin the exact viewer capability set. Add the task-by-task plan with its
parallel groups and the verification commands that work here.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Follow-up to the terminal file viewer spec. Verified against tauri 2.11.0,
tauri-build 2.6.0 and tauri-utils 2.9.0 sources: with no app manifest the
ACL skips app commands entirely; with one, every command must be granted
to the calling window, unlisted commands are denied, and deny-* is global.
The spec derives the manifest from generate_handler! in build.rs and fails
the build when a command is missing from, or duplicated across, the
capability files.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`toClaudePayload` matched `/\r?\n/`, so a bare CR that is not part of a
CRLF went through verbatim — and a bare CR *submits* in a Claude prompt
and *runs* the line in a shell, which is the terminator the function's
own contract says it never appends. A `<textarea>` cannot produce one,
but `load_in` returns whatever a hand-edited or externally written notes
file holds, so the guarantee has to cover that rather than only what the
editor can type.
`Note.pinned` is persisted and sorted on, but nothing in the app sets
it: there is no pin control and no indicator. The spec stated the
ordering rule as though pinning existed and §8 did not list it, so the
spec is amended to say `pinned` is reserved and inert in v1, and pinning
is added to the out-of-scope list. No UI is added — a user-facing
affordance does not belong in a fix wave.
Also renamed NotesPanel.test.tsx's "deletes the selected note and falls
back to another": `useNotes` is mocked in that file and the mocked list
never changes, so the fallback was never exercised. A name that claims
coverage which is absent is worse than an absent test, because it makes
the gap invisible. The real assertion now lives against the real hook in
NotesPanel.shared.test.tsx.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjL1E2JFNctUqCYotUwqqb
Seven tasks, each ending in a testable deliverable: the store, the IPC
surface, the hook, the two shared helpers, the send button, the tab, and
the dock.
Two extractions are folded in rather than left for later, both because
this feature would otherwise duplicate knowledge that is already written
down. `\x1b\r` becomes `lib/claudeInput.ts` so the hard-won comment in
`TerminalView` stays the single source of truth for a sequence that must
never be "simplified" to `\n`. The session display-name rule becomes
`lib/sessionName.ts`, which is a fix rather than a precaution: the rule is
currently written twice inside `MainTabs.tsx`, both copies local and
non-exported, and the send-target picker would have made three.
The spec is also corrected in three places against what the code actually
does. `migration_store` is a free-function module with no struct, so the
notes store is too, and the "read-modify-write under the store's Mutex"
line described a shape that file does not have — the upsert takes an
explicit process-wide write lock instead, and the read path takes none.
`useProjectSave` has no debounce; its only timer is a 2500 ms reset of the
"Saved" label. And the storage section now specifies the durable write
`migration_store` uses — fsync the file, rename, fsync the directory —
rather than `projects_store`'s bare rename, because notes are prose
nothing else holds a copy of.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjL1E2JFNctUqCYotUwqqb
Notes are discrete, addressable items with a button that puts one into a
running Claude session's prompt. That is deliberately not what
`claude_instructions` does — that field is *ambient*, merged into the
container's CLAUDE.md on every start and always in context. Nor is it a
`NOTES.md` in the workspace, which the agent can read but the user cannot,
once the container is stopped. Discrete items, fired on demand, readable
with the container down, is the gap neither of those covers.
Storage is one file per project under the app data dir, following
`migration_store.rs` rather than living on the `Project` record: that record
is rewritten on every blur by the debounced save path, so notes there would
mean the whole project list is rewritten per keystroke-batch and a note edit
could clobber a Config edit. `migration_store.rs` already documents that
reasoning for itself.
Two findings are worth more than the design they support.
**Newlines already have a verified answer.** A note body has newlines; typed
as raw keystrokes each one submits a separate prompt, so a note would arrive
as N truncated messages. `TerminalView.tsx` already sends `\x1b\r` for
Shift+Enter and its comment states those are the in-band bytes, not a guess,
with an explicit warning against simplifying to `\n` because a shell would
run the line. Send-to-agent reuses that sequence through one shared helper,
and — from the same comment — only offers `claude` sessions as targets,
since bash's readline has no binding for it and merely bells.
**The dock cannot widen the OS window.** A throwaway Tauri app was built and
run on KDE Plasma to find out, because the app has no window-geometry code to
reason from. Under XWayland every test passed exactly. Under native Wayland
the same binary asked +420 and got +600, moved the height +276 without being
asked, compounded that offset on every call, and ended reporting 5400x2900 on
a 4800x2700 monitor. Worse, `outer_position()` did not fail — it returned
`Ok(0,0)` for a window that was not at 0,0, so a "cannot determine position,
do not grow" fallback never fires. A clean failure could have been handled; a
plausible wrong answer cannot be detected from the value itself.
AppImages get XWayland because linuxdeploy-plugin-gtk forces GDK_BACKEND=x11;
the .deb and .rpm do not. The split is therefore by *packaging*, not platform
— two users on identical hardware would see different behavior. So the dock
takes space inward on every backend, which also costs nothing: the
ResizeObserver in `TerminalView.tsx` already reflows xterm and resizes the
container PTY on width change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjL1E2JFNctUqCYotUwqqb