docs GitHub

Design systemDraft

Spec lists by team

This page sorts the Gaps sections of the component specs by team, so each team has one list to work from. The specs stay the source of truth: when you close an item, tick it in the spec Gaps list in the same commit, and this page follows.

Lumen redesign is Lumen, redesigned. Its Gaps list comes first for capsule-pro, ahead of the items below.

How items are assigned:

  • Deck items go to pwa (the deck shell, deck/css, deck/js, deck/views, Glass), unless the file is chat's (deck/chat/) or native-core's (the Settings view). An item that touches files of two teams is listed under both and marked shared.
  • App items go to mobile. Lumen items go to capsule-pro.
  • System items fix a board, icons.txt or a token, and go to app-design.
  • One core item has no team yet (see the end).

pwa

Owns the Deck shell: the rail, top bar, phone shell, sheets, Now, Find, Planner, Vault, Devices, Glass, the shared CSS in deck/css/deck.css and the icons.

Start here

  1. Tokens, not hand typed values: drop the old --r-2, --signal and the --scrim and --float redefined in sheet.css; read deck/css/tokens.css (button, sheet, icons, rail).
  2. One button system: .btn and .sb fold into one button with the five variants, hold and busy (button).
  3. Sentence case: .btn is mono caps with letter spacing; use Instrument Sans 13/18 weight 600 (button).
  4. 44 touch targets: the phone shell switches at 720, not 760, and .ibtn needs the 44 size (phone shell, icon button).
  5. AA and disabled: no opacity for disabled, a quiet fill and --label ink instead (button, sheet).
  6. Status marks: the running ring, crossed circle and hollow done dot in deck.css, and the 18 badge (status mark).
  7. Violet only for needs you: remove the beacon wash and rules in Glass, the top bar, desktop Now, held cards and failed marks (glass frame, top bar, needs row, card).

All open items

Account picker row, deck/js/need-sheet.js (account picker row)

  • The phone draft sheet has no From row; the picker opens as a sheet with 56 rows.

Agenda, deck/views/planner.js, deck/css/views/planner.css (agenda)

  • No hour grid, event blocks or now line; the agenda is rows.
  • The firing banner labels the ring " · ring 2" in beacon, not "Ringing again · 2 of 4" in --text-2.
  • Snooze has no duration in its label, and there is no Dismiss.
  • No "Live" status; no missed banner copy "Missed while the box was down".

Ask card, deck/js/need-sheet.js (ask card)

  • Phone sheet primary reads "Approve" and key hints read ⏎/esc; spec: "Allow once", A and D.

Avatar, deck/css/deck.css, deck/css/sheet.css (avatar)

  • .initial is 24 with a --rule-strong border, mono 11 weight 500; use the sans initial, no border, meta 12/16 weight 600.
  • .avatar (the person) is 28, mono 11 with letter spacing, bordered; use 32 or 34 circle, sans, no border.
  • .nsh-tile is 22 with radius 6; use 24 with radius 6.

Banner, deck/css/views/planner.css (banner)

  • No shared banner: .pl-banner and chat's .term-note each draw their own, and the planner's banner uses a beacon border; use --hover fill and no border. Shared with chat.

Button, deck/css/deck.css, deck/css/sheet.css (button)

  • .btn is JetBrains Mono 12, weight 500, upper case with letter spacing; use Instrument Sans 13/18 weight 600, sentence case.
  • No secondary, outline (plain .btn is it but unnamed), hold or busy; disabled uses opacity: 0.45 and primary disabled keeps the bone fill.
  • .btn-ghost ink is --text-2; use --text. Radius uses the old --r-2.
  • A second system, .sb (min 46) and .sb-primary (54, 17/22) with opacity disabled; fold into the one button at 44 and 54.

Card, deck/css/views/now.css, deck/css/views/planner.css (card)

  • No shared card class: .np-card, .pl-card, chat's .gate-card and .cv-tool each define their own radius and padding; make one .card with --radius-card. Shared with chat.
  • Held cards on main draw body rules in --beacon-rule: violet as a border, remove.

Chip, deck/css/deck.css (chip)

  • .chip is 26 tall with radius --r-1 and ink --text, no on state; use 28, round, --text-2, and .chip-on.
  • .tag is mono 11 with a --rule-strong border; use sans 12 on a --hover fill.

Command bar, deck/views/find.js, deck/css/views/find.css, deck/js/capsule.js, deck/js/commands.js (command bar)

  • Desktop has no ⌘K palette: ⌘K focuses a 420 px field in the top bar with a dropdown, and Find is a separate full page ("Find or ask").
  • Desktop placeholder reads "Find or ask", not "Jump to anything, or ask juno".
  • Phone switch is at 760 px (PHONE_QUERY), not 720.
  • Vault codes never show in Find; the Layout board places codes in Find on the phone.

Command result card, deck/views/find.js (command result card)

  • ⌘K Run results open a terminal line; draw the card under the Run row.

Credential sheet, deck/js/sheet.js (credential sheet)

  • Nothing built: the sheet on any needs_credential answer, from vault.need and vault.connect; the centred card and the phone sheet.

Device row, deck/views/vault-places.js, deck/css/views/vault.css, deck/js/health.js (device row)

  • Devices lists only vault autofill browsers (vault.devices), not the relay devices from relay.devices.list; no path, holds or trust columns.
  • Remove is a confirm button "Revoke" / "Revoke now", not a hold; no Rename.
  • The relayed dot in deck/js/health.js is amber; use --label.

Draft card, deck/js/need-sheet.js, deck/js/editable.js (draft card)

  • No "Send at 9:00 tomorrow"; no D key on Discard. Shared with chat.
  • Presence line wording "Face ID covers sends until 14:32, confirmed 6 min ago" (only on the phone sheet).

