docs GitHub

Design systemDraft

Needs row

The one-row primitive of Needs you: every kind that waits on you draws as this row, on Now on every surface and in Lumen's list. The rows and the one count come from waiting.list and waiting.count (see One list and one count, below). Drawn on the boards "Needs you, phone and desktop", "Needs you, teammate kinds", "States, every list, every size" (decided, many items) and "Devices, trusting a browser for the vault" (device trust).

SurfaceImplementing fileStatus
Deck, phonedeck/js/need-rows.js, deck/js/now-phone.js, deck/css/views/now.css (work/pwa)built
Deck, desktopdeck/views/now.js, deck/css/views/now.css (main)partial
Appapps/app/app/(tabs)/index.tsx NeedRow, src/ui/Row.tsx, src/ui/SwipeRow.native.tsx, src/ui/SwipeRow.web.tsx (work/mobile)partial
Lumenlocal/capsule/native/Sources/UI/AgentDeskView.swift WaitingRow, WaitingList (work/capsule-pro)partial

Anatomy

  1. Tile. Agent tile, 32 square, radius 8, fill --hover, the agent's initial at 13/600 --text. Kinds with no agent put a 16 icon in the tile in --text-2: reminder (calendar), usage pause (pause bars), device trust (phone or globe).
  2. Title line. Verb and object ("Send email to Sam", "Push q3-report", "kit has a question", "design is stuck"), 600, one line, ellipsis. Age right, meta size --label: "now", "12m", "3h", "2d"; a reminder shows its time ("15:00").
  3. Detail line. One line, --text-2: the command in mono for an ask (git push origin q3-report), the subject for a draft, the question, the host for a grant, the reason for merge failed, stuck and usage.
  4. Kind line. Meta size --label: "Ask · kit · Harlow Legal", "Reminder · you added it". A teammate's ask adds a fourth line in the same style: "asked by Q3 report".

