Skip to content

[Spec 761] Surface multiple architects in Tower dashboard (3.0.6 hotfix)#762

Merged
waleedkadous merged 29 commits into
mainfrom
builder/spir-761
May 19, 2026
Merged

[Spec 761] Surface multiple architects in Tower dashboard (3.0.6 hotfix)#762
waleedkadous merged 29 commits into
mainfrom
builder/spir-761

Conversation

@waleedkadous
Copy link
Copy Markdown
Contributor

Summary

PR #757 (spec #755) shipped the routing primitive for multi-architect workspaces in 3.0.5 — Tower routes afx send architect from 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 status formatter changes.

Closes #761

Changes

Phase 1: /api/state exposes the architects collection

  • packages/types/src/api.ts: ArchitectState gains name: string. DashboardState gains architects: ArchitectState[]. Scalar architect preserved as a backward-compat pointer to architects[0]. Companion fix: Annotation.parent made 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: handleWorkspaceState now iterates entry.architects to build the collection. The architect named main is moved to index 0 when present. The inline response type literal at lines 1452-1461 is removed and replaced with const state: DashboardState, preventing future drift.

Phase 2: Dashboard renders one tab per architect

  • New packages/dashboard/src/components/ArchitectTabStrip.tsx: left-pane tab strip, rendered only when N > 1.
  • New packages/dashboard/src/lib/architectPersistence.ts: per-workspace localStorage helpers using window.location.pathname (URL-encoded, globally unique) as the key suffix.
  • packages/dashboard/src/hooks/useTabs.ts: pushes one Tab per architect. First architect keeps the bare id: 'architect'; subsequent use architect:<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: extracted renderPersistentTerminals(tabs, activeId, toolbarExtra?) helper. Left pane gains an independent activeArchitectName state separate from activeTabId (so flipping a builder tab on the right doesn't blank the architect on the left). One-way sync effect mirrors useTabs's activeTab into activeArchitectName when it lands on an architect. Strip clicks update activeArchitectName and write localStorage directly.

Single-architect workspaces see no behaviour change. N=1 renders a bare Terminal exactly as before; no strip, no extra DOM nodes.

Testing

  • Phase 1: 6 new tests in spec-761-api-state.test.ts covering N=0/1/2/3, main-first ordering, dead-session skip, non-main-only scalar consistency.
  • Phase 2: 11 tests in useTabs.architects.test.ts, 4 in ArchitectTabStrip.test.tsx, 8 in App.architect-tabs.test.tsx (totalling 22 new dashboard tests).
  • All 2932 existing @cluesmith/codev package tests pass.
  • All existing dashboard tests pass (App.terminal-persistence.test.tsx, architect-toolbar.test.tsx, TabBar.test.tsx, SplitPane.test.tsx, MobileLayout.test.tsx).
  • Pre-existing failure noted: packages/dashboard/__tests__/scrollController.test.ts fails on main (unrelated to this work, source last touched 2026-04-13). Dashboard tests are not in porch's gated test runner (pnpm test from repo root only runs @cluesmith/codev). Left as-is for a follow-up triage.

Spec / Plan / Review

  • Spec: codev/specs/761-surface-multiple-architects-in.md
  • Plan: codev/plans/761-surface-multiple-architects-in.md
  • Review: codev/reviews/761-surface-multiple-architects-in.md
  • Project artifacts (consult outputs, rebuttals): codev/projects/761-surface-multiple-architects-in/

Review Iteration Summary

  • Spec phase: Gemini REQUEST_CHANGES (iter-1, deferred items addressed by architect's slicing directive; iter-2 stale Solution Approach steps removed). Claude APPROVE.
  • Plan phase: Gemini REQUEST_CHANGES (tab ID convention, localStorage key, toolbarExtra threading — all addressed). Claude COMMENT (Annotation.parent mismatch — addressed via small companion type fix).
  • Phase 1: Gemini APPROVE, Claude APPROVE.
  • Phase 2: Gemini REQUEST_CHANGES (sync effect for deep-link / auto-switch — addressed). Claude REQUEST_CHANGES (right-pane-blanks-on-reload bug — addressed by moving persistence to App.tsx).
  • Codex unavailable across all phases (vendored binary missing from worktree's pnpm node_modules; pnpm rebuild blocked by harness's permission classifier). Architect previously accepted 2-of-3.

…roach steps, getTerminalsForWorkspace boundary) and Claude minor observations
… 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
Copy link
Copy Markdown
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 activeArchitectName state rather than the global activeTabId, so right-pane content stays put when switching architects on the left. The third concern (mobile N=1 label regression — solo-architect mobile users see main instead of Architect) is real but cosmetic, affects a tiny population, and will be filed as a 1-line follow-up bugfix. Shannon's blocked use case (multi-architect desktop) is fully unblocked by this PR. Approved — please merge with gh pr merge 762 --merge --admin.

@waleedkadous waleedkadous merged commit 20cb5dc into main May 19, 2026
6 checks passed
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Surface multiple architects in Tower dashboard + VS Code extension + afx status

1 participant