Form controls, deck/css/deck.css (form controls)

  • .input is 40 tall on --panel with a 15 size; use 32 on --bg at 13 (40 is the large field). Focus has no ring.
  • The toggle .sw is 34 x 20 with an on track of --text; use 32 x 18 and --primary-bg.
  • .seg has a --rule border and 26 segments with aria-pressed; use the --hover container, 28 segments and radiogroup semantics.
  • No stepper or select component (native select.input in settings).
  • .search is 420 wide on --panel with a --rule border; use the field.

Glass frame, deck/glass/ (glass frame)

  • The idle chip uses --beacon-ink text, border and --beacon-wash; spec is neutral.
  • Read-only and hold banners use --beacon-wash (.gl-banner, .gl-notice-hold).
  • Copy says "Someone has control from <surface>", not "Your phone has control".
  • Error rows and messages tint violet (.gl-msg-err, .gl-tr-err, .gl-danger).
  • Accent reads the raw --signal variable instead of --focus.

Glass mini-view, Now (Glass mini-view)

  • Nothing built: the card on Now under the acting agent's row. Shared with chat (the thread header, the Step link).

Icon button, deck/css/deck.css (icon button)

  • .ibtn radius uses the old --r-2; switch to --radius-button. No 44 size, no filled round variant, no busy.

Icons, deck/js/icons.js (icons)

  • Drawings differ from icons.txt for now, projects, memory, agents, vault, settings, search, plus, mic, send (points right, not up), file, copy, bell, clock, key.
  • Missing planner, devices, x (has close), failed, chev-d (has chevron), more, stop, faceid, eye, hand, qr, box, wifi-off, refresh, drive, link, download, share, globe, shield, pause, play, cable, alarm, todo, unlock.
  • Extra names not in the set (lock, watch, pin, mute, edit, lines, mail, branch, login, pass, ask): add to icons.txt through a design review or retire.
  • mark() fills the dot with --signal, which the tokens no longer define; use --mark-dot and --beacon-dot.

Key hint, deck/css/deck.css (key hint)

  • .kbd is mono 11 with padding 0 6 and radius --r-1; use 12/16, padding 0 5, min width 20, --radius-chip.
  • Ask hints read "⏎" and "esc" where the design keys are A and D; lower-case "esc" should be "Esc".

List row, deck/css/views/find.css, deck/css/views/planner.css (list row)

  • No generic row: chat's .thread-row and .fb-row, .pl-row and .fd-row each set their own height, padding and type; make one .li with the variants. Shared with chat.
  • Failed marks are violet in places; use the crossed circle in --text-2.

List, deck/css/deck.css (list)

  • .rows and .section-head have no sticky group headers, no filter chips with counts, no key footer; long lists page with "Show earlier" only.
  • No skeleton rows (lists show "Loading devices." text); no slow-box line at 10 s.

Needs row, phone, deck/js/need-rows.js, deck/js/now-phone.js, deck/css/views/now.css (needs row)

  • Kind line omits the kind (thirdLine is "agent · project"); no "asked by", no decided state; kinds limited to ask, draft, question, pair.
  • An approve goes at once with no Undo; a draft's right swipe opens the sheet instead of committing Send with the Face ID glyph.
  • Swipe is pointer events in JavaScript, not a scroll-snap strip.
  • Title 16/21, detail 15/20, age 13; the tile has a --rule-strong border.

Needs row, desktop, deck/views/now.js, deck/css/views/now.css (needs row)

  • A card per item with inline actions, not list and detail; no J, K, A, D keys.
  • Time and error text in --beacon-ink, rules in --beacon-rule, gold --recall-ink: remove.

Authenticator code, deck/views/vault-item.js, deck/css/views/vault.css (otp)

  • No next code; the code sits in a field row labelled "Code", not "Authenticator".
  • Ring is 20 px, and seconds render "18s" without the space.
  • Codes come from the box each period, not computed on the device, so offline shows nothing.
  • Copy is an icon button with the tooltip "This vyred has no vault.copy yet" when missing.
  • No phone Codes list with tap-to-copy rows.

Needs row, waiting, deck/js/needs.js (needs row)

  • deck/js/needs.js merges its own sources; draw from waiting.list, count from waiting.count and waiting.changed, answer through answer.tool; add the Pairing row.
  • Push resolves by row id, so one answer clears every device.

Phone shell, deck/js/app.js, deck/css/deck.css, deck/js/capsule.js, deck/js/sheet.js (phone shell)

  • Switches at 760 px, not 720.
  • The avatar opens a Settings sheet, not the Places sheet; there is no pin-a-fourth-page.
  • Header gap is 12 and padding 0 12 0 16 (spec 14 and 0 16).
  • Push and pop run 300 and 240 ms on their own curves, not --motion-panel and --ease.
  • Lumen hides on every pushed screen, including Planner.

Pill, deck/js/reconnect.js, deck/js/pwa.js, deck/css/deck.css (pill)

  • The reconnect state draws as .reach, a full-width line above the view with a bottom rule; make it the floating pill.
  • 60 s words read "Reconnecting since 14:32"; use "No answer from the box since 14:02 · Retry now".

Presence line, deck/js/need-sheet.js, deck/js/person.js (presence line)

  • Wording "Face ID covers sends until 14:32, confirmed 6 min ago"; spec: "Face ID confirmed 12 min ago · covers sends until 15:02".
  • No "Last Face ID 34 min ago" line in the lapsed state.
  • The sign-in sheet's primary reads "Sign in with your passkey"; spec: "Sign in with Face ID" plus the code path button and the unlock list.

