# Proof audit pass 1 — Mark unread menu omission

Read-only refresh of [3027afd797](https://git.cafe/pingdotgg/t3code/commit/3027afd79720618974d965e7076e77e8aac1d38b) against [e3592efe7f](https://git.cafe/pingdotgg/t3code/commit/e3592efe7f). Full diff: seven files, 122 insertions, 12 deletions. This is a proposed proof matrix, not an execution report. No captures were taken or runtime fixtures installed by this reviewer.

## Scope derived from the diff

| Caller | Affected? | Entry path and expected change |
|---|---|---|
| Current Sidebar.tsx single thread, handleThreadContextMenu | YES | Right-click thread row with selection cleared; buildThreadActionMenuItems now receives hasActiveRun. Active: Mark unread absent. Finished: present. |
| LegacySidebar.tsx single thread, handleThreadContextMenu | YES | Enable legacySidebarEnabled, right-click a single thread; independently constructs the menu and gates Mark unread with threadRuntimeIsActive. |
| ChatHeader.tsx → useThreadActionMenu | YES | Title/chevron click, keyboard activation of title button, or header context menu; shared builder now receives hasActiveRun. Test persisted server thread, not a draft. |
| Current sidebar multi-select, handleMultiSelectContextMenu | YES | Filters selectedThreads with canMarkThreadUnread; buildBulkMarkUnreadContextMenuItem omits zero eligible and labels only eligible count. Execution loops over the same filtered list, then clears selection. |
| Legacy sidebar multi-select, handleMultiSelectContextMenu | YES | Filters selectedThreadEntries with canMarkThreadUnread; passes markUnreadCount to buildMultiSelectThreadContextMenuItems; execution loops over the same filtered entries and clears selection. Archive/delete retain total-selection counts and existing eligibility. |
| Draft/project menus, Settings, command palette | NO | No changed Mark unread caller here; no separate capture required. Legacy sidebar preference is setup only. |
| Native mobile iOS/Android | NO | No Mark unread feature per task brief; no native-mobile captures/builds. A 390px web viewport is web, not native mobile proof. |
| Desktop | YES, shared items | All five web caller paths feed desktopBridge.showContextMenu, which renders an Electron native menu rather than the web DOM fallback. |

Source inspected: apps/web/src/components/{Sidebar,LegacySidebar,threadActionMenu.logic}.tsx/.ts; apps/web/src/hooks/useThreadActionMenu.ts; apps/web/src/components/chat/ChatHeader.tsx; apps/web/src/localApi.ts; packages/client-runtime/src/state/models.ts; packages/contracts/src/orchestrationV2.ts. New shared-menu test covers hasActiveRun false/true. Sidebar.logic.test.ts adds active-only omission in both bulk builders and a mixed eligible-count test (running excluded; idle, completed, null included). These tests do not execute either component action loop. Read-only source tracing verifies both loops target the filtered list; runtime captures must still establish caller wiring and observable behavior. Tests were inspected, not run in this audit.

## Minimal runtime fixtures

Reuse the lead’s existing real active and finished threads on server 7374. For true multi-select active-only proof, add/reuse one more active thread under the lead’s control: A1 active, A2 active, F1 finished. Three threads are the minimum simultaneous fixture; one active selection alone may route to the single-thread caller and cannot prove bulk omission. Use short, non-sensitive titles. Keep all three in the same sidebar section, unsnoozed/unsettled/unarchived, with selection cleared. Use an ordinary supported provider; the predicate is provider-independent, so no six-provider visual cross product. Keep optional menu capabilities identical across base/head.

| Runtime path | Predicate / expected single-thread menu | Proof level |
|---|---|---|
| preparing, queued, starting, running, waiting | Active → absent | Record running as the representative in all three callers. Check remaining statuses via focused fixture/assertion or inspected projection, not five repeated screenshot sets. |
| queued with activeRunId null; waiting | Active → absent although archive eligibility differs | Explicitly verify projection/classification; do not substitute !threadRuntimeCanArchive for the new predicate. |
| completed | Inactive → present | Record representative finished thread in all three callers; activate Mark unread and show its unread/completion indicator. |
| interrupted, failed, cancelled, rolled_back | Inactive → present | Focused fixture/predicate check; no additional theme/width captures. |
| idle, null/undefined runtime | Inactive → present | One projection/predicate check; no separate visual matrix. |

These classification checks are proof obligations, not a request to add an exhaustive new test suite for this small omission. A completed thread fixture must have a completed assistant turn and a read watermark initially, so selecting Mark unread has an observable effect.

## Bulk fixture matrix — both sidebars independently

| ID | Selection | Expected menu and action | Proof |
|---|---|---|---|
| B1 | A1 + A2 (both active) | Mark unread absent, no residual gap. Archive/delete retain their own existing rules and total count 2. | Open the actual multi-select menu in current and legacy sidebars; show both rows selected. A single selected active row is insufficient. |
| B2 | A1 + A2 + F1 | Mark unread (1), although total selection is 3. Invoke it: only F1 is marked unread; selection clears. | Capture menu and resulting F1 completion/unread indicator in each sidebar. Keep F1 off-route during the action so visiting it does not immediately acknowledge it. Reset F1 to read between independent action trials. |
| B3 | A1 + F1 | Mark unread (1), total 2. | Optional narrower mixed fixture; B2 already proves mixed count. No additional recording required. |
| B4 | Two finished/read threads, if already available | Mark unread (2); both become unread, selection clears. | Optional positive bulk control, not a new fixture/session requirement. F1 single-thread positive controls plus B2 cover the minimum. |

B2 visual output alone cannot prove that no command was sent for an active thread, because active runs hide the completion pill. In W1, retain a sanitized outgoing thread.mark-unread command trace (thread identities mapped to A1/A2/F1) or before/after visited-state evidence: exactly F1 targeted, no A1/A2 mutation, for each sidebar. On a server without visited tracking, inspect the local unread state instead of claiming an RPC happened. Source tracing already verifies filtered dispatch at this head; do not label a screenshot as runtime dispatch proof. No source instrumentation or database writes are needed. Reopen a menu after runtime changes; eligibility is a menu-opening snapshot, not a new promise to update an already-open native menu.

## Minimum capture matrix

Every row below is an interaction row and requires a recording. Extract endpoint frames from the recording if still before/after images are needed; static images alone do not satisfy these rows. No full theme × runtime × platform product.

| ID | Surface / width / theme | Affected callers covered | Minimum sequence and acceptance |
|---|---|---|---|
| W1 | Web 1440px light | Current sidebar single + bulk, legacy sidebar single + bulk, header | In one recording concatenate: current single A1/F1, current B1/B2, legacy single A1/F1, legacy B1/B2, header A1/F1. Toggle sidebar preference between sections. Active single and active-only bulk omit Mark unread without gaps; finished retains it; mixed shows Mark unread (1). Include header title/chevron click and header right-click. Invoke B2 separately in each sidebar and retain the sanitized dispatch/state proof described above. |
| W2 | Web 1440px dark | Same five paths | Repeat brief open/dismiss A1/F1 single menus and B1/B2 bulk menus in both sidebars, then header A1/F1. Check readable menu, correct mixed count, ordering and omission without residual separator/space. W1 owns detailed mutation proof. |
| W3 | Web 390px light | Same five paths | Open sidebar drawer, capture A1/F1 single and B1/B2 bulk menus; repeat legacy; close drawer and tap title/chevron for header A1/F1. Clear selection before single menus. Active absent / finished present / mixed count 1; menu stays within viewport and reachable. Record actual selection and context trigger; label modifier-click or synthetic right-click accurately, not touch-long-press proof. |
| W4 | Web 390px dark | Same five paths | Same compact sequence as W3, including both bulk states in each sidebar. Check narrow menu clipping and contrast; use same fixtures to avoid incidental differences. |
| I1 | Web 1440px, reuse W1 | Header and current sidebar | Can be appended to W1: focus title, Enter/Space opens menu, arrows navigate, Escape dismisses; invoke finished Mark unread and show indicator after navigating away if needed. Show one active→finished run transition, then close/reopen menu and observe item return. No need to require live mutation of an already-open snapshot menu. |
| D1 | Native desktop | All five paths through Electron native menus | UNVERIFIED — infrastructure: headless-desktop missing on this VM, per lead. Do not install, launch another host, or access fleet for this pass. Shared web menu construction and filtered action paths verified by source inspection; web recordings can verify shared web behavior, not native rendering/interaction. If infrastructure becomes available later, one normal-width/theme recording should cover single A1/F1, B1/B2 in each sidebar and header A1/F1, with one positive action. |

Minimum web capture sessions: four, one per 1440/390 × light/dark, with I1 folded into W1. Each session can concatenate brief caller segments rather than a separate session per caller or selection. For before/after proof, capture the active omission on base and head at matching dimensions/themes; pair representative endpoint frames for each of the four configurations. Include active single omission, B1 active-only bulk omission, and B2 total-to-eligible count change from base Mark unread (3) to head Mark unread (1); concatenate these caller segments in the existing sessions rather than starting more sessions. Finished control and detailed interactions need only head recordings unless a regression appears. Do not start a second server for base: lead must arrange a safe equivalent base capture or mark before proof unavailable. Suggested heights: 1440×900 and 390×844; record actual CSS viewport dimensions.

## Seed strategy and environment ownership

Lead owns the already-running isolated .t3 dev server on port 7374. Reviewer must not start/restart/stop it, attach a browser, or change its database. Prefer existing seeded real app threads: recent completed/read thread for control and a disposable real run for running→completed interaction. Never point any server at ~/.t3/userdata or edit its live database.

A real-data seed deliberately retains stopped threads, so it may supply no active example. If deterministic active states are missing, lead may use a test-only app/projection fixture in the isolated environment: feed coherent typed thread shells/runtime summaries through the same client projection path the real app reads. Include project/thread/run identity and finished turn/watermark data. Label recordings as projection-fixture evidence; mocked runtime alone proves menu presentation, not provider execution or persisted Mark unread. Prefer a real disposable run and real RPC for I1 when practical. Do not mutate running event-sourced SQLite projections ad hoc, edit production source for screenshots, forge only DOM menu contents, or run real user agents from copied data. Rare transient states can use focused projection/predicate checks rather than expensive provider orchestration.

All browser/fixture actions remain with the lead under its existing authorization. This document grants no new browser permission. Local/remote/tunnel clients consume the same runtime field; there is no changed transport/contract, so no extra connection-mode capture is justified. Keep evidence free of pairing URLs, secrets, and copied private conversation content.

## FRICTION / handoff

- No browser/server was started or inspected; all visual and interaction rows remain pending, not passed.
- Native desktop D1 is UNVERIFIED — infrastructure: headless-desktop missing on this VM (lead-provided). Shared web construction/action code verified by inspection; pending web captures can establish shared web runtime behavior only. No native desktop execution claimed.
- Seeded stopped threads cannot establish active behavior; obtain a disposable real run or label a coherent projection fixture.
- Shared single-menu and bulk helper tests are present and inspected, not executed by this reviewer. Component caller wiring, queued/waiting classification, and interaction require the targeted proof above.
- Prior bulk scope objection is resolved in source at this head: both sidebars omit active-only bulk actions, count eligible threads, and dispatch only to the filtered list. B1/B2 runtime evidence remains pending. Existing active + finished fixtures need a second active thread to prove true active-only multi-select. Do not claim that a one-row menu covers bulk.
- Width 390 does not itself establish touch behavior. Record supported title tap; explicitly label sidebar pointer-context emulation if that is what was used.
- Before capture at the base revision is not available from this audit. Do not disrupt the lead-owned head server to manufacture it.

Native mobile is out of scope: no native-mobile action, build, capture, or install. This refresh performed no browser/server actions, source edits, fixture creation, or tests. No PR exists for this task. No source edits, pushes, forge comments, merges, or watches were performed.
