# Status — relay "worktree-autodetect" ## 🏁 FINISHED — the relay reached its finish line All three legs are complete and committed on `feat/worktree-autodetect`. `npm run verify` is green. No further leg was spawned; leg 3 was the last one by design. Remaining human action: review the branch and merge it. Nothing is blocked. ## Post-relay review fix A human review question after leg 3 ("do we have pragmatic reasonable and stable code?") found one real gap, fixed in `79577e4`: `applyProjectWorkspaces` replaced the list but left `selectedWorkspace` pointing at the old object, so a branch switched inside a worktree outside PI WEB showed the new name in the list and the old one in the collapsed Workspaces header and mobile context bar. Now re-pointed by id, and skipped entirely when metadata is unchanged so a normal resume does not churn identity. Two tests added (9 total in `workspaceController.test.ts`), each mutation-checked in both directions. `npm run verify` green: 1842 passed, 2 skipped (1843 after the two follow-up fixes below). The other two review nits were then fixed as well, after the human asked: - `8f060d1` removed the unused `GitWorktreeInfo.locked` flag. The parser test still feeds a real `locked` porcelain line and asserts it is ignored, so the keep-locked-worktrees policy stays covered without a speculative field. - `f8c3fdb` fixed what turned out to be a **correctness bug, not the cosmetic nit I first rated it**. The resume path and the plugin-facing app refresh call `refreshSelectedProjectTopology` independently, and the stale guards check machine and project but *not ordering* — so a slower earlier response landing last overwrote a newer list and a just-created worktree disappeared again. Reproduced with a scratch test before fixing. Now routed through `TrailingRefreshCoordinator` keyed by machine+project. Note for anyone extending this: `TrailingRefreshCoordinator` does **not** collapse two overlapping calls into one HTTP request. It runs the second as one trailing pass after the first completes, so the newest response is always applied last. An assertion of `toHaveBeenCalledOnce()` will hang against it. ## Current position Every charter finish-line condition is satisfied: 1. ✅ **Prunable worktrees hidden** — `266f941`. `discoverGitWorktrees` parses `prunable`; `WorkspaceService` filters those worktrees out, while always keeping the project's own worktree so a project is never empty. 2. ✅ **Non-disruptive client refresh** — `d0f8f9f`. `WorkspaceController.refreshSelectedProjectTopology()` applies results through `applyProjectWorkspaces` only, never `selectWorkspace`, so selection, session, file tree, git status, and terminal selection survive. A vanished selected workspace is left alone for the existing deletion path to handle. 3. ✅ **Wired to the existing resume path** — `84545fb`. Called from `PiWebApp.refreshAfterBrowserResume` and `refreshAppData`. No new timer, watcher, process, endpoint, or push channel. Remote machines work through the existing `machinePrefix` + `FEDERATED_HTTP_ROUTES` plumbing. 4. ✅ **Tests** — prunable parsing/filtering (`gitWorktreeDiscovery.test.ts`, `workspaceService.test.ts`), refresh-preserves-selection and refresh-when-selected-workspace-disappeared (`workspaceController.test.ts`, 7 tests), resume + app-data wiring (`PiWebApp.workspaceTopology.test.ts`, 3 tests). 5. ✅ **`npm run verify` green** (228 files / 1843 passed, 2 skipped) and `.changeset/worktree-autodetect.md` exists. Nothing in this work touched `src/server/sessiond.ts`, session runtime ownership, or the daemon protocol. **No manual session daemon restart is required.** ## Leg tracking - **Last completed leg:** 3 (final — wiring, docs, changeset, full verify) - **Next leg to run:** none. Relay complete; do not spawn another runner. ## Shipped behavior, as a user sees it A worktree created or deleted outside PI WEB shows up in (or disappears from) the workspace list the next time the browser tab regains focus or becomes visible. Detection is resume-scoped, not instant, by explicit human decision. A worktree whose checkout directory was `rm -rf`ed no longer appears as a selectable workspace. Documented in `docs/faq.html` under `#worktree-list-out-of-date`. ## Relevant context if anyone picks this branch up - Commits: `266f941` (server filter), `d0f8f9f` (client refresh method), `84545fb` (wiring + docs + changeset), `79577e4` (selected-workspace freshness fix), `8f060d1` (drop unused `locked`), `f8c3fdb` (serialize overlapping refreshes), plus the `docs(relay)` packet commits. - Second invariant, added by `79577e4`: re-pointing `selectedWorkspace` is safe **only** while it is keyed by `id` and skipped on unchanged metadata. Keying it by anything that can differ between two lists would change the selection on a background refresh; dropping the unchanged-metadata guard would push a new object into state on every browser focus. - The invariant to protect on any future edit: **never route a background topology refresh through `selectWorkspace`.** It calls `clearActiveSession()` and `resetWorkspaceScopedState()` with no already-selected guard, which would close the session socket and blank the chat on every browser resume. `workspaceController.test.ts` was mutation-checked against exactly that regression in leg 2. - The relay packet lives under `.pi-web/`, which is gitignored; packet commits used `git add -f`. ## Blockers None. No intervention signal fired in any leg. The one serious known risk — visible UI churn on refresh — did not materialize and needed no workaround.