Rail, deck/js/app.js, deck/css/deck.css (rail)

  • The rail is 216 px with text rows 34 tall at 14 px, not the 72 px icon rail.
  • Order is Now, Projects, Memory, Agents, Chat, Vault, Settings; Planner and Devices are missing.
  • The count is violet mono 11 px text, not the 18 px badge; no 99+ cap.
  • Brand sits in the top bar (.brand, 216 wide) instead of the mark at the top of the rail.
  • Uses raw --r-2 radius and the old --beacon-wash elsewhere in the shell.
  • No ⌘1 to ⌘9 place keys.

Sheet, deck/js/sheet.js, deck/css/sheet.css, deck/js/need-sheet.js (sheet)

  • Motion is 340 in and 240 out on a custom curve; use --motion-sheet and --motion-panel with --ease.
  • The page behind scales to 0.94 over black; remove, the scrim is enough.
  • --scrim and --float are redefined in sheet.css; read the tokens.
  • Desktop is always the 560 centred card; no right sheet. Buttons are .sb at 46 and 54 with opacity for disabled; use the button spec.
  • Title is 26/32; use 22/28.

States, deck/css/deck.css, deck/js/empty-actions.js, deck/js/now-phone.js (states)

  • No skeleton outside the phone Now card; loading is text.
  • Offline is a full-width line (.reach), not the pill; no 2 s and 60 s stages.
  • Long lists use "Show earlier", not "Load 40 more" with a count; no sticky group headers.
  • Failed marks are violet in places (status marks); spec is the neutral crossed circle.

Status mark, deck/css/deck.css (status mark)

  • No running ring, crossed circle or hollow done dot in deck.css; .dot.recall and relayed health dots use gold.
  • Rail count is violet text, not a badge; .needs-pill is a violet wash.
  • The phone tab bar badge is 16 tall, 10 px text; use 18 and meta size.

Stepper and checks, deck/views/pair.js, deck/js/pair-steps.js, deck/css/pair.css, deck/js/phone-setup.js, deck/onboard/onboard.js (stepper and checks)

  • No laptop "Add your phone" sheet in Devices; no relay QR step and no optional Tailscale step.
  • phone-setup.js is a three-step card on Now (install, notifications, passkey), not the five live checks.

Tabs, deck/css/deck.css, deck/glass/index.js, deck/css/views/memory.css (tabs)

  • No Session, Terminal, Files tabs or teammate tabs; .seg is used with aria-pressed or role="group" in places and role="tablist" only in Glass.
  • .seg draws a --rule border, 26 tabs and --hover for the selected fill; use the --hover container and the --panel selected tab.
  • .mem-tab in Memory is a third tab style (34 tall, text only); fold into these two.

Tip, deck/js/tips.js (tip)

  • Nothing built: the footer chip, the empty state line, the What's new card on tips.updated. Shared with chat (the composer hint line).

Toast, deck/js/now-phone.js, deck/css/views/now.css, deck/css/views/vault.css (toast)

  • Two toasts: .np-toast on Now and .vt-toast in the vault; make one shared toast.
  • No in-place variant; an approve's toast has no Undo (the answer goes at once).
  • Toast shadow is --light-top; use --float. Words 15/20; use the type steps.

Top bar, deck/js/app.js, deck/css/deck.css (top bar)

  • Bar is 48 tall, flex not the three-column grid; brand and the box address sit on its left.
  • No page title in the bar; views draw their own headings.
  • The search field is 420 wide inside the bar, not the centred command bar trigger.
  • The needs pill uses --beacon-wash fill and a pill shape; spec is text in --beacon-ink on no fill.
  • No right-slot page primary with N.

Done when

  • npm run tokens -- --check passes and no Deck CSS file defines a value the tokens hold.
  • npm run design:audit passes for every board you changed, and you looked at each PNG.
  • The Deck tests pass (node --test "deck/**/*.test.js").
  • Every Deck item above is ticked in its spec, so no spec lists an open Deck gap on a pwa file.

chat

Owns the chat view in the Deck: turns, tool rows, diffs, ask, question, plan and draft cards, the terminal, the thread list, and the composer and mode chips (the composer is shared with native-core).

Start here

  1. Tokens and one button system: Stop is ghost, make it secondary; the ask card's "Always in" is ghost, make it outline (composer, ask card).
  2. Sentence case and the type steps: the mono 11 caps step labels, turn footers, steer markers and tool meta become meta 12 sans (question card, turn, tool row).
  3. Status marks: failed uses the crossed circle, not beacon ink; running is a spinner and a --text-2 verb (tool row, status mark).
  4. Violet only for needs you: remove the beacon wash on the ask card, .diff-del and the terminal error dots (ask card, diff, terminal).
  5. The words: "Edits allowed" and "Plan first", not "Accepts edits" and "Plan mode" (mode chip).
  6. The plan card, which is not built at all (plan card).

All open items

Account picker row, deck/chat/gate-item.js (account picker row)

  • The draft card has no From row and no account picker; add it, from vault.connections.list {capability: "send_mail", surface: "chat"}.

Ask card, deck/chat/ask-item.js (ask card)

  • .cv-ask.ask-card fills --beacon-wash (a violet wash); spec: --panel with --rule.
  • Title weight 500; spec: 600.
  • "Always in" is a ghost button; spec: outline.
  • No per-button busy verb; all three disable with no spinner.

Avatar, deck/chat/chat.css (avatar)

  • .av-agent is 28 with --r-2 and a border; .av-person is 28. Neither size is in the scale.

Banner, deck/chat/term.css (banner)

  • No shared banner: .term-note draws its own; use the shared banner. Shared with pwa.

