7.7 KiB
Status — relay "worktree-autodetect"
✅ 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 3 — the final leg.
Current position
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: 2 (client: non-disruptive topology refresh method)
- Next leg to run: 3 (final)
Next task — leg 3
Wire the refresh to the existing resume path, document it, add the changeset.
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.
Finish with npm run verify (charter requires green) — this is the final leg.
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
Facts established in leg 0 — trust these, they were verified against the running code and real git; do not re-derive them:
- Worktrees are already derived, never registered.
WorkspaceService.list()shells out togit worktree list --porcelainon everyGET /projects/:projectId/workspacesrequest. There is no server-side cache and no invalidation to design. A worktree created outside PI WEB is already discovered — the gap is purely that the browser never re-asks. projects.jsonholds projects only, not workspaces. So "auto-adoption" is a non-problem: nothing needs to be written to a registry, and there is no adopt-vs-visible distinction to design. This collapsed most of the feature's apparent complexity.- Cost of discovery is ~2ms (measured: 20 sequential
git worktree list --porcelainruns in 42ms on this repo). prunableis real and load-bearing. Afterrm -rfing a worktree directory withoutgit worktree remove,git worktree list --porcelainstill lists it, with an addedprunable gitdir file points to non-existent locationline. PI WEB currently shows this as a normal selectable workspace.lockedappears as a bare valueless line.- The resume path already exists and is already debounced.
src/client/src/appShell/browserResumeController.tslistens to windowfocusand documentvisibilitychange, batches per animation frame, and collapses concurrent requests viaTrailingRefreshCoordinator. It callsPiWebApp.refreshAfterBrowserResume()(~line 432), which already refreshes the selected session, machine activities, and workspace-deletion runs. Workspace topology is the one thing missing from that list. - Remote machines come for free.
workspacesApi.workspaces(projectId, machineId)routes throughmachinePrefix, andGET /projects/:projectId/workspacesis already inFEDERATED_HTTP_ROUTESinsrc/shared/federatedRoutes.ts. No transport work needed. - Leg 2's method is inert by construction and covered.
refreshSelectedProjectTopology()applies results viaapplyProjectWorkspacesonly, so it writes at mostworkspacesandworkspacesByProjectId. Seven tests insrc/client/src/controllers/workspaceController.test.tscover: new worktree appears, selection + session + file tree + terminal state preserved andclearActiveSessionnot called, selected workspace disappeared (selection deliberately left alone), stale project response discarded, stale machine response discarded, rejection reported to the error sink without touchingstate.error, and no-project no-op. Leg 2 mutation-tested this: injecting the plausibleselectPreferredWorkspace+selectWorkspaceversion made 2 tests fail, so the guard is real and not vacuous. Leg 3 must not weaken these tests to fit its wiring. WorkspaceControllerDependenciesgainedonBackgroundError(optional, defaults toconsole.warn).PiWebAppconstructsWorkspaceController; 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.
WorkspaceServicenow takes an optionalWorkspaceGitPort({ isGitRepository, discoverGitWorktrees }) in its constructor, defaulting to the real git implementation, so workspace policy is testable without a repo.parseGitWorktreeList(stdout)is exported fromgitWorktreeDiscovery.tsfor pure parser tests. Server-sideWorkspaceshape is unchanged —prunable/lockedlive onGitWorktreeInfoonly and never reach the API, so the client needs no type changes. - The danger is
selectWorkspace. It callsclearActiveSession()andresetWorkspaceScopedState(). A refresh must apply the new list viaapplyProjectWorkspacesonly, and must not route throughselectWorkspacewhen the selection is still valid.ProjectActivityOwnershipCoordinatoris the existing precedent for background topology hydration that does not disturb selection — read it if leg 2 needs a model. - No session daemon involvement. Nothing in this design touches
src/server/sessiond.ts, session runtime ownership, or the daemon protocol. No manual sessiond restart needed.
Progress documentation expected of each runner
- Commit the slice (Conventional Commit message).
- Update this file: current position, leg tracking, next task, blockers.
- Append to
log.md: what, why, artifacts, exact checks run and results. - Add the
.changeset/*.mdfragment no later than leg 3.
Decisions settled by the human (do not re-open)
- Latency: resume-scoped is acceptable. Detection on tab refocus/visibility is the agreed behavior. Do not add a timer or a watcher to shorten it.
- Removed worktrees: hide them. Filtering
prunableworktrees out of the workspace list is wanted and approved. - Sibling overlap: assume the other session does nothing. This relay owns the
workspace-topology refresh seam outright. Build it here, do not design for sharing,
and do not read
/srv/dev/pi-web-worktrees/worktree-create-ui. If that branch later merges something overlapping, resolving it is that branch's problem, not this one's.
Blockers
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.