[Spec 761] Surface multiple architects in Tower dashboard (3.0.6 hotfix)#762
Merged
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.
Contributor
Author
|
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 |
waleedkadous
added a commit
that referenced
this pull request
May 19, 2026
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
5 tasks
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.