Card, deck/chat/chat.css (card)

  • No shared card class: .gate-card and .cv-tool define their own radius and padding; use the one .card with --radius-card. Shared with pwa.

Command result card, deck/chat/core/tool-detail.js (command result card)

  • Slash commands print terminal text in a tool row; ask with render: true and draw the card.

Composer, deck/chat/composer.js, deck/chat/core/composer-state.js, deck/chat/tray.js, deck/chat/pickers.js (composer)

  • No Attach, vault chip or Dictate; placeholder "Steer kit, or Alt+Enter to queue for after".
  • Mode labels "Accepts edits" and "Plan mode"; ⇧Tab walks three modes and never Doesn't ask.
  • Rewind offers three restores; spec: one list, the "Also undo file changes" checkbox, the hold.
  • Todos pinned above the composer on the desktop; spec: side panel.
  • Stop is ghost; spec: secondary.

Credential sheet (credential sheet)

  • A tool row or send that answers needs_credential opens the sheet instead of printing the error.

Diff, deck/chat/core/line-diff.js, deck/chat/lib/diff.js, deck/chat/chat.css (diff)

  • Removed lines strike through (text-decoration: line-through); spec: no strike.
  • Removed text is --label; spec: --text-2.
  • Added sign in the signal colour; spec: --label sign, colour only in the fill.
  • Mono 12.5/21; spec: 12/20.
  • Legacy .diff-del in deck/chat/chat.css uses the beacon colour and wash; remove it.

Draft card, deck/chat/gate-item.js (draft card)

  • No "Send at 9:00 tomorrow"; no D key on Discard. Shared with pwa.
  • Badge "Held for you" beside the title; spec: header dot and "Draft to send" label.

Form controls, deck/chat/chat.css (form controls)

  • Two checkboxes: .cv-chk on is bone, the design is --text.

Glass mini-view (Glass mini-view)

  • Nothing built: the thread header frame and the tool row's Step link. Shared with pwa.

List row, deck/chat/chat.css (list row)

  • No generic row: .thread-row and .fb-row set their own height, padding and type; use the one .li. Shared with pwa.

Mode chip, deck/chat/composer.js, deck/chat/core/composer-state.js (mode chip)

  • Labels are "Accepts edits" and "Plan mode"; use "Edits allowed" and "Plan first". The order is default, acceptEdits, plan; use plan, default, acceptEdits, bypass.
  • ⇧Tab never reaches Doesn't ask, and the box's threads.mode refuses bypass (ADR 0030); the 27 Sep decision needs a person-only path in core (proposed) and an ADR update.
  • No inverse chip, no mode icons, no menu (the chip only cycles); the model chip shows the model only, not provider and auth.

Pill, deck/chat/chat.css (pill)

  • .cv-queued-row is a dashed bordered box for a queued chat message; the offline queued line is the plain clock and words above.

Plan card, deck/chat/core/grouping.js, deck/chat/core/tool-detail.js (plan card)

  • Not built. The plan text shows as an unfolded tool row with no approve controls; the ask of kind plan needs this card, the Needs row "kit has a plan to approve", and the mode choice.

Popover, deck/chat/pickers.js (popover)

  • .composer-menu and .cv-menu rows use their own sizes; align to 28 and 30 dense rows.
  • No mode popover with descriptions yet; mode labels read "Accepts edits" and "Plan mode".

Presence line, deck/chat/gate-item.js (presence line)

  • The gate card has no presence line.

Question card, deck/chat/question.js, deck/chat/lib/answers.js (question card)

  • .cv-q-chip step label is mono 11 px caps tracked; spec: meta sans, sentence case.
  • Question 15/23 at weight 500, labels 14/20 at 500; spec: read 600 and base 600.
  • Card border --rule-strong, radius 10; spec: --rule, radius 12.
  • Choice numbers mono 11 on the left; spec: kbd chip on the right.

Status mark, deck/chat/chat.css (status mark)

  • Failed state words use --beacon-ink in chat.css (.cv-state-*).

Suggestions, deck/chat/pickers.js, deck/chat/composer.js (suggestions)

  • The / and @ pickers read their own command and file lists; call suggest.query per keystroke and draw its lanes and groups. Shared with native-core.
  • No agents, projects, threads or people after @; no Words lane; no ghost text.
  • No suggest.picked on accept.

Terminal, deck/chat/term.js, deck/chat/term.css, deck/chat/lib/term-link.js (terminal)

  • Key bar is one row of nine (Esc Tab Ctrl Alt ← ↑ ↓ → Paste); spec: two rows of seven with / | ~ - and Enter.
  • Watch line reads "Watching at 120x40 · Take size"; spec: names the owner device.
  • Connecting dot uses the gold --recall; error and blocked dots use --beacon-dot; spec: --label and the failed mark.
  • xterm font 13 with line height 1.2; spec: mono 12/18.

Tip (tip)

  • Nothing built: the composer's hint line. Shared with pwa.

Tool row, deck/chat/core/grouping.js, deck/chat/core/tool-detail.js, deck/chat/chat.css (tool row)

  • .cv-tool is a bordered card (38 high, --panel, radius 8); spec: a borderless 28 line.
  • Failed uses --beacon-ink and a beacon border; spec: crossed circle in --text.
  • Running state word is lowercase in the signal colour; spec: spinner plus --text-2 verb.
  • Meta is 11 px; spec: 12.

