Decisions

ADR-081: UI shell consolidation grammar — scopes, card shape, empty-state prose

The Platform UI (ai-frontend) has reached ~70 shipped routes across the five modes (Home / Ship / Operate / Infra / Experiment), the persona homes (`/me`, `/ops`, `/team`), a growing set of organisation-level fleet…

#081

ADR-081: UI shell consolidation grammar — scopes, card shape, empty-state prose

Status: Proposed (amended 2026-04-28) Date: 2026-04-24 Extends: ADR-054 (three-mode routes), ADR-058 (five-mode platform UI), ADR-059 (project-first IA), ADR-062 (design-system + component strategy), ADR-065 (persona-aware IA + niceties), ADR-066 (microvisualisations), ADR-070 (playbook UI contract)

Context

The Platform UI (ai-frontend) has reached ~70 shipped routes across the five modes (Home / Ship / Operate / Infra / Experiment), the persona homes (/me, /ops, /team), a growing set of organisation-level fleet routes (/projects, /playbooks, /docs, /pipelines, /operators, /assets, /build, /patches), and the project trees (/[project], /platform/[project], /restricted/[project] and their tabs). Each landed in its own PR, each solved its own local hierarchy; the aggregate reads as "assembled by many agents" rather than "built by one team".

On 2026-04-24 a UI consolidation audit of the shipped surface (ai-frontend/docs/ui-consolidation-audit-2026-04-24.md, shipped in ai-frontend PR #191 / v0.16.79) graded the whole fleet on four higher-order axes — navigation shell, card grammar, empty-state prose, and mode-tinting coverage. The findings that force this ADR:

  1. 38 cards across 10 shape-clusters. Variance is not inherently harmful — cluster-A ("H2 plain + <p class="empty">") already holds 13 of the 38 cards and is the de-facto canonical shape. But cluster-A is emergent, not declared; a new card has no reference to hit.
  2. 33 unique empty-state strings in 8 tone patterns. Ranges from terse one-liners ("No playbook runs yet.") to multi-paragraph explainers ("No pushes in the last snapshot. When you push to a fleet repo, it appears here."). Within-file inconsistency exists (e.g. infra/hetzner ships both "None." and "No block volumes attached."). Six distinct "Loading…" strings.
  3. Zero mode-tint palette mismatches. The mode-tokens.css
    • <html data-mode> mechanism is the one part of the shell that works cleanly across every route group — including fleet and project routes that correctly inherit Home's calm slate per ADR-059 §9. Don't touch it.
  4. Two structural drifts. (1) modeFromPathOrNull has no fallback for fleet-scope, so the mode-pill underlines Home on 14 non-Home fleet routes (/pipelines, /playbooks, /docs, /projects, /assets, /build, /patches, and their sub-routes). (2) /experiment (mode home) and /experiments (fleet catalogue) collide — same name, one letter apart, different scopes, different palettes.

The proximate cause of 3-of-4 structural problems is the same absence: fleet-scope is implicit but not ADR'd. ADR-054 named modes. ADR-059 §9 named project scope. Fleet scope — the organisation-level routes that are neither modes nor projects — accumulated as a fallback through the FLEET_TOP_SEGMENTS allowlist in mode-store.svelte.ts:121, which exists to exclude fleet paths from project-path matching, not to document what fleet scope is. Every fleet-route inconsistency (mode-pill underline, sidebar content, card layout) traces back to this unnamed scope.

This ADR names the four scopes, declares a canonical card shape, a canonical empty-state grammar, and a canonical loading string. It does not specify Svelte components (that is ADR-062's and gyrum-ui's job) and it does not prescribe a big-bang rewrite (see §7).

Decision

Adopt a four-scope taxonomy for the Platform UI shell, declare a canonical card grammar and empty-state tone, and execute the consolidation as a trickle — one small PR per session against the audit's Top-10 — so parallel product work continues uninterrupted.

§1 — Scope taxonomy (first-class decision)

Four scopes. Each route in the Platform UI belongs to exactly one. Shell behaviour (mode-pill, left rail, palette) is a pure function of the scope.

Scope URL shape Mode pill Left rail Palette
Mode /, /ship*, /operate*, /infra*, /experiment (singular) Active mode underlined Mode-specific sections Mode palette
Fleet /projects, /playbooks, /docs, /pipelines, /operators, /assets, /build, /patches, and their index-style detail pages that aren't inside a project Dim ( marker, no underline) Fleet sections Home calm slate (inherited)
Project /[project], /platform/[project], /restricted/[project] and their tabs (Overview / Build / Run / Docs / Todos) Dim ( marker, no underline) Project tabs with Fleet link below (ADR-059 §9) Home calm slate (inherited)
Build (optional, TBD) /[buildID], /retro/[buildID], /compare/[idA]/[idB] — detail/report viewers Dim Either Fleet rail or no rail (call-out below) Home calm slate (inherited)

Build scope is a deliberate placeholder. The /[buildID] viewer ships a bespoke StickyHeader with its own status glyphs and a self-contained breadcrumb — a mini-app nested under the root shell rather than a page in it. This is ADR-053-era drift and was called out in the tokens audit before this one. ADR-081 does not force a decision — either accept Build as a fourth peer scope with explicit chrome-override rules, or refactor the viewer to fit Fleet scope with its existing rail. The choice is deferred; §10 records it as a follow-up open question. Until then, Build routes are documented-as-drift but not prescribed-as-fixed.

The mode-pill treatment for Fleet and Project is identical — both are dim, both show the marker, neither underlines a mode tab. A user on /pipelines and a user on /focus both see the same "no active mode" state; the sidebar content (fleet sections vs project tabs) tells them which scope they are in. This preserves the ADR-054/058 five-mode contract for /ship, /operate, /infra, /experiment and / while giving Fleet + Project a clean "not-a-mode" visual.

Palette decision. Fleet inherits Home's calm slate rather than taking its own colour. The audit recorded zero palette mismatches today because the code already does this via the modeFromPath fallback, and the operator's experience has been consistent. A dedicated fleet palette (e.g. a warmer neutral) would close a symmetry gap but would require new tokens, new contrast testing, and new cross-surface propagation into gyrum-ui when it lands — for no present user-facing benefit. Stay with Home-inherited slate. If a future need emerges (e.g. "fleet routes feel too similar to project routes"), a sibling ADR revisits the call.

§2 — Card grammar

The canonical card shape, derived from cluster-A in the audit (13 of the 38 shipped cards):

┌─ Title    ⦿count ──────────┐
│ ⦿ dot · content · action   │  ← 0..n rows
│ ⦿ dot · content · action   │
│ [empty state if 0 rows]     │
│ [error state with retry]    │
└─────────────────────────────┘

Parts:

  • HeaderH2 title, optional count badge, optional "See all →" link at the right. Leading icon only when it is a semantic-supporting label per ADR-062's visual-language guide (§1.5) — "encouraged but not required". The HomePage Fleet-health and Recent-deploys cards are the only two that carry H2-with-icon today; either promote icons to every card header (Top-10 item #9) or drop the two — don't leave as an outlier.
  • Row — optional status dot (ADR-062's <gy-status-pill> token vocabulary) + content (truncates with ellipsis) + optional right-aligned action. Rows are 0..n; a list of zero rows falls through to the empty state.
  • Empty state — one-line muted text; optional illustration slot; optional CTA. Prose grammar in §3.
  • Error state — inline role="alert" region, one-line message, Retry button. Rendered in place of rows, not overlaying them.

ADR-081 specifies the shape. It does not specify the component. Component extraction is ADR-062's remit; gyrum-ui lands a <gy-card-section> (or equivalent) when the trickle PRs have converged enough card bodies to make the API stable. The audit already names <SectionHeader> (H2 + count-chip + "See all") as the first extraction candidate — that work happens in gyrum-ui, not ADR-081.

Cards that are legitimate outliers (cluster-E link-card grids on /projects, cluster-I takeover on ProjectRun's "No runtime" banner, cluster-H chart placeholders on ExperimentHome) do not need to retrofit to cluster-A. The grammar applies to cards that render data rows; it does not apply to cards whose job is navigation or chart canvas. A trickle PR that touches an outlier card must either adopt the grammar or record the deliberate deviation in a short comment.

§3 — Empty-state prose grammar

One canonical tone: calm, one sentence, action-oriented when applicable. Three templates cover the full population:

Template A — first-use empty
"No X yet."

Template B — event-driven empty
"No X yet. When Y happens, they'll appear here."

Template C — permanent empty
"No X. This Z only does W."

Examples (every one drawn from the audit):

  • Template A: "No playbook runs yet." (RecentRunsCard) — cluster-A canonical.
  • Template B: "No pushes in the last snapshot. When you push to a fleet repo, it appears here." (/me) — hint-slot form, same grammar.
  • Template C: "No triggers. This playbook only runs on manual invocation." (TriggersList) — asserts state, explains why.

Kill these patterns (the audit's §3.9 worst mismatches):

  • Multi-paragraph explainers inside a card empty state. If the region needs three sentences, the page needs a help panel. Move the explanation there.
  • Different verbs for the same concept. The audit flagged "reporting" vs "configured" vs "registered" across near-identical card populations. Pick one verb per concept and hold it.
  • Placeholder text like "After X tranche" — this was already killed from /operate before the audit. Don't reintroduce.
  • Within-file inconsistency. "None." vs "No block volumes attached." on the same infra/hetzner page is the worst shape of drift — both sit next to each other in the same rendered DOM. The three-word phrase with a full stop wins neither on information nor on rhythm. Always prefer the one-sentence form.

Tone tolerance. /me, /ops, and /projects surfaces are new-user onboarding pages; their multi-sentence "how this populates" empties are genuinely helpful and are codified as Template B via the hint slot on the existing <EmptyState> component. Power-user surfaces (OperateHome, infra detail pages) stay on Template A — terse, no hint. The split is intentional; the grammar is shared.

§4 — Loading-state grammar

One phrasing: "Loading…" (with the ellipsis character, not three periods). Optional noun only when the load is unambiguous and the noun adds information: "Loading alerts…", "Loading TODO.md…". Inside a card with an H2, the noun is redundant with the heading — use the bare "Loading…" form.

Don't invent new verbs ("Fetching…", "Retrieving…", "Pulling…"). Don't mix ellipsis styles. The audit found six distinct "Loading" strings; one is sufficient.

§5 — Mode-pill rule

The mode-pill's active state is a pure function of the scope:

modeFromPathOrNull(pathname) →
  'home' | 'ship' | 'operate' | 'infra' | 'experiment' |
  'fleet' | 'project' | null

Mapping (all path matches are prefix-matches unless noted):

  • /'home'
  • /ship*'ship'
  • /operate*'operate'
  • /infra*'infra'
  • /experiment (exact) → 'experiment'
  • /[project-slug], /platform/*, /restricted/*'project'
  • /projects, /playbooks, /docs, /pipelines, /operators, /assets, /build, /patches'fleet'
  • /experiments (plural) → 'fleet' (see §10 open question)
  • Everything else → null

The UI treats 'project' and 'fleet' identically: mode-pill renders in its dim state with a marker in place of any underline. No highlight. The five underline slots are reserved for the five modes; they are not overloaded to mean "fleet" or "project".

Why the function returns 'fleet' rather than null for fleet routes. Returning null would work for the dim-pill case, but callers that want to distinguish fleet from "unknown" (sidebar, analytics, keyboard shortcut enablement) need a named value. The function returns the scope; the pill's render logic collapses 'fleet' | 'project' into "dim" at the view layer.

This closes Top-10 item #1 (mode-pill underlines Home on 14 fleet routes) with a bounded two-line change to modeFromPathOrNull.

§6 — Relationship to existing ADRs

ADR-081 is a pure extension — it does not contradict any accepted ADR. It names shapes that were previously emergent and codifies the majority-winning pattern from the shipped surface.

  • ADR-054 (three mode-home routes). Unchanged. ADR-081 does not touch the mode-home route contract; it only adds peer scopes (Fleet, Project) that were already implicit.
  • ADR-058 (five-mode platform UI). Unchanged. The five-mode pill, palette table, and Cmd+1..5 shortcuts are preserved verbatim. ADR-081 adds the dim state; it does not change any mode's active state.
  • ADR-059 (project-first IA). Unchanged. ADR-081 cites §9 as the palette precedent (projects inherit Home's calm slate) and extends the same inheritance to Fleet. ADR-059's project-tab shape (Overview/Build/Run/Docs/Todos) is preserved.
  • ADR-062 (design system + component strategy). Unchanged. ADR-081 references the token vocabulary but does not specify component APIs. gyrum-ui's <gy-card-section>, <gy-empty-state>, <gy-error-state>, and <gy-mode-switcher> all consume ADR-081's grammar without ADR-081 prescribing their signatures.
  • ADR-065 (persona-aware IA). Unchanged. /me, /ops, /team are fleet-scope by URL (they are not modes and not projects) and correctly use the Template-B empty-state form as new-user onboarding surfaces. The persona-home exception is named in ADR-065, not redefined here.
  • ADR-066 (microvisualisations). Unchanged. ADR-081's card grammar leaves the row and header shapes open to embed sparklines and timeline strips exactly as ADR-066 specifies.
  • ADR-070 (playbook UI contract). Unchanged except where ADR-070's empty-state prescriptions for the <PlaybookRunner> report conflict with §3. In that case ADR-081's three templates win. Audit-verified: ADR-070's shipped empties on RunsList, RunEventsTimeline, and TriggersList already match Template A or Template C — nothing to change.

§7 — Rollout: trickle, not sweep

Per operator direction on 2026-04-24: "a slow trickle of improvements". One consolidation PR per session after ADR-081 lands. Priority order from the audit's Top-10. Each PR brings one route or card-cluster to the grammar. No "UI redesign week".

Proposed cadence:

  • 1-2 trickle PRs per session.
  • ~10 small PRs across 10 sessions.
  • Each PR touches one route file or one component. Under 200 lines changed. Normal 3-persona review gate.
  • Parallel product work continues uninterrupted. Trickle PRs are never a blocker for feature work.

Trickle priority ladder (first eight from the audit's Top-10; items #9 and #10 are opt-in):

  1. Fix modeFromPathOrNull to return 'fleet' for fleet-scope segments — mode-pill dim state rendering (§5). [Top-10 #1]
  2. Populate OPERATE_NAV and EXPERIMENT_NAV in SideRail mirroring INFRA_NAV / SHIP_NAV. [Top-10 #2]
  3. Resolve the Platform-project Run tab 404 — either hide the Run link for Platform class or ship a disabled-tab treatment. [Top-10 #3]
  4. Migrate infra/repos/[...full_name] H2 treatment to cluster-A shape. [Top-10 #4]
  5. Canonicalise /me + /ops on the shared <EmptyState> component with hint slot, retiring the bespoke .empty-box / .error-box pair. [Top-10 #5]
  6. Sweep loading strings to "Loading…" everywhere a noun is redundant with the containing H2. [Top-10 #6]
  7. Collapse ShipHome experiments-table duplication into the /experiments fleet primitive. [Top-10 #7]
  8. Resolve /experiment vs /experiments URL collision — see §10 open question #2.

Items #9 (HomePage H2-with-icon) and #10 (/[buildID] mini-app chrome) are tracked but not scheduled. Item #9 is a deliberate tie-break (either direction resolves the outlier); item #10 is the deferred Build-scope question from §1.

§8 — Enforcement (advisory, non-blocking)

Dana (the docs-advisory persona) gains a lint pass for touched UI files. For every Svelte file in a PR, Dana asserts:

  • Card shape compliance. If the file defines a <section> or <article> with the card class and renders data rows, the shape must include a header, rows-or-empty, and rows-or-error — or a justified one-line comment documenting the deviation.
  • Empty-state prose check. A basic grep over .empty and EmptyState props checks that text matches one of the three §3 templates by regex (/^No .+ yet\.$/, /^No .+ yet\. When .+\.$/, /^No .+\. This .+\.$/) or the existing hint slot carries the explanation.
  • Loading string check. All Loading strings match "Loading…" or "Loading <noun>…" with the ellipsis character.

Dana posts findings as PR comments. They do not block merge — the gate is advisory, per the existing review contract (Dana is the fourth reviewer, advisory; Priya/Marcus/Lin carry the 2-of-3 formal gate). Goal: every new UI PR self-audits against the grammar, drift is visible at review time, and the trickle PRs get measurable.

Consequences

Easier.

  • New routes have a reference: scope classification is a three-line decision, card shape is a copy-paste, empty-state prose is a template fill-in.
  • Fleet-scope is no longer implicit. A new fleet route (e.g. /operators, /patches) knows its rail source, its mode-pill state, and its palette before it's built.
  • Mode-pill no longer lies about Home on 14 routes — the fix is bounded and testable.
  • Component extraction to gyrum-ui has a grammar to encode. The shape precedes the API rather than emerging from three conflicting call sites.
  • The trickle cadence means consolidation is visible progress every session rather than a large dedicated project.

Harder.

  • Two structural fixes (mode-pill modeFromPathOrNull update, Dana lint) must land before the trickle can measure itself. Dana's lint rule has to be written in the gyrum-review-pr infrastructure.
  • The marker visual is a new design affordance — first trickle PR touching the ModeSwitcher has to make the aesthetic call (em-dash, ⦿, plain dim text). Minor; flagged as §10 open question #3.
  • Legitimate outlier cards (cluster-E link-grids, cluster-I takeovers, cluster-H chart placeholders) now require a one-line justification comment. Small maintenance tax.
  • The four-scope taxonomy is a model the team has to carry. A future fifth scope (notifications panel? admin console?) must justify itself against the four or it silently drifts.

What we sign up to maintain.

  • The four-scope taxonomy as the shell-contract source of truth. modeFromPathOrNull is the code-level enforcement point; new top-level path segments extend the fleet list explicitly or they fall into null.
  • The three empty-state templates. New prose must land in Template A, B, or C — or justify its deviation. Dana's lint catches drift at review time.
  • The "Loading…" phrasing. New loading strings adopt it or fall through to Dana's comment.
  • The trickle cadence. If the consolidation pace stalls (no trickle PRs for 3 consecutive sessions), revisit — either the grammar is wrong, or the priority order is wrong, or the effort per PR is under-estimated. Either failure mode is actionable.

Alternatives considered

  • Big-bang UI redesign week. Rejected. Would halt product work for days, concentrate risk into one merge, and front-load decisions that the audit did not validate (e.g. component API choices). The trickle approach lets each PR validate a small slice and lets product work continue.
  • Give Fleet its own palette (warmer neutral). Rejected for v1. The audit found zero mode-tint mismatches — the current Home-inherited treatment works. Introducing a fleet palette adds tokens, contrast testing, and cross-surface propagation for no present user-facing benefit. Leave as §10 open question if a future need surfaces.
  • Use "Alpha" or "catalogue" naming to resolve /experiment vs /experiments. Captured as §10 open question #2 and task #227 — not resolved in this ADR. Prerequisite bug fix before the trickle begins.
  • Make Dana's lint a hard gate. Rejected. The grammar is new and the lint rules are heuristic (regex over prose is inherently fuzzy). Hard-gating would cause review friction before the trickle has validated the rules; advisory lets the corpus grow and the rules tighten organically.
  • Specify Svelte components directly in ADR-081. Rejected. ADR-062 owns component specification and gyrum-ui ships the code. ADR-081 specifies the shape; the component library catches up. Same separation as ADR-066 (microvisualisations spec) vs gyrum-ui (<gy-sparkline> implementation).
  • Make Build-scope a fourth peer in this ADR. Rejected. The /[buildID] viewer is ADR-053-era drift with its own bespoke chrome; committing it as a peer scope locks in the drift, and refactoring it to fit Fleet is a larger decision than the trickle can absorb. Defer.

Open questions

The ADR deliberately defers four decisions. Each has enough information to act on but not enough to commit now.

  1. Build-scope as fourth peer vs refactor to Fleet. §1 and §10 call out /[buildID], /retro/[buildID], /compare/*. Not blocking trickle PRs — they remain as-is until either a dedicated rewrite lands (tokens-audit punch list) or a later ADR commits the peer-scope interpretation.
  2. /experiment vs /experiments URL collision. Different scopes, different palettes, one letter apart. Captured as task #227 and must resolve before the Top-10 trickle begins — a trickle PR that touches the mode-pill will hit this collision immediately. Candidates: rename /experiments to /experiments/catalogue, or accept the split and document it. Escalated to the first trickle PR's scope.
  3. Mode-pill dim marker character. §1 and §5 say " marker" but the character choice (em-dash , ⦿, plain dim text, or a purely visual faded treatment) is aesthetic. Defer to the first trickle PR that touches ModeSwitcher.svelte.
  4. Fleet palette. Default is Home-inherited slate (current behaviour, zero mismatches). A warmer neutral is revisitable if fleet/project visual-distinction becomes a user request.

No questions escalated beyond these four.

Amendment 2026-04-28: warp gy-* as canonical fleet UI source

Why now

On 2026-04-28 (~10:50 UTC), while operating /platform/warp during the deploy-flow swarm, the operator's verbatim reaction was "fontsizes, everything is wrong" — surfaced against the ai-frontend shell, not warp's. Warp's UI at warp.gyrum.ai had remained the operator's most-trusted surface across the same period. The pattern was already memorialised in operator memory (feedback_warp_is_design_reference): warp's gy-* component library is the de-facto fleet design canon, and the design-system audit direction set by warp #287 ("apply gyrum-ui to warp") had been pointing the wrong way — gyrum-ui was being treated as upstream of warp when in reality warp was the more consistent and operator-trusted source.

ADR-081 §2 and §6 already deferred component-implementation choice to ADR-062 + gyrum-ui, but did not name a concrete component source. Without that naming, trickle PRs and the warp epic #481 children (r1..r4) had no ADR-grounded reason to prefer warp's tokens over factory's accumulated CSS. This amendment closes that gap.

Decision

Warp's gy-* component library at gyrum-labs/warp/web/src/lib/components/ is the canonical implementation source for fleet UI. gyrum-ui's contract is to mirror warp's tokens — not the other way around. New tokens originate in warp; gyrum-ui re-exports them; ai-frontend (and any other fleet consumer) pulls them via gyrum-ui.

This amendment is purely additive over the original ADR. It does not change:

  • the four-scope taxonomy (Mode / Fleet / Project / Build) from §1;
  • the canonical card grammar shape from §2;
  • the three empty-state templates (A / B / C) from §3;
  • the "Loading…" rule from §4;
  • the mode-pill rule and modeFromPathOrNull shape from §5;
  • the trickle cadence and Top-10 priority ladder from §7.

The amendment names the component source for the shape that ADR-081 already declared — it does not redraw the shape.

Implementation pointers

The canonical components in warp/web/src/lib/components/ that map to ADR-081's grammar:

  • <gy-card> — the card shell that implements §2's overall shape (header + rows + empty/error fall-through).
  • <gy-section-header> — the §2 header with H2 title, optional count badge, and optional "See all →" right-link. This is the audit's first-named extraction candidate.
  • <gy-empty-state> — Templates A / B / C from §3, with a hint slot for Template B's "When Y happens, they'll appear here." form per ADR-081 §3 and ADR-065 persona-home tone tolerance.
  • <gy-error-state> — the inline role="alert" region from §2 with the Retry button.
  • <gy-status-pill> — the row status-dot vocabulary referenced in §2 ("ADR-062's <gy-status-pill> token vocabulary").
  • <gy-button>, <gy-badge> — the supporting primitives the row action and count badge depend on.

ADR-081 §2 and §3 are the shape contract; the components above are their implementation source.

Reverse-direction note

Warp #287 ("apply gyrum-ui design system to warp kanban") moved tokens from gyrum-ui into warp. In the period since, warp's gy-* library has matured into the operator's most-trusted surface while gyrum-ui has lagged. This amendment names the dominant flow as the opposite — warp is upstream; gyrum-ui mirrors. New tokens originate in warp's component library; gyrum-ui re-exports them; ai-frontend consumes via gyrum-ui. Warp #287's earlier direction is not rejected — its tokens-into-warp work seeded the canon — but the ongoing flow is reversed.

What this unlocks

This amendment is the r0 prerequisite for warp epic #481. The children r1..r4 — warp #482, #483, #484, #485 — migrate the four operator-frequent ai-frontend pages onto the warp gy-* canon. Each child PR cites this amendment version in its PR body so the principle-aware reviewers can verify against the rule the author read.

Guideline citation format

PRs migrating to the canon cite Guideline: warp-gy-canon@2026-04-28 in their PR body, per the knowledge-layer convention (CLAUDE.md "Knowledge & context" section, ADR-117). The citation lets the persona reviewers confirm the implementation against the exact amendment version the author worked from.

What this does NOT do

  • It is not a big-bang migration. The trickle cadence from §7 still holds — one route or card-cluster per PR, under 200 lines changed, parallel product work uninterrupted.
  • It does not refactor warp gy-* itself. The library is the source; this amendment names it, it does not edit it.
  • It does not change the trickle cadence for routes outside the four operator-frequent ones identified in warp #481. Other routes follow the §7 ladder unchanged.
  • It does not specify component APIs. Component signatures live with warp's library and gyrum-ui's mirror, per the original ADR-081 §6 separation.

Supersedes: none. Superseded by: leave blank until a later ADR reverses this one.

Reference audit: ai-frontend/docs/ui-consolidation-audit-2026-04-24.md (PR #191 / v0.16.79) — 71 routes, 38 cards, 33 empty-state strings, 2 structural drifts, 0 palette mismatches. This ADR is the decision layer; the audit is the evidence layer.

Component canon: gyrum-labs/warp/web/src/lib/components/ (the gy-* library; mirrored by gyrum-ui). See warp.gyrum.ai/CLAUDE.md for the agent-bootstrap doc.