Compass ADE shell — fold the bottom dock into the right sidebar
Status: Active
Internal design record — July 2026. A UI/UX design pass on the Compass ADE dev shell, amending the frozen shell record (
../compass-ade-shell/design.md, merged in #461, built by #467). It supersedes that record’s D7 (the bottom dock), amends its D5 (the right sidebar), and deliberately reverses its resolved decision 7 (dock/sidebar independence). The frozen record is not edited — records freeze on merge; this record carries the delta.
Problem / Intent
Section titled “Problem / Intent”The maintainer’s hands-on feedback on the shipped shell, verbatim:
The bottom pane is too small to read anything in an agent session. Just making it taller eats too much screen. Change it so it’s a sidebar — bake it into the right-hand sidebar (so you can have Supervisor, Warden, OR the status info open). The right sidebar then needs to be larger by default. We’ll also need to resize both sidebars, but that can come later. The goal: the user can look at the swimlane board while also talking with the supervisor.
The pinned bottom dock (ADE-shell D7, ../compass-ade-shell/design.md:192-201:
“The pinned bottom dock becomes tabbed [Dispatcher | Warden]”, shipped as
[Supervisor | Warden]) is capped at max-height: 260px (app.css:1229) — too
short to read an ACP conversation, and stealing that height from the board.
Intent: remove the dock row entirely and move its two communicable agent views
into the right sidebar’s activity bar (ADE-shell D5,
../compass-ade-shell/design.md:134-172) as first-class tabs, alongside a new
Status tab carrying the dock’s metrics/feed facet — so a full-height, readable
Supervisor conversation can sit beside the swimlane board.
This reverses ADE-shell resolved decision 7
(../compass-ade-shell/design.md:654-656: “Independent — the bottom dock is
agent-conversation, the right sidebar is workstream/VCS; both may be open at
once, no coupling”). The maintainer made that reversal deliberately: the dock
was too short to read, and a full-height sidebar column fixes it. The cost —
you can no longer see a fleet conversation and a workstream pane at the same
time — is accepted; the sidebar becomes one radio group over both.
All file references are relative to apps/ui/src/ unless prefixed,
and cite the post-#467 tree (branch cook-compass-ade-tabs, head ad29c49f).
#467 must be merged before T1 begins.
Approach
Section titled “Approach”Extend the existing activity-bar seam rather than inventing a second container:
the right sidebar already renders an icon-per-tab activity bar
(RightSidebar.tsx:409-427) driven by one store signal
(activeRightTab, store.ts:347-348) and one exhaustive tab table
(RIGHT_SIDEBAR_TAB_BY_ID, constants.ts:68-74). The dock’s two agent views
become three new tabs in that same bar (Supervisor, Warden, Status), grouped
above the existing workstream tabs with a divider. The dock’s grid row, CSS
block, component, and store state are then deleted outright — no shims.
Two alternatives were pre-empted by the feedback itself or the brief:
- Taller / resizable dock — rejected verbatim (“just making it taller eats too much screen”). The vertical dock competes with the board for the scarce axis; the sidebar column is the abundant one.
- A second active-tab signal (keep
activeDockTabbesideactiveRightTab) — rejected: one activity bar is one radio group, so one signal (activeRightTab) absorbs the fleet tabs and the dock signals are removed (D1). Two signals would imply two independently-visible surfaces, which is exactly the layout being removed.
A structural constraint shapes the plan: the tab table is a mapped object keyed
on the full RightSidebarTab union — “TypeScript rejects the module unless
EVERY tab has an activity-bar entry” (constants.ts:64-67), and the rendered
bar derives from it (“adding a tab to the union forces an entry above, and that
entry appears here automatically”, constants.ts:76-79). So a union change, its
tab entries, and their rendered panes are compile-coupled and must land in the
same slice. Tasks below are therefore vertical (store + constants + render +
CSS + tests), split by tab set, with the dock deletion as its own slice.
Decisions
Section titled “Decisions”D1–D7 below are this record’s decisions. The superseded record’s decisions are always cited as ADE-shell Dn.
D1 — One activity bar, one signal: the dock state is removed
Section titled “D1 — One activity bar, one signal: the dock state is removed”activeRightTab absorbs the fleet tabs. The union
(store.ts:39: export type RightSidebarTab = "files" | "vcs" | "pr";) widens
to six members, split into named subsets so the grouped bar and the
chrome-hiding rule (D5) key off types, not string lists:
export type FleetTab = "supervisor" | "warden" | "status";export type WorkstreamTab = "files" | "vcs" | "pr";export type RightSidebarTab = FleetTab | WorkstreamTab;The dock’s store surface is deleted, not aliased — every member, signal, and exposure site:
export type DockTab = "supervisor" | "warden";(store.ts:34)dockOpen: Accessor<boolean>/toggleDock: () => void(store.ts:216-217; signalstore.ts:339, definitionstore.ts:511, exposedstore.ts:644-645)activeDockTab: Accessor<DockTab>/setActiveDockTab: (tab: DockTab) => void(store.ts:219-220; signalstore.ts:340, exposedstore.ts:646-647)
rightOpen/toggleRight (store.ts:213-214) are retained unchanged — closing
the sidebar hides the fleet tabs too, and the existing topbar pane toggle
restores it (App.tsx:101-109).
D2 — Grouped activity bar: fleet on top, divider, workstream below
Section titled “D2 — Grouped activity bar: fleet on top, divider, workstream below”Resolved by the maintainer (not an open fork): the bar renders two groups — the fleet group (Supervisor · Warden · Status) on top, a divider, then the workstream group (Files · VCS · PR). Rationale: the fleet agents are always-on and are the new primary use (talk to the Supervisor while watching the board); the workstream tabs are card-scoped (they follow the selected Bridge card).
ActivityBarItem (constants.ts:53-59) gains a group discriminator and an
optional agent reference:
export type RightTabGroup = "fleet" | "workstream";
export interface ActivityBarItem { id: RightSidebarTab; /** Single-glyph icon. */ icon: string; /** Short label under the icon / for the tooltip. */ title: string; /** Activity-bar group: fleet renders above the divider, workstream below. */ group: RightTabGroup; /** Fleet agent tabs: the agent whose StateDot badges the tab icon. */ agentId?: string;}The per-tab agent state dot carries over from the dock, which renders a
StateDot inside each dock tab today (BottomDock.tsx:93-95); the fleet
activity-bar tabs keep that at-a-glance liveness by overlaying the same
StateDot component (StateDot.tsx:16,
export const StateDot: Component<{ state: AgentState }>) on the tab icon,
resolved through agentId. This also matches the per-tab status dot the
frozen record already established as the external ADE mirror for activity-bar
tabs (ADE-shell D5, ../compass-ade-shell/design.md:149-156) — no new mirror
claim is made here.
Icons: Supervisor ◆ and Warden 🛡 are the dock’s existing glyphs
(BottomDock.tsx:19-22); Status gets ▦ (“Fleet status”). The tab buttons
reuse the existing .r-tab pattern — classList active state, title +
aria-label + aria-pressed (RightSidebar.tsx:412-424).
D3 — Supervisor/Warden are pure full-height conversations; Status is its own tab
Section titled “D3 — Supervisor/Warden are pure full-height conversations; Status is its own tab”Resolved by the maintainer (not an open fork): the Supervisor and Warden tabs render only a full-height ACP conversation — no metrics facet stealing height; a readable conversation is the whole point. The Supervisor’s fleet metrics + recent-decision feed move to a separate Status tab.
Salvage map from the deleted dock:
| Dock piece | Source | New home |
|---|---|---|
AcpConversation render | BottomDock.tsx:130 | Supervisor/Warden panes render <AcpConversation agent={…} /> full-height; the component is already shared and exported (AgentView.tsx:51-52: “Shared by the agent-view session pane and the bottom dock (D7)”) |
AGENT_BY_DOCK_TAB | BottomDock.tsx:14-17 | Dissolved into ActivityBarItem.agentId (D2) — one source of truth, no separate map |
SupervisorFacet (metrics + feed) | BottomDock.tsx:26-65 | StatusPane in RightSidebar.tsx, full-width; metrics logic extracted as a pure, exported fleetMetrics helper (T2) |
.dock-facet/.dock-metrics/.dock-feed CSS | app.css:1295-1354 | Rehomed under a .r-status* namespace (T2); the 300px facet width cap (app.css:1297) is dropped — the pane fills the sidebar |
.dock, .dock-head, .dock-tab*, .dock-spacer, .dock-sub, .dock-body CSS | app.css:1221-1293, 1357-1361 | Deleted (T3) |
Accepted losses, deliberate: the dock’s collapse-to-header-row affordance
(BottomDock.tsx:109-117) and its model · state subtitle line
(BottomDock.tsx:104-107) have no sidebar equivalent. State stays visible via
the activity-bar StateDot (its title/aria-label carry the human label,
StateDot.tsx:22-23); the model name is no longer shown on this surface.
The Status pane’s content is an exact salvage: workstream counts (active =
in_progress + in_review, queued, todo, blocked — the countState
logic at BottomDock.tsx:28-30,33-52) over store.workstreams(), plus the
Supervisor’s feed (BottomDock.tsx:53-62; Agent.feed, stub-data.ts:195).
It works with no workstream selected, so no fleet tab ever shows the
SelectPrompt fallback the workstream tabs use (RightSidebar.tsx:378).
D4 — Grid loses the dock row; the sidebar widens via --right-w
Section titled “D4 — Grid loses the dock row; the sidebar widens via --right-w”The shell grid (app.css:101-111) currently reserves row 3 for the dock:
grid-template-rows: var(--topbar-h) 1fr auto var(--usage-h);grid-template-areas: "topbar topbar topbar" "left main right" "dispatch dispatch dispatch" "usage usage usage";The dispatch row and area are removed — 4 rows become 3:
grid-template-rows: var(--topbar-h) 1fr var(--usage-h);grid-template-areas: "topbar topbar topbar" "left main right" "usage usage usage";Since .right occupies only the middle row (grid-area: right,
app.css:998), deleting the dock row automatically extends both sidebars and
the main pane to full height between topbar and usage bar — that is the
mechanism giving the conversation its height.
Width: .right { width: 300px; … } (app.css:997-1005) is too narrow for a
readable conversation (the dock gave its facet alone 300px,
app.css:1295-1302). A --right-w variable is added to :root
(app.css:6-52) and .right consumes it:
:root { --right-w: 400px;}
.right { width: var(--right-w);}400px is the locked default — the midpoint of the delegated 380–420px
range, comfortable for the 13px/1.55 conversation text (.block-text,
app.css:878-881). The variable is the single knob the deferred resize task
(T4) will drive.
D5 — Card-scoped chrome hides on fleet tabs
Section titled “D5 — Card-scoped chrome hides on fleet tabs”.r-main unconditionally renders the workstream detail header and the
repo/branch dropdown above the pane (RightSidebar.tsx:373-374:
<Show when={ws()}>{(w) => <WorkstreamDetailHead w={w()} />}</Show> +
<RepoBranchDropdown />; components at RightSidebar.tsx:222 and
RightSidebar.tsx:252). Both are card-scoped and meaningless above a
Supervisor chat, so when the active tab’s group is fleet they are not
rendered; when a workstream tab is active the sidebar renders exactly as
today. The predicate derives from the tab table — no parallel list:
const fleetActive = (): boolean => RIGHT_SIDEBAR_TAB_BY_ID[store.activeRightTab()].group === "fleet";D6 — Default tab is Supervisor; card selection does not steal the tab
Section titled “D6 — Default tab is Supervisor; card selection does not steal the tab”Two locked implementer choices on the new signal’s behavior:
- Boot default
"supervisor"(today"files",store.ts:347-348). The shell boots onto the Bridge board (store.test.ts:67), so the default layout is exactly the maintainer’s goal: board + Supervisor conversation side by side. It also preserves the dock’s default surface (createSignal<DockTab>("supervisor"),store.ts:340). - No auto-switch on card select. Selecting a Bridge card
(
selectWorkstream,store.ts:206) keeps the active tab; it only updates what the workstream tabs will show when visited. Auto-jumping to a workstream tab would yank the Supervisor conversation away mid-thought — the opposite of the always-on-fleet rationale (D2). Maintainer-confirmed (Resolved decisions #1).
D7 — Sidebar resize is deferred; the variable seam lands now
Section titled “D7 — Sidebar resize is deferred; the variable seam lands now”Resizing both sidebars is explicitly deferred by the maintainer (“can come
later”). This record does not design the drag interaction; it only lays the
seam: --right-w ships in D4/T1, and the left sidebar’s hardcoded
width: 244px (app.css:255-257) is noted as the future --left-w
counterpart. T4 is the stub carrying the follow-up contract.
Prerequisite: #467 merged (this record cites its tree). Vertical,
compile-coupled slices (see Approach), each landing with its own bun test
cycle in apps/ui — tests first, red → green, written via the
Tester. All changes live under apps/ui/src/ and extend the
AppStore seam; no new state pattern.
T1 — Fleet conversations move into the sidebar
Section titled “T1 — Fleet conversations move into the sidebar”The sidebar grows the fleet group with the two conversation tabs; the dock still exists (untouched) until T3, so this slice is purely additive and the UI stays functional at every commit.
Scope: split/widen the union + flip the default (store.ts:39, store.ts:347-348;
update the stale tab-list doc comments at store.ts:36-38 and store.ts:228);
add the group model + two fleet entries (constants.ts:53-82); render groups +
divider + state dots in the activity bar and the two ACP panes
(RightSidebar.tsx:361-431); hide card-scoped chrome on fleet tabs (D5); add
the fleet CSS + --right-w (D4 width only — the grid row survives until T3).
Interfaces:
// store.ts — replaces RightSidebarTab ("files" | "vcs" | "pr", store.ts:39).// T2 widens FleetTab with "status".export type FleetTab = "supervisor" | "warden";export type WorkstreamTab = "files" | "vcs" | "pr";export type RightSidebarTab = FleetTab | WorkstreamTab;
// store.ts:347-348 — the boot default flips "files" → "supervisor" (D6):const [activeRightTab, setActiveRightTab] = createSignal<RightSidebarTab>("supervisor");
// constants.ts — ActivityBarItem (constants.ts:53-59) gains group + agentId:export type RightTabGroup = "fleet" | "workstream";
export interface ActivityBarItem { id: RightSidebarTab; icon: string; title: string; group: RightTabGroup; agentId?: string;}
// constants.ts:68-74 — the mapped object gains the fleet entries (declaration// order = render order, fleet first) and is now EXPORTED for the D5 predicate:export const RIGHT_SIDEBAR_TAB_BY_ID: { [K in RightSidebarTab]: ActivityBarItem & { id: K };} = { supervisor: { id: "supervisor", icon: "◆", title: "Supervisor", group: "fleet", agentId: "agent-supervisor", }, warden: { id: "warden", icon: "🛡", title: "Warden", group: "fleet", agentId: "agent-warden", }, files: { id: "files", icon: "🗀", title: "Files", group: "workstream" }, vcs: { id: "vcs", icon: "⎇", title: "Version control", group: "workstream" }, pr: { id: "pr", icon: "⇄", title: "Pull request", group: "workstream" },};
// constants.ts:80-82 — RIGHT_SIDEBAR_TABS (flat) is REPLACED by the grouped// view; its sole consumer is the activity bar (RightSidebar.tsx:9,410):export const RIGHT_SIDEBAR_TAB_GROUPS: readonly { group: RightTabGroup; items: readonly ActivityBarItem[];}[] = (["fleet", "workstream"] as const).map((group) => ({ group, items: Object.values(RIGHT_SIDEBAR_TAB_BY_ID).filter( (t) => t.group === group, ),}));
// RightSidebar.tsx — component-local helpers. Agent resolution mirrors the// dock's STUB_AGENTS.find pattern (BottomDock.tsx:69-70,78-79); no new store// surface, no new fixture shape.const fleetActive = (): boolean => RIGHT_SIDEBAR_TAB_BY_ID[store.activeRightTab()].group === "fleet";const agentFor = (item: ActivityBarItem): Agent | undefined => item.agentId ? STUB_AGENTS.find((a) => a.id === item.agentId) : undefined;/* app.css — new, alongside the existing .r-activity/.r-tab block * (app.css:2074-2106). --right-w + .right per D4. */.r-activity-divider { height: 1px; margin: 4px 6px; background: var(--border-strong);}
.r-tab { position: relative; /* added property; block otherwise as app.css:2084-2095 */}
.r-tab .state-dot { position: absolute; right: 3px; bottom: 3px;}
/* Fleet panes host a flex conversation, not a scrolling document — the * counterpart of .r-pane { overflow-y: auto } (app.css:1945-1948). The * border-right suppression mirrors the dock's own .acp override * (app.css:1357-1361) against .acp's base border (app.css:835). */.r-pane.fleet { display: flex; overflow: hidden;}
.r-pane.fleet .acp { flex: 1; min-height: 0; border-right: none;}Render: the .r-activity nav (RightSidebar.tsx:409-427) iterates
RIGHT_SIDEBAR_TAB_GROUPS with a .r-activity-divider between groups; fleet
tabs append <StateDot state={agentFor(item).state} />. The pane Switch
(RightSidebar.tsx:376-405) gains supervisor/warden matches rendering
<AcpConversation agent={…} /> (import from ./AgentView exactly as the dock
does, BottomDock.tsx:5); .r-pane gains classList={{ fleet: fleetActive() }};
the two chrome components wrap in <Show when={!fleetActive()}> (D5).
Tests (red first): store.test.ts — boot default is "supervisor";
setActiveRightTab reaches a fleet and a workstream value.
RightSidebar.test.ts (currently pure-helper tests only, filterFileTree,
RightSidebar.test.ts:3,28) — RIGHT_SIDEBAR_TAB_GROUPS partitions the union
exactly: fleet group first, every RightSidebarTab appears exactly once, fleet
items carry agentId.
T2 — The Status tab
Section titled “T2 — The Status tab”Scope: widen FleetTab with "status" (the mapped object then rejects the
module until the sixth entry exists — the exhaustiveness lever,
constants.ts:64-67); add the entry; add StatusPane + the extracted pure
metrics helper; rehome the facet CSS. The dock still renders its own facet
until T3 — one slice of deliberate duplication, then the original dies.
Interfaces:
// store.ts — FleetTab gains the status tab (D3):export type FleetTab = "supervisor" | "warden" | "status";
// constants.ts — the compile-forced sixth entry (fleet group, no agentId —// Status is a pane, not an agent conversation):status: { id: "status", icon: "▦", title: "Fleet status", group: "fleet" },
// RightSidebar.tsx — the salvaged SupervisorFacet metrics (BottomDock.tsx:26-52),// extracted pure and exported for tests exactly like filterFileTree// (RightSidebar.test.ts:3):export interface FleetMetrics { /** in_progress + in_review, the dock's "active" count (BottomDock.tsx:35). */ active: number; queued: number; todo: number; blocked: number;}export function fleetMetrics(workstreams: readonly Workstream[]): FleetMetrics;
// RightSidebar.tsx — the pane: metrics strip over store.workstreams(), then// the Supervisor's feed (Agent.feed, stub-data.ts:195), resolved via// RIGHT_SIDEBAR_TAB_BY_ID.supervisor.agentId:const StatusPane: Component = () => JSX;/* app.css — the .dock-facet/.dock-metrics/.dock-feed styles * (app.css:1295-1354) rehomed; the 300px facet cap (app.css:1297) and the * facet border-right (app.css:1300) are dropped — the pane fills the sidebar. * Inner .m-val/.m-label/.at class names carry over unchanged. */.r-status { display: flex; flex-direction: column; min-height: 0;}
.r-status-metrics { /* from .dock-metrics, app.css:1304-1311 */}
.r-status-metric { /* from .dock-metric (+ .m-val/.m-label), app.css:1313-1330 */}
.r-status-feed { /* from .dock-feed, app.css:1332-1339 */}
.r-status-feed-item { /* from .dock-feed-item (+ .at), app.css:1341-1354 */}Tests (red first): fleetMetrics — counts by state over a hand-built
workstream list (active sums in_progress + in_review; blocked counted
separately; states outside the four buckets ignored); groups-partition test
updated for the sixth tab.
T3 — Remove the dock
Section titled “T3 — Remove the dock”Scope: delete the component, its store state, its render sites, its CSS block, and the grid row — the change that makes the fold real. After T1/T2 the sidebar already carries every dock capability, so this slice is pure deletion.
Interfaces (removals, with every definition/exposure site):
// store.ts — REMOVED:export type DockTab = "supervisor" | "warden"; // store.ts:34dockOpen: Accessor<boolean>; // store.ts:216; signal store.ts:339; exposed store.ts:644toggleDock: () => void; // store.ts:217; defined store.ts:511; exposed store.ts:645activeDockTab: Accessor<DockTab>; // store.ts:219; signal store.ts:340; exposed store.ts:646setActiveDockTab: (tab: DockTab) => void; // store.ts:220; exposed store.ts:647File-level removals:
components/BottomDock.tsx— deleted whole (AGENT_BY_DOCK_TAB,DOCK_TABS,SupervisorFacet, and the render body go with it; their survivors were rehomed in T1/T2 per the D3 salvage map).App.tsx— theBottomDockimport (App.tsx:5), the unconditional<BottomDock />(App.tsx:138), and the dock pane-toggle button (App.tsx:92-100, the▂button callingstore.toggleDock()); update the shell header comment (App.tsx:16-26, which still describes “a pinned bottom dock”).app.css— the grid row +dispatcharea (app.css:101-111, per D4); the whole dock block (app.css:1221-1361); the file header comment (app.css:1-4, “a pinned bottom dock ([Supervisor | Warden])”).
Tests (updated in the same slice — the removal breaks them by design):
store.test.ts:76— the boot assertionexpect(s.dockOpen()).toBe(true).store.test.ts:350-398— the pane-toggles matrix: the dock case (store.test.ts:372-377) is removed and theothersarrays for left/right (store.test.ts:364,370) drop theirdockOpenprobes.store.test.ts:400-413— the “dock tab selection (D7)” describe is removed; its intent (both fleet agents reachable) is already covered by T1’ssetActiveRightTabtests.
bun test green across the package closes the slice; vite dev smoke: board +
full-height Supervisor conversation side by side, no dock row.
T4 — Deferred: sidebar resize handles
Section titled “T4 — Deferred: sidebar resize handles”Deferred — not part of this change’s implementation PRs. Recorded so the follow-up lands on a clean seam instead of re-designing the widths.
Scope when picked up: drag handles on both sidebars writing the two width
variables; --right-w exists from T1; --left-w is introduced then (the left
sidebar’s width is hardcoded 244px today, app.css:255-257). The drag
interaction, clamping bounds, and persistence are intentionally not
designed here.
Interfaces (the follow-up’s store contract, fixed now so the variable seam stays stable):
// store.ts — deferred follow-up only:leftWidth: Accessor<number>; // px, drives --left-wrightWidth: Accessor<number>; // px, drives --right-wsetLeftWidth: (px: number) => void; // clampedsetRightWidth: (px: number) => void; // clampedFinal gate
Section titled “Final gate”bun test green in apps/ui; markdownlint clean on this record;
living-spec check: docs/specs/product/compass.md contains no dock references
(zero matches for dock|Dock this session), so no spec pointer needs updating
— re-verify at execution time.
- T1 — Fleet conversations into the sidebar: union split +
"supervisor"default (store.ts:39,347-348), groupedActivityBarItem+RIGHT_SIDEBAR_TAB_GROUPS(constants.ts:53-82), activity-bar groups + divider + state dots + ACP panes + chrome hide (RightSidebar.tsx:361-431),--right-w: 400px+ fleet CSS. Tests: store default/reachability; group partition. - T2 — Status tab:
FleetTab+"status", sixth tab entry,StatusPane+ exportedfleetMetrics,.dock-*facet CSS rehomed to.r-status*. Tests:fleetMetrics; partition updated. - T3 — Remove the dock: store members (
store.ts:34,216-220,339-340,511,644-647),components/BottomDock.tsx,App.tsx:5,16-26,92-100,138, grid row (app.css:101-111) + dock CSS (app.css:1221-1361) + header comments; updatestore.test.ts:76,364,370,372-377,400-413. - T4 — Deferred: sidebar resize handles (
--left-w/--right-wseam; contract above; no drag design here). - markdownlint this record clean; re-verify the living spec needs no pointer update.
Global Constraints
Section titled “Global Constraints”- The frozen record’s reasoning is never rewritten. This record supersedes
ADE-shell D7, amends ADE-shell D5, and reverses ADE-shell resolved decision 7.
The only edit to
docs/designs/product/compass-ade-shell/design.mdis the bidirectional supersede/amend pointer the house convention requires — a one-blockquote note atop D7 and D5 pointing forward to this record, so a reader of the frozen decision in isolation learns it was overridden (the sanctioned move in../../platform/docsite.md:283-288, “avoid contradictory records onmain”). No decision prose below those pointers is altered; records still freeze on merge (docs/README.md). - One store, one pattern. Every state change extends the
AppStoreseam (store.ts); no second store, no parallel signal for the same axis (D1). - The fixture is the seam. No new fixture shapes are needed; agent
resolution keeps the existing
STUB_AGENTSread pattern the dock used (BottomDock.tsx:4,69-70), and workstream counts readstore.workstreams(). - No agent-product or persona names. The moat agents appear only by their in-app roles (“Supervisor”, “Warden”); the external ADE mirror is cited through the frozen record (ADE-shell D5), never re-cited from its source here.
- Evidence discipline. Every claim about current code carries file+line
from this repo, read this session on
cook-compass-ade-tabs@ad29c49f. No external-ADE source was read this session, so no new external citations are made — external-mirror rationale is inherited from the frozen record’s already-established D5 evidence. - Prerequisite #467. All cited lines are post-#467; T1 starts only after it merges.
- Tests-first. Each task’s new behavior lands red → green via the Tester;
bun testinapps/uigates every slice. - Commit convention.
docs(product): …for this record;Co-Authored-By: seal <noreply@sealedsecurity.com>; markdownlint clean (blank lines around headings/lists/fences/tables, languages on fences, leading+trailing table pipes).
Spec impact
Section titled “Spec impact”Spec-impact: none. This is UI-app-state only — the AppStore seam, the
components, and CSS. No compass.v1 payload, RPC, or enum changes; the
agent-liveness contract and its UI projection (ADE-shell D9/D11) are untouched.
The living spec (docs/specs/product/compass.md) contains no dock references
to reconcile (checked this session); implementing PRs re-verify per the docs
convention.
- Store churn breaks existing tests by design. The union widening and the
dock-member removal touch
store.test.tsat known sites (store.test.ts:76,364,370,372-377,400-413). Mitigation: T1/T2 are purely additive (dock untouched, all existing tests stay green); every removal and its test updates land together in T3, so a red test always points at its own slice. - A one-slice duplication window. Between T2 and T3 both the dock facet and the Status pane render the same metrics/feed. Accepted: it keeps every intermediate commit functional and the deletion atomic; the window is one slice long.
- The 400px default is a guess until resize lands. On narrow viewports a
fixed 400px column may crowd the board.
--right-wis deliberately the single knob (D4), and T4 (deferred) replaces the guess with user control. - Conditional card-select feedback. With a fleet tab active, clicking a Bridge card no longer visibly changes the sidebar (D6). If this reads as dead-click confusion in use, the remedy is a small selection cue on the workstream tab icons — not an auto-switch (Resolved decisions #1).
Resolved decisions (maintainer-confirmed)
Section titled “Resolved decisions (maintainer-confirmed)”Two forks were surfaced to the maintainer during the design pass and confirmed as designed; they are recorded here as settled decisions (the reasoning survives the freeze). This record poses no open questions.
- Card select never auto-switches the sidebar tab (confirmed). Selecting a Bridge card keeps the active tab — it only updates what the workstream tabs show when visited; it never yanks a Supervisor conversation away. The stated goal is watching the board while talking to the Supervisor, so conversation persistence wins (D6). If dead-click feedback ever proves confusing, the remedy is a selection cue on the workstream tab icons — not an auto-switch.
- Status tab shows fleet metrics + the Supervisor feed, not a merged
fleet-wide feed (confirmed). An exact salvage of the dock facet — the
metrics + the Supervisor’s recent-decision feed as shown today
(
BottomDock.tsx:26-65) — is what the Status tab renders (D3). Merging other agents’ feeds (e.g. the Warden’s,stub-data.ts:289-292) into one stream is a scope expansion this record deliberately avoids.