Turn, deck/chat/session.js, deck/chat/core/session-state.js, deck/chat/chat.css (turn)

  • Steer marker reads "Steered at step N" in mono 11 px in the signal colour; spec: "you steered here · after 3 steps · 14:32", meta sans, --label.
  • Turn footer .cv-turn is mono 11 px; spec: meta 12 sans.
  • .cv-text is 15/24; spec: 15/22.
  • Thinking body border 2 px --rule-strong; spec: 1 px --rule.

Done when

  • npm run design:audit passes for the Session boards you changed, and you looked at each PNG.
  • The chat tests pass (node --test "deck/chat/**/*.test.js").
  • Every item above is ticked in its spec, so no spec lists an open Deck gap on a deck/chat/ file.

native-core

Owns the Settings view in the Deck (deck/views/settings-keys.js and its CSS), the hub, and the composer with chat.

Start here

  1. Chips only when they mean something: no source chip for a default value, and "Claude Code file" becomes the source itself (settings row, chip).
  2. Status marks and plain words: refusals say "Not saved" with the failed mark, not the raw error (settings row).
  3. The restart banner and the resting apply hints "Next session" and "After restart" (banner, settings row).
  4. A named Reset with Undo (settings row).
  5. The key id only in search results, not in mono under every label (settings row).

All open items

Account picker row (account picker row)

  • Settings, Vault, Connections: draw these rows with a More menu (label, default, revoke).

Banner (banner)

  • No restart banner on the Settings page.

Chip, deck/views/settings-keys.js (chip)

  • Source labels include "Default" and "Not set"; show no chip for a default value, and add the "Claude Code file" chip.

Credential sheet (credential sheet)

  • Onboarding's Claude step and Settings, Vault, Connections open this sheet.

Settings row, deck/views/settings-keys.js, deck/css/views/settings-keys.css (settings row)

  • The source chip is always shown, including "Default" and "Not set"; show it only when the value is not the default, and drop "Not set".
  • "Claude Code file" is a second chip on every Claude-owned key, beside the source; it should be the source itself, with the row read-only and Open file.
  • Reset is a bare "Reset" that does not name its target; no Undo after a reset.
  • Apply hint is printed after a save as "Applied", "From the next session", "Restart vyred to apply"; use the resting hints "Next session" and "After restart" and print nothing for live keys.
  • No "Saved", no reserved slot (meta and note lines grow the row), no restart banner.
  • The key id is printed under every label in mono; show it only in search results.
  • Refusals print the raw error; use "Not saved" with the failed mark.

Suggestions, the composer (suggestions)

  • The chat-core composer calls suggest.query per keystroke, with ghost text and suggest.picked. Shared with chat.

Done when

  • npm run design:audit passes for the Settings boards you changed, and you looked at each PNG.
  • The Deck tests for the Settings view pass (node --test "deck/**/*.test.js").
  • Every item above is ticked in its spec.

mobile

Owns the app (apps/app, Expo) on the phone and tablet.

Start here

  1. Load Instrument Sans: labels render in the system font today (phone shell).
  2. One button system: Button gains 28 and 54, hold, busy, leading icon and key hint, and screens stop rolling their own (button).
  3. 44 targets: the 44 single and two-line rows, and IconButton at 44 (list row, icon button).
  4. Status marks: StatusMark gains the badge, count, status word, the cross for failed and elapsed time on running (status mark).
  5. One Icon component from the set, with react-native-svg (icons).
  6. Violet only for needs you: the ask card border stays neutral while open (ask card).
  7. The shell: header labels and page swipe instead of the tab bar, the floating Lumen and Places as a bottom sheet (phone shell, sheet).

