Uh oh!
There was an error while loading. Please reload this page.
[Spec 761] Surface multiple architects in Tower dashboard (3.0.6 hotfix) - #762
Conversation
…e gemini+claude review feedback
…roach steps, getTerminalsForWorkspace boundary) and Claude minor observations
…cerns resolved by slicing)
… Claude (Annotation.parent mismatch) findings; pin toolbarExtra threading
…tion Adds the `architects: ArchitectState[]` field to DashboardState. The existing scalar `architect` field is preserved unchanged as a backward- compat pointer to `architects[0]`. Architects are emitted in insertion order with `main` (when present) moved to index 0 so consumers can rely on it as the default architect. Type drift between the shared `DashboardState` interface and `tower-routes.ts:handleWorkspaceState` is eliminated by importing `DashboardState` and typing the local `state` variable against it. The previously inline literal at lines 1452-1461 is removed. Companion type fix: `Annotation.parent` is made optional in packages/types/src/api.ts. The shared type required `parent`, but neither the handler nor any dashboard consumer populates or reads it. Agent-farm-internal `Annotation` (packages/codev/.../types.ts:29) is a different type and is unaffected. No dashboard consumers are migrated to the new field in this phase — that's phase 2. Single-architect workspaces see no behaviour change.
Adds multi-architect support to the Tower dashboard. When a workspace has more than one architect registered, the left pane gains a small tab strip (ArchitectTabStrip) above the terminal area, with one button per architect. Single-architect workspaces (the dominant population) render the same bare Terminal as before — no strip, no extra DOM nodes. Key changes: - useTabs.ts: pushes one Tab per state.architects[] entry. First tab keeps the bare id 'architect' (DOM-snapshot stability AND id stability across N=1<->N>1 transitions); subsequent tabs use 'architect:<name>'. Falls back to scalar state.architect for deploy-window safety. - useTabs.ts: deep-link parser recognises ?tab=architect:<name>; unknown name falls back to the first architect tab. Bare ?tab=architect still works via the existing match-by-type. - useTabs.ts: auto-switch skip for architect tabs removed, so post-load architects (added via afx workspace add-architect) auto-focus the way newly-spawned builders do today. - useTabs.ts: persists active architect via localStorage on selectTab, reads it on mount. - App.tsx: extracts renderPersistentTerminals(tabs, activeId, toolbarExtra?) helper. toolbarExtra is threaded onto the active terminal only. - App.tsx: left pane tracks activeArchitectName independent of the global activeTabId, so flipping a builder tab on the right doesn't blank the architect on the left. State is initialized from localStorage and updated on strip click. - ArchitectTabStrip.tsx: new component, reuses the right-pane TabBar's CSS classes for visual consistency, no close buttons. - architectPersistence.ts: new helper. Uses window.location.pathname (URL-encoded full workspace path) as the localStorage key suffix, not DashboardState.workspaceName (which is just path.basename and would collide between workspaces with the same basename). Tests added: - useTabs.architects.test.ts (11 tests): tab construction, deep-link parsing, localStorage round-trip, auto-switch behaviour. - ArchitectTabStrip.test.tsx (4 tests): rendering, active styling, click handling, no close buttons. - App.architect-tabs.test.tsx (6 tests): N=0/1/2 rendering, tab switching without remount, localStorage write-on-click and restore-on-load. All existing dashboard tests pass (including App.terminal-persistence and architect-toolbar regression suites).
Three fixes from the phase 2 review round: 1. (Gemini) Add a sync effect in App.tsx so deep-links (?tab=architect:<name>) and post-load auto-switches — which update useTabs's activeTabId — also propagate into the independent left-pane activeArchitectName state. Without this, ?tab=architect:sibling would correctly change activeTabId but the left pane (driven by activeArchitectName) would stay on main. 2. (Claude) Critical bug: useTabs's localStorage restoration set activeTabId to an architect tab on reload, which blanked the entire desktop right pane (work/analytics/team divs all hide when activeTab.type is 'architect'). Removed both readActiveArchitect() from useTabs AND the localStorage write from selectTab — App.tsx already owns architect persistence via the strip-click handler. Tradeoff: mobile users lose architect-tab-restore-on-reload (single tap to recover); desktop users are fixed. 3. (Claude) Deploy-window fallback: when the scalar state.architect from an older server response lacked the new name field, buildArchitectTabs produced tabs with label/architectName === undefined. Defaulted to 'main' so the fallback path always yields a renderable tab. Tests: - Added App-level regression test asserting the right pane keeps WorkView visible on reload after architect selection. - Added App-level test asserting deep-link syncs the left pane. - Updated useTabs tests to reflect the corrected behaviour (no localStorage involvement in useTabs). 22 of 22 dashboard tests pass.
waleedkadous
commented
May 19, 2026
Architect approval — diff scanned. The builder caught and routed around two of three Codex spec/plan concerns during implementation: (1) tab-ID rule reconciled (tests match the first-vs-rest convention); (2) desktop interaction model uses an independent |
Uh oh!
There was an error while loading. Please reload this page.
PR #762's N>1 left-pane branch wraps the architect terminal in two new divs (.architect-pane and .architect-pane-body) but never added matching CSS. The wrappers collapsed to intrinsic block height and the architect terminal rendered at ~1/4 of the SplitPane left side for multi-architect workspaces. The N=1 path renders <Terminal> directly so the existing unit-test DOM-snapshot check did not catch it. Fix: - index.css: .architect-pane is now a flex-direction:column host with height:100% min-height:0; .architect-pane-body has flex:1 min-height:0 so it fills the remaining space below the tab strip. - architect-pane-layout.test.ts: Playwright regression test that mocks /api/state with N=2 architects and asserts the architect-pane fills .split-left vertically and architect-pane-body reaches the bottom. Fixes#766
Summary
PR #757 (spec #755) shipped the routing primitive for multi-architect workspaces in 3.0.5 — Tower routes
afx send architectfrom a builder back to its spawning architect — but kept the user-facing surface scalar. The sibling architect was registered and addressable, but invisible to the human running the dashboard.This v1 hotfix for 3.0.6 makes the multi-architect topology first-class in the Tower dashboard. Out of scope (deferred to follow-up issues): VS Code Workspace sidebar listing,
afx statusformatter changes.Closes#761
Changes
Phase 1:
/api/stateexposes the architects collectionpackages/types/src/api.ts:ArchitectStategainsname: string.DashboardStategainsarchitects: ArchitectState[]. Scalararchitectpreserved as a backward-compat pointer toarchitects[0]. Companion fix:Annotation.parentmade optional in the shared type (was required, but the handler never populated it and no consumer reads it).packages/codev/src/agent-farm/servers/tower-routes.ts:handleWorkspaceStatenow iteratesentry.architectsto build the collection. The architect namedmainis moved to index 0 when present. The inline response type literal at lines 1452-1461 is removed and replaced withconst state: DashboardState, preventing future drift.Phase 2: Dashboard renders one tab per architect
packages/dashboard/src/components/ArchitectTabStrip.tsx: left-pane tab strip, rendered only when N > 1.packages/dashboard/src/lib/architectPersistence.ts: per-workspacelocalStoragehelpers usingwindow.location.pathname(URL-encoded, globally unique) as the key suffix.packages/dashboard/src/hooks/useTabs.ts: pushes oneTabper architect. First architect keeps the bareid: 'architect'; subsequent usearchitect:<name>. Deep-link?tab=architect:<name>is supported; unknown names fall back to the first architect tab. Auto-switch skip for architect tabs is removed so newly-added architects auto-focus.packages/dashboard/src/components/App.tsx: extractedrenderPersistentTerminals(tabs, activeId, toolbarExtra?)helper. Left pane gains an independentactiveArchitectNamestate separate fromactiveTabId(so flipping a builder tab on the right doesn't blank the architect on the left). One-way sync effect mirrorsuseTabs's activeTab intoactiveArchitectNamewhen it lands on an architect. Strip clicks updateactiveArchitectNameand write localStorage directly.Single-architect workspaces see no behaviour change. N=1 renders a bare
Terminalexactly as before; no strip, no extra DOM nodes.Testing
spec-761-api-state.test.tscovering N=0/1/2/3, main-first ordering, dead-session skip, non-main-only scalar consistency.useTabs.architects.test.ts, 4 inArchitectTabStrip.test.tsx, 8 inApp.architect-tabs.test.tsx(totalling 22 new dashboard tests).@cluesmith/codevpackage tests pass.App.terminal-persistence.test.tsx,architect-toolbar.test.tsx,TabBar.test.tsx,SplitPane.test.tsx,MobileLayout.test.tsx).packages/dashboard/__tests__/scrollController.test.tsfails onmain(unrelated to this work, source last touched 2026-04-13). Dashboard tests are not in porch's gated test runner (pnpm testfrom repo root only runs@cluesmith/codev). Left as-is for a follow-up triage.Spec / Plan / Review
codev/specs/761-surface-multiple-architects-in.mdcodev/plans/761-surface-multiple-architects-in.mdcodev/reviews/761-surface-multiple-architects-in.mdcodev/projects/761-surface-multiple-architects-in/Review Iteration Summary
pnpm rebuildblocked by harness's permission classifier). Architect previously accepted 2-of-3.