diff --git a/docs/designs/decisions/ui/DL-418.md b/docs/designs/decisions/ui/DL-418.md new file mode 100644 index 000000000..927c91d1a --- /dev/null +++ b/docs/designs/decisions/ui/DL-418.md @@ -0,0 +1,6 @@ +--- +id: DL-418 +decision: "Compass loads its brand faces itself: `@font-face` rules in `tokens.css` for Space Mono (regular, bold), Departure Mono, and IBM Plex Mono from OFL files bundled under `apps/ui/src/design/fonts/`, `font-display: swap`; host-installed fonts are never assumed" +status: "Active (Matt, 2026-10-07)" +record: ../../ui/compass-brand-parity/design.md#a1--ship-the-brand-faces +--- diff --git a/docs/designs/decisions/ui/DL-419.md b/docs/designs/decisions/ui/DL-419.md new file mode 100644 index 000000000..2b666f98b --- /dev/null +++ b/docs/designs/decisions/ui/DL-419.md @@ -0,0 +1,6 @@ +--- +id: DL-419 +decision: "Compass corners are square, as on rigel.build: the `--cx-radius-*` tokens and every `border-radius` declaration are deleted and a stylelint rule bans the property; this supersedes the radius values in ux-foundation D1" +status: "Active (Matt, 2026-10-07)" +record: ../../ui/compass-brand-parity/design.md#a2a--square-corners +--- diff --git a/docs/designs/decisions/ui/DL-420.md b/docs/designs/decisions/ui/DL-420.md new file mode 100644 index 000000000..778587089 --- /dev/null +++ b/docs/designs/decisions/ui/DL-420.md @@ -0,0 +1,6 @@ +--- +id: DL-420 +decision: "A live agent's state dot is a client-side join over the per-account `AccountSession` that the event stream keeps, projected through `agentDotState`, with comms presence as the fallback. On READY the order is waiting (presence WAITING) > done > idle; done means the last WORKING→READY time is after the agent's last-opened time, which is persisted per workspace in `localStorage`. No proto change" +status: "Active (Matt, 2026-10-07)" +record: ../../ui/compass-brand-parity/design.md#a3--agent-lifecycle-is-a-client-side-join-over-session-status +--- diff --git a/docs/designs/decisions/ui/DL-421.md b/docs/designs/decisions/ui/DL-421.md new file mode 100644 index 000000000..40990c282 --- /dev/null +++ b/docs/designs/decisions/ui/DL-421.md @@ -0,0 +1,6 @@ +--- +id: DL-421 +decision: "`StateDot` `working`/`waiting`/`done` cells follow the rigel.build renders (the other five glyphs keep their DL-150 cells); the glyph scales only by integers, 1× (9px) in dense spots and 2× (18px) in tree rows; the working pulse breathes between 0.7 and 1" +status: "Active (Matt, 2026-10-07)" +record: ../../ui/compass-brand-parity/design.md#a4--site-glyph-cells-and-an-integer-2-scale +--- diff --git a/docs/designs/decisions/ui/DL-422.md b/docs/designs/decisions/ui/DL-422.md new file mode 100644 index 000000000..d5bae1ca6 --- /dev/null +++ b/docs/designs/decisions/ui/DL-422.md @@ -0,0 +1,6 @@ +--- +id: DL-422 +decision: "The agent tree is a main view at `/agents`, the site's Manager tree as depth-indented agent cards, listed first in the left sidebar's view links and first in the palette's view destinations; the left-sidebar tree stays the compact navigator with the 2× state glyph" +status: "Active (Matt, 2026-10-08)" +record: ../../ui/compass-brand-parity/design.md#a5--the-agent-tree-the-highlight +--- diff --git a/docs/designs/decisions/ui/DL-423.md b/docs/designs/decisions/ui/DL-423.md new file mode 100644 index 000000000..7c95e03fb --- /dev/null +++ b/docs/designs/decisions/ui/DL-423.md @@ -0,0 +1,6 @@ +--- +id: DL-423 +decision: "A channel message renders the rigel.build anatomy on `.msg` directly: author handle in text color, a kind tag (`AGENT`/`HUMAN`/`SYSTEM`), an `ASK` flag for an ask block and a `STEER` flag for an @-mention of an agent, time pushed right in faint, body in dim text; a human message sits on the raised surface with a 2px accent left rule. The `.msg-role` author colors and `--cx-author-system` are deleted" +status: "Active (Matt, 2026-10-07)" +record: ../../ui/compass-brand-parity/design.md#a6--message-anatomy +--- diff --git a/docs/designs/decisions/ui/DL-424.md b/docs/designs/decisions/ui/DL-424.md new file mode 100644 index 000000000..75de4f9a8 --- /dev/null +++ b/docs/designs/decisions/ui/DL-424.md @@ -0,0 +1,6 @@ +--- +id: DL-424 +decision: "Compass text entry uses the D3 primitives, each imported by the component that uses it: top search and the shortcuts search are `cx-search`, new-topic fields are `cx-input`, and the channel composer is a `textarea.cx-composer` that grows to its content up to 40vh (Enter sends, Shift-Enter inserts a newline, Enter during IME composition does nothing) with a primary `cx-btn` send; a new `--cx-border-field` (mute) edges text boxes" +status: "Active (Matt, 2026-10-07)" +record: ../../ui/compass-brand-parity/design.md#a7--text-entry-uses-the-primitives +--- diff --git a/docs/designs/decisions/ui/DL-425.md b/docs/designs/decisions/ui/DL-425.md new file mode 100644 index 000000000..eed67c537 --- /dev/null +++ b/docs/designs/decisions/ui/DL-425.md @@ -0,0 +1,6 @@ +--- +id: DL-425 +decision: "The topbar brand is the Compass needle mark alone at 24px, replacing the plain-text \"Compass\" name, with \"Compass\" as its accessible name; purple lives only in that SVG asset; `surfaces.md` Mark placement names the needle instead of the sigil wordmark" +status: "Active (Matt, 2026-10-08)" +record: ../../ui/compass-brand-parity/design.md#a8--the-compass-mark-in-the-top-bar +--- diff --git a/docs/designs/decisions/ui/DL-430.md b/docs/designs/decisions/ui/DL-430.md new file mode 100644 index 000000000..866438ebe --- /dev/null +++ b/docs/designs/decisions/ui/DL-430.md @@ -0,0 +1,6 @@ +--- +id: DL-430 +decision: "Status greens and type weight follow rigel.build: `--cx-ci-pass` is the working green and `--cx-review-approved` is cyan (`--cx-ok` stays the syntax-tier green); chrome carries hierarchy by color, case, and letter-spacing, and a stylelint rule allows only weight 400 outside markdown headings" +status: "Active (Matt, 2026-10-07)" +record: ../../ui/compass-brand-parity/design.md#a9--site-greens-and-hierarchy-by-color +--- diff --git a/docs/designs/decisions/ui/DL-437.md b/docs/designs/decisions/ui/DL-437.md new file mode 100644 index 000000000..0edf3624c --- /dev/null +++ b/docs/designs/decisions/ui/DL-437.md @@ -0,0 +1,6 @@ +--- +id: DL-437 +decision: "Compass surfaces follow rigel.build with stronger pane edges: shell chrome (topbar, both sidebars, the Session Log) sits on `--cx-bg` and pane heads on `--cx-bg-panel`; `--cx-border` is night-2 for lines inside a pane, and `--cx-border-strong` is selection and draws every pane edge (both sidebars, the Session Log, and the topbar's bottom edge)" +status: "Active (Matt, 2026-10-08)" +record: ../../ui/compass-brand-parity/design.md#a2b--surfaces-and-lines +--- diff --git a/docs/designs/decisions/ui/DL-438.md b/docs/designs/decisions/ui/DL-438.md new file mode 100644 index 000000000..84385a5e4 --- /dev/null +++ b/docs/designs/decisions/ui/DL-438.md @@ -0,0 +1,6 @@ +--- +id: DL-438 +decision: "Backlog and Done are Bridge segments that keep their list layouts: the control is `Issues | PRs | Backlog · N | Done`; `/backlog` and `/done` stay routed and render the Bridge on that segment, so every path, chord, and palette entry keeps working; the left sidebar drops both links; Issues/PRs stay the DL-097 local signal, and the board keyboard group runs only on them" +status: "Active (Matt, 2026-10-08)" +record: ../../ui/compass-brand-parity/design.md#a10--backlog-and-done-inside-the-bridge +--- diff --git a/docs/designs/ui/compass-brand-parity/design.md b/docs/designs/ui/compass-brand-parity/design.md new file mode 100644 index 000000000..fdcea9d16 --- /dev/null +++ b/docs/designs/ui/compass-brand-parity/design.md @@ -0,0 +1,754 @@ +# Design: Compass Brand Parity with rigel.build + +Builds on: [UX foundation](../compass-ux-foundation/design.md) (DL-148, DL-150, DL-184), [glyph primitives](../compass-glyph-primitives/design.md) (DL-367), [channels in the agent tree](../compass-channels-in-agent-tree/design.md) +Refs: RIG-4774 (parent RIG-4770); RIG-1622 (agent tree); ledger DL-418 to +DL-425, DL-430, DL-437, DL-438 +Depends on: #1811 (RIG-4771 chrome cleanup), #1814 (RIG-4772 session +stream), #1644 (DL-399 local baselines) +Siblings: RIG-4771 (chrome cleanup, pane edges), RIG-4772 (Session Log +stream), RIG-4773 (Bridge toggle), RIG-4775 (Settings), RIG-4776 +(Backlog/Done; Matt's answer lands here as A10). This record does not redo +their work. + +## Problem / Intent + +Matt's dogfood review: the Compass UI does not look like +[rigel.build](https://rigel.build). The colors look bad, the agent status +icons do not show, messages are plain text, the composer does not read as a +composer, and the top search, new-topic name, and first-message fields look +unstyled. This record finds the concrete causes in `apps/ui/src/` and plans +eleven small PRs that bring Compass to the marketing site's look, with the +agent tree as the highlight. Matt's RIG-4776 answer on the Backlog and Done +views is folded in as A10. + +Out of scope: Settings (RIG-4775), usage display (RIG-4771 deletes +`UsageBar`), and the pane-edge rules themselves. +PR #1811 (RIG-4771) puts the `.right` and `.log-panel` edges on +`--cx-border-strong` and hands the token values back to this record; T2b +sets them. + +## Approach + +The reference is the public site: [rigel.build](https://rigel.build) and +[rigel.build/compass](https://rigel.build/compass), whose shipped CSS uses +the same `--rigel-*` primitive values as `apps/ui/src/design/tokens.css` +(night `#011627`, panel `#0e2a45`, night-2 `#0b2942`, fog `#d6deeb`, haze +`#89a4bb`, mute `#5f7e97`, blue `#82aaff`). The primitives already match. +The gaps are in five places: fonts that never load, a surface and line model +that hides edges, semantic aliases and weights that differ from the site, +component CSS that is never imported, and an agent-state data path that +drops most states. A10 is not a site gap: the site has no Backlog or Done +view. It carries Matt's RIG-4776 answer. + +### Gap audit + +| Site element (rigel.build) | Compass today | Gap | Task | +| --- | --- | --- | --- | +| Brand faces load from `@font-face` (`/fonts/SpaceMono-Regular.ttf`, `/fonts/DepartureMono-Regular.otf`) | No `@font-face` anywhere under `apps/ui`; `--cx-font-ui: var(--rigel-mono)` names Space Mono, but nothing loads it, so the host falls back to `ui-monospace` | The whole UI renders in the wrong face outside CI (CI pins the faces through fontconfig, so baselines hide it) | T1 | +| Display face for headings (`.bridge-heading` in Departure Mono 22px) | `--cx-font-display` is used by one rule in `app.css`; the face never loads | Fixed when the face loads | T1 | +| Square corners everywhere: cards, inputs, `.seg`, and square status pips (`.dot` 10×10, `.ci-badge` 7×7) | `--cx-radius-sm/md/lg` are 3/6/10px, used by 57 declarations; 26 more literal radii (`.tree-agent` 4px, `.bridge-link .count` 10px, round pips at 50%, 999px pills) | Rounded corners and round pips are off-brand | T2a | +| Night-2 hairlines drawn on the night page (`html,body{background:var(--rigel-night)}`, `.topbar{border-bottom:1px solid var(--rigel-night-2)}`); raised is kept for cards, menus, the root card, and the human message | Topbar, `.left`, `.right`, and `.log-panel` sit on `--cx-bg-raised`; `--cx-border` is `color-mix(in srgb, var(--rigel-panel) 70%, transparent)`; night-2 and raised are the same hex (`#0b2942`) | Pane edges barely show; the site's own line color would vanish on raised chrome | T2b | +| Form input: raised ground, mute border, 11×14 padding, blue border on focus (site `input` rule) | Text boxes have no visible edge | No field border token | T7 | +| CI success badge in green, approved review in cyan (selectors in A9) | `--cx-ci-pass` and `--cx-review-approved` both resolve to `--cx-ok`, the syntax-tier `--rigel-success` (`#22da6e`) | Wrong greens on every board card | T9 | +| Hierarchy by color, case, and letter-spacing; no `font-weight` outside `@font-face` | 48 `font-weight: 600/700/bold` rules in `app.css`; Space Mono ships 400 and 700 only and `base.css` sets `font-synthesis: none`, so every 600 renders bold | Chrome reads heavy and loud | T9 | +| Agent state glyph: 9×9 cells drawn at 16px (`.ac-glyph svg{width:16px;height:16px}`), state-colored, state label beside it; alive glyphs pulse 0.7→1 (`ac-pulse`) | `StateDot` draws 9×9 CSS px; live data reaches only 4 of 8 states; most rows show a grey hollow square; `cx-state-dot-pulse` dips to 0.4 | Icons "do not show" (root cause below) | T3, T4 | +| Manager tree: depth-indented agent cards with spine elbows, glyph, name, colored state label, issue chip; root card raised with a 2px blue left rule; a blue pip runs down live spines (`spine-flow 1.4s` on `.tree-spine[data-flow="1"]`) | Left-sidebar text rows (`.tree-agent`, 12px name, 9px dot); `.tree-children` is a static 1px rule; no overview | No agent overview, no tree motion | T5 | +| Thread message: author in fog 12px, kind tag (`MANAGER`, `HUMAN`), `ASK` flag in blue and `STEER` flag in cyan (`.msg-flag.ask`, `.msg-flag.steer`), time right-aligned in mute, body in haze 13px; human messages on raised with a 2px blue left rule | `.msg-role` colors authors blue (`--cx-accent`), bright green (`--cx-ok`), or magenta (`--cx-author-system`), weight 600; `.msg-at` sits beside the name; no tag or flag | Messages read as plain text with loud names | T6 | +| (no composer on the site; the site `input` rule is the reference) | `Composer` is a one-line ``; its border is `--cx-border` on `--cx-bg-raised` | Composer does not read as a composer | T7 | +| Text inputs | `.new-topic-name`/`.new-topic-message` carry only flex sizing (browser-default box); `.topbar-search-input` has no `font: inherit` and a faint border | Fields look unstyled | T7 | +| (site CSS is all loaded) | Six primitives are never imported: `ask`, `badge`, `input`, `loader`, `panel`, `tree`. Two have users today: `ShortcutsOverlay` renders `cx-search`, and `Palette` renders `.cx-loader[data-topology="bar"]`; both are unstyled | Users of an unimported primitive fail silently | T7 | +| Compass needle mark (`/compass-c-needle.svg`, purple north half on the navy tile) | Top bar shows the 11×11 `logo` `Glyph` in `--cx-accent` blue beside a plain-text "Compass" | Product mark missing | T8 | + +### Why the status icons do not show + +The SVG itself is fine: `StateDot` (`apps/ui/src/components/StateDot.tsx`) +emits a `viewBox="0 0 9 9"` `crispEdges` SVG of `rect` cells filled with +`currentColor`, and `state-dot.css` colors it per `data-state`. The cause is +upstream and in sizing: + +1. **The live set is coarse.** Every agent row renders + `` (`AgentLeaf` in + `LeftSidebar.tsx`). `lifecycle` comes from `joinAgents` + (`apps/ui/src/roster.ts`): `lifecycle: info ? info.lifecycle : "stopped"`, + where `info` is the comms presence entry. Presence is a 4-value enum + (`AgentPresence` in `proto/compass/v1/comms.proto`: IDLE, WORKING, + WAITING, OFFLINE), and `presenceLifecycle` (`apps/ui/src/live/adapt.ts`) + maps `OFFLINE → "stopped"`. The server's `presenceFor` + (`go/internal/presence/presence.go`) folds STOPPED, ERRORED, + DISCONNECTED, and "no session" into OFFLINE. So live Compass can only + show working, idle, waiting, and stopped. Done, paused, error, and + disconnected never render. +2. **The full session state is dropped.** `AgentSessionStatus` (with its + `state`) does arrive on `SubscribeEvents`, but on main `applyPayload` in + `runEventStream` (`apps/ui/src/live/events.ts`) keeps only + `runtime.set(account, adaptRuntimeMarker(payload.value))`. + `adaptRuntimeMarker` documents that "the identity/lifecycle fields the + status also carries are the presence path's job". #1814 (RIG-4772) keeps + the state for the Session Log, but nothing feeds it to the dot. + `agentDotState` (`apps/ui/src/agent-state.ts`), the 8-state projection + built for this, has no production caller. +3. **Most rows are grey.** Any agent without a live session is "stopped": a + hollow outline in `--cx-st-stopped` (`--rigel-mute`). Idle is a mute + block too. In a fleet that is mostly idle or offline, the colored glyphs + (green `»`, amber `?`, cyan tick, red `!`) almost never appear. +4. **The glyph is small.** `.cx-state-dot` is `width: 9px; height: 9px` + (`state-dot.css`). The brand spec names "the 12px row-dot size" + (`docs/specs/brand/state-icons.md`), and the site draws the tree glyph at + 16px. +5. **Geometry drifted.** The site's cells for `working`, `waiting`, and + `done` differ from `STATE_CELLS` (A4 lists them). `idle` matches. + +### A1 — Ship the brand faces + +Load Space Mono (regular, bold), Departure Mono, and IBM Plex Mono with +`@font-face` in `tokens.css`, from font files under +`apps/ui/src/design/fonts/`. The files are the OFL faces already committed +in `apps/eng-docs/public/fonts/`. Vite bundles them as assets for both hosts. +`font-display: swap`, as on the site. + +CI pins the faces through fontconfig (`fontDirs` in +`tools/toolchain/chromium-e2e-env.nix`: nixpkgs `google-fonts` Space Mono and +`departure-mono`). The `SpaceMono-Regular.ttf`, `SpaceMono-Bold.ttf`, and +`DepartureMono-Regular.otf` files in `apps/eng-docs/public/fonts/` have the +same SHA-256 as those nixpkgs files, so the glyph outlines in CI do not +change. Shots can still differ if Chromium rasterizes a webfont differently +from a system face; T1 treats any diff as expected and recaptures. + +### A2a — Square corners + +The site draws no rounded corner except one footer tile, and its status pips +are square (`.dot` 10×10, `.ci-badge` 7×7). `docs/specs/brand/surfaces.md` +asks for "flat square cards". Compass has 57 `var(--cx-radius-*)` +declarations and 26 literal radii in CSS, including `.tree-agent` 4px, the +round pips (`border-radius: 50%`), and 999px pills. `boot-styles.ts` sets one +more, `border-radius:4px`, in a TypeScript style string. + +Delete `--cx-radius-sm`, `--cx-radius-md`, and `--cx-radius-lg` from +`tokens.css`, and delete every `border-radius` declaration under +`apps/ui/src`. A stylelint rule keeps the CSS that way; it does not read +`boot-styles.ts`, so T2a edits that file by hand. Three tokens all set to 0 +would be dead weight, and would still leave the 26 literal radii. This +supersedes the radius line of ux-foundation D1 ("Radius: +`--cx-radius-sm|md|lg` (3/6/10)"). + +### A2b — Surfaces and lines + +The site draws night-2 lines on night ground: `html,body` are night, +`.topbar` has `border-bottom:1px solid var(--rigel-night-2)`, and its product +mock puts thread and lane heads on panel. Raised is kept for cards, menus, +the root agent card, and the human message. Compass puts all chrome on +`--cx-bg-raised`: `.topbar`, `.left`, `.right`, and `.log-panel`. Night-2 and +raised are the same hex (`#0b2942`), so the site's line color is invisible +on Compass chrome. `--cx-border` (panel at 70%) and `--cx-border-strong` +(panel) are within a few levels of raised, which is why the pane edges in +the ask do not show. Note that `apps/ui/src/design/surfaces.md` already +puts the topbar on `--cx-bg`, so `app.css` has drifted from its own spec. + +Compass takes the site's surface model with pane edges one step stronger +(DL-437). Chrome (`.topbar`, `.left`, `.right`, `.log-panel`) moves to +`--cx-bg`, and heads (`.conv-head`, `.av-pane-head`) to `--cx-bg-panel`. +`--cx-border` becomes night-2, the site's line, for lines inside a pane. +`--cx-border-strong` becomes selection and draws every pane edge: 1.57:1 on +night, against the site's 1.23:1. #1811 puts the `.right` and `.log-panel` +edges on it; T2b moves the `.left` and `.topbar` edges from `--cx-border` to +it, or the left edge would be night-2 on night. No new surface token. +Keeping raised chrome with selection and mute lines was rejected: it is not +the site's look. A separate new token, `--cx-border-field: var(--rigel-mute)`, +is the site's input edge, used only by `input.css` (A7). + +### A3 — Agent lifecycle is a client-side join over session status + +T3 builds on #1814 (RIG-4772). That PR keeps one `AccountSession +{ sessionId, state }` per agent account from `AgentSessionStatus` events in +`runEventStream`, emits it through `onSessions`, clears it on resync, and +stores it as `accountSessions`. T3 adds no second map. It projects that map +through `agentDotState` in `joinAgents`: + +| Inputs for an account | `lifecycle` | +| --- | --- | +| session known | `agentDotState(session.state, { awaitingInput, turnDoneUnopened })` | +| no session, presence entry | `presenceLifecycle(presence)` (today) | +| neither | `"stopped"` (today) | + +- `awaitingInput` is `presence?.lifecycle === "waiting"`. The server sends + presence WAITING for STARTING or READY with an open ask (`presenceFor`). + Today `agentDotState` reads `awaitingInput` only for STARTING and WORKING; + READY returns `turnDoneUnopened ? "done" : "idle"`. T3 changes the READY + arm so the order is **waiting > done > idle**. Without that change the + join would turn every waiting agent grey. +- `turnDoneUnopened` is `turnEndedAtUnixMs > lastOpenedAt(account)`. + - `AccountSession` gains `turnEndedAtUnixMs?: number`. `applyPayload` sets + it to the event time (`SubscribeEventsResponse.at_unix_ms`) when the + account's previous state in the map is WORKING and the new state is + READY. Other events for the same `sessionId` carry the previous value. + - `lastOpenedAt` is a per-agent time persisted in `localStorage` under + `compass.lastOpened.${workspace}`, the same shape as the pin store + (`loadPinnedAgents`/`savePinnedAgents`). One store effect writes it: + while the focused view's route is `view === "agent"`, it records + `max(Date.now(), turnEndedAtUnixMs)` for that agent, and again on each + new turn end. The `max` stops a client clock behind the server from + leaving the tick on. The effect keys on the route, not on `openAgent`, + so every way in counts: `openAgent`, a Mod-click or middle-click + (`dispatchLayout` open), a deep link, a tab focus, back/forward, and an + agent DM. A background tab open does not focus the view, so it does not + count. + - Because both sides are times, a cold-start ring replay and a page reload + give the same answer as the live stream. A resync clears the map; the + replay re-observes the edge with its original time. The residual: an + agent whose WORKING event fell out of the 1024-event ring shows `idle`, + not `done`. + +No proto change. Done, error, and disconnected become reachable, and +waiting is kept. There is no cold-start seed: `GetAgentStatus` is +admin-only (`classifyProcedure` in `go/internal/auth/admin_gate.go`) and its +snapshot drops STOPPED and ERRORED (`isTerminal` in +`go/internal/board/projection.go`). An agent whose last status left the ring +shows its presence state. A server status read open to the owner is filed +only if dogfood shows a missing `error`. + +### A4 — Site glyph cells and an integer 2× scale + +Replace the `working`, `waiting`, and `done` entries of `STATE_CELLS` with +the site's cells. Site `working` is two 5-cell chevrons +(`1,2 4,2 2,3 5,3 3,4 6,4 2,5 5,5 1,6 4,6`); Compass draws two 7-cell +chevrons over rows 1-7. Site `done` is a 7-cell tick +(`1,4 2,5 3,6 4,5 5,4 6,3 7,2`); Compass draws 9 cells. Site `waiting` is +`3,1 4,1 5,1 2,2 6,2 6,3 4,4 5,4 4,5 4,7`. The other five glyphs have no +site render and stay. `StateDot` gains `scale?: 1 | 2`; `2` sets +`data-scale="2"` and an 18px box. Integer scale keeps every cell a whole +2×2 block; the site's 16px is a non-integer 1.78×. The sidebar tree and the +agent tree (A5) use 2×; dense spots (right-sidebar tab badge, Bridge cards, +agent header) keep 1×. + +The site's working glyph breathes from 0.7 to 1 (`ac-pulse`). Compass +`cx-state-dot-pulse` dips to 0.4, which reads as blinking. Set the low +point to 0.7. + +### A5 — The agent tree, the highlight + +The tree is a main view at `/agents` (DL-422), first in the left sidebar's +view links and first in the palette's view destinations. It is deep-linkable +and a top-level window per DL-160. A Bridge `Tree` segment and a +sidebar-only restyle were rejected. The view draws the site's Manager tree +from `agentTree(store.agents())` (`stub-data.ts`): + +- Row: `padding-left: depth × 26px`, with a 1px elbow spine in + `--cx-border` to the parent (site `.tree-row`, `.tree-spine`). +- Card: grid `20px 1fr auto`; 2× `StateDot`; handle; meta line with + `AGENT_STATE_LABEL[state]` in the state color; issue chip on the right. No + child count: the site's `N workers` counts dispatched workers, Compass has + no live worker source, and the spine already shows child agents. +- Root cards: raised ground and a 2px `--cx-accent` left rule. +- Issue chip (`agentIssueChip`): the agent's active issues + (`assignee === account.id && isActiveState(state)`); none → no chip; one → + `issueKey(issue, multiForge)`; more → `N open`. +- Spine flow: a spine to a `working` child carries `data-flow="1"`, and a + 1×6px `--cx-accent` pip runs down it (site `spine-flow`, 1.4s). This is the + chase-light pip that ux-foundation D6 names in the Manager-tree reference. + It is the view's one unbounded motion; the state-dot column stays the + sanctioned exception (`apps/ui/src/design/motion.md`, Budget). The period + is a new token zeroed under reduced motion, like `--cx-pulse-period`. +- Click or Enter on a card calls `store.openAgent(account.id)`. + +The sidebar tree stays the compact navigator and gets the 2× glyph (A4). + +### A6 — Message anatomy + +`MessageRow` (`ChannelView.tsx`) is the only emitter of `.msg`, and the agent +view's chat goes through the same `ChannelView`, so the rules restyle `.msg` +directly with no extra scope. The row emits the site anatomy: + +- `.msg-author`: handle, `--cx-text`, `--cx-text-sm`. +- `.msg-tag[data-kind]`: `AGENT`, `HUMAN`, or `SYSTEM` from the author's + `kind`; 1px border, `--cx-text-xs`, letter-spaced. The site says + `MANAGER`; Compass uses its own domain word. +- `.msg-flag`: `ASK` (`--cx-accent`) when the message has an ask block, and + `STEER` (new `--cx-msg-steer`, cyan) when a text block @-mentions an agent + account. The `Composer` doc defines an @-mention of an agent as a steer. +- `.msg-at`: pushed right (`margin-left: auto`), `--cx-text-faint`. +- Body: `--cx-text-dim` at `--cx-text-md`. A human message gets + `--cx-bg-raised` and a 2px `--cx-accent` left rule, with body in + `--cx-text`. + +Delete the `.msg-role` color rules and `--cx-author-system`; nothing else +uses them. + +### A7 — Text entry uses the primitives + +Each component imports the primitive CSS it uses, as `RuntimeMarker.tsx` +imports `runtime-marker.css` and `LayoutNotice.tsx` imports `button.css` and +`toast.css`. Six primitives are never imported today. This wires the two +that have or gain a user (`input`, `loader`) and adds a `button.css` import +where `ChannelView` starts to render `cx-btn`. The four with no user (`ask`, +`badge`, `panel`, `tree`) stay unimported until a component first renders +them; importing unused CSS adds bytes and no fix. + +- `TopBarSearch` imports `input.css`; its input → `cx-search`. +- `ShortcutsOverlay` imports `input.css`; its `cx-search` field starts + rendering as designed. +- `Palette` imports `loader.css`; its `.cx-loader[data-topology="bar"]` + starts rendering. +- `ChannelView` imports `input.css` and `button.css`. + - `NewTopic` name and first-message inputs → `cx-input`; start button → + `cx-btn`. + - `Composer` → `