diff --git a/docs/architecture-invariants.md b/docs/architecture-invariants.md index 2ebae840..3f56d670 100644 --- a/docs/architecture-invariants.md +++ b/docs/architecture-invariants.md @@ -1039,7 +1039,7 @@ Further detail: in the Settings section order, the version and the updater lead ⚠️ **`Session.lastSubmitAt` is persisted state, not a runtime counter.** `start()` reassigns `_claudeSessionId = resumeSessionId || id` on every launch — including the re-attach path for a mux session that survived the restart — so a recovered pane always points the viewer at its *launch* conversation, even when the CLI moved on via `/clear` hours earlier. The submit anchor is the only thing that can correct that without user input, so it round-trips through `SessionState.lastSubmitAt` and is restored in `restoreMuxSessions()`. Drop it from `toState()` and recovered panes silently show the pre-`/clear` transcript until the user types again. Restoring a *stale* anchor is safe: the resolver's staleness guard rejects any candidate transcript older than the one the pane is currently on, which is exactly the shape of a respawn into a fresh conversation. ⚠️ **The Claude viewer emits one message per model message and groups them with `turn`; it never concatenates them.** A Claude transcript is an append-only event log, so one logical exchange spans many rows: tool-result rows, meta/image/skill rows, compact summaries, task/team notifications, sidechains, replayed assistant snapshots. Rendering a card per *row* was the original bug (#169) — but the fix overshot to one card per *human turn*, which fused up to 74 distinct model messages into a single card and reported it as one message. One assistant row IS one whole model message: measured across a real `~/.claude/projects` (CLI 2.1.220-2.1.251) no assistant row carries more than one content block and no `message.id` carries more than one text block, so there was never anything to reassemble, and no adjacent pair of assistant rows continues a table, a list, or an open code fence. Each row is therefore its own message carrying `{kind, label, role, text, timestamp, turn}`; the frontend renders a same-role run inside one `turn` as badge-less continuation segments (`.rv-msg-cont`), which is what keeps a p90 of 11 messages per turn from reading as card spam. ⚠️ **A prompt typed while Claude is working is recorded ONLY as an `attachment/queued_command` row** — the CLI never re-emits it as a `user` row — so reading only `user` rows lost 162 of 353 user cards on that corpus AND lost the turn boundary each one carries, which is what let an assistant run fuse in the first place. Take it only when `attachment.origin.kind === 'human'` and `commandMode === 'prompt'`; the CLI's own queue entries (`commandMode: 'task-notification'`) carry no `origin` key at all. The shape is not a documented CLI contract, so every field check must fail closed. ⚠️ **`data.text` (no `?context=full`) is frozen on the last assistant row and must never be derived from `messages.at(-1)`** — agent pollers hash it (`skills/codeman/preamble.sh`), and the last message can be the user's own queued prompt. The BRIEF view therefore does not render `text`: it asks for `?context=turn`, which returns the assistant messages of the last ANSWERED turn (`selectLastAnsweredTurn()` in `src/web/response-viewer-transcript.ts`: the highest `turn` that has an assistant row, so a prompt queued after the answer does not blank the view), and renders them with the same numeric-`turn` continuation gate as the full view. `text` alone was the final row of a median-3-row turn — a "Done." tail with the substance in the rows before it. Readers that emit no `turn` (Codex, the pane parser, an older server) return `text` only, and the brief view falls back to one card for them. Replayed assistant snapshots are still deduped, and the tool/task/skill/compact/team metadata filtering is unchanged. Related: a recovered `restored-` tmux placeholder carries a **stale cwd**, so transcript lookup by working directory finds nothing; it rebinds to the matching top-level Claude transcript UUID instead when that match is unambiguous. Tests: `test/routes/session-routes-claude-last-response.test.ts`, `test/response-viewer-turn-segments.test.ts`. Purely client-side (no `renderIndexHtml` step): the template ships with `btn-response-viewer-header--hidden` and `applyHeaderVisibilitySettings()` (settings-ui.js) toggles it after settings load. Hiding must go through that marker class — the base rule is `display:inline-flex !important`, so an inline style can't override it. `showResponseViewer` is in the `displayKeys` per-device set (settings-ui.js), so it does NOT sync across devices. -**File Viewer button** (header, 1.4.1) is **shown by default on desktop** since `211f3c0` (post-1.8.0): toggle under App Settings → Header & Panels → Header buttons → File Viewer (`showFileViewerButton`, in the per-device `displayKeys` set, fallback default `true`). Purely client-side like the response viewer: the template now ships the button VISIBLE (no `--hidden` class) and `applyHeaderVisibilitySettings()` toggles the `btn-file-viewer--hidden` marker class after settings load; phones still hide it via mobile.css. The button toggles the file-browser panel open/closed without opening the settings modal (`panels-ui.js`). The same commit set the **default desktop header** to WS/CPU/MEM + File Viewer + gear: the token-count chip (`showTokenCount`, no settings-UI toggle) and the lifecycle-log button (`showLifecycleLog`) both default **OFF** now (templates ship them hidden; stored prefs still honored). The plan-usage chip default is unchanged (opt-in, see Plan-usage chip). The **Cron toolbar button** joined the same opt-in pattern in 1.6.0: template ships `btn-cron--hidden`, `applyHeaderVisibilitySettings()` toggles it via the per-device `showCronButton` setting (default OFF, App Settings → Header & Panels → Scheduling); cron jobs themselves are unaffected. +**File Viewer button** (header, 1.4.1) is **shown by default on desktop** since `211f3c0` (post-1.8.0): toggle under App Settings → Header & Panels → Header buttons → File Viewer (`showFileViewerButton`, in the per-device `displayKeys` set, fallback default `true`). Purely client-side like the response viewer: the template now ships the button VISIBLE (no `--hidden` class) and `applyHeaderVisibilitySettings()` toggles the `btn-file-viewer--hidden` marker class after settings load. **On a phone it is opt-in (#759):** the handheld branch of `getDefaultSettings()` defaults it OFF, `html.mobile-init` in `mobile.css` hides it until the settings have been applied (the template ships it visible, so it would flash otherwise), and switching it on in App Settings on that phone shows it there with a 44px touch target. `mobile.css` no longer hides it at phone width, so that default and the startup rule are the whole lock; `test/mobile-header-buttons-policy.test.ts` pins both in the gate, and `test/mobile/file-browser.test.ts` drives the real page at 390px. The setting never reaches the server (stripped from the PUT, absent from the schema), so a desktop cannot switch it on for a phone. ⚠️ A device that is not a handheld takes the desktop default at any width, so a desktop window or an iPad split view narrower than 600px now shows the button too. The button toggles the file-browser panel open/closed without opening the settings modal (`panels-ui.js`). The same commit set the **default desktop header** to WS/CPU/MEM + File Viewer + gear: the token-count chip (`showTokenCount`, no settings-UI toggle) and the lifecycle-log button (`showLifecycleLog`) both default **OFF** now (templates ship them hidden; stored prefs still honored). The plan-usage chip default is unchanged (opt-in, see Plan-usage chip). The **Cron toolbar button** joined the same opt-in pattern in 1.6.0: template ships `btn-cron--hidden`, `applyHeaderVisibilitySettings()` toggles it via the per-device `showCronButton` setting (default OFF, App Settings → Header & Panels → Scheduling); cron jobs themselves are unaffected. ### Session list layout (header strip vs. left sidebar) diff --git a/docs/file-viewer-edit-plan.md b/docs/file-viewer-edit-plan.md index decfe515..91868043 100644 --- a/docs/file-viewer-edit-plan.md +++ b/docs/file-viewer-edit-plan.md @@ -45,8 +45,9 @@ target escapes the workspace is already rejected. Ownership is `findSessionOrFai - CSS: `src/web/public/styles.css:9320-9430`. Overlay `z-index: 2000`, window `80vw/80vh`, capped `900x700`. There are **no `.file-preview-*` rules in `mobile.css` at all**. -**Reachability on phones.** The header File Viewer button is hidden below 430px -(`mobile.css:482`, locked by `KNOWN_PHONE_HIDDEN` in `test/mobile-header-buttons-policy.test.ts`), so on a +**Reachability on phones.** When this plan was written the header File Viewer button was hidden below 430px +(`mobile.css:482`, locked by `KNOWN_PHONE_HIDDEN` in `test/mobile-header-buttons-policy.test.ts`); since +#759 it shows on a phone once switched on there, and stays off by default. Without it, on a phone the preview overlay is reached through: 1. an attachment card's **Preview** button (`panels-ui.js:3451`), which is exactly the "agent just wrote a diff --git a/docs/wiki/Mobile-Guide.md b/docs/wiki/Mobile-Guide.md index d494864b..3b6360a5 100644 --- a/docs/wiki/Mobile-Guide.md +++ b/docs/wiki/Mobile-Guide.md @@ -191,12 +191,14 @@ last answer as scrollable text instead, with a **More** button for additional co Persian, Arabic and Hebrew answers read right to left there. The [File Viewer](Working-With-Files) works on phones too, including edit mode, which is -enough to fix a typo an agent introduced while you are away from your desk. +enough to fix a typo an agent introduced while you are away from your desk. Its header button +is off on a phone until you turn on **File Viewer** under **App Settings → Header & Panels** +on that phone. ## What is deliberately not on phones - Extra header buttons. New header controls are kept off phones by policy, with a test that - enforces it. + enforces it. The one exception is File Viewer, which you can switch on (see above). - The Approvals bell. Phones get the NEEDS YOU strips on the home screen instead. - The desktop home tab rail, which needs a wide window. - Lineage lines, which are a desktop overlay. diff --git a/docs/wiki/The-Dashboard.md b/docs/wiki/The-Dashboard.md index 445865f4..59d6e22c 100644 --- a/docs/wiki/The-Dashboard.md +++ b/docs/wiki/The-Dashboard.md @@ -154,7 +154,7 @@ The right side of the header. Almost all of these are off until you enable them | Connection dot | Always on | SSE connection health. Green is connected. | | Font size `-` / `+` | Always on | `Ctrl +` / `Ctrl -` do the same. | | CPU / MEM | On | Server resource use. Drawn as a compact pill by default; see Header Stats Style below. | -| File Viewer | On | Toggles the file browser panel. | +| File Viewer | On, off on phones | Toggles the file browser panel. | | Settings gear | Always on | App Settings. | | Plan usage chip | On, desktop only | Live subscription usage: Claude (needs its telemetry exporter, which the same setting installs), Codex limits and the Copilot monthly quota. | | Session Manager | Off | The full session list, live and historical. | diff --git a/docs/wiki/Working-With-Files.md b/docs/wiki/Working-With-Files.md index 8ea81756..10aeff5d 100644 --- a/docs/wiki/Working-With-Files.md +++ b/docs/wiki/Working-With-Files.md @@ -8,7 +8,8 @@ until you get home. A panel that browses the active session's working directory, or the git worktree its agent moved into ([Git Worktrees](Git-Worktrees#when-an-agent-moves-into-a-worktree)). Its header -button is on by default; if it is missing, re-enable it in **App Settings → Header & Panels**. +button is on by default on a desktop and off on a phone. The setting is per device: switch it +in **App Settings → Header & Panels** on the device you want it on. The panel and its search list up to 5,000 files and folders per load, shallow levels first and taking turns between folders, so one huge folder cannot use up that limit before the ones after it, diff --git a/src/web/CLAUDE.md b/src/web/CLAUDE.md index 7379c637..62d14ba4 100644 --- a/src/web/CLAUDE.md +++ b/src/web/CLAUDE.md @@ -60,7 +60,7 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L **Settings surface** (`#appSettingsModal` + `#sessionOptionsModal` + `#createCaseModal`): one `set-*` language shared through a single `:is(...)` id scope in styles.css. App Settings' rail is a table of contents over ONE scrolling document (`switchSettingsTab` scrolls); Session Options and Add Case really switch (`switchOptionsTab` / `switchCaseModalTab`), and their larger per-modal size blocks are the design, not drift. ⚠️ **The load/save contract is `getElementById` by id**: renaming or dropping a control id silently stops it loading or saving. ⚠️ The Session Options "Session" entry still keys off `context` (label-only rename). ⚠️ Add Case keeps its legacy `.form-row` markup via an adapter; every `
` there needs `.set-adv-chev` plus both marker suppressions. ⚠️ Model cards and the effort segment are views over hidden `