From 95e2272f893589fb01fbc198155fa948c8235cbd Mon Sep 17 00:00:00 2001 From: Randalix Date: Fri, 23 Oct 2026 14:00:48 +0200 Subject: [PATCH 001/101] feat(edit-history): edit history panel for local claude sessions Part 1 of #643 (local sessions only; remote follows as its own PR). An opt-in, per-device header button opens a panel with the active session's edits, newest first: every Edit/Write/MultiEdit/NotebookEdit of the main agent and its subagents with its diff, plus images a Bash call rendered or the agent sent. Remote, Docker and non-claude sessions answer `unsupported`. - Reads only on request: `?sync=1` when the panel opens; while it is open on that session it re-reads after an edit-capable `transcript:tool_end` or a `transcript:complete` from the existing transcript watcher. No hook, timer, second watcher or new SSE event; a wiring guard pins it. - Main transcript: the path the transcript watcher follows (hook-reported), else `.jsonl` under the session's own CLAUDE_CONFIG_DIR override or the server's config dir. A config dir set outside Codeman is found with the next hook; until then the panel says so. - Store: dataPath('edit-history')/.json, 0600 via tmp+rename, capped at 500 entries and 4 MiB of diff text per session, deleted with the session, blocked in isSensitivePath, never fed to /api/search. Every route goes through findSessionOrFail with req. - Images only through existing file surfaces: file-raw inside the workspace, the attachment route with notify:false outside it. - textContent rendering, bounded byte-offset reads, marker-class header button, hidden on phones (locked in the mobile policy test). Docs: Working-With-Files, Settings-Reference, architecture invariants, API reference, CLAUDE.md key pattern. Co-Authored-By: Claude Opus 5.5 (1M context) --- CLAUDE.md | 2 + config/test-suites.ts | 1 + docs/api-reference.md | 12 + docs/architecture-invariants.md | 16 + docs/wiki/Settings-Reference.md | 8 +- docs/wiki/Working-With-Files.md | 46 ++ src/edit-history-local-source.ts | 218 ++++++++ src/edit-history-parse.ts | 330 +++++++++++ src/edit-history-store.ts | 451 +++++++++++++++ src/session.ts | 9 +- src/web/edit-history-sync.ts | 307 +++++++++++ src/web/public/app.js | 6 + src/web/public/edit-history-ui.js | 643 ++++++++++++++++++++++ src/web/public/i18n.js | 33 ++ src/web/public/index.html | 25 + src/web/public/mobile.css | 2 + src/web/public/settings-ui.js | 8 +- src/web/public/styles.css | 291 ++++++++++ src/web/routes/edit-history-routes.ts | 130 +++++ src/web/routes/index.ts | 1 + src/web/sensitive-path.ts | 4 + src/web/server.ts | 31 ++ test/edit-history-local-source.test.ts | 134 +++++ test/edit-history-parse.test.ts | 355 ++++++++++++ test/edit-history-store.test.ts | 445 +++++++++++++++ test/edit-history-sync.test.ts | 379 +++++++++++++ test/edit-history.browser.test.ts | 468 ++++++++++++++++ test/mobile-header-buttons-policy.test.ts | 10 +- test/mocks/mock-session.ts | 7 + test/routes/edit-history-routes.test.ts | 261 +++++++++ test/sensitive-path.test.ts | 4 + 31 files changed, 4631 insertions(+), 6 deletions(-) create mode 100644 src/edit-history-local-source.ts create mode 100644 src/edit-history-parse.ts create mode 100644 src/edit-history-store.ts create mode 100644 src/web/edit-history-sync.ts create mode 100644 src/web/public/edit-history-ui.js create mode 100644 src/web/routes/edit-history-routes.ts create mode 100644 test/edit-history-local-source.test.ts create mode 100644 test/edit-history-parse.test.ts create mode 100644 test/edit-history-store.test.ts create mode 100644 test/edit-history-sync.test.ts create mode 100644 test/edit-history.browser.test.ts create mode 100644 test/routes/edit-history-routes.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index 02704d0f..15dd8e25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -232,6 +232,8 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph **Git status indicator** (`showGitStatus`, per-device, default OFF; `src/git-workspace-status.ts`, `routes/git-status-routes.ts`, `git-status-ui.js`): the bottom-bar indicator and its panel read `GET /api/sessions/:id/git-status` and `/git-diff`, read-only and offline (it never fetches and never writes). ⚠️ git never runs on a repository at or inside a Docker case workspace (walk-up, scan and diff alike, and a cached repository list is re-checked against the CURRENT Docker roots), since a container could plant a clean filter that runs on the host; remote and Docker sessions answer `unsupported`. ⚠️ `git-diff` takes `repo`/`path`/`kind` only as keys matched against the workspace's own repository list (`findWorkspaceRepo()`) and that repository's current status, never as paths. ⚠️ Keep `--no-optional-locks`, `core.fsmonitor=false` and `log.showSignature=false` on every call and `--no-ext-diff --no-textconv` on diffs; clean filters still run, which is why the Docker rule exists. ⚠️ An enclosing repository at `$HOME` or above is ignored (`isUnrelatedAncestor()`), and is identified by one cached `rev-parse` before any full status runs. Everything git supplies renders via `textContent`. Tests: `test/git-workspace-status.test.ts`, `test/routes/git-status-routes.test.ts`, and `test/git-status.browser.test.ts` (browser suite, not in the gate). +**Edit history panel** (`showEditHistory`, per-device, default OFF; `src/edit-history-parse.ts`, `edit-history-store.ts`, `edit-history-local-source.ts`, `web/edit-history-sync.ts`, `routes/edit-history-routes.ts`, `edit-history-ui.js`): which files a LOCAL claude session edited (main agent and subagents, labelled), each edit's diff, and images a Bash call produced. Read from the session's transcripts by byte offset, never from git. `GET /api/sessions/:id/edits[?sync=1]`, `GET …/edits/:entryId` (unified diff), `POST …/edits/older`. ⚠️ **Reads only on request:** the panel sends `?sync=1` when it opens and, while it is open on that session, after an edit-capable `transcript:tool_end` or a `transcript:complete` (debounced). The server never syncs on its own: no hook, no timer, no second watcher; `server.ts` hands the reader to the routes and only calls `remove`/`flushAll` itself (wiring-guard test). The main transcript is the path the session's `TranscriptWatcher` follows (reported by the hooks), else `.jsonl` under the session's `CLAUDE_CONFIG_DIR` override or the server's (`resolveClaudeTranscript`); a config dir set outside Codeman is found with the next hook, until then `unavailable` / `no-transcript`. ⚠️ The store holds every diff, secrets included: `dataPath('edit-history')/.json`, 0600 via tmp + rename, capped per session (500 entries, 4 MiB diff text), deleted with the session, blocked in `isSensitivePath`, never fed to `/api/search`. Images use the existing file surfaces only (workspace `file-raw`; outside it the attachment route with `notify: false` and its extension allowlist); no route here serves a file. A Bash image counts only when the file's mtime lies in the call's window (±2 s) and no entry already covers that file version. Remote, Docker and non-claude sessions answer `unsupported`. Tests: `test/edit-history-*.test.ts`, `test/routes/edit-history-routes.test.ts`, `test/edit-history.browser.test.ts` (browser suite). + **Raw file bodies are streamed and range-aware**: `file-raw`, the attachments `/raw` route, `GET /api/download` and the rendered-HTML route `GET /html-view//...` share `sendFileBody()`, advertise `Accept-Ranges: bytes` and answer `Range` with `206` + `Content-Range` (single-range, parser in `src/web/http-range.ts`); without it `