All open items

  • Account picker row, app/need/[id].tsx: nothing built; the From row and the picker sheet. (account picker row)
  • Agenda: no planner. (agenda)
  • Ask card, src/session/Rows.tsx: border turns --beacon while open; spec: neutral border. (ask card)
  • Ask card, src/session/Rows.tsx: Allow once and Deny only; no Always in the project, no reason, no busy state. (ask card)
  • Avatar, src/ui/Row.tsx, src/ui/Screen.tsx: the agent tile is a circle (radius.full) with --text-2 ink at 32; it needs the rounded square and a shared Avatar component with the four sizes. (avatar)
  • Banner, src/ui/NotifyBar.tsx, src/ui/SignInBar.tsx: inline bars with their own styles; build one Banner with icon, fact, detail and one action. (banner)
  • Button, src/ui/Button.tsx: four variants at 44 and 32 only; add 28 and 54, hold, busy, leading icon and key hint; ghost ink is text2; screens roll their own buttons. (button)
  • Card, src/session/Rows.tsx, src/vault/views.tsx: AskCard and TrustCard are inline styles; extract one Card with header and footer slots. (card)
  • Chip, app/devices.tsx: the devices badge is bordered with radius chip; no filter chips, no source chip. (chip)
  • Command bar: no Find, no command bar, no Lumen. (command bar)
  • Command result card: nothing built; the three views in the chat-core transcript, rows only. (command result card)
  • Composer, src/session/Composer.tsx: text only; no chips, attach, prefixes, queued row, pills or rewind; input 16 not 17. (composer)
  • Credential sheet: nothing built; the bottom sheet with Paste and Face ID on Connect. (credential sheet)
  • Device row, app/devices.tsx: no holds line, no More, no Rename or Remove hold, no Update ready. (device row)
  • Device row: trust note reads "Presence follows"; name the proof ("Face ID follows"). (device row)
  • Device row: trust tags draw as outline chips; use the tag (fill --hover, no border). (device row)
  • Device row: no 40 tile and no right-hand path column; the path is a text line under the name. (device row)
  • Diff: not built; edits show as a tool row with no diff. (diff)
  • Draft card, app/need/[id].tsx: fields are read-only; spec: edit in place. (draft card)
  • Draft card: no presence line; no "Send with Face ID" state; sends from the Deck or Lumen "for now". (draft card)
  • Form controls, src/session/Composer.tsx: only the composer's text input exists (16 px); build the other eight at touch sizes. (form controls)
  • Glass frame: no Glass view. (glass frame)
  • Glass mini-view: nothing built; the card from sight.frame stills, the pill when collapsed. (Glass mini-view)
  • Icon button: no icon button; screens use Pressable with text. Build IconButton with 44 default and the icon component. (icon button)
  • Icons: no icon component; build one Icon from the table with react-native-svg. (icons)
  • Key hint: no key hints; add them for iPad and web builds with a hardware keyboard. (key hint)
  • List row, src/ui/Row.tsx: one 86 tall three-line shape for everything (ROW_HEIGHT); add the 44 single and two-line variants and selected, focused states. (list row)
  • List row: rows sit on --bg with a bottom border, not in a card; the avatar is round, not a tile. (list row)
  • List, src/ui/List.tsx: no sticky group headers, no Load 40 more, no chips (virtualized above 100 rows is correct). (list)
  • Mode chip, app/session/[id].tsx: the mode is a text line under the composer and the header chip is "agent · provider · model · project"; build both chips and the mode sheet. (mode chip)
  • Needs row, app/(tabs)/index.tsx: kind line is "agent · project"; only gate and ask kinds; no decided state. (needs row)
  • Needs row, src/ui/SwipeRow.*.tsx: commit at 40% of the row width, not 100 or a fling; reveal has no icon or Face ID glyph. (needs row)
  • Needs row, waiting: read waiting.list and waiting.count; the Now label badge and the app icon badge from the one count; the Pairing row with its code field. (needs row)
  • Needs row: rows sit on --bg full width, not in a --panel card. (needs row)
  • Authenticator code: no vault codes. (otp)
  • Phone shell, app/(tabs)/_layout.tsx: a bottom tab bar (Now, Chats, Agents) instead of the header labels and page swipe. (phone shell)
  • Phone shell, app/places.tsx: no floating Lumen; Places is a modal screen, not a bottom sheet. (phone shell)
  • Phone shell, app/_layout.tsx: Instrument Sans is not loaded, so labels render in the system font. (phone shell)
  • Pill, src/state/connection.ts: toConnection and the outbox list exist; no pill or queued line is drawn. (pill)
  • Plan card: not built. (plan card)
  • Popover, app/session/[id].tsx: no popover or picker; mode is plain text. (popover)
  • Presence line, src/state/needs-model.ts: coverage is computed, nothing is drawn; sends go to the Deck or Lumen. (presence line)
  • Question card: not built; the ask card sends you to the Deck. (question card)
  • Rail: no rail at 720 and up; tablets get the phone tab bar. (rail)
  • Settings row, app/settings.tsx: no settings rows; build the phone layout (44 controls, reserved line, stepper). (settings row)
  • Sheet: no sheet; Places is a modal screen and Needs detail is a pushed screen; build the bottom sheet on Reanimated with the same detents. (sheet)
  • States, src/ui/Screen.tsx: Empty only; no skeleton, no offline pill (useConnection is unused), no decided state. (states)
  • Status mark, src/ui/StatusMark.tsx: no badge, count or status word; done uses a 1 px border (1.5); the failed mark is a slashed circle, not the cross; no elapsed time on running. (status mark)
  • Stepper and checks, app/pair.tsx: pairing shows a status line only; no steps, checks or QR. (stepper and checks)
  • Suggestions, src/session/Composer.tsx: nothing built; take the chat-core list with 44 rows, 4 at most. (suggestions)
  • Tabs: no tabs; build Tabs with the segmented and strip forms. (tabs)
  • Terminal: not built. (terminal)
  • Tip: nothing built; the Places sheet line and empty screen lines. (tip)
  • Toast, src/ui/UndoToast.tsx: Undo text is --focus (bone); use --text, 600. (toast)
  • Toast, src/ui/UndoToast.tsx: floating only; no in-place variant; no ⌘Z on the web build. (toast)
  • Tool row, src/session/Rows.tsx RunRow: no icon, no elapsed timer, no detail; "Hide"/"Show" text instead of a chevron. (tool row)
  • Tool row: status mark dot instead of the icon. (tool row)
  • Top bar, src/ui/Screen.tsx: no desktop or tablet top bar; Screen draws a phone title only. (top bar)
  • Turn, src/session/Rows.tsx: no author line (tile, name, time). (turn)
  • Turn: your bubble is --panel with a --rule border; spec: --hover fill, no border. (turn)
  • Turn: thinking reads "Thought · N characters" and never opens; spec: "Thinking · 8 s", expandable. (turn)
  • Turn: steer marker reads "Steering" / "Steered at step N"; spec: "you steered here · after 3 steps · 14:32". (turn)

Files are under apps/app/ on work/mobile.

Done when

  • npm run tokens -- --check passes and screens read apps/app/src/theme/tokens.ts, not typed values.
  • npm run design:audit passes for the phone boards you changed, and you looked at each PNG.
  • The app's own tests pass on work/mobile.
  • Every App item above is ticked in its spec.

capsule-pro

Owns Lumen on the Mac (local/capsule/native/Sources/): the panel, the list of what waits, the session panel, presence and the menu-bar item.

