Smithers MVP · design

Design architecture

Twelve diagrams that answer the recurring design questions. Link one by its anchor, for example architecture.html#merge-rule.

Read on 2026-10-02 from .specs/product/mvp.md (decisions M-01 to M-36), .specs/engineering/spec.md, .specs/engineering/ui-components.md v0.4, .specs/engineering/tickets/README.md, apps/app/AGENTS.md, the design README.md and mock/src/.

01

Layers: Paper tokens up to catalog commands, and who owns each

#layers
Paper tokensstyles/tokens.cssDesign
--brand --sediment-* --ember-* --paper-* · --ink-* --lane-0…5
PrimitivesT-UI-01Design
ActorChip AP S StateWord Working · Implement Tone live · attention · failed · done · quiet
Card Viewscards/views/<Card>View.tsxDesign
HomeViewTodoViewTodoView repair partsDraftViewConfirmViewBranchViewCodeEditorViewDiffViewTerminalViewRunViewFlowViewAgentViewSetupViewSettingsViewMembersViewSecretsViewCommandsViewDocsViewDebugApiViewProposalView
ShellT-UI-07 · T-UI-08Design
conversation of cardstimeline rail and noticescrumbs and branch treecomposer ⌘Kmaximize overlay
Containers<Card>Container · T-APP-nnFrontend lead (b8)
subscribe to topicsmap to CardProps<M,V,G>filter actions[] and gestures for the viewerbind argscardActions → flowActionview state per memberframes and URL
Topics and catalog commandsspec.md §7.2 · §6.1Engineering
hometodo:<n>branch:<id>run:<id>conversation:<branch>confirmations:<member>catalog.mvp.json/todo.answer/merge
Designtokens through shell visualsT-UI-01 … T-UI-23,TODO repair parts included
Frontend lead (b8)containers, state, routing
Engineeringtopics and commands
  • Design builds every visual piece; engineering wires data and actions (Will, 2026-10-02).
  • The seam is one zod view model per card, packages/rpc/src/<Card>Card.ts (T-APP-19). Until it lands, Views build against ui-components.md with local fixtures. Local fixtures are provisional; acceptance uses T-APP-19 schema fixtures.
  • A View exists only for a spec.md §14.3 row. Retained cards (Issue, PR, Review findings, Wiki, flow Form) keep their schemas.

Source spec.md §14.2.1, §14.3, §14.3.0, §7.2, §6.1; mvp.md §2 (rules 1 and 4), §6.4, M-35, M-36; ui-components.md v0.4 (opening diagram, Rules, Order of need, Completion gates); apps/app/src/mainview/styles/tokens.css. Engineering view ↗

02

The View/Container seam: data comes in as props, gestures leave as catalog tags

#seam

Example: Answer on T9, a shared-state action

