An audit viewer for Claude Code. It answers a single question, reliably: which files did Claude touch, in which repo, with what diff, and in which session? — without opening an IDE, without AI chat, and without depending on git or hooks.
Status: Phase 2 complete + polish — a desktop app (Tauri 2.x) with the Rust parser as its native backend, already usable day to day on Linux (
.deb+ AppImage). Phases 0 (parser/CLI), 1 (web UI) and 2 (packaging) are complete; after Phase 2 we added 9 parser tests, a resilient watcher, zoom, a custom titlebar, active-session focus, and macOS adaptation. Phases 3 (honesty + git) and 4 (editing) are pending — details in ROADMAP.md.
When Claude Code becomes the one doing most of the changes, your job shifts to steering and reviewing: the IDE stops being where you write and is relegated to "show me what it touched." But keeping an IDE — or a heavy Electron-style Git client — open just to review diffs wastes anywhere from hundreds of MB to several GB of RAM. arrow does exactly that one part — seeing what Claude changed, repo by repo and session by session — in a lightweight, focused app.
And while the tooling space around Claude Code is crowded, nobody covers exactly this: a graphical
UI, no chat, with the repository → touched files → diff/editor hierarchy as a navigable audit
trail. The closest options are a terminal TUI (claude-file-recovery), a web chat client
(claude-code-viewer), or large GUIs oriented toward running agents (opcode, AGPL). Anthropic
closed the "recoverable edit history" feature request (#36542) as not planned — so the gap is
real, even if the margin is narrow.
There is no need to install a session-log hook. Claude Code already persists everything
needed, natively and in a structured form (verified on Claude Code v2.1.x):
| Data | Native source |
|---|---|
| Repos | The cwd field of each record (the real path; we don't decode the directory name, which is ambiguous). |
| Sessions | Each ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl. The file name is the sessionId. |
| Touched files | type:"user" records with toolUseResult.filePath (only Edit/Write/MultiEdit). |
| Diff (before/after) | toolUseResult.structuredPatch — the exact hunks {oldStart, oldLines, newStart, newLines, lines}. This is already the per-session diff. |
| "Before" / point-in-time | ~/.claude/file-history/<sessionId>/<hash>@v<n> — snapshots of the prior content. |
git diff is kept as an optional secondary view (the full working tree), and only when the repo
is a git repo: it does not attribute by author or by session, and many repos aren't git at all.
- Only what goes through
Edit/Write/MultiEditis captured. Changes made via the session'sBashcommands (sed, prettier, build,mv,rm) do not appear. The correct label is always "edits via Claude's tools", never "everything Claude did". - The
userModifiedflag signals drift (the user edited between the read and the write): it's marked with ⚠. - The JSONL format is internal, undocumented, changes between versions, and auto-deletes at ~30
days (
cleanupPeriodDays). Hence the defensive parsing: an invalid line is skipped, never crashing.
cargo build --release
# Summary: all repos -> sessions -> files (+/-)
./target/release/arrow --list
# Full diff of a repo (filters by a substring of the cwd)
./target/release/arrow --repo my-project
# A specific session (prefix of the sessionId)
./target/release/arrow --session 14385fed
# Normalized JSON (the contract the UI consumes)
./target/release/arrow --repo my-project --jsonOptions: --projects-dir <path> (defaults to ~/.claude/projects), --repo, --session,
--list, --json, and --content --file <path> [--session <id>] (emits {before, after} for a
file, for the UI's diff view).
A Vite + Svelte 5 app that consumes the parser through a local dev-server which runs the arrow
binary (in web/vite.config.ts). The diff view uses CodeMirror 6 + @codemirror/merge.
cargo build --release # the dev-server runs target/release/arrow
cd web && npm install
npm run dev # http://localhost:5173Layout: a sidebar repo → session → touched files (with +/- and ⚠ userModified), and a central
panel with the before/after diff of the selected file. Total frontend weight: ~93 KB gzip.
The same UI, packaged in Tauri 2.x: the Rust parser is the native backend (no sidecar, no
HTTP server). The Svelte UI is reused as-is; only its transport layer (web/src/lib/api.ts) detects
the environment and uses Tauri invoke() inside the app or fetch in the browser — so npm run dev keeps working for fast iteration.
# system requirements (Linux/Debian/Ubuntu/Pop!_OS), once:
sudo apt install -y libwebkit2gtk-4.1-dev libxdo-dev libayatana-appindicator3-dev librsvg2-dev
cargo install tauri-cli --version "^2" # Tauri CLI
# development: native window with frontend hot-reload
cargo tauri dev # (from the repo root)
# installer: .deb + AppImage in src-tauri/target/release/bundle/
cargo tauri buildmacOS: the app adapts to the OS on its own (native titlebar with traffic lights + correct font weight). To build the
.app/.dmg— which can only be done on a Mac — see MACOS.md.
- Native backend (
src-tauri/): twoinvokecommands —report()andcontent(file, session)— that wrap the parser library (arrow = { path = ".." }, see Architecture). The AppImage runs standalone, reading~/.claude/projectsdirectly from Rust. - Lightweight footprint: Tauri uses the system's native webview (WebKitGTK on Linux), it
does not bundle a Chromium like Electron — so the app lives on the order of ~200 MB of RAM (PSS)
in use, a fraction of an equivalent Electron desktop client (which usually runs into several
GB). The installer weighs ~77 MB (AppImage) / ~2 MB (
.deb). - Native live refresh: a
notifywatcher over~/.claude/projectsemits areport-changedevent (debounced) and the UI refreshes instantly; a slow polling fallback is kept as a backstop. - Active-session focus: the repo(s) of the active session (the one with the most recent
activity) are shown at the top, plus any repo touched in the same burst (~10 min,
BURST_WINDOWinweb/src/lib/time.ts); the rest sink into Other repos as they age relative to the active one. The green dot marks those focused repos with recent activity (the redundantlivetext badge was removed). Honest: "active session" = most recent activity on disk, not a running process (arrow cannot know the latter). - Window and zoom: a custom titlebar (
decorations:false) with minimize/maximize/close buttons, drag, and double-click to maximize — guaranteeing cross-distro controls (on GNOME/Pop!_OS the WM doesn't paint them reliably). VSCode-style UI zoom withCtrl +/−/0: native to the webview (setZoom, so it doesn't throw off CodeMirror), persistent across sessions. - Linux/WebKitGTK: the app sets
WEBKIT_DISABLE_DMABUF_RENDERER=1in itsmain()(avoids the white screen caused by DMABUF/NVIDIA); thefont-weight(+100) bug is already compensated inweb/src/app.css(font-weight: 350).
The parser lives in a library (src/lib.rs): pure functions build_report(projects_dir) and
file_content(projects_dir, file, session) + the serializable structs. Two frontends consume it:
src/main.rs (the CLI, with flags intact) and src-tauri/ (the desktop backend). Zero duplicated
logic; the same source of truth for terminal, web, and native app. The parser ships 9 unit tests
(cargo test) over fixture transcripts in a tempdir, covering the non-obvious parts: defensive
parsing, top-level transcripts only, grouping by git root, +/− counting, filtering of
~/.claude/, and recency ordering.
- Phase 0 — native parser
JSONL → repo/session/file/diff, terminal output and--json. - Phase 1 — local web UI (Vite + Svelte 5) consuming the parser: sidebar
repo → session → files(Antigravity-style) + per-file diff with CodeMirror 6 +@codemirror/merge. (Read-only; editing is Phase 4.) - Phase 2 — packaging in Tauri 2.x (
.deb+ AppImage). The Rust parser was extracted into a library (src/lib.rs) and is the native backend (no sidecar); the CLI and the app share it. Dual-mode frontend (invoke/fetch), anotifywatcher →report-changedevent for live refresh, and WebKitGTK mitigation. (The sidebar focus was later refined to "active session + ~10 min burst" with the green dot as the sole indicator; see ROADMAP.) - Phase 3 — honesty + git: a "git diff working tree" toggle,
userModifiedmarking, a point-in-time timeline reusingfile-history. - Phase 4 (deferred) — real editing with save-to-disk, GitHub integration (PRs/commits).
Operational tracking, technical debt, and a backlog of ideas (search, stats, export, shortcuts): see ROADMAP.md. The data contract lives in SPEC.md.
Rust (parser/backend) · CodeMirror 6 (UI, Phase 1+) · Tauri 2.x (packaging, Phase 2+).
On Linux/WebKitGTK you'll need to apply WEBKIT_DISABLE_DMABUF_RENDERER=1 and compensate for the
font-weight (+100) bug — both documented for Phase 2.
Contributions are welcome — bug reports, ideas, and pull requests alike. Please read CONTRIBUTING.md for how to build, test, and submit changes, and our Code of Conduct. To report a security issue, see our Security Policy. When in doubt, keep the product honest: arrow shows edits via Claude's tools, never everything Claude did.
MIT.