Kinds (the kind line's first word): Ask, Draft, Question, Sign-in grant, Reminder, Plan, New teammate, Merge failed, Stuck, Usage, Device, Pairing. Oldest first, always. The group header carries the beacon dot and "Needs you" in --beacon-ink (see the list spec); the row itself is never tinted, bordered or washed in violet.

Sizes

Desktop (list pane)Phone (Now card)
Padding, tile gap, line gap10 by 16, 10, 212 by 14, 12, 2
Title13/18, 60017/24, 600
Detail13/18 (mono 13)13/18 (mono 13)
Kind line, age12/1612/16
Height (3 lines)7686

Fixed height per line count, so a virtualized list never measures; at larger phone text sizes the title wraps to two lines and the row grows. Desktop rows sit on --bg with a --rule bottom border; phone rows sit in a card (--panel, --rule, radius 10) with --rule between rows.

States

  • Hover (desktop) and pressed (phone, no transition): fill --hover. Focus: 2 px --focus outline, offset -2.
  • Selected (desktop). Fill --signal-wash; kind line and age step up to --text-2. The detail pane answers the selected row.
  • Swiping (phone). The row slides over the reveal: right shows the primary on --primary-bg with --primary-ink label and 20 icon (check); left shows the second action on --hover in --text. Each reveal is 100 wide. When the primary needs presence and no proof covers it, the reveal shows the Face ID glyph (Touch ID on a Mac, fingerprint on Android) beside its label.
  • Committed. On the frame the swipe commits, the row's height goes to 0 over --motion-tap and the undo toast shows (see the toast spec). The answer waits --motion-undo (4 s) in the outbox, then sends. Undo cancels it and the row comes back in its place.
  • Refused. The row returns; the kind line reads the reason in --text with the failed mark.
  • Decided. Neutral: the tile shows a 16 glyph in --text-2 (check, x, clock), the title drops to 400, the detail line reads who decided and when, and the kind line goes. Rows stay under a "Decided today" group for the day, then live in the session's history.
  • Offline. Never cached: "Needs you needs the box." and "Last checked 14:02."

Keyboard and touch

Phone swipe, by kind (right commits the primary; left is the second action with Undo 4 s):

KindRightLeft
AskApproveDeny
DraftSend (Face ID glyph unless covered)Discard
Sign-in grantAllow (Face ID glyph unless covered)Deny
QuestionAnswer (opens the sheet: an answer needs a choice)Later (hidden here 1 h)
New teammateApproveNot now
StuckRetryCancel
UsageResume anywaynone
DeviceTrust (Face ID glyph, always)Not now
ReminderDoneSnooze 5 min
PairingPair (opens the code field: a pairing needs its code)Not now
Plan, Merge failedopen the sheetnone

The swipe is a scroll-snap strip on the compositor (native: the UI thread). Release commits at a full 100 reveal or a 0.5 px/ms fling past 24; 40 to 100 rests open; under 40 closes. Tap opens the detail sheet with Open session. Asks and questions never need a proof.

Desktop, on the focused list: J and K move, A allows (the primary of any kind), D denies or discards, ⌘⏎ sends a draft, F asks a teammate to fix a failed merge. The selection moves to the next row after an answer. Footer: "J K move · A allow · D deny · ⌘⏎ send" with key-hint chips.

Lumen: the query field holds focus, so ↑ and ↓ move, ⏎ opens the card, A allows.

One list and one count: waiting

Every surface draws Needs you from cohesion's waiting module (ADR 0036 decision 4), and from nothing else. No surface merges asks, held drafts, reminders and pairings on its own, and none keeps its own count.

Rows. waiting.list {limit?} returns {rows, count, by_kind, partial?}. Each row maps onto this row as follows:

FieldDraws as
idthe row's key: the undo, the outbox, push and every device resolve by it
kindthe kind line's first word and the tile: ask is Ask (Question when answer.fill names answers), draft is Draft, reminder is Reminder (calendar icon), pairing is Pairing (laptop icon)
titlethe title line, as given ("Send email to Sam", "Pair the Mac "alex's MacBook Air"")
detailthe detail line; mono only when it is a command or a path
project, threadthe kind line after the kind ("Ask · kit · Harlow Legal"); tap or ⏎ opens the thread
atthe age on the title line; the surface sorts oldest first by at (the tool answers newest first)
sourcenot shown; it names the owner for errors ("Couldn't reach the planner")
answerwhat the primary and the second action call (below)

Answering. The row's primary calls answer.tool with answer.input and what the person gave for each name in answer.fill: nothing more for [] (a draft's Send, a reminder's Done), the choice for decision (Approve, Deny), the chosen answers for answers (the question card), the code for code (a pairing). The owner does the work (threads.answer, gate.approve, planner.done, link.pair.approve); the surface never calls anything else. When the answer lands, waiting.changed fires and the row leaves on every device at once.

The count. One number everywhere, waiting.count {} ({count, by_kind}), kept fresh by waiting.changed {count, by_kind}, never by counting rows on the screen:

WhereHow it shows
Deck rail, Nowthe 18 badge (rail.md, status-mark.md)
Deck top bar, any page but Nowthe needs count (top-bar.md)
Phone, the Now page label and the app iconthe 18 badge; the app icon's badge number
Lumenthe group header "Needs you · 3" and the menu-bar mark's dot
Menu barthe mark's violet dot while the count is above 0
Status line and CLI"3 need you" (vyre needs)
Faviconthe badge

When partial names a source, the count shows what was read and the list ends with one row in --label: "Couldn't reach the planner · Retry". Kinds that waiting does not merge yet (Sign-in grant, New teammate, Merge failed, Stuck, Usage, Device, Plan) stay where they are today and are not counted by a surface on its own; each is a gap for cohesion below.

Pairing. A Mac asks to join. Tile: the laptop icon. Title "Pair the Mac "alex's MacBook Air""; detail the node and login (alex-mba · alex); kind line "Pairing · expires in 8 min" (the link TTL). The primary is Pair; it opens the code field in place of the detail line (desktop, the detail pane; phone, the sheet): a field, mono 13 (17 on the phone), 6 characters, "Enter the code shown on the Mac", then Pair (primary) with the Touch ID glyph (Face ID on the phone), since pairing always needs a proof. A wrong code reads "That code doesn't match. 2 tries left." in the help line. Not now leaves it until it expires. Decided: "Paired by you · 14:22" or "Expired after 10 min".

Motion

Collapse over --motion-tap with --ease; the reveal tracks the finger; a release that does not commit springs back over --motion-panel. Reduced motion: no height animation.

Copy

  • Decided: "Allowed once by you · 14:22", "Denied by you · 14:25", "Sent by you · 14:30", "Expired after 24 h" with a ghost "Ask again".
  • Hint under the phone card until the first swipe: "Swipe right to approve, left to deny."
  • Never "Are you sure", "Waiting on you" or caps.

Accessibility

One button labelled with the whole row: "kit, Harlow Legal, wants to push q3-report, git push origin q3-report, 4 minutes ago. Actions: Approve, Deny, Open." Swipe actions are accessibility actions (native) and real buttons shown while the row has focus (web). The group label says "Needs you", so status is never colour alone; meta on --signal-wash steps up to --text-2.

Gaps

Deck, phone (work/pwa)

  • 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.

Deck, desktop (main)

  • 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.

App (work/mobile)

  • Kind line is "agent · project"; only gate and ask kinds; no decided state.
  • Commit at 40% of the row width, not 100 or a fling; reveal has no icon or Face ID glyph.
  • Rows sit on --bg full width, not in a --panel card.

Adopting waiting.list, every surface

  • Deck, phone and desktop (work/pwa): 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.
  • App (work/mobile): 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.
  • Lumen (work/capsule-pro): the list and the "Needs you · n" header from waiting.list; the menu-bar dot from waiting.changed; answer through answer.tool.
  • CLI and status line (work/polish-cli): vyre needs and the status line count from waiting.count.
  • Push (work/pwa): resolve by row id, so one answer clears every device.
  • cohesion: 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.

Lumen (work/capsule-pro)

  • Two lines, no kind line, a 7 dot for the tile, 40 tall; header "WAITING ON YOU" in caps.
  • Selected row has a 2 px violet left bar (violet as a border): use the --hover fill only.