take the first cwd of a transcript, not the last

Opening a chat from a non-default folder moved the cwd picker into a subdirectory of
that folder — three of the four sessions under ~/dockers/officer.dev jumped to
`platform`, the fourth to `Lyrics` — and everything after it was scoped to the wrong
directory. The list itself stayed correct, which is what made it confusing.

Two functions in claude-sessions.ts derived the cwd in opposite directions. parseSummary
(:103) keeps the first `cwd` it sees; parseClaudeTranscript (:182) overwrote it on every
entry, so it ended up with whatever a tool last cd'd into. A session's home is where it
was launched — that is how Claude Code files the transcript on disk and how the list
groups it — so the first one is right and the two now agree. It also stops a late entry
clobbering `fallbackCwd` when the caller already knew which group it was loading from.

Pre-existing, but only reachable since rows became real links (3682269): clicking one is
a route change now, so the deep-link resolver in ChatHistory/index.tsx:69 — previously
hit only on refresh or a pasted URL — runs on every click, and it feeds detail.cwd
straight into setActiveCwd.

Verified against the four real transcripts in that group: all four now resolve to
~/dockers/officer.dev. C4/C5/C6 pass in the browser (click, refresh, and fresh-tab
deep-link all land on the right folder).

Also adds docs/nav-test-checklist.md — the 56-check matrix for the navigation refactor
and the sidecar work, recovered from the transcript of the session that wrote it and
lost it (eff24773). The X block no longer needs its own branch now that sidecars is
merged. C is done, L/J/D/P/X/R are not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-30 06:48:33 +00:00
co-authored by Claude Opus 5
parent 4e16a874dd
commit f5d30245c1
2 changed files with 120 additions and 1 deletions
+114
View File
@@ -0,0 +1,114 @@
# Navigation refactor + sidecars — runtime test checklist
**56 checks.** Each line is a single yes/no. Reference them as "J3 works, D5 needs fix".
Provenance: written by the session that did the navigation refactor (`eff24773`, last message
2026-07-30T06:21Z) and never saved before that session was lost. Recovered from its transcript.
**Pre-req:** `master` at `3c3b67c` or later covers **everything**, including the **X** block — the
original note said X needed the `navigation-refactor` branch, but the sidecar work is merged now, so
nav and sidecars are on master together. Restart before testing (`pm2 restart officer`) and
hard-refresh the browser; a stale bundle looks exactly like a failure. Watch the console as you go.
Status key: `[x]` verified working · `[!]` found broken, fixed, re-verified · `[ ]` not yet tested.
---
## C — Chat: session list & routing (`/chat`) — 6/6 ✅
- [x] **C1** Click a chat row → URL changes to `/chat/<id>`.
- [x] **C2** Right-click a row → "Copy link" gives `/chat/<id>` (a real link, not a button).
- [x] **C3** Clicking a row loads that conversation in the right panel.
- [!] **C4** The cwd/folder picker shows the correct folder for the opened chat. **Was broken — fixed.**
- [x] **C5** Refresh while on `/chat/<id>` → lands back in the same chat.
- [x] **C6** Paste a `/chat/<id>` URL into a fresh tab → opens that chat with the right cwd.
### C4: what was wrong
Opening any session from a non-default folder moved the picker to a **subdirectory** of that folder —
three of the four sessions under `~/dockers/officer.dev` jumped to `platform`, the fourth to `Lyrics`.
The list stayed correct; only the picker moved, and everything after it was scoped to the wrong dir.
Two functions in `claude-sessions.ts` derived cwd in opposite directions:
| | line | rule |
|---|---|---|
| `parseSummary` (builds the list) | `:103` | `if (entry.cwd && !cwd)`**first** cwd wins |
| `parseClaudeTranscript` (opens one) | `:182` | `if (entry.cwd)`**last** cwd wins, overwritten every entry |
A transcript records a `cwd` on every entry, so the last one is wherever a tool happened to `cd` — not
the session's home. Fixed by taking the first cwd in both, which is also where Claude Code files the
transcript on disk.
Pre-existing, but **surfaced** by the nav work: once rows became real `<Link>`s (`f35c145`), clicking
one is a route change, so the deep-link resolver in `ChatHistory/index.tsx:69` — previously reached only
on refresh or a pasted URL — now runs on every ordinary click, and it calls `setActiveCwd(detail.cwd)`.
## L — Chat: long-transcript lazy-load — 0/8
- [ ] **L1** Open a very long chat → instantly at the newest message (no long scroll or visible jump).
- [ ] **L2** Open is fast (only the tail loads, not thousands of messages).
- [ ] **L3** Scroll near the top → older messages load (brief spinner at top).
- [ ] **L4** After older messages load, the view stays put (doesn't snap to top or bottom).
- [ ] **L5** Keep scrolling up → eventually reaches the true first message; loading stops.
- [ ] **L6** Send a new message in a resumed chat → appends at the bottom, view follows.
- [ ] **L7** A short chat (<20 msgs) behaves normally — all visible, no spinner.
- [ ] **L8** "Jump to bottom" appears when scrolled up, and works.
## J — Jobs (`/jobs`) — H1 — 0/8
- [ ] **J1** Click a job row → URL changes to `/jobs/<id>`.
- [ ] **J2** The clicked job's detail renders on the right.
- [ ] **J3** The active row is highlighted.
- [ ] **J4** Right-click a row → "Copy link" gives `/jobs/<id>`.
- [ ] **J5** The row's stop/delete button works and does **not** navigate.
- [ ] **J6** Refresh on `/jobs/<id>` → same job detail loads.
- [ ] **J7** Both panels (Running/Queued **and** History) navigate correctly.
- [ ] **J8** Different detail types still render (script terminal / pipeline steps / download).
## D — Dashboards (`/dashboards`) — H2 — 0/13 (highest risk: never runtime-tested)
- [ ] **D1** Click a row → URL becomes `/dashboards?selected=<id>`.
- [ ] **D2** The preview panel shows that dashboard.
- [ ] **D3** The clicked row is highlighted.
- [ ] **D4** Preview's "open" arrow overlay → `/dashboards/<id>` (full page).
- [ ] **D5** "New Dashboard" → create form appears; `?selected=` cleared from the URL.
- [ ] **D6** Create a dashboard → navigates to the full `/dashboards/<id>`.
- [ ] **D7** Edit (pencil) → edit form appears; does **not** navigate.
- [ ] **D8** Save an edit → preview shows the edited dashboard (`?selected=<newid>`).
- [ ] **D9** Delete → removed; if it was selected, the preview clears.
- [ ] **D10** Edit/delete buttons do **not** trigger navigation (siblings of the link, not nested).
- [ ] **D11** A dashboard row **outside** `/dashboards` opens `/dashboards/<id>` directly.
- [ ] **D12** Mobile: selecting shows the detail panel; back clears it.
- [ ] **D13** Deep-link `/dashboards?selected=<id>` in a fresh tab → preview shows it.
## P — Projects (`/projects`) — H3 — 0/15 (mirror of D, also never runtime-tested)
- [ ] **P1** Click a row → URL becomes `/projects?selected=<id>`.
- [ ] **P2** The preview shows that project.
- [ ] **P3** The clicked row is highlighted.
- [ ] **P4** Preview "open" arrow → `/projects/<id>` (full page).
- [ ] **P5** "New Project" → create form; `?selected=` cleared.
- [ ] **P6** Create a project → navigates to `/projects/<id>`.
- [ ] **P7** Edit (pencil) → edit form; no navigation.
- [ ] **P8** Save an edit → preview shows the edited project (`?selected=<newid>`).
- [ ] **P9** Delete → removed; clears preview if selected.
- [ ] **P10** Publish (rocket) → opens the publish dialog; does **not** navigate.
- [ ] **P11** Publish/edit/delete buttons don't trigger navigation.
- [ ] **P12** A project row **outside** `/projects` opens `/projects/<id>` directly.
- [ ] **P13** `/projects/new` → lands on `/projects` in create mode.
- [ ] **P14** Mobile: select shows detail; back clears it.
- [ ] **P15** Deep-link `/projects?selected=<id>` → preview shows it.
## X — Chat lazy-load × sidecars — 0/3
- [ ] **X1** Open a long chat → **no** spurious "⚠️ some output could not be recovered" banner.
- [ ] **X2** Send a new turn → appends correctly below the loaded tail.
- [ ] **X3** Scroll up to load history, then send a turn / reconnect → prepended older messages are
**not** wiped.
## R — Quick regressions — 0/3
- [ ] **R1** Search/filter boxes in Jobs / Dashboards / Projects still filter the list.
- [ ] **R2** No new console errors on `/jobs`, `/dashboards`, `/projects`, `/chat`.
- [ ] **R3** Browser back/forward moves between selections sensibly (selection is in the URL now).
+6 -1
View File
@@ -179,7 +179,12 @@ function parseClaudeTranscript(filePath: string, sessionId: string, fallbackCwd
} catch { } catch {
continue; continue;
} }
if (entry.cwd) sessionCwd = entry.cwd; // First cwd wins — a session's home is where it was launched, which is how Claude Code files the
// transcript on disk and how parseSummary groups it in the list. Later entries record wherever a tool
// happened to cd to, so taking the last one made the detail disagree with the list: opening a session
// moved the cwd picker to a subdirectory. It also let a stale entry clobber `fallbackCwd` even when the
// caller already knew the group it was loading from.
if (entry.cwd && !sessionCwd) sessionCwd = entry.cwd;
if (entry.message?.model && !model) model = entry.message.model; if (entry.message?.model && !model) model = entry.message.model;
const content = entry.message?.content; const content = entry.message?.content;