Example of a shared-state action, Answer on T9. A live topic feeds a Container, which maps it to CardProps for a View and binds each action's args. The View has three action sources: buttons, row actions and gestures. Its handler calls onAction with the action's tag and its args merged with the form input; the Container runs the catalog command through flowAction; the command commits state and the topic sends a delta. onView patches go to this member's private view state. Local state, focus and the clipboard never leave the View. Only props and callbacks cross the C-UI-08 line. Live topic todo:<n> · home branch:<id> · run:<id> engineering · spec.md §7.2 Catalog command /todo.answer T9 slash, button and agent run it engineering · spec.md §6.1 <Card>Container frontend lead (b8) subscribes to its topics maps them to CardProps<M,V,G> filters actions[] and gestures binds args: { n: "9" } cardActions → flowAction stores onView patches per member, private (§14.1.2) spec.md §14.2.1, §14.1.2 C-UI-08 only these cross <Card>View design (T-UI-nn) props only buttons: actions[], in order rows: each row's own actions gestures: gestures[name] 1 onAction(action.tag, {...action.args, ...input}) data-flow={action.tag} 2 onView(patch) maximize, tab, scroll: private 3 local only: state, focus, clipboard cards/views/<Card>View.tsx subscribe flowAction {n: 9, text} state commits; delta ≤ 1 s props 1 args + input 2 view patch
type Action = {                 // filtered for the viewer by the container
  tag: CatalogTag               // the catalog's tag type (T-CAT-01), opaque to the View
  label: string                 // "Answer", "Merge", "Retry"
  args?: Record<string, string> // bound by the container, e.g. { n: "12" }; the View passes them back unchanged
  primary?: boolean
  disabled?: { reason: string } // e.g. "Waiting for T8"
  input?: FormField[]           // present when the action needs a form
}
type BaseView = { maximized: boolean; tab?: string; filter?: string }
type CardProps<M, V = {}, G extends string = never> = {
  model: M
  actions: Action[]                             // buttons, in order
  gestures: Partial<Record<G, Action>>          // non-button gestures by name
  onAction: (tag: CatalogTag, input?: Record<string, string>) => void
  view: BaseView & V                            // this member's view state (§14.1.2)
  onView: (patch: Partial<BaseView & V>) => void
}
// handler 1: onAction(action.tag, {...action.args, ...input})
args
Bound by the Container, e.g. { n: "9" }. The View passes them back unchanged.
gestures
Non-button gestures by name: hover, go to definition, a docs link. A missing one does nothing.
disabled.reason
Shows as text in place of the button (#card).
input
A form for exactly the missing fields, never a usage sentence (FORM LAW).
onView
This member's view state: maximize, tab, filter, scroll, selection. Private; never shared state.
Handlers
Each does one of three: 1 onAction, 2 onView, 3 local state, DOM focus or the clipboard (copyText).
Three doors
A slash command, a button and the agent run the same command (THREE-DOOR LAW).
  • A missing action means no button; a missing gesture does nothing. The Container filters actions[] and gestures for the viewer; the View never decides who may act.
  • Tags are opaque. The View hands back action.tag with {...action.args, ...input}, and the control carries data-flow={action.tag}.
  • Handler 3 never leaves the View: roving tabindex, Escape to close, controlled inputs, disclosures that don't persist, Copy.
  • A field the View needs but the props lack is a spec change. Raise it with the tech lead (8a), who updates spec.md §14.3, ui-components.md and T-APP-19 together.

Source mvp.md §2 (rule 1), Appendix A (three doors); ui-components.md v0.4 (Rules: three handler kinds, gestures; Shared types: Action, BaseView, CardProps<M,V,G>); spec.md §14.2.1, §14.1.2, §14.1.4, §6.1, §6.1.4, §7.2, §7.2.1; check C-UI-08; apps/app/AGENTS.md (THREE-DOOR LAW, FORM LAW); apps/app/src/mainview/cards/CardActions.ts (cardActions), apps/app/src/mainview/flows/FlowAction.ts (flowAction).

03

Shell anatomy: one conversation per branch, a timeline rail on desktop, pills on narrow screens

#shell

Desktop: a viewport at least 1180 px wide. Alice's screen in retry-webhooks.

Desktop shell. Crumbs and the open branch tree at the top. The timeline rail on the left with a top pinned edge, the band of entries on screen, the list, a bottom pinned edge and docked notices. The conversation of cards in the middle. Chat ⌘K at the bottom right. SMITHERS acme/api / main / retry-webhooks ▾ acme/api1 needs you terminal 1pnpm dev +1 above main moved#216 merged on GitHub “Why does T9 wait 30 s?” retry.tsLines 4–9 cited T9 Retry failed webhooksAsks: backoff or timeout? retry-webhooksAlice: editing retry.ts terminal 2Running pnpm test Review2 findings T12 Send one reset emailAgent: edited mailer.ts T10 Fix the flaky checkout testFailed: lint T13 Show the currencyWaiting for a machine Run · docs flowWorking T10Failed: lint terminal 2pnpm test +2 below T9 needs you Asks: backoff or timeout? Answer Ben Why does T9 wait 30 s before each retry? S Smithers for Ben deliver() sleeps a fixed 30 s before each retry. Context · 3 src/webhooks/retry.ts main for (let attempt = 1; attempt <= 5; attempt++) { await sleep(30_000) T9 Retry failed webhooks Needs you BO retry-webhooks 2nd in stack · from #212 · Ben Plan Implement Verify Review Propose Merge Each retry waits 30 s, so the retry test times out… Answer Answer retry-webhooks Awake AP Aliceediting retry.ts:10 Coding agent for Benat Implement Chat ⌘K main T8upgrade-stripe T9retry-webhooks ben/retry-try-2 T10fix-checkout-race T11log-retries 1 2 3 4 5 6 7 8 9
Narrow shell: no timeline. One pill per edge, in the header and footer rows. Notices float at the bottom left. SMITHERS acme/api / main ▾ ↑ 2 live above T12 Send one reset email Working · Implement Plan Implem… Verify Review Propose Merge Open branch Inspect Stop Drop #235 Show the currency on i… Ben T9 needs you Asks: backoff or timeout? Answer ↓ 1 new below Chat ⌘K 12 12 13

Narrow (390 px)

⌘K opens the composer and palette, one floating card at the top of the page over the conversation. Ask Smithers to work on something… Queue Chat ⌘K 10

Composer and palette open

Maximize shows the same card larger, over a scrim, with Restore. The chat controls stay visible. Retry failed webhooks Restore Run timeline Selected cell Chat ⌘K 11

A card maximized (here the Run card: Inspect)

  1. 1Crumbs. Repository / main / branch. A parent crumb goes up; the last opens the tree.
  2. 2Branch tree. main, each item's branch in stack order, forks under their origin, with presence. ↑ ↓ move, ⏎ opens, ← goes up; focus returns to the crumb.
  3. 3Conversation. The branch's shared entries: prompts with their author, Smithers' answers with Context · n, and cards.
  4. 4Top edge. Live work above the screen, each with its single action. Two pinned rows, people-needed first, then “+N above”, which opens every hidden entry.
  5. 5Band. The entries on screen.
  6. 6List. One line per entry. Title; run entries also have an optional fast-model summary. Live entries pulse, Needs you is gold, failures are ember.
  7. 7Bottom edge. Live work below. Two pinned rows, then “+N below”, which opens every hidden entry.
  8. 8Notices. At most three, then “+N more”. Docked in the rail.
  9. 9Chat ⌘K. Opens the composer and palette in this branch's conversation.
  10. 10Composer and palette. Floats at the top over the conversation. Esc, a press outside or ⌘K again closes it; focus returns to the opener.
  11. 11Maximize. The same component, larger, over a scrim, with Restore; the chat control stays. Keyboard reachable; focus returns to the opener.
  12. 12Narrow edges. Below 1180 px: no timeline, one pill per edge, in the header and footer rows.
  13. 13Narrow notices. Float at the bottom left.

Shared by everyone on the branch

  • Entries: prompts with their author, answers, events
  • Shared cards, excluding private Confirm and uncommitted Draft
  • Receipts: what an act did, as its own entry
  • Run entries' summaries
  • Claude Code and Codex conversations from branch terminals, read-only: no edit, resend, answer, approve, retry, stop or steer S2

Each person's own

  • Scroll position and last-seen entry
  • Card view: maximized, tab, filter, scroll, selection
  • Theme
  • Hidden notices
  • Drafts: composer text, and a Draft card until Commit
  • Confirm cards: only the asker sees one; others see nothing until the shared receipt
  • Everything embeds in the conversation. Nothing opens full screen unless the person maximizes it (EMBED LAW).
  • The timeline is desktop only. Hiding a notice preserves the entry; any still-applicable action remains available.
  • UI-only flows (theme, open, maximize, navigate, palette, filters) change only the screen of the person who ran them, even when their app agent ran them.

Source mvp.md §6.4 (Home card, Branch conversations, Toasts, Timeline, Browser notifications), M-08, Appendix B.1; spec.md §14.1.1–§14.1.4, §14.2, §14.4, §14.5.1–§14.5.4, §15.1.5, §7.2.2; ui-components.md v0.4 (Rules: view state); apps/app/AGENTS.md (EMBED LAW, onboarding brief on the composer); mock AppFrame.tsx, Rail.tsx, Tree.tsx, mock.css (PORT · the left rail).

04

Card anatomy: header, body, one actions row, and one entry per thing

#card
  1. 1Title. The ref chip and the title.
  2. 2Status word. One pill per state (#states).
  3. 3Header end. Owner, presence or a save state. See the variants below.
  4. 4Maximize. Becomes Restore when maximized.
  5. 5Body. Meta line, step strip, run flags, the question, evidence.
  6. 6Actions row. Only the actions supplied, in order: ghost buttons, then the one solid primary or a reason. A question is open, so no Stop.

Button weights

MergeOne per card: its primary act AnswerA row's one action; a secondary act InspectQuiet acts: Open branch, Inspect, Stop Merges after T8A reason, in place of the button; never a disabled button

Header end, by card

BOTODO: its owner APFile: editors, then “Saved to the machine” BOAPTerminal: owner, then watchers maya/todo-flowRun with no TODO: its branch

One entry per thing

A TODO's run: no entry of its own
↓ its flags ride the TODO card
T9 TODO card
↓ Inspect
RunView, maximized: the monitor. No second conversation entry.
A run that is no TODO's (learning, a flow run) has its own Run entry. One entry per (kind, id): showing it again jumps there.
  • A View renders only the actions it is given. Stop is refused while a question or approval wait is open (spec.md §10.7.1), so this card has none.
  • One solid button per card, for its primary act, with ghost buttons before it.
  • One persistent entry per entity, keyed (kind, id). Showing it again jumps to that entry (spec.md §14.5.1).
  • Header slots are presentation only: they show model fields and add none.
  • A reason replaces the button it blocks, as text. An Action with disabled.reason renders that way.
  • Card body lines hold 12 words or fewer, in product words only.

Source mvp.md §2 (rules 4 and 7), §6.6 (TODO card), §6.14 (Monitor, Inspect); spec.md §14.2, §14.3 (TODO and Run rows), §14.6b, §11.6.4, §10.7.1, §4.1.0a; ui-components.md v0.4 Rules, Action, T-UI-04; apps/app/AGENTS.md (EMBED LAW, MINIMAL TEXT); design README (Visual rules); mock parts.tsx (Card), cards/Todo.tsx, cards/Run.tsx (RunFlags), world.ts (showCard).

05

Identity: every participant has one mark, wherever it appears

#identity
MarkReads asActorPresenceActivity rowLine flagTerminal headerChat author
AP
Alicea person: a circle in their color personcolor_index 1 editing retry.ts:10 steers, answers Alice owner, or a watcher with prompt author
S
Smithers for Benthe app agent for Ben: its square in Ben's color agent smithersfor_member Ben · id: its session · color_index 0 Smithers for Ben · reading retry.ts Smithers for Ben steered T9 Smithers for Benwiki pages none: never types answers Ben, as everyone sees it
S
Smithersinstall events only: the ink square systemnever an agent none Rebased onto T8 none: edits no lines none none
C
Claude Code for Benan external agent: its own square in Ben's color agent claude-codefor_member Ben · id: its session · color_index 0 Claude Code for Ben · terminal 1 Claude Code for Ben answered T9 Claude Code for Ben Claude Code for Ben, in Ben's terminal; Ben stays its owner its terminal conversation, read-only; prompts name Ben S2
Coding agent for Bena TODO's agent: its square in the owner's color agent coding · T9for_member Ben · id: the run · color_index 0 Coding agent for Ben · Verify on T9; breathes while working steps, Read, Context, Asks Coding agent for Ben its own session asks through Needs you
R
Reviewer for Bena review run: its own square in Ben's color agent reviewerfor_member Ben · id: the review run · color_index 0 Reviewer for Ben · T9 at 5e7d2b0 Reviewer for Ben: no blocking issues none: reads only none none
Coding agentacting for nobody: the default agent color agent codingno for_member · id: the run · color_index 6 at a learning run's step a background run's steps Coding agent its own session none
MC
Maya via SSHthe person is the actor: her circle, a terminal badge person · via sshcolor_index 2 editing retry.ts:2 Maya via SSH changed 12 files Maya · SSH none: her own client none
Changed outside Smithersno single session can claim it: a neutral square outsidecolor_index 7 none Changed outside Smithers · 3 files none; the File card says “Changed outside Smithers · Compare” none none
@login
@logina GitHub user who is not a member: the GitHub mark githubcolor_index 7 none @login on rows from GitHub none none none
  • People are circles; agents, Smithers included, are rounded squares.
  • Every agent doing work is its own participant with a stable id: Smithers, Claude Code, the coding agent, reviewers (M-34). It never collapses into the person's avatar.
  • An agent acting for a member, Smithers included, takes that member's color and reads “Claude Code for Ben”. Plain “Smithers” in ink marks install events (system), never an agent.
  • Only a person's own doors keep their circle, with a small badge: “Maya via SSH”, “Ben's terminal”, “Ben via CLI”.
  • color_index: 0–5 a member's color, also for anything acting for them; 6 an agent acting for nobody; 7 neutral, for GitHub users and outside writes. Tokens per index: #color.

Source mvp.md §3.1, §6.8 (Presence, External changes), M-21, M-27, M-32, M-34; settled ruling on M-34 marks (2026-10-02); spec.md §7.3.1, §7.3.2, §14.6a.1; ui-components.md v0.4 Actor, T-UI-01; mock parts.tsx (Avatar, actorName, flagName), world.ts (isSmithers, forWhom, via), mock.css (PORT · avatars), css/code.css (flags).

06

Color semantics: fixed status colors, and actor color as a separate channel

#color

Status meanings: ui-components.md Tone. Token table v1, light and dark, each pair subject to contrast checks.

Teal · live or act

Working, Starting, the current step, live timeline rows, the solid button, the focus ring.

LD
--brand#0f766e · #45c4b2
LD
--action-primary#0d6760 · #45c4b2
LD
--success#0b5b57 · #7fbfb3 · ✓ marks
LD
--ring-border#3e9e90 · #7fbfb3
Working · ImplementMerge

Gold · a person is needed

Needs you, stack attention, an approval toast, a wait for a person, a stale sync, the notification ask, a merge blocked until a person acts.

LD
--attention#d08a1d · #f0c169
LD
--attention-text#8c5a08 · #f0c169
LD
--attention-washe8a33d 16% · f0c169 14%
Needs youWaiting for a person since 10:42synced 6 min ago · Retry

Ember · failed

Failed, a failed check (required ones too), a failed background run, “Merged · not active”.

LD
--danger#a4442a · #d97757
ember-600 · ember-400
Failedlint

Neutral · settled or automatic

Queued, Paused, In review, Merged, Dropped, the wait for merge, waiting for a machine, system and order waits such as “Merges after T8”.

LD
--text-muted#665f54 · #a8a193
LD
--surface-2#efeae0 · #1a2422
LD
--border-solid#e4ddcf · #293532
QueuedPausedIn reviewMerged

Actors · who, never what state

Colors 0–5 mark members and any agent or Smithers acting for them. Undelegated defaults: teal for an agent acting for nobody (6), neutral for GitHub users and outside writes (7). Ink marks install events.

LD
--lane-0#9b4d31
#eda381 · Ben
LD
--lane-1#376f91
#8cc5e8 · Alice
LD
--lane-2#78639a
#bea6e6 · Maya
LD
--lane-3#82651e
#d9be70
LD
--lane-4#387667
#8acaba
LD
--lane-5#a24f6b
#e5a1b8
LD
--text#211d18
#ece7db · system
LD
--brand#0f766e
#45c4b2 · 6
BOSBen, and for BenAPAlice, and for Alicefor nobodySinstall events

Retired as status and agent chrome

Violet no longer marks a status or an agent. Identity violet stays: color 2, Maya.

LD
--ai#6a5ce0 · #a99ff7
still defined in tokens.css
  • Status colors have fixed semantics: live is teal, attention is gold, failed is ember; done and quiet are neutral. In review is quiet.
  • Actor color is a separate channel. It says who, never what state, and a status never borrows an actor color.
  • Both themes gate acceptance: every swatch has a light and a dark value.

Source ui-components.md v0.4 (Tone, Actor); settled rulings on Tone and M-34 marks (2026-10-02); design README (Visual rules); mvp.md §6.3 (Sync status), §6.4 (Timeline), §6.12 (Pinned versions), §12; spec.md §4.4, §14.5.2; apps/app/src/mainview/styles/tokens.css; mock mock.css (PORT · identity and state tokens, PORT · TODO state, “Teal means live or act” overrides).

07

TODO lifecycle: nine states, each with one glyph and one pill

#states
TODO states. Main line: Queued, Starting, Working, In review, Merged. Starting becomes Working when the run attaches, new or resumed. A question, an approval, a conflict or a move off the item opens a wait and makes the TODO Needs you. Needs you leaves only when its last open wait settles: to Working when work is required, to Queued when the machine was released, and to In review when no new work is needed, as after Discard. Stop takes Working to Paused while no question or approval is open, and Resume takes Paused to Queued. Starting or Working can fail; Retry queues a new attempt. In review goes back to Working on changes requested or a steer, and to Needs you on a conflict, a move off the item or an outside push. Any unmerged state can be Dropped. any unmerged state machine granted run attached new or resumed PR opened a person merges asks, approval, conflict, moved off last open wait settles; work required last wait settles after release; work required last wait settles; no new work, as after Discard conflict, moved off, or outside push changes requested · steer Stop no question or approval open Resume start fails run error Retry: new attempt Drop Queued Starting Working · Implement In review Merged done Needs you Paused Failed Dropped or PR closed on GitHub
QueuedWaiting for admission; may be dropped. “Waiting for a machine · #2”
StartingNobody. Machine granted; waits for its run to attach, new or resumed
Working · ImplementAnyone on the branch watches or steers
Needs youQuestions: any member. Approvals that gate a merge: a maintainer
PausedResume or drop
FailedRetry, steer or drop; evidence is kept
In reviewA person reviews and merges
MergedNobody. This is done
DroppedNobody. Its PR closes; later items rebase

Waits

  • Run waits: question, approval. Opened while working.
  • Branch waits: conflict, moved off, outside push. Opened from any unmerged state.
  • Settling one wait never settles another. Each open wait shows on the TODO card with its own action.

Precedence: the first that holds

  1. Merged
  2. Dropped
  3. Needs you: any wait open
  4. Paused
  5. Failed
  6. In review, Working, Starting or Queued: the run's phase

When the last open wait settles: Working, or Queued after release, if work is needed; In review if not; Paused if stopped; Failed if blocked.

  • Each state has one glyph and one pill, the same on every card, row and timeline line.
  • Merged is done. Learning is a receipt on the merged card (“2 lessons”), never a state.
  • Drawn from mvp.md §4.1 and spec.md §4.1. spec.md's transition table is authoritative and adds rarer edges: a PR reopened on GitHub within 7 days revives a Dropped TODO, and a merge on GitHub counts from any unmerged state.

Source mvp.md §4.1, M-15; spec.md §4.1 (with §4.1.0a precedence and §4.1.1 queue copy), §10.7.1, §10.7.4, §10.8.0–§10.8.2; settled rulings on Stop and answers (2026-10-02); mock parts.tsx (StateGlyph, StatePill, stateLabel), mock.css (PORT · TODO state).

08

One durable attempt: work stages, system acceptance, then wait

#run
Attempt 1 was interrupted at Implement after a machine restart; a resumable restart would have continued it. Retry started attempt 2 as a new run: Plan, Implement, Verify, Review, Propose, then a wait for merge that needs no machine. A steer or a requested change loops the run back to Implement. A rebase or an edit loops it back to Verify. A clean rebase keeps the patch, so it goes from Verify straight to Propose and keeps the prior review; a conflict-resolved rebase or an edit reruns Review. A merge or a drop ends the run. Attempt 1interrupted, kept Planplan Implementimplement Interrupted after machine restart A resumable restart continues this attempt. Retry after interruption: attempt 2, a new run from step one. Attempt 2proposed, held Planplan Implementimplement Verifycandidate · check Reviewreview Proposestack.propose system acceptance capture: system Wait for mergeneeds no machine steer · changes requested reruns on a new patch clean rebase: same patch-id, review kept rebase onto a new tip, or an edit: back to Verify merged or dropped: the run ends

The TODO card's step strip mirrors the run

Working at Implement, also after a steer
  1. Plan
  2. Implement
  3. Verify
  4. Review
  5. Propose
  6. Merge
In review: held at the wait for merge
  1. Plan
  2. Implement
  3. Verify
  4. Review
  5. Propose
  6. Merge
Clean rebase: checks rerun at Verify; the prior review stays
  1. Plan
  2. Implement
  3. Verify
  4. Review
  5. Propose
  6. Merge
Conflict-resolved rebase or an edit: Verify, then a new review
  1. Plan
  2. Implement
  3. Verify
  4. Review
  5. Propose
  6. Merge
After the wait, onBack toThen
a steer or requested changeImplementVerify, Review, Propose
a clean rebaseVerify: checks onlyPropose; the prior review stays (same patch-id)
a conflict-resolved rebase, or an editVerifya new Review, then Propose
a merge or a dropnonethe run ends

Inside a step: phases and cells, as Inspect shows them

Implement
3
Ran tests · 1 failed ×34 min
The retry test times out every time.
Thrashing: TestRetryBackoff failed 3×
Ran pnpm test · 1 failedRan the webhook tests. The retry test timed out.
title: the step plus its recorded output✦ the fast model's one-line summarya deterministic rule, no modelcell: a label, then ✦ its explanation
4
Asked a person
Asked whether to change the delay or raise the timeout.
Waiting for a person since 10:42
BOUse backoff() from lib/backoff everywhere we sleep before a retry.
a durable wait, with sincea person's own words carry no ✦
5
Edited 2 files
summary pending: the title stands alone, with no error state
  • One attempt is one durable run of its pinned flow version. It lives until the TODO merges or is dropped; Retry starts a new attempt beside the old one.
  • A machine restart re-runs no finished step: the run resumes, or shows interrupted with Retry (spec.md §19.1).
  • Capture (stack.candidate) and Propose (stack.propose) are system acceptance, not overridable flow steps. The step strip keeps its labels.
  • After Propose the run holds at the wait for merge. The wait needs no machine: the machine is released once safe-idle, and its slot stays held until the stop is confirmed.
  • A clean rebase reruns checks only, and the prior review stays (same patch-id). A conflict-resolved rebase or a code change reruns review. Any rebase voids an approval.
  • Phase titles and cell labels are deterministic. ✦ marks what the fast model wrote, only for runs someone inspects.

Source mvp.md §6.12 (Pinned versions), §6.14 (Monitor), Appendix B.5, J11; settled rebase ruling (2026-10-02); spec.md §10.3.1, §8.3.2, §10.4.1, §10.4.3, §10.4.4, §10.4.5, §10.5.3, §10.6.2c, §10.7.1, §11.4, §19.1, §11.6.3, §11.6.4, §14.5.3; ui-components.md T-UI-04 (steps), T-UI-10 (signals), T-UI-12 (RunModel); mock cards/Run.tsx, parts.tsx (StepStrip), cards/Flow.tsx, journeys/run.ts. Engineering view ↗

09

Merge readiness: one decision, read by every merge surface

#merge-rule
Home rowTODO cardReview & mergeall read oneMergeready · waiting · blocked · merging · done
Outside the predicate. Merged: “Merged into main”, no button (done). Authorizer. A session of an owner or maintainer. A member gets no Merge and reads the same reason, or “Ready · a maintainer merges”.

MergeReady(todo, reviewed_head_sha) · rows in order · the first that fails gives reason

  1. 1 · stateIn review: no open wait, not paused?noblocked
  2. 2 · orderFirst unmerged item in the stack?nowaitingMerges after T8
  3. 3 · attentionNo stack attention open?noblocked
  4. 4 · mergingNo merge fence set?nomerging
  5. 5 · recheckingGeneration accepted after machine checks and review; PR push settled; PR head set?nowaitingChecks running on 5e7d2b0
  6. 6 · recheckingNo rebase pending; based on main's tip?nowaitingRebase pending
  7. 7 · pending_workNo pending work since that generation?1nowaiting
  8. 8 · stale_headReviewed head = PR head = GitHub's head?noblockedon GitHub ↗
  9. 9 · checks review_required githubRequired GitHub checks pass, required reviews met, not draft, mergeable?noblockedlint failedrequiredon GitHub ↗
  10. readyEvery row holdsMergeReview & merge adds “Merges as Ben”

1 Row 7: no unconsumed input above inputs_seq (steer, amendment, review comment delivered as a steer, run answer), and the valid newest capture equals the accepted tree. A missing capture blocks.

Gold: a person must act. Neutral: system and order waits. Ember: a failed check.

Evidence is per revision

1b2c3d4earlier revision
typechecktestlintGitHub required 3/3
Review: No blocking issues
Approved by Ben
↓ T8 merged; clean rebase onto main, same patch-id
5e7d2b0current revision
typechecktestlintGitHub required 0/3
Review: No blocking issues · kept from 1b2c3d4, same patch-id
Approval cleared by rebase · checks rerun
↓ when its checks pass
Merge T9 into main?
You approved 1b2c3d4. Review 5e7d2b0.
CancelReview & merge
Or a conflict-resolved rebase, or a code change: a new patch
9e8f7a6new patch
typechecktestlintGitHub required 0/3
Review running on 9e8f7a6 · no earlier review: the patch-id differs
Approval cleared · checks and review rerun
  • One rule for every merge surface: no card offers a merge another card would refuse.
  • MergeReady's nine rows run in order. The first failing row's reason replaces Merge, as text.
  • Role and session sit outside MergeReady, in the authorizer. Only required GitHub checks block; machine checks and review count through the accepted generation (row 5).
  • A clean rebase reruns checks only and keeps the prior review (same patch-id). A conflict-resolved rebase or a code change reruns review; an earlier review shows only when its patch-id matches. Any rebase voids an approval.

Source mvp.md §4.2 (Rebase, Merging), §6.3 (Checks finish, GitHub branch protection), §6.10, M-05; spec.md §10.6.1, §10.6.2, §10.6.2a, §10.6.2b, §10.6.2c, §10.4.3, §10.5.3; ui-components.md v0.4 (Merge, Evidence); settled merge and rebase rulings (2026-10-02); mock cards/Home.tsx, cards/Todo.tsx (Evidence), cards/Confirm.tsx. Engineering view ↗

10

App agent permissions: runs at once, asks once, opens your merge, or has no path

#permissions

Runs at once A · run

  • Reads: code, branches, runs, the wiki
  • Steer, answer, stop, resume, retry
  • Rebase, fork, run a flow
  • Wiki writes: pages keep history
  • The asker's own screen: theme, open, maximize
  • Open the asker's terminal; never type in it
SSmithers for BenStopped T10.
The reply is the receipt.

One-click confirmation A✓ · confirm

  • Commit, amend or drop a TODO
  • Add to stack
  • Bring in or Discard an outside push (Discard: maintainer)
  • Delete a wiki page
  • Propose a flow or agent edit
  • Review a PR (/review)
A role above member needs the requester to hold it; only their own session approves.
Commit a new TODO?
/todo.new Send one reset emailCClaude Code for Ben asked
Only BenCancelCommit ⏎
Only the asker sees Confirm; others see nothing until the shared action receipt. A TODO it names shows “Needs Ben” to everyone. The Draft stays the editing and proposal model.

Merge A✓ · review_merge

  • Opens the asker's own private Review & merge. Only a requesting owner or maintainer gets one.
Merge T8 into main?
#88 · rev 5e7d2b0typechecktestGitHub 5/5
Merges as BenCancelMerge
Agents never approve or merge.

No agent path – · never

  • Members and roles
  • Secrets
  • Settings: model access, capacity, GitHub App
  • Approvals that gate a merge
Only by the person's own hand in the app.

B.3: the coding agent's actions render in the cards a person's do

read · ls · glob · grepA “Read” line in the branch activity; each file opens the File cardReadretry.tsbackoff.ts
write · edit · apply_patchAn attributed edit in the File and Diff cards, with its line flagCoding agent for Benawait sleep(backoff(1))
bash · testIts own Terminal session on the branch; anyone can watchterminal 2Coding agent for Ben
memory · recallThe Context line; the preflight cell in InspectContext · 3
askNeeds you, with Answer, on the TODO cardNeeds you
  • The app agent acts with the asker's authority and shows as Smithers for that person. It never merges or approves.
  • External agents through the CLI or skill follow the same run, confirm and never rules.
  • Only the asker sees Confirm; others see nothing until the shared action receipt.
  • A flow renders the same card whoever runs it, and each run is its own instance. Exceptions: a Confirm card, seen only by its asker, and a Draft, seen only by its author until Commit. Only the actor's mark tells a person's act from an agent's.

Source mvp.md §3.1, Appendix B (legend, B.1, B.2, B.3, B.4, B.6), M-21, M-33, M-34; settled Confirm ruling (product, 16:51, 2026-10-02); spec.md §5.2, §5.3, §5.4, §6.1.2, §14.3 (Confirm, Draft), §14.5.1, §15.1.3, §15.1.5; ui-components.md v0.4 T-UI-05; mock cards/Act.tsx, cards/Confirm.tsx, cards/Draft.tsx, journeys/ask.ts, journeys/agent.ts, world.ts (ask, pressed).

11

Component traceability: each design ticket, its View, the mock that draws it, and the reel steps that show it

#components
TicketViewMock sourceReel stepsStage
T-UI-01T-APP-09ActorChip StateWord Toneparts.tsx Rail.tsx?j=states&s=1?j=j3&s=2?j=j3&s=10S1
T-UI-02T-APP-03 · T-FLW-12SetupView SettingsViewcards/Setup.tsx?j=j1&s=3?j=j1&s=8?j=j1&s=30?j=states&s=41S1
T-UI-07T-APP-16 · T-APP-17BranchTree EntryRow ContextLine, Earlier archiveThe mock has no Earlier node. S2: read-only Claude Code and Codex conversations (spec.md §14.5.5).Tree.tsx AppFrame.tsx parts.tsx?j=j6&s=10?j=agent&s=6?j=ask&s=6S1
T-UI-06T-APP-01 · T-GH-08HomeViewcards/Home.tsx?j=j4&s=1?j=states&s=3?j=j10&s=14S1
T-UI-11T-APP-15CodeEditorView (File, read-only), DiffViewcards/Code.tsx?j=j1&s=12?j=j1&s=21?j=j11&s=7S1
T-UI-03T-APP-02DraftViewcards/Draft.tsx?j=j2&s=2?j=j2&s=5?j=j7&s=3S1
T-UI-04T-APP-02TodoView: every state, question and approval forms, failure, evidence, PR line, merge controlcards/Todo.tsx?j=j4&s=2?j=states&s=5?j=states&s=10?j=j10&s=11S1
T-UI-08T-APP-07 · T-APP-18ToastStack EdgeMap TimelineRail.tsx?j=j2&s=10?j=j2&s=11?j=j4&s=11S1
T-UI-05T-APP-04ConfirmView (one_click, review_merge)cards/Act.tsx cards/Confirm.tsx?j=agent&s=15?j=ask&s=10?j=states&s=16S1
T-UI-09T-APP-06MembersViewcards/People.tsx?j=j1&s=24?j=j1&s=25?j=states&s=39S1
T-UI-14T-CAT-01CommandsView (/help)cards/Commands.tsx?j=ask&s=1S1
T-UI-10T-APP-05FlowViewcards/Flow.tsx?j=j5&s=7?j=j11&s=5?j=states&s=36?j=states&s=37S1
T-UI-12T-FLW-07RunView monitor and Inspectcards/Run.tsx?j=run&s=1?j=run&s=2?j=run&s=6?j=j11&s=1S1
T-UI-13T-FLW-08AgentView and model rolescards/Agent.tsx cards/Setup.tsx?j=j11&s=14?j=j11&s=15?j=j1&s=8S1
T-UI-23T-STK-08 · T-MCH-08 · T-GH-06TodoView repair parts: conflict view, moved-off and outside-push forms, Fork, Add to stackThe mock puts Fork and Add to stack on the Branch card.cards/Todo.tsx cards/Branch.tsx?j=states&s=7?j=states&s=8?j=j10&s=6?j=states&s=24?j=j7&s=6?j=j7&s=11S1
T-UI-15T-APP-10 · T-COL-05BranchView with moved-off controlscards/Branch.tsx?j=j3&s=2?j=j7&s=6?j=states&s=23?j=states&s=24S2
T-UI-16T-APP-11File and Diff states: reload, gone, renamed, Restore, Comparecards/Code.tsx?j=states&s=25?j=states&s=26?j=states&s=27?j=states&s=29S2
T-UI-17T-APP-12TerminalViewcards/Terminal.tsx?j=j3&s=3?j=j3&s=5?j=agent&s=10?j=states&s=31S2
T-UI-18T-APP-13SecretsViewcards/People.tsx?j=j1&s=27?j=j1&s=29?j=states&s=40S2
T-UI-21T-APP-20DocsView (/docs)none yetThe mock lists /docs only in /help.none yetS2
T-UI-22T-APP-21DebugApiView (/debug-api)none yetThe mock lists /debug-api only in /help.none yetS2
T-UI-19T-APP-14Co-editing visuals on CodeEditorViewcards/Code.tsx cards/Wiki.tsx?j=j3&s=8?j=states&s=28?j=j8&s=6S3
T-UI-20T-FLW-06ProposalView and the lessons receiptcards/Learning.tsx cards/Todo.tsx?j=j5&s=13?j=j5&s=14?j=j8&s=4S3
  • Rows follow ui-components.md v0.4 Order of need. Stage 1 comes first; within a stage, the order a fresh install meets each component in J1, then J2, J4, J5 and J11.
  • Step links open the built mock (node .specs/design/mock/build.mjs). s=n shows the frame after step n.
  • Mock sources and reel steps are guidance. Acceptance is C-UI-12: each View renders every T-APP-19 fixture in light and dark, at 1280 and 390 px.
  • A change to a retained card's fields first adds its spec.md §14.3 row.

Source mvp.md §5 (journeys), §11 (stages 1 to 3), M-35, M-36; ui-components.md v0.4 (Order of need, Completion gates, Props per component); spec.md §14.3, §14.3.0; tickets/README.md; tickets T-UI-01 to T-UI-23; mock src/journeys/*.ts (step numbers checked 2026-10-02), src/cards/*.tsx.

12

Where things live: the mock today, the app's planned card layout

#files

The mock (design)

.specs/design/
├─ README.md               decisions the mock embodies
├─ architecture.html       this page
└─ mock/
   ├─ build.mjs            → dist/index.html, one file
   ├─ check.mjs            every step renders
   ├─ copy.mjs             copy lint
   ├─ shot.mjs             screenshots
   └─ src/
      ├─ world.ts          the model; mergeReadiness, revise
      ├─ parts.tsx         Avatar, actorName, StepStrip, Card
      ├─ AppFrame.tsx      shell: crumbs, conversation,
      │                    composer, maximize
      ├─ Rail.tsx          timeline, edges, notices
      ├─ Tree.tsx          branch tree
      ├─ cards/*.tsx       one file per card
      ├─ journeys/*.ts     15 reels; states.ts shows
      │                    every card state
      ├─ mock.css, css/    PORT rules move to the app;
      │                    MOCK rules stay
      └─ Player.tsx …      the review harness

The app (planned)

apps/app/src/mainview/
├─ styles/tokens.css       Paper tokens
├─ cards/
│  ├─ views/               design · props only
│  │  ├─ HomeView.tsx  TodoView.tsx  DraftView.tsx
│  │  ├─ ConfirmView.tsx  BranchView.tsx
│  │  ├─ CodeEditorView.tsx  DiffView.tsx
│  │  ├─ TerminalView.tsx  RunView.tsx  FlowView.tsx
│  │  ├─ AgentView.tsx  CommandsView.tsx
│  │  ├─ SetupView.tsx  SettingsView.tsx
│  │  ├─ MembersView.tsx  SecretsView.tsx
│  │  ├─ DocsView.tsx  DebugApiView.tsx
│  │  ├─ ProposalView.tsx
│  │  └─ shell parts: BranchTree, EntryRow, ContextLine,
│  │     ToastStack, EdgeMap, Timeline
│  ├─ <Card>Container.tsx   engineering · path not fixed
│  ├─ CardRenderers.tsx    renders Containers
│  └─ CardActions.ts       cardActions
└─ flows/FlowAction.ts     flowAction
apps/app/src/docs/         bundled pages, toc.ts (T-APP-20)
docs/api/openapi.yaml      Debug API operations, bundled
packages/rpc/
├─ src/<Card>Card.ts       zod view model (T-APP-19)
│                          TodoCard, DocsCard, DebugApiCard …
└─ test/fixtures/<Card>.ts fixtures for the stories
                           Todo, Docs, DebugApi …
packages/smithers/ui/src/  @smthrs/ui: shared primitives
  • Each new §14.3 card maps to a View; mock files may combine/split those presentations. Retained cards keep existing renderers.
  • PORT and MOCK CSS annotations are migration guidance. The fixture gates are acceptance.
  • world.ts is the design's view of the data, not an API. The props are ui-components.md, then the zod schemas.
  • Each View ships fixture stories for every state its props allow: light and dark, at 1280 and 390 px (C-UI-12).

Source design README (Run it, Porting); ui-components.md v0.4 (opening diagram, Rules, Completion gates, T-UI-21, T-UI-22); spec.md §14.2.1, §14.3 (Docs, Debug API rows), §14.3.0; delta.md §9; tickets T-UI-01 to T-UI-23 (Scope, Changes); mock build.mjs, check.mjs, copy.mjs, shot.mjs.

Smithers MVP design architecture · smithers-06 · 2026-10-02. Contradictions between sources go to the design lead, not onto this page.