From f5d30245c184ca3c28e3f792eda1c6f7d2b7fa6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 30 Jul 2026 06:48:33 +0000 Subject: [PATCH] take the first cwd of a transcript, not the last MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/nav-test-checklist.md | 114 ++++++++++++++++++++++++ src/servers/api/chat/claude-sessions.ts | 7 +- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 docs/nav-test-checklist.md diff --git a/docs/nav-test-checklist.md b/docs/nav-test-checklist.md new file mode 100644 index 00000000..05f26342 --- /dev/null +++ b/docs/nav-test-checklist.md @@ -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/`. +- [x] **C2** Right-click a row → "Copy link" gives `/chat/` (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/` → lands back in the same chat. +- [x] **C6** Paste a `/chat/` 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 ``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/`. +- [ ] **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/`. +- [ ] **J5** The row's stop/delete button works and does **not** navigate. +- [ ] **J6** Refresh on `/jobs/` → 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=`. +- [ ] **D2** The preview panel shows that dashboard. +- [ ] **D3** The clicked row is highlighted. +- [ ] **D4** Preview's "open" arrow overlay → `/dashboards/` (full page). +- [ ] **D5** "New Dashboard" → create form appears; `?selected=` cleared from the URL. +- [ ] **D6** Create a dashboard → navigates to the full `/dashboards/`. +- [ ] **D7** Edit (pencil) → edit form appears; does **not** navigate. +- [ ] **D8** Save an edit → preview shows the edited dashboard (`?selected=`). +- [ ] **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/` directly. +- [ ] **D12** Mobile: selecting shows the detail panel; back clears it. +- [ ] **D13** Deep-link `/dashboards?selected=` 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=`. +- [ ] **P2** The preview shows that project. +- [ ] **P3** The clicked row is highlighted. +- [ ] **P4** Preview "open" arrow → `/projects/` (full page). +- [ ] **P5** "New Project" → create form; `?selected=` cleared. +- [ ] **P6** Create a project → navigates to `/projects/`. +- [ ] **P7** Edit (pencil) → edit form; no navigation. +- [ ] **P8** Save an edit → preview shows the edited project (`?selected=`). +- [ ] **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/` directly. +- [ ] **P13** `/projects/new` → lands on `/projects` in create mode. +- [ ] **P14** Mobile: select shows detail; back clears it. +- [ ] **P15** Deep-link `/projects?selected=` → 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). diff --git a/src/servers/api/chat/claude-sessions.ts b/src/servers/api/chat/claude-sessions.ts index 940a9e35..9d3c4cbd 100644 --- a/src/servers/api/chat/claude-sessions.ts +++ b/src/servers/api/chat/claude-sessions.ts @@ -179,7 +179,12 @@ function parseClaudeTranscript(filePath: string, sessionId: string, fallbackCwd } catch { 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; const content = entry.message?.content;