# Proof audit, pass 1 — fixture matrix

PR [#33042](https://git.cafe/pingdotgg/t3code/pull/33042), mirror [#33042](https://github.com/pingdotgg/t3code/pull/33042), issue [#33024](https://git.cafe/pingdotgg/t3code/issues/33024).

Reviewed branch `fix/issue-33024`, head [28ee80fa3a](https://github.com/pingdotgg/t3code/commit/28ee80fa3a26b4663950345034f971887d0ad2f8); fetched `origin/main`, then ran the requested merge-base command: [4c361199f4](https://github.com/pingdotgg/t3code/commit/4c361199f47f6ea32a2c14835df392640d0f7c63). Checkout read-only. No source edits, server starts, captures, test execution, pushes, or forge comments. This is a code-derived capture specification, not visual verification. All previous captures are treated as missing. Pass completed on 2026-11-10 within 20 minutes of a 45-minute budget. Budget: 45 minutes; completed within budget.

## Findings (first)

1. **Proof is insufficient.** Every affected fixture A01–A19 is missing. Existing prose describing older screenshots is not evidence for this head. Capture matching before/after states using the fetched merge-base and this exact head; label revision, client, theme, viewport and fixture ID outside the product image or in the evidence caption.
2. **Native mobile is a separate visible change.** Its false Waiting state is a grey glyph, often without text or a timer; `ready` also changes opacity and title weight. Both Home and ThreadNavigationSidebar consume the card. A responsive web screenshot proves neither native path.
3. **The guard is about a presented latest run, not necessarily an empty runs array.** `deriveLatestThreadRun` excludes held queued runs. A bound thread containing only held queued runs enters the new null-runtime branch too (A14). Server shell construction also filters silent idle-compaction runs before selecting latest; a shell-only no-latest fixture must not be described as zero stored runs unless inspected.
4. **The background-work exception must be retained.** For a projection with no presented latest run, `derivePendingBackgroundWork` returns only pull-request watch tasks (not arbitrary provider pending tasks). A raw shell can carry other hold shapes, but those are harness-only unless the real server produces them. A watched runless thread is still legitimately Waiting; removing the last watch is a distinct timed transition (A17).
5. **Several direct callers have no new visual status.** Web sidebar search does not render runtime Waiting, and palette pills derive Waiting from pending work. Archive/loading/empty/error branches do not become new layout branches merely because their shell object changes. Keep these as controls instead of demanding a full unrelated UI cross-product. No concrete implementation defect is established by this proof-only pass.

## Caller and render-path inventory

Neither changed file is a React component. The inventory follows both runtime producers to their consumers; fixture equivalence below is scoped to the changed early-return condition, not every unrelated feature in these large screens. A18 covers the distinct guest-request placement branch; A19 covers shell-only silent-compaction filtering.

| Producer / every production direct caller | Render path / consequence | Fixtures |
|---|---|---|
| `shellRuntime` → `presentThreadShell`; shell-cache builder in [packages/client-runtime/src/state/threadShell.ts:233](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/packages/client-runtime/src/state/threadShell.ts#L233) | Shared shell atoms → web Sidebar/row and native Home/ThreadNavigationSidebar; inbox partition, status, receding, working counts, provider fallback, snooze/settlement gates | A01–A11, A17; C01–C08 |
| `presentThreadShell` → `scopeThreadShell` → native `archivedThreadList` | Native archive list title/time/actions; no status display | C07 |
| `presentThreadShell` → web `ArchivedThreadsPanel` | Settings archive list, loading/error/empty/project/environment groups; no status display | C07 |
| `presentThreadShell` → web `ThreadNotificationCoordinator` | Status observer/notifications; ready is not a completion without a completed latest run, so importing/un-settling must not create a Done notification | A01 recording, C05 |
| `presentThreadShell` → web `ChatView.logic` draft conversion and `presentThreadShellFromProjection` | Draft is already unbound; direct detail can hydrate before shell index | A12, A16; C07 |
| `presentThreadShell` → native `pending-thread-creation`, `draft-thread-shell`, `use-thread-selection.threadDetailToShell` | Pending/unbound drafts unchanged; selected detail fallback uses both runtime paths | A12, A16; C07 |
| `deriveThreadStructureRuntime` → web `ChatView` | Loaded detail phase, busy/Stop/composer, error banner, provider selection, branch/environment lock, runless-child status/timer | A12–A16 |
| `deriveThreadStructureRuntime` → web `GuestPage` | Guest MessagesTimeline uses phase and separate runless start; runless import has no busy indicator both before/after | C08 optional guest view of A12; child timing A13 |
| `deriveThreadStructureRuntime` → `deriveThreadRuntime` → web `ChatView.logic.presentThreadShellFromProjection` | Detail-first shell and runtime with live updatedAt | A12, A14–A16 |
| `deriveThreadRuntime` → native `use-thread-selection` and `use-thread-composer-state` | Selected thread shell fallback, composer busy/interruptible run, runless timer, provider handoff/resumable run | A12–A16 |
| `deriveThreadStructureRuntime` → `deriveUsageLimitNoticeRows` → `threadDetail` visible item atoms | A failed latest run is required before notices are filtered; no-run null-runtime fixture cannot hide usage-limit history | A15; C05 |
| `useComposerSelection` type import only; web/mobile test-fixtures | No runtime call at type import; fixtures are not production entry points | No additional row |

Further shell consumers: [apps/web/src/components/BranchToolbarBranchSelector.tsx:135](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/apps/web/src/components/BranchToolbarBranchSelector.tsx#L135) treats a non-null runtime as “thread started” (branch defaulting and session-stop choice; A16). [apps/web/src/components/CommandPaletteThreadItems.tsx:123](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/apps/web/src/components/CommandPaletteThreadItems.tsx#L123) uses status indicators and provider/model fallback (C03). [apps/web/src/components/sidebar/SidebarThreadRow.tsx:1618](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/apps/web/src/components/sidebar/SidebarThreadRow.tsx#L1618) renders separate search rows. Web row hover tooltip can display runtime error (A15). Native [apps/mobile/src/state/thread-provider-switching.ts:9](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/apps/mobile/src/state/thread-provider-switching.ts#L9) also treats runtime/latest user message/latest run as start markers; ordinary imports retain their message marker (A12).

## Conditions, props and interactions collapsed into fixtures

- **Guard/data:** provider bound vs unbound; latest presented run present vs null; true zero runs vs held-only queue; history present vs zero messages; no hold vs awaited watch; malformed/stale session error; native-child lineage and runless root node. Shell `status=failed` suppresses `parkAtIdle`; projection failure/usage-limit precedence remains when a latest run exists. Failed-with-no-run is a raw-shell boundary, not a normal import.
- **Counts/lengths:** Working 1→0 (A01), multiple imports + surviving watch 3→1 (A02), expanded/collapsed/selected retention (A03/A04); zero/one metadata line and short/long title (A01/A02/A07/A08); empty history (A16), nonempty history (A12), no matches/loading/error/zero-thread lists (C07). Settled pagination (10 initial, 25 additional on native), hidden/unloaded counts and selected-row exceptions are unchanged; no extra pagination capture is needed for this guard.
- **Placement/layout:** active card, pinned card, Working card, settled/snoozed slim, boring folder and multiline folder, collapsed folders, search card/excerpt, screen vs native sidebar pane. Boring mode disables Working. Pin/snooze/explicit settle precedence is unchanged. Subagent and side-chat lineages are excluded from lists independent of runtime. Filtering by environment/project/tag affects membership but not guard behavior.
- **Status priority:** approval → input → guest request precede runtime status; web preparing differs from working, native combines preparing/queued/starting/running/waiting as working; idle means Waiting; failure vs usage-limit; Woke then Done/age on ready rows. Native Waiting/Working status labels are intentionally null (icons/timers carry them). No latest run means no unseen-completion Done badge. A09 covers Woke priority revealed by the fix.
- **Paint/platform:** light/dark are sampled pairwise because this diff adds no theme styles. Wide/narrow web and native phone/tablet panes are distinct paths. iOS/Android have different row insets/radii/dividers; Android title line-height is measured onTextLayout, and large text with a long title is sampled in A02. Selected/unselected native cards differ; web hover/focus overlays the trailing status with actions, so move the pointer off-row for endpoint stills. Web/desktop share React; no Electron-specific code changes.
- **Orthogonal unchanged props:** project favicon, remote machine/environment label, provider stack/instance badge, PR/stack badge, branch/worktree/diff, terminal count, drafts, queue badge, title regeneration, drag overlay, rename input, capability/permission flags. Use some metadata in A02/A08/A10 to catch reflow, without a Cartesian product. When runtime provider identity falls back to modelSelection, ordinary imported identity is the same; intentionally mismatched identities would be synthetic compatibility testing.
- **Interactions:** import; un-settle/settle (row, context menu, palette/keybinding route to same command); snooze preset/custom/wake/expiry/ack; pin/unpin; Working/Snoozed/Settled/folder expand/collapse; selecting and cold opening thread; sidebar search and palette keyboard selection; native swipe/long-press; provider/branch/worktree selection for empty-bound boundary; watch/unwatch; queued hold release; child progression. Rename/delete/archive/restore/reorder/drag/drop, error-dismiss, guest-share/message and reconnect remain control paths. All rows whose evidence depends on an interaction or time are **recording**; still endpoints cannot replace those recordings. Stable age labels should be captured away from rollover boundaries and recorded long enough to expose any unwanted work timer.

## Capture profiles and minimum

| Profile | Client × viewport × theme |
|---|---|
| W-D | Real web, 1440×900 CSS px, dark |
| W-L | Real web, 1440×900 CSS px, light |
| W-N | Real web, 390×844 CSS px, light, navigation drawer open for list evidence |
| N-I | Native iOS, phone 390×844 logical points or nearest available device (report actual), dark |
| N-I-L | Same native iOS phone, light |
| N-A | Native Android, phone 412×915 logical dp or nearest available device (report actual), light |
| N-T | Native tablet sidebar, 1024×1366 logical units or nearest available device, dark |

Each profile listed in an affected row is a required before/after pair for that row. C01/C02/C04 are required controls that can share A-row takes. C03/C05–C08 are inventoried but excluded from the minimum when they remain invariant. Multiple rows may share one uninterrupted recording if fixture transitions are explicitly labelled in the evidence index and endpoint frames are easy to identify. No need to repeat a whole recording for both themes: record the relevant transition in each listed distinct client/layout and retain the requested theme endpoints. A static image alone does not discharge a recording row. Keep native captures at device resolution; logical sizes above identify the layout, not resized PNG dimensions.

## Fixture matrix (all evidence absent)

**I (base imported fixture):** system/server-created root thread, `historyOrigin=v1_import`, bound `activeProviderThreadId`, idle raw shell, `latestRunId=null`, `activeRunId=null`, `runs=[]`, one synthetic user and assistant message, no pending runtime request, no pending guest message, no actionable plan, no goal, no background work, no errors, no pin/archive/delete, no snooze. Imported state is explicitly Settled; make it active with S2 for A rows. Keep provider selection and provider instance identical before/after.

**B (held fixture):** same no-run base, but one active pull-request watch produces an awaited monitor; use both bound and unbound variants for C04. Full projection and shell must agree about watch state. For A02 counts, seed exactly two I rows and one B row; exclude other Working members.

| ID | Named fixture | Diff affected | Data needed | Before → after / acceptance | Minimum viewport × theme | Evidence kind | Real-app seed / harness boundary | Status |
|---|---|---|---|---|---|---|---|---|
| A01 | Imported, sole Working member; expanded card | Yes: shell guard | I; one un-settled, unpinned import, no snooze/tasks/requests. Claude and Codex share the guard; use Claude here. | Before: Working 1 + grey Waiting (web), grey waiting glyph with no text/timer (native). After: ordinary active row with age; no Working header. Native title becomes stronger and content stops receding. | W-D + W-N + N-I + N-A | recording + endpoint stills | S1 → S2. Record Un-settle, move pointer off the web row, retain final state beyond the 2 s shelf grace, reload once. Native: swipe/menu Un-settle on the imported settled row. | MISSING |
| A02 | Multiple imported rows, remaining real Working member, long title + metadata | Yes: shell guard | I-Claude short + I-Codex title ≥100 characters; add branch metadata to Codex; add one held watch row B. All three active; shelf expanded. Android text scale 1.3 for this case. | Working count 3 → 1; both imports appear once above Working, watched row stays inside. Long title truncates on web / wraps to at most two lines on native; native ready text weight/opacity changes without clipping age. Metadata remains. | W-D + N-A (font scale 1.3) | recording + endpoint stills | S1/S2 for two imports; S3 for watch (or label whole composite harness if no inert watch). Rename/update metadata through thread menu/RPC before the take. Record expanding remaining Working shelf. | MISSING |
| A03 | Collapsed Working shelf, imported row not selected | Yes: placement/count/visibility | I active; Working enabled, collapsed; another route selected. | Before: only Working 1 header exposes the hidden imported row on expansion. After: row visible in active list and zero-count header absent. | W-D + N-I | recording | S1/S2; keep another route open; collapse/expand Working. Include re-entry after a route change. | MISSING |
| A04 | Collapsed Working shelf, imported route selected | Yes: selected-row exception | I active, selected; Working collapsed. Native sidebar uses selectedThreadKey; web uses routeRowThreadKey. | Before: selected row is retained under collapsed Working. After: selected row remains visible in active list; no duplicate, correct highlight. Native selected colors must survive runtime removal. | W-D + N-T | recording | S1/S2; open imported thread, collapse Working; native open navigation sidebar on tablet. Select another row then return. | MISSING |
| A05 | Working section disabled | Yes: label only, placement already active | I active, unpinned; working shelf preference off; boring sidebar off. | Same active location before/after, Waiting web label / native waiting glyph becomes age. No Working header on either side. | W-L + N-A | recording | S1/S2; Settings search “Working section” on web; native workingShelfEnabled=false via preferences UI or approved isolated preference fixture. Record switch and return to list. | MISSING |
| A06 | Pinned imported card | Yes: label; pinning takes precedence over Working | I active and pinned, no tasks; short title; another unpinned row as position reference. | Pinned position and pin icon persist; false waiting state becomes age. Unpin leads to active list after, Working before. | W-D + N-I | recording | S1/S2; Pin in context/long-press menu, then unpin. Do not confuse pin with settlement. | MISSING |
| A07 | Boring sidebar, compact folder row | Yes: status occupies the age slot | I active; boringSidebarEnabled=true, boringSidebarMultilineRows=false; project folder open, then closed. | Folder row Waiting → age; no Working shelf in either revision. Closed-folder waiting and ready are both quiet for this never-completed thread: no invented unread/working dot. Active selected row is retained when folder closes. | W-L | recording + endpoint still | S1/S2; Settings → Labs → Boring sidebar, Two-line folder rows off. Open/close folder and open thread. No native equivalent of this Labs layout. | MISSING |
| A08 | Boring sidebar, multiline row with metadata | Yes: status slot in a different row layout | I active; boring sidebar + two-line rows on; non-null branch, long title; optional PR badge as available, not necessary. | Waiting → age; second line (branch/provider and any badge) stays intact, title ellipsis and row height remain appropriate. The empty-metadata multiline case collapses to compact geometry covered by A07. | W-D | recording + endpoint still | S1/S2; same Labs settings, Two-line folder rows on. Set branch through app/RPC in the isolated workspace, expand project folder. | MISSING |
| A09 | Snooze expires, Woke shown then acknowledged | Yes: shelf + status priority + time | I active; snoozedUntil soon in future, snoozedAt older; lastVisitedAt before expiry; no requests. Include a manual Wake thread now step on a second import. | Before: expired thread incorrectly enters Working as Waiting. After: active row shows Woke; acknowledge/visit replaces Woke with age. Snoozed count decreases at expiry without refresh. Folder mode also changes Waiting → Woke; include that state in web take. | W-D (card and folder) + N-A | recording; stills insufficient | S1/S2 then S2 snooze with ISO deadline 5–10 s ahead. Record from before deadline through wake, acknowledge, resnooze and manual unsnooze. Preserve raw timestamps; no clock jump cut. | MISSING |
| A10 | Native navigation sidebar, selected and unselected cards | Yes: separate caller/pane/platform paint | I active; Home contains import; open ThreadNavigationSidebar from detail. Include selected import and a second unselected import with long title. | Same corrected membership as Home; selected color stays legible, unselected native ready card becomes bold/full opacity; phone drawer and tablet pane retain insets. iOS and Android have separate appearance modules. | N-I + N-A (phone drawer); N-T covered A04 | recording + endpoint stills | Same isolated server seeded by S1; pair native client, open import, reveal sidebar, select neighbor and return. Responsive web is not native evidence. | MISSING |
| A11 | Native search card with content excerpt | Yes: native search reuses status-bearing card | I active, long title; query matching imported message rather than title; searchMatch present; project/environment metadata visible. | Search removes shelf headers in both revisions, but native card still changes waiting glyph/dimming to age/ready emphasis. Message excerpt remains; selection opens matching content. Web search does not display this runtime status (C03). | N-I-L | recording | S1/S2; Home search for a unique synthetic message token absent from title; tap result. Repeat in sidebar search in the same take if that caller is used. | MISSING |
| A12 | Imported history open, normal navigation and cold direct link | Yes: projection guard and detail-to-shell fallbacks; expected visual continuity | I with one user + assistant message, bound provider thread, runs=[]; no requests/tasks. Test warm shell-backed route and cold detail-first route. | History remains readable, no perpetual work indicator or timer, composer ready to accept text and no Stop. Web internal phase changes ready → disconnected, which is not itself a user-visible connection failure. Native shell fallback and loaded projection agree; provider choice persists. | W-D + W-N + N-I + N-A | recording + endpoint stills | S1/S2; web route /<environmentId>/<threadId> (encoded route params); cold open in a new authenticated session, then normal list navigation. Native deep-link/navigation to the same ref. For deterministic detail-before-shell ordering use labelled harness S4, do not claim a race was proved by a normal reload. | MISSING |
| A13 | Provider-native runless child, working → terminal | Yes: second changed guard; runtime becomes null while child status uses root node | S4 child: lineage.relationshipToParent=subagent, creationSource=provider, activeProviderThreadId bound, runs=[], runless root_turn node with startedAt; parent has matching subagent record. Transition node running → waiting → completed; include failed terminal variant. | Child stays absent from all list shelves. Parent card/breadcrumb and opened child show real subagent status; running elapsed indicator continues, completed/failed terminal labels render, composer remains read-only. Null runtime must not stop the separate runless timer. | W-D + N-I | recording; stills insufficient | Live provider-native child is possible only with a real provider turn; no such turn is needed for this pass. Use labelled S4 parent + child harness derived from existing runless-root-turn fixtures. Cover a bound-provider native-child shape (Codex/OpenCode/Grok); unbound Claude/Cursor is equivalent control. | MISSING |
| A14 | Bound import with only held queued runs | Yes: no presented latest run is broader than runs.length===0 | S4: imported/bound thread; runs=[queued run with queueHeld=true], no executed/other run, no hold-producing background work; queued-message display present. | Runtime becomes null, import does not falsely wait in Working; held queue remains visible with Resume/Send controls. Releasing hold gives queued/connecting then a real run normally. Do not label it zero stored runs. | W-D + N-A | recording | Importer cannot directly create queueHeld records. Use labelled S4 projection/shell harness based on latestUnheldRun and the existing held-only test, now binding provider thread; drive hold release in harness. A real app fixture requires a supported queue/Stop workflow and verification that no executed run remains; do not assume it is reachable. | MISSING |
| A15 | Runless bound thread with stale provider-session error | Yes: returned runtime carried error before; now null | S4 runless bound import; providerSessions includes current instance with lastError="Synthetic stale session error"; no run and no background work. Shell variant may carry lastError with idle status. | Before: idle runtime can expose stale error in loaded web detail / hover tooltip. After: no runtime-derived error banner or tooltip error; history remains. This is a boundary fixture, not a claimed importer failure. No usage-limit notice is hidden without a failed run. | W-D | recording (open detail and hover tooltip) | No deterministic app/RPC path creates an imported session error without a run. Label S4 harness; use full detail + shell, open row tooltip before/after. Do not manufacture a failed run or the guard is not exercised. | MISSING |
| A16 | Bound, empty thread (zero visible messages) | Yes: null runtime changes “started”/environment-lock heuristics | S4 bound non-child thread, runs=[], visibleTurnItems=[], messages=[], no tasks; modelSelection instance matches provider instance. Local and worktree mode candidates. | Ready empty composer; provider/environment/worktree controls follow their real availability, no false busy state. Exercise branch picker: runtime absence changes BranchToolbarBranchSelector.threadStarted and whether a session stop is requested. Imported nonempty history A12 remains environment-locked by messages. | W-D + N-I | recording | Real importer rejects transcripts without visible user text, so this is labelled S4 harness. Populate a sandbox Git project and drive provider/branch/environment controls only within that fixture; do not assert a supported import flow for it. | MISSING |
| A17 | Runless bound watch removed while list stays mounted | Yes: dynamic held → unheld boundary | I active with watch B; no runs, no pending requests. Remove final watch; retain list mounted across update. | Waiting is valid while held on both revisions. After unwatch: before remains falsely Waiting; after runtime null, active row/age and no Working shelf (allow shared 2 s exit grace, then require stability). No new run is introduced by the driver. | W-D + N-A | recording; stills insufficient | S3 watch false via app/RPC. Use an inert approved watch fixture; otherwise labelled S4 drives projection + shell updates and tracker clock. Capture must identify harness and cannot substitute for A01 real-import proof. | MISSING |
| A18 | Imported thread with pending guest message, no agent request | Yes: shelf changes although Request label has priority | I active + pendingGuestMessageCount=1, no approval/input, no runs/tasks. | Before: Request-labelled row still classified Working; after: Request-labelled row belongs to active list. Request badge and guest message stay visible; no spurious Done state. Approval/input variants remain C06. | W-D + N-A | recording + endpoint still | Enable a guest link for the synthetic import and send one synthetic guest message through the real guest page, if available in the isolated app. Otherwise labelled S4 shell/projection harness; record dismissal or owner reply in harness without claiming a real import flow. | MISSING |
| A19 | Shell hides a silent idle-compaction run; detail owns it | Yes: shell guard can apply even while detail has a run | Bound thread whose only stored run is filtered by isSilentRunForShell; raw shell latestRunId=null, no hold, while full detail has the compaction run. | Sidebar should not acquire the false Waiting state; detail still shows compaction work through its real run. Composer and list have intentionally different run visibility. Never describe this as zero stored runs. | W-D | recording; stills insufficient | No deterministic importer/RPC recipe established in this pass. Label S4 controlled shell + projection fixture, using the server silent-run criteria: run not failed, its user message marked as idle compaction, and no failed compaction item. See [ProjectionStore.ts:1739](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/apps/server/src/orchestration-v2/ProjectionStore.ts#L1739). Drive active compaction to completion; confirm raw shell remains no-latest while detail owns run. This is boundary proof, not a claimed import reproduction. | MISSING |
| C01 | Imported, still Settled (slim) | Runtime changes; no visible difference until un-settled | I as imported, settledOverride=settled; collapsed and expanded Settled shelf; both Claude and Codex imports. | Slim title/provider/age row and counts unchanged; selected row can remain visible when shelf collapsed. Un-settle transition is A01/A03. | Reuse A01/A03 take; include settled starting frame | recording (shelf toggle / un-settle) | S1; importer sets settledOverride=settled and binds provider thread with no runs. | MISSING — required control, co-capture |
| C02 | Imported, snooze still in future (slim) | Runtime changes; waiting status masked by snooze layout | I active but future snooze, no request/error; at least one other active row. | Snoozed slim row shows wake label, not Waiting; future snooze stays hidden from Working. Manual wake / expiry covered A09. | Reuse A09 starting frames | recording | S1/S2 then snooze; expand/collapse Snoozed shelf before deadline. | MISSING — required control, co-capture |
| C03 | Web sidebar search + command palette | Invariant for ordinary I; no status change here | I; query title then synthetic message; palette active selection; no background work. | SidebarSearchResultRow always shows age (no waiting status path). Palette leading pill uses pending background work rather than runtime idle, so no Waiting pill for plain I even before. Provider/model subtitle remains consistent. | W-D; can append to A12 take | recording (search/palette/navigation) | S1/S2; sidebar search, then command palette thread search and keyboard select. Preserve result excerpt / highlight. | MISSING — optional regression capture |
| C04 | Runless watch holds completion, bound + unbound | Guard exception unchanged | B: two no-run threads, one provider-bound import and one fresh unbound; awaited monitor watch, un-settled. | Both legitimately remain Working/Waiting on both revisions; no run timer start exists, native icon can have no text. Stop at stable held state; removal is A17. | Reuse A02/A17 frames | recording when toggling watch; stable endpoint stills | S3 if inert app watch available, otherwise clearly labelled S4. For detail use pullRequests[].watch; raw provider pending tasks alone are ignored without latest run. | MISSING — required control, co-capture |
| C05 | At least one presented run: status variants | Unchanged guard branch; outside minimum visual set | Latest presented run non-null: preparing; queued/starting; running; waiting; running+blockedOnThreadWait; completed seen/unseen; failed ordinary/usage_limit; interrupted/cancelled/rolled_back. Include stopped completed + hold task. | Web Preparing differs from other Working; native combines them; failed/limited/error text, Done, and legitimate held Waiting remain. All bypass changed early return; tested logic is more useful than exhaustive screenshots here. | No additional minimum capture; optional W-D + N-A composite | recording for run/timer/status transitions | Use Replay (dev) through app for run-bearing cases; specialized usage-limit/background fixture S4 when Replay lacks state. Do not start paid providers for general UI proof. | MISSING — optional, unchanged |
| C06 | Attention priority over runtime | Runtime changes for runless bound fixture; approval/input priority invariant | S4 I + approval; user_input; pending child approval/input; combined priority approval > input. No actionable latest run. Guest-only case is A18. | Approval/Input labels persist and remain out of Working for approval/input/child attention. Guest-only requests have a separate placement effect captured in A18. | No additional minimum; optional W-D + N-A | recording if answering/dismissing; static harness otherwise | Importer cannot create pending runtime requests. Label S4. Normal live requests have runs and are C05 controls. | MISSING — optional boundary, unchanged labels/placement |
| C07 | Fresh unbound draft / archive / empty and loading states | Unchanged or no runtime rendered; excluded from minimum | New thread unbound, no runs; archived import; zero threads; loading shell/detail; archive error; no search hits. Project/environment/tag filters can exclude I entirely. | Fresh draft already had null runtime. Archive lists display title/time and actions, no runtime status. Loading/error/empty UI uses query state; no guard-driven layout. Native pending-new-task row has its own pending state. | No additional minimum; optional W-D + N-A | recording for create/archive/restore/loading/reconnect interactions | New Thread and Archive/Restore in real app; fault/loading states require controlled transport harness S4. Hidden/deleted/side-chat/subagent list exclusion is independent of runtime. | MISSING — optional, unchanged |
| C08 | Electron / connection modes / remaining orthogonal paint | Shared render equivalence; no new platform/transport branch | Same I in Electron and web over remote/relay/tunnel; light/dark, hover/focus/multiselect, drag/context menu, renamed/regenerating/draft/terminal/PR badges, older-server no visited watermark. | No Electron IPC or wire schema changes; Electron wraps web. Ordinary metadata uses existing fields; native iOS/Android differences captured in A01/A10. Without visited tracking a woken row may show age instead of Woke; optional boundary of A09. | No additional minimum; optional Electron 1440×900 dark / remote smoke | recording for interaction, reconnect, wake/age changes | Same S1 server; pair the chosen client using normal app connection flow. Existing isolated UI harness for missing server capabilities/old-server fields. Do not use narrow web capture as mobile evidence. | MISSING — optional, equivalent paths |

## Seeding instructions for the capturer

**S1 — real import, no agent turn required.** Use one new timestamped directory under `/tmp/lead-33042-r1/proof-audit-1/` per run. Give it its own temp user-home-shaped tree, isolated T3 home, and an actual temporary Git workspace. Run dev with `T3CODE_DEV_SEED=0`; use an explicit `--home-dir` pointing inside that run directory. Do not reuse the developer database. Configure the isolated Claude/Codex provider `config.homePath` to the synthetic homes (safer than changing the shell's HOME or touching credential homes). A temp HOME passed to a dedicated server process is an alternative supported by the scanner; leave parent tooling/auth alone. The existing provider-instance homePath setting wins over environment fallback.

Place Claude JSONL at `<run>/home/.claude/projects/-synthetic/import-claude.jsonl`. Each record has `sessionId`, `cwd` pointing at the actual synthetic workspace, and a timestamp. Include `type:user` with `message:{role:user,content:"SYNTHETIC_IMPORT_TOKEN review a tiny fixture"}`, then `type:assistant` with `message:{role:assistant,content:"Synthetic completed answer"}`. Omit model metadata and let configured defaults supply it. A `type:custom-title` record with `customTitle` names the thread, or rename through the app after import. Put short and long title variants in separate files/session IDs.

Place Codex JSONL under `<run>/home/.codex/sessions/YYYY/MM/DD/rollout-<session-id>.jsonl`. Start with `type:session_meta`, `payload:{id:<unique>,cwd:<workspace>}`, then `type:event_msg`, `payload:{type:user_message,message:"SYNTHETIC_CODEX_TOKEN inspect this fixture"}`, and `type:response_item`, `payload:{type:message,role:assistant,content:[{type:output_text,text:"Synthetic Codex answer"}]}`, each with an ISO timestamp. Use timestamps at least ten minutes before capture so queued-start grace does not interfere. The actual scanner record handling is in [apps/server/src/project/AgentSessionScanner.ts:547](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/apps/server/src/project/AgentSessionScanner.ts#L547). It rejects sessions without visible user text, so A16 cannot be a real import.

Open `/welcome`, scan/import the synthetic workspace, finish onboarding as appropriate. The importer creates `historyOrigin=v1_import`, binds provider thread, creates no app runs, and explicitly settles the thread ([apps/server/src/project/AgentSessionImporter.ts:318](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/apps/server/src/project/AgentSessionImporter.ts#L318)). Verify raw shell/projection in a private driver log before capture: bound activeProviderThreadId; runs length 0; latestRunId=null; activeRunId=null; idle server shell; no pending request/background work. Do not infer these from an old age or a Waiting label. Pair native mobile to this same isolated server to reuse the exact server data.

**S2 — real user operations or authenticated app RPC.** Routes are `/<environmentId>/<threadId>` via the app router's normal encoded params; mobile uses the same scoped ref. Sidebar row/context menu, palette or keybinding entry points use the shared operation commands. The authenticated method is `orchestration.dispatchCommand` with the command itself as payload ([packages/contracts/src/orchestrationV2.ts:4279](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/packages/contracts/src/orchestrationV2.ts#L4279)). Use a fresh unique `commandId` per write and the fixture threadId. Relevant command shapes:

```json
{"type":"thread.unsettle","commandId":"unique","threadId":"fixture","reason":"user"}
{"type":"thread.settle","commandId":"unique","threadId":"fixture"}
{"type":"thread.snooze","commandId":"unique","threadId":"fixture","snoozedUntil":"<future ISO time>"}
{"type":"thread.unsnooze","commandId":"unique","threadId":"fixture","reason":"user"}
{"type":"thread.pin","commandId":"unique","threadId":"fixture"}
{"type":"thread.unpin","commandId":"unique","threadId":"fixture"}
```

Use the client command layer/app driver for auth and wire framing; the examples are payloads, not a claim that unauthenticated HTTP POST works. UI settings: “Working section” (`sidebarWorkingShelfEnabled`); Labs “Boring sidebar” (`boringSidebarEnabled`) and “Two-line folder rows” (`boringSidebarMultilineRows`). Native uses `workingShelfEnabled` and per-shelf expansion preferences. Cold open A12 needs warm and detail-first paths: only a controlled transport/harness can guarantee the latter ordering.

**S3 — background watch.** `thread.pull-request.watch` payload adds `host`, `repository`, `number`, `watching:true/false`, optionally a `link` containing `url` and `source`; see [packages/contracts/src/orchestrationV2.ts:3121](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/packages/contracts/src/orchestrationV2.ts#L3121). A real watch can react to external checks and wake an agent, so use an inert test PR/provider fixture; no actual watch was started by this audit. A projection must carry `thread.pullRequests[].watch`, a non-merged snapshot, and non-dismissed source. Matching raw shell uses an awaited monitor in `pendingBackgroundTasks`. Bound and unbound no-run watch cases have identical visible Waiting output and can share C04. If no inert end-to-end path is available, label the capture **HARNESS — runless PR watch**, never “real-app watch.” Do not use provider pendingBackgroundTasks alone to simulate projection holds without a latest run: [packages/shared/src/orchestrationV2PendingBackgroundWork.ts:247](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/packages/shared/src/orchestrationV2PendingBackgroundWork.ts#L247) ignores them.

**S4 — labelled harness capture.** Use real shared derivation plus the real web/native component with controlled contract-shaped shell and projection data, not a hand-drawn mock. Reuse the test fixture builders and runless-child/held-only cases in [packages/client-runtime/src/state/threadExecution.test.ts:304](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/packages/client-runtime/src/state/threadExecution.test.ts#L304) and [packages/client-runtime/src/state/threadExecution.test.ts:425](https://github.com/pingdotgg/t3code/blob/28ee80fa3a26b4663950345034f971887d0ad2f8/packages/client-runtime/src/state/threadExecution.test.ts#L425). Seed production states through app/RPC when possible; no direct database writes are necessary for A01–A12/A18. If a capturer instead needs a stopped-server event fixture, follow the repo's SQLite fixture instructions and retain provenance; do not write the live install. A13–A16 and A19 are specifically labelled harness cases because a deterministic app-only seed is not established here. A fake shell/projection must not be presented as a real importer result. Pair raw state assertions with the image/video so the second guard is actually exercised. No harness was built by this audit.

## Proof acceptance and coverage

A01–A19 all require evidence before declaring the complete guard-surface matrix discharged. A13–A16 and A19 can be labelled harness proof; they cannot replace the real importer + list/composer pass. C01/C02/C04 can be co-captured. For an intentionally narrower maintainer acceptance set, explicitly document omitted affected boundary rows and why; do not silently mark them covered. Runtime/model identifiers for the audit are private operational metadata: omit them from uploads/captions/logs. Use attribution: **AI-assisted; verified with the tests and tools listed above.** (This pass used code/forge inspection only and ran no tests.)

The complete four-file diff was inspected. Focused tests were read, including the imported shell regression, changed bound runless-child test, held-only queue, usage-limit and watch controls. Tests and all UI captures were not run in this pass. No servers or agent turns were started, no processes need teardown, no worktree was removed. Disk available was 109 GB (above the 5 GB stop threshold). No evidence/raw logs were deleted.

FRICTION: none

VERDICT: INSUFFICIENT A01–A19 (all affected rows; C01/C02/C04 required controls also missing)
