docs(relay): record worktree-autodetect leg 2

This commit is contained in:
Federico Jaramillo Martinez
2026-07-26 22:25:59 +02:00
parent 213ec104ea
commit 1970ba14bc
2 changed files with 129 additions and 22 deletions
+43 -22
View File
@@ -3,39 +3,40 @@
## ✅ APPROVED — relay is live
The human approved the reduced scope in leg 0 and answered every open question. There are
no outstanding decisions. Run leg 2.
no outstanding decisions. Run leg 3 — the final leg.
## Current position
Leg 1 is complete and committed (`266f941`). The **server** side of the feature is done:
prunable worktrees are no longer reported as workspaces. Nothing in the client has changed
yet, so newly created worktrees still only appear on a full page load.
Legs 1 and 2 are complete and committed (`266f941`, `d0f8f9f`). The server hides prunable
worktrees, and the client now has a proven-inert refresh method. **Nothing calls it yet**,
so newly created worktrees still only appear on a full page load. Leg 3 wires the trigger
and is the last leg.
The design remains reduced scope: detection piggybacked on browser resume. No watchers,
no timers, no new processes, no new push channel. Breakdown is in `plan.md`.
## Leg tracking
- **Last completed leg:** 1 (server: hide removed worktrees)
- **Next leg to run:** 2
- **Last completed leg:** 2 (client: non-disruptive topology refresh method)
- **Next leg to run:** 3 (final)
## Next task — leg 2
## Next task — leg 3
Non-disruptive workspace topology refresh in the client.
Wire the refresh to the existing resume path, document it, add the changeset.
See `plan.md` → "Leg 2" and **read it fully before writing the method** — it spells out the
plausible-looking wrong implementation and exactly why it is destructive. Summary: add
`refreshSelectedProjectTopology()` to `WorkspaceController` that re-lists the selected
project's workspaces and applies them through `applyProjectWorkspaces` **only** — never
through `selectWorkspace`, which has no already-selected guard and would clear the active
session and all workspace-scoped state on every alt-tab. Guard against machine/project
changing mid-flight; `console.warn` on failure, never `setState({ error })`.
See `plan.md` → "Leg 3". Summary: call `this.workspaces.refreshSelectedProjectTopology()`
from `PiWebApp.refreshAfterBrowserResume` (~line 432) and `refreshAppData` (~line 485) —
touch only those two methods in that 2300-line file. Optionally also on
`connectRealtime`'s `onReconnect`, only if it costs nothing. Then one short honest doc
paragraph under `docs/` (detection is resume-scoped, not instant; do not grow `README.md`),
and the `.changeset/*.md` fragment — **still missing, and leg 3 owns it**.
Files: `src/client/src/controllers/workspaceController.ts`,
new `src/client/src/controllers/workspaceController.test.ts`.
Finish with `npm run verify` (charter requires green) — this is the final leg.
Do **not** wire anything into `PiWebApp` in leg 2 — that is leg 3, deliberately after the
refresh is proven inert.
Signature detail leg 3 needs: `refreshSelectedProjectTopology()` takes no arguments,
returns `Promise<void>`, never rejects (failures go to the injected background error sink,
defaulting to `console.warn`), and no-ops when no project is selected. So it can be dropped
directly into the existing `Promise.all` without a `.catch`.
## Relevant context for the next runner
@@ -65,6 +66,20 @@ real git; do not re-derive them:
- **Remote machines come for free.** `workspacesApi.workspaces(projectId, machineId)`
routes through `machinePrefix`, and `GET /projects/:projectId/workspaces` is already in
`FEDERATED_HTTP_ROUTES` in `src/shared/federatedRoutes.ts`. No transport work needed.
- **Leg 2's method is inert by construction and covered.** `refreshSelectedProjectTopology()`
applies results via `applyProjectWorkspaces` only, so it writes at most `workspaces` and
`workspacesByProjectId`. Seven tests in
`src/client/src/controllers/workspaceController.test.ts` cover: new worktree appears,
selection + session + file tree + terminal state preserved and `clearActiveSession` not
called, selected workspace disappeared (selection deliberately left alone), stale project
response discarded, stale machine response discarded, rejection reported to the error sink
without touching `state.error`, and no-project no-op. Leg 2 mutation-tested this: injecting
the plausible `selectPreferredWorkspace` + `selectWorkspace` version made 2 tests fail, so
the guard is real and not vacuous. Leg 3 must not weaken these tests to fit its wiring.
- **`WorkspaceControllerDependencies` gained `onBackgroundError`** (optional, defaults to
`console.warn`). `PiWebApp` constructs `WorkspaceController`; leg 3 may pass a message-
prefixing sink to match sibling controllers, but the default is already correct — this is
not required work.
- **Leg 1 shipped a seam leg 2 does not need but should know about.** `WorkspaceService`
now takes an optional `WorkspaceGitPort` (`{ isGitRepository, discoverGitWorktrees }`)
in its constructor, defaulting to the real git implementation, so workspace policy is
@@ -101,6 +116,12 @@ real git; do not re-derive them:
## Blockers
None. Leg 2 is clear to run. The one thing to watch is leg 2's own intervention trigger:
if the refresh cannot be made non-disruptive without visible UI churn, stop rather than
working around it.
None. Leg 2's main known risk (UI churn on refresh) did **not** materialize: applying the
list through `applyProjectWorkspaces` alone required no workaround, and
`handleWorkspaceChange` early-returns on an unchanged selected workspace id, so a
fresh-but-equal list causes no downstream churn. No intervention signal fired.
Leg 3's own watch item: after wiring, confirm nothing in the resume path re-derives
selection from the refreshed list. If wiring turns out to need a change *inside*
`refreshSelectedProjectTopology` to stay non-disruptive, that is the intervention signal —
stop rather than relaxing the invariant.