Final design: harness, account and checkout in the composer
2026-10-11 · implementer spec. Combines P2 (harness tabs, account cards), W1 (words: what on the left, where on the right) and I2 (icons: t3code two-row footer, ⋯ on phone). Frames are drawn at real app size (12px root, 760px max composer, 404px picker) and scaled to fit. Toggle light/dark top right.
project / branch and context % on the right, on every width including phone.icon project / branch, dirty dot, ↑↓, rebase/merged, warning) is moved, not redrawn. It keeps its menu, including Move to worktree.A · Session header
The segmented chip keeps Skills, Side threads and Settle. The checkout segment and the account pill (or OpenCode instance badge) are removed. Struck-through hatching marks what goes.
B · Shared parts: account badge and checkout cell
Both appear in the words row, the icons footer, the ⋯ sheet and the picker. Build each once.
C · Picker: every state
Desktop is the drop-up from the harness control; phone is the sheet. The content is identical apart from 50px rows on the phone. Model, Context, Effort and Agent rows are today's, unchanged.
D · Words composer: every state
Each word is a button that opens the picker at its own section. The checkout cell opens the checkout menu. Desktop width is measured on the composer, so opening the files pane can push it into the narrow tier.
E · Icons composer: every state
F · Implementer notes
| Tier | Trigger | Words row | Icons composer |
|---|---|---|---|
| Wide | not compact, composer ≥ 600px | badge · account · model · window · effort · mode | checkout · % | two rows; footer has labels |
| Narrow | not compact, composer < 600px | drops the account word (the badge stays) | two rows; harness, effort and mode lose their labels |
| Phone | sessionViewState.compact (≤ 767px viewport) | badge · short model · effort · mode | checkout · % | one line: logo, ⋯, send |
- Header (
SessionBar.svelteidentityBlock): remove<GitIdentity>,<SessionAccountPill>and<InstanceBadgeMenu>while a session is open. The composer is mounted for every open session, so there is no state where the header still needs them.SessionAccountPill.sveltethen has no caller; delete it. - Checkout cell: move
GitIdentity.svelteunderinput/and addpresentation: "words" | "control" | "row". The trigger markup, truncation floors and menu stay as they are. Workspace hint and mismatch use the menu path the phone already has (warning icon in the cell, Move and Dismiss in the menu) at every width, which retires the desktop-only inline hint buttons. Test IDs:composer-checkout(trigger) andcomposer-checkout-menu, replacingsession-bar-identityandsession-bar-checkout. - Drafts are unchanged. There is no checkout yet, so the composer renders no checkout cell and
NewSessionContextstays where it is (above the composer on a phone, below it on desktop). - Account badge: extend the
driverIconsnippet inInstanceModelPicker.svelteto take the instance and draw the letter badge, replacing theisCustomaccent badge. The colour comes fromAccountDot's hash, so export its class lookup rather than copying it. Show the badge only when the driver has 2 or more instances. Gotcha: a session that never switched runs on the daemon's defaultclaudeinstance, which is missing from the configured list. Treat it as an account card named "Claude" (the same fallbackSessionAccountPilluses). - Picker: delete the
harnessview and thepicker-row-harnessdrill-in. The root gets two sections above the existing rows.
Harness:SegmentedControlfor 2–3 drivers, a scroll-snap row of 104px tabs for 4+. Test IDspicker-harness-{driver}.
Account or Instance: a card grid for 2–3, a scroll-snap row of 108px cards for 4+. Test IDspicker-account-{instanceId}.
Both rows scroll the selected item into view when the picker opens, and fade the right edge while more is hidden. - Card behaviour. In a draft, a tap selects at once; that is today's
handleInstanceSelectplus the project binding. In an open Claude session, a tap on another account runsAccountSwitch's flow: quotas are read fresh when the picker opens, thenpreviewContinuationRpc→HandoffDialog→switchSessionAccount. Pull that flow out ofAccountSwitch's menu so the cards can call it. Limited accounts and the current one can't be picked; an account whose quota can't be read can. In an open OpenCode session, instance cards are locked, as instances are today. - Harness lock: in an open session, the other harness tabs show a lock and are
aria-disabled, with today's copy "Harness is fixed for this session". - Icons theme: on desktop, split the trigger into a harness control (
composer-harness-trigger, which opens the picker at the top) and the model control (model-picker-trigger, which opens it at Model). Effort opens it at Effort. Mode is today'sPermissionModeSelector. On a phone, ⋯ iscomposer-moreand openscomposer-more-sheet. ⋯ carries the amber dot while the checkout has a workspace hint or mismatch. A mode other than the default stays visible beside ⋯ as a tinted shield. - Words theme: add
composer-word-accountafter the badge (wide tier only), then the checkout cell, then the existingcomposer-word-context-usageat the far right. On a phone the model shortens (Sonnet 5 → S5) and the window word drops; 1M still shows, because it isn't the default. - Verification: the composer is covered by
features/*.feature, so runpnpm acceptance:visual, then recapture and review the baselines. Add a scenario for each of: 4+ accounts scroll, 4+ harnesses scroll, phone words row shows the project, phone ⋯ sheet, and a header with no checkout. Remember that new feature files must be added to the list inscripts/acceptance-visual.sh.