Start here

  1. The redesign first: the answer card grows then scrolls, no empty headings, the footer states, layered Esc, then Vyre Memory, voice and computer use (Lumen, redesigned, Gaps).
  2. Tokens, not hand typed values: Theme.swift maps tokens to old names and types its own sizes; read Tokens.generated.swift (Lumen on the Mac).
  3. One button system: AgentButton uses Tokens primary colours, Radius.button and the five variants (button).
  4. Sentence case: "HELD FOR YOU", "WAITING ON YOU", "OFFLINE" and the mono caps section headers become sentence case (ask card, list, states).
  5. 44 targets: rows are 40 tall; use 44 (list row, needs row).
  6. Violet only for needs you: the selected row has a violet left bar; use the --hover fill (needs row, Lumen on the Mac).
  7. Status marks: the ring for Pulse, the badge, neutral relayed health (status mark).
  8. The icon set as SwiftUI shapes instead of SF Symbols (icons).

All open items

  • Account picker row, UI/CapsuleView.swift: no "Send from" group; read vault.connections.list {capability, surface: "capsule"}, with the states, the Sign in fix and Connect another. (account picker row)
  • Agenda, Host/Planner.swift: the banner says "Missed: Timer"; no ring count. (agenda)
  • Ask card, UI/AgentDeskView.swift HeldCardView: buttons read "Allow" and "Deny"; no "Always in <project>", no reason line. (ask card)
  • Ask card: label "HELD FOR YOU" in mono caps, tracked; spec: "Permission", sentence case. (ask card)
  • Avatar: no avatar; Lumen board draws 20 tiles with radius 5. (avatar)
  • Banner, UI/AgentDeskView.swift WaitingHint: a 30 tall hint line; banners are not drawn in Lumen panel. (banner)
  • Button, UI/AgentDeskView.swift AgentButton: primary is a bone (text) fill, radius 6, pressed at 0.8 opacity; use Tokens primary colours, Radius.button, the five variants and the states. (button)
  • Lumen on the Mac, UI/Theme.swift: maps tokens to old names and hand-types sizes (query 22, title 14, label 10.5 mono, rows 40); only colours, status and Radius.card come from tokens. (Lumen on the Mac)
  • Lumen on the Mac: placeholder reads "Search, calculate, ask, or @ a session". (Lumen on the Mac)
  • Lumen on the Mac: list header is "WAITING ON YOU · n" in mono caps in the attention colour; the focused row has a violet left bar instead of signalWash. (Lumen on the Mac)
  • Lumen on the Mac: offline banner reads "OFFLINE" in caps, not "Works offline · 1 queued". (Lumen on the Mac)
  • Lumen on the Mac: always dark; no Tokens.paper. (Lumen on the Mac)
  • Lumen on the Mac, UI/PresenceView.swift: reads "Touch ID to approve exactly this." with no 30 min covered line. (Lumen on the Mac)
  • Lumen on the Mac: AgentButton primary is primaryBg (bone) with primaryInk, radius 6. (Lumen on the Mac)
  • Lumen on the Mac, Host/Panel.swift: radius is Tokens.Radius.card (12), not sheet. (Lumen on the Mac)
  • Lumen on the Mac: Tokens.generated.swift has no shadow tokens, so --float cannot come from it yet. (Lumen on the Mac)
  • Card, UI/AgentDeskView.swift HeldCardView: hand-typed sizes and a caps mono label; use Tokens.Radius.card and the 12/600 sentence-case label. (card)
  • Chip, UI/CapsuleView.swift, Extensions/sight/SessionPanel.swift: chips are capsule-shaped with gold icons (Theme.recall); use the tag and filter shapes and neutral ink. (chip)
  • Command result card: nothing built; the card under the input for a run command, 5 rows, ⌘O to the Deck. (command result card)
  • Composer, UI/CapsuleView.swift, Extensions/sight/SessionPanel.swift: plain field; no steer, queue, Stop, modes or prefixes. (composer)
  • Credential sheet: nothing built; the inline row and secure field, "Connect Deepgram to talk" in place of the copied terminal command. (credential sheet)
  • Diff: not built. (diff)
  • Draft card, HeldCardView: field keys in caps, tracked; label "HELD FOR YOU"; spec: sentence case. (draft card)
  • Draft card: no scheduled send; no presence line under Send. (draft card)
  • Form controls, UI/CapsuleView.swift, UI/AgentDeskView.swift: system TextField and TextEditor styling; no toggles, segments or steppers. (form controls)
  • Glass mini-view: nothing built; the step pill for an agent's computer, ⌘O to Glass. (Glass mini-view)
  • Icon button, Extensions/sight/SessionPanel.swift, UI/CapsuleView.swift: close and mic buttons are SF Symbols at 11 to 13 pt with no hover fill or focus ring; draw the set's icons in a 28 square with Tokens colours. (icon button)
  • Icons: SF Symbols everywhere (xmark, chevron.down, sparkle.magnifyingglass, mic.fill); draw the set as SwiftUI Shapes from the same path data. (icons)
  • Key hint, UI/CapsuleView.swift KeyCap: SF Rounded 10.5 semibold in Theme.stone, 17 square, filled Theme.raised; use JetBrains Mono 12, 20 tall, no fill, Tokens label ink. (key hint)
  • List row, UI/CapsuleView.swift Row: 40 tall with hand-typed sizes (14, 12, 11.5); selected is a rounded raised fill with a 3 px --focus capsule on the left; use 44, the type steps and the --hover fill. (list row)
  • List, UI/CapsuleView.swift SectionHeader: mono 10 caps with tracking; use 12/600 sentence case. (list)
  • List, UI/AgentDeskView.swift WaitingList: caps at 9 rows (Theme.maxRows) with no count or more. (list)
  • Mode chip: none; the session panel needs the provider chip at least. (mode chip)
  • Needs row, UI/AgentDeskView.swift WaitingRow: two lines, no kind line, a 7 dot for the tile, 40 tall; header "WAITING ON YOU" in caps. (needs row)
  • Needs row, waiting: the list and the "Needs you · n" header from waiting.list; the menu-bar dot from waiting.changed; answer through answer.tool. (needs row)
  • Needs row: selected row has a 2 px violet left bar (violet as a border); use the --hover fill only. (needs row)
  • Pill, UI/AgentDeskView.swift OfflineBanner: a 30 tall line with "OFFLINE" in caps and "vyred is not running on this Mac. Start it with vyre up."; use the pill words, sentence case, no caps label. (pill)
  • Plan card: not built (proposed: the Needs row with Start building on ⌘⏎, the card opening in Chat). (plan card)
  • Popover, UI/AgentDeskView.swift ActionMenuView: caps title with tracking and a 2 px Theme.signal left bar on the active row; use the --signal-wash fill and a 12/600 sentence-case header. (popover)
  • Presence line, UI/PresenceView.swift: only the proof prompt ("Confirm it's you", "Touch ID to approve exactly this."); no covered line under Send and no lapsed "Send with Touch ID" label. (presence line)
  • Question card: not built. (question card)
  • States, OfflineBanner: reads "OFFLINE" in caps; no queued count; no skeleton. (states)
  • Status mark, WaitingRow, UI/CapsuleView.swift, Host/MenuBarItem.swift: waiting dot uses Theme.attention at 7 px; tool rows use SF Symbols; Pulse is a bone 7 px dot, not the ring; no badge; relayed health is not neutral. (status mark)
  • Suggestions, UI/CapsuleView.swift: the @ target ranks its own agents and sessions; draw suggest.query rows under the local rows, never moving a drawn row; ghost text; suggest.picked. (suggestions)
  • Tip: nothing built; the line under the empty input, ⌘. to dismiss. (tip)
  • Toast: no toast; add the floating variant under Lumen's list for answers given there. (toast)
  • Tool row, UI/CapsuleView.swift ToolRows: SF Symbols instead of the stroke set; no folding, no detail. (tool row)
  • Turn, UI/CapsuleView.swift, UI/AgentDirectView.swift, Extensions/sight/SessionPanel.swift: three renderers; one turn view. (turn)
  • Turn: author in mono caps; spec: sans 600, sentence case. (turn)
  • Turn: no steer marker, no thinking row, no turn footer. (turn)

