- src/http/history.ts: listSessions() reads ~/.claude/projects/*/*.jsonl
(mtime-sorted, top 50), parseSessionMeta() extracts cwd + first user prompt
(pure, 4 unit tests); GET /sessions
- terminal-session: opts.initialInput (typed ~700ms after the shell is ready)
- tabs.newTabForResume(cwd,id): new tab in the project dir running
'claude --resume <id>'
- public/history.ts: 🕘 panel listing sessions (project · time · preview) with
Resume buttons
- Verified: /sessions returns 44 real sessions; panel lists project/time/preview.
216 tests green.
The tab connection dot (green/amber/red) already shows connection state, so the
'[terminal] Connecting…/Connected' and 'Disconnected — reconnecting' lines just
cluttered the scrollback. Rely on the dot; keep only the actionable exit message.
- title-util.cwdFromOsc7: parse cwd from OSC 7 (file:// URL); 4 unit tests
- terminal-session: register OSC 7 handler → cwd getter; opts.cwd → sent in the
attach frame on a fresh session
- protocol/types: ClientMessage attach.cwd (absolute path); validated
- session/manager/server: thread cwd → createSession spawn cwd (defaults homeDir)
- tabs: '+' (newTab) opens in the active tab's reported cwd, falls back to home
- Verified: server attach.cwd spawns there; cd /private/tmp → + → new tab pwd is
/private/tmp. 212 tests green.
- M3: settings.ts (⚙ panel: dark/light/solarized theme + font size A-/A+),
persisted to localStorage; terminal-session.applyTheme; TabApp.applySettings
applies to all + new tabs
- M7: dashboard.ts (▦ overlay listing every tab with connection dot + folder +
Claude status; click a row to focus; live re-render while open);
TabApp.snapshot()/focusTab()
- Browser-verified: light theme renders + persists; dashboard lists tabs and
focuses on click. 208 tests green, typechecks + build ok.
- types/protocol: ClientMessage approve/reject; ServerMessage status.pending
- server: POST /hook/permission HELD until the client decides; approve/reject
ws messages resolve it with {hookSpecificOutput.decision.behavior allow|deny};
5-min timeout + release-on-disconnect fall back to Claude's own prompt
- manager.handleHookEvent threads pending flag
- setup-hooks: PermissionRequest uses the held curl (writes decision to stdout);
status events stay fire-and-forget
- FE: terminal-session approve()/reject() + pending state; tabs approval banner
(Approve/Reject) for the active tab's held request
- Tests: protocol approve/reject; integration ⑧ held→approve→allow. 207 green.
- Browser-verified: banner 'Claude wants to use Bash' → Approve → resolves allow.
- title-util.ts: folderFromTitle() parses 'user@host:~/path' → last folder
segment (home stays ~); pure, dependency-free, 7 unit tests
- terminal-session.ts: onTitleChange → folderFromTitle so the tab auto-names
to the cwd folder and updates live on cd
- Browser-verified: ~ → cd project → 'web-terminal' → cd docs → 'docs' → cd ~ → '~'.
198 tests green, typecheck + build ok.
- terminal-session.ts: track SessionStatus (connecting/connected/reconnecting/
exited) + onStatus callback; FIX: wire onTitleChange (onTitle was in opts but
never bound — auto-title was dead)
- tabs.ts: per-tab status dot (color = connection state), unread flag set on
background output + cleared on activate, HTML5 drag-to-reorder (preserves
active tab, persisted)
- style.css: .tab-dot colors, .unread ring, .dragging/.drag-over feedback
- Browser-verified: green dot connected, unread ring after bg output, reorder
AAA|BBB→BBB|AAA persists across reload. 191 tests green, typechecks + build ok.