Paths are under local/capsule/native/Sources/ on work/capsule-pro. The terminal spec lists the Lumen as not used (it opens the terminal in the Deck); that is not an item to build.

Done when

  • npm run tokens -- --check passes and no Swift view types a size, colour or radius the tokens hold.
  • npm run design:audit passes for Lumen boards you changed, and you looked at each PNG.
  • Lumen tests pass (local/capsule/native/Tests).
  • Every Lumen item above is ticked in its spec, and the items capsule.md adds.

app-design (system items)

These fix the boards, icons.txt or the tokens, not a surface.

  • Account picker row: monochrome provider glyphs (google, microsoft, mail, slack) and a mail icon in icons.txt; the AccountRow board. (account picker row)
  • Chip: the boards draw tags with radius 5; the token is --radius-chip (4). (chip)
  • Command result card: the ResultCard board (table, text, card, error). (command result card)
  • Credential sheet: the CredentialSheet board (key, file, sign-in, Lumen row). (credential sheet)
  • Form controls: the stepper is drawn twice (a --hover fill in Settings, a --rule-strong outline in Project settings); the spec takes the fill. (form controls)
  • Glass mini-view: the GlassMini board. (Glass mini-view)
  • Icons: add minus and unlock to icons.txt; the Vault board draws the faceid frame without its face, which is not in the set. (icons)
  • Key hint: the "Settings · account and project scopes" board draws ↵; use ⏎. (key hint)
  • Mode chip: the composer chip radius 6 is not a token; add one or use --radius-button. (mode chip)
  • Suggestions: the Suggestions board (the three lanes, ghost text, Lumen groups). (suggestions)
  • Tabs: the teammate phone strip is drawn 40 tall; the spec makes it 44. (tabs)
  • Tip: the tip (lightbulb) icon in icons.txt; the TipLine board. (tip)

Done when npm run design:audit passes on every changed board and each item is ticked in its spec.

Other teams

Items in the specs that belong to teams outside the surfaces. The specs stay the source of truth.

cohesion

  • Suggestions: the @ lane in chat asks file sources too (the composer's file list as an offered source). (suggestions)
  • Needs row: merge the kinds waiting does not have yet (Sign-in grant, New teammate, Merge failed, Stuck, Usage, Device, Plan), each from its owner's tool. (needs row)

memory-iq

  • Suggestions: offer the entity source (people, places and aliases from memory already loaded) with suggest.offer, never a model call. (suggestions)

vault

  • Account picker row: a default per capability (proposed vault.connections.update {id, default}) and a last used time on each row. (account picker row)
  • Credential sheet: vault.need returns purpose in plain words and the provider's key page for Open. (credential sheet)

platform

  • Command result card: the result shape on the commands field (view, title, rows, text, actions) and render: true on the call. (command result card)

polish-cli

  • Command result card: --view frames for the seven commands without JSON output (capsule, connect, hooks, mcp, sideview, voice, send). (command result card)
  • Needs row: vyre needs and the status line count from waiting.count. (needs row)
  • Suggestions: later, the same lanes and order under the prompt; Tab accepts. (suggestions)

Not assigned

  • Device row, core: relay.devices.remove and relay.devices.trust both ask for presence; Remove (a hold) and Stop trusting need no proof. Parked with relay; the summary should name the device, not its id ("Trust browser Chrome on alex's Pixel 8 fully"). (device row)