Proof audit 1 for [#33283](https://git.cafe/pingdotgg/t3code/pull/33283), read-only, 2026-11-10.

Reviewed local merge `68414fb0aa` of pushed [15aa7d018f](https://git.cafe/pingdotgg/t3code/commit/15aa7d018f32bbb335977b4c75acb9833a387789) against local origin/main. The supplied trimmed matrix proves the adapter routing and automatic recovery, but does not cover every affected render path requested for this pass. Missing fixtures are **N, A, S, M, W, B, PN0, PNT** below. These are evidence gaps, not demonstrated code defects. No existing still pair needs recapture. Existing D recordings remain usable as behavioral evidence with the qualifications below.

The capture-SHA assertion needs narrower wording: `git diff 107c62505b HEAD -- packages/provider-pi` is **not empty**; main changed `src/server/status.ts`. The adapter and adapter tests are byte-identical between captured fix and reviewed head. The only before/after source changes between [c6ca605630](https://git.cafe/pingdotgg/t3code/commit/c6ca6056309904f89b90b6055d797973be0a0939) and [107c62505b](https://git.cafe/pingdotgg/t3code/commit/107c62505bbaa56b1f7d2c9cdd73a5fd66fc0831) are the two adapter files. The later status-probe change is not a reason to discard the captures of this adapter behavior. The core usage banner, overflow/popover renderer, historical failure row and shared recovery derivation remain unchanged since capture; later main also changes unrelated composer and sidebar code, so these are historical proof of this change, not a current-head full-app snapshot.

File-specific observation: **D-before.mp4** retains the raw red session-error overlay over the prompt from approximately +3.94 s to +34.12 s, and ends with prompt hover controls and composer focus/caret. **D-after.mp4** also has that raw overlay briefly at +3.269 s (cleared at +3.394 s), as well as the disclosed return at +92.842 s (cleared at +93.445 s). The procedure's statement that after showed the usage banner instead should acknowledge the initial transient. Both clips finish in the claimed state; the supplemental D stills remove the pointer/focus mismatch. These are visible app/session-state transitions, not ghosting in the settled PNGs. Preserve the raw clips and disclose these differences; do not hide them with masking or retouching.

No missing capture is needed for the supposed visible “Usage limit reached after N retries” replacement heading: the current failure is deliberately hidden by `deriveUsageLimitNoticeRows`, and the historical web warning renderer replaces the retry label with “Usage limit reached.” plus an optional reset. R's absence of that wording is consistent with the code, not a failed capture.

The independent fixture matrix follows. “Affected” includes changed data reaching existing renderers. Every recording row requires the interaction or deadline sequence; a still does not prove it. Setup interactions used only to obtain a stable terminal state do not turn U/T/R's static presentation into timing tests. Status **ok (test-only)** means explicitly no separate visual capture is required, not that an unprovided screenshot was inspected. Named transition fixtures can share one recording with clear checkpoints.

| ID | Fixture and required input / expected difference | Affected by diff | Minimum capture: viewport × theme × form | Real-app seed / route / interaction | existing capture status |
|---|---|---|---|---|---|
| C | Non-limit model error, including “Rate limited”, empty/missing error fallback; class stays provider_error. | No; negative control | Focused adapter test only | Pi fake RPC emits assistant message_end/error or failed auto_retry_end with non-limit text. | **ok** — test-only; focused log reports non-limit control passing. |
| U | Assistant message_end/error containing usage limit or usage_limit, no parseable positive minutes. Current failure row disappears; generic amber notice, unknown reset, empty composer play control. | Yes | Web 1440×1000 dark, still pair | Existing stub U + seed U; autoResumeLimitedThreads=false and snoozeLimitedThreads=false; open /$environmentId/$threadId. | **ok** — U-before/after and both diffs. |
| Z | Usage limit with “Try again in ~0 min”; no reset and no automatic recovery. Same rendered shape as U. | Yes, same visual equivalence class as U | Reuse U visual; focused adapter test for ~0 boundary | Return 429 with a past resets_at through real Pi, or existing fake-Pi ~0 test. | **ok** — U covers layout; focused ~0 regression test covers distinction. |
| T | Positive minutes; resetAt=receipt+N minutes+30 s; recovery off. Reset text and Resume at reset / Snooze until reset; play control. | Yes | Web 1440×1000 light, still pair | Stub T + seed T; both recovery settings off; same thread route. | **ok** — T-desktop pair and diffs; reset reads 11:36 AM. |
| TN | Same T data in narrow container: description hidden behind info control, actions wrap to a second line. | Yes | Web 390×844 dark, still pair | Open T at narrow viewport, closed popover. | **ok** — T-narrow pair and diffs; title/actions fully visible. |
| R | Retryable disconnect followed by usage-limit finalError from failed auto_retry_end; retry attempt/maxAttempts retained, retryable=false. Current failure becomes same no-reset notice as U. | Yes; second changed adapter caller | Web 1440×1000 dark, settled terminal still pair | Existing stub R and addR; first server_error disconnect, then usage_limit_reached; maxRetries=1. | **ok** — R pair and diffs; before 1/1 retry heading, after banner. |
| RT | Same failed retry caller with positive reset text instead of absent reset. | Yes, same renderer as T | Reuse T layout; no additional visual row required | Fake Pi auto_retry_end/finalError containing positive-minute text, or real Pi retry followed by 429. | **ok** — visually collapses to T; no claim of a separate real-Pi timed-retry capture. |
| D | Auto-resume setting on, positive short reset, not settled/pending/snoozed. Resets→Resumes/Cancel, deadline dispatches continuation, banner disappears, timed historical warning and successful reply appear. | Yes | Web 1440×1000 dark, **recording** pair plus settled still pair | Existing D stub/seed, live UTC, autoResumeLimitedThreads=true; no manual continuation. | **ok** — both MP4s and D-settled pair/diffs; overlay/focus qualifications above. |
| N | Narrow details interaction: reveal the hidden reset/unknown-reset description, close it; include no-reset and armed narrow states to cover no-actions vs wrapped long Cancel label. | Yes; previously unreachable Pi notice/description | Web 390×844 dark, **recording**, named U/T/armed checkpoints | U then T thread routes; tap/hover the info control; arm T recovery. No DOM fabrication. | **missing** — TN shows only the closed info icon; neither hidden reset content nor these interactions is shown. |
| A | Per-thread manual Resume at reset→Saving→Cancel auto-resume→cancel→Resume; no duplicate dispatch after cancellation. | Yes, now reachable for Pi | Web 1440×1000 dark, **recording** | T with both settings off; use real banner actions. RPC equivalent is thread.metadata.update with limitRecovery {runId,resetAt,autoResume:true/false}. | **missing** — D proves global auto-arming/deadline, not manual arm/cancel. |
| S | Recovery-owned snooze→Wake now→wake; global snooze setting; combined auto-resume+snooze; later independent snooze changes displayed Resumes time. At reset, wake-only leaves thread failed and snooze action disabled. | Yes; explicit PR problem includes Snooze limited threads | Web 1440×1000 dark, **recording**, several named checkpoints | Set snoozeLimitedThreads=true on short-reset T, with auto-resume initially off; use Wake now / Snooze until reset, then combine with auto-resume. thread.metadata.update limitRecovery.snooze; thread.snooze can set a later snoozedUntil. | **missing** — every existing timed capture has snooze=false; Wake now, wake-only expiry, and delayed display are absent. |
| M | Manual continuation from a no-reset limit; nonempty draft swaps play back to send, clearing restores play; click Resume and obtain success. Historical no-reset warning appears without raw error text. | Yes; deriveResumableRunId and historical row | Web 1440×1000 dark, **recording** | U (or R) then configure stub's next response as success; type/clear draft and use real play control. Resume dispatch carries manualContinuationOfRunId. | **missing** — play icon is captured, its operation and no-reset historical row are not; D is a timed automatic continuation. |
| W | Limit after real work: assistant content and at least two tool items before failure. Usage-limit pauses retain tool groups; provider_error itemizes failed work. Expand/collapse and then resume. | Yes; existing timeline conditional reads changed failure.class | Web 1440×1000 dark, **recording**; can share M | Extend loopback stub to return a legitimate Pi read/tool call on synthetic files before returning limit on the next model request. Use real timeline expansion. If synthetic projection is used instead, label it a **harness capture**. | **missing** — all existing failures have no preceding tool work, so the differing fold/group path is unexercised. |
| B | Outside active chat: limited sidebar status, amber hover-card error label, closed-folder attention state; inactive-thread warning toast/notification title instead of generic failure. | Yes; status resolves from lastErrorClass | Web 1440×1000 dark, **recording** with sidebar/folder/hover/toast checkpoints | Enable in-app notifications, view another thread while U fails; open/close project folder and hover U. Use same environment's synthetic project. Native OS notification is optional corroboration, not a substitute for in-app row. | **missing** — all selected captures explicitly close sidebar; active-thread captures do not exercise the inactive-thread notification. |
| PN0 | React Native thread detail, unknown reset: native warning card wording, no recovery links, play control; native list Limited label. | Yes; separate native renderer | One native phone viewport dark, **recording**; identify iOS or Android/device dimensions | Connect an isolated native client to same seeded dev environment; open U via thread navigation, return to list. This is not a 390px web capture. | **missing** — PR explicitly did not check mobile. |
| PNT | Native timed limit: dated reset text, both links, scheduled/owned-snooze labels, manual wake/resume and historical warning after continuation. | Yes; separate native renderer | Same native phone dark, **recording**; can share PN0 | T/D with native card actions; server settings and metadata as A/S; native list→detail→list. | **missing** — no native capture. The card itself has no iOS/Android branch, so one native platform is the minimum representative; the other remains unverified. |

Minimum additional recording **sets**, avoiding duplication: (1) narrow web N; (2) desktop-width web A/S/M/W with explicit fixture resets/checkpoints; (3) web B while observing an inactive thread; (4) native PN0/PNT in one session. Four sets can cover the eight missing rows. Use before/after where behavior is available; on main, absent Pi recovery controls are themselves the expected before result. Keep the existing U/T/TN/R/D material. There is no need to rerun every row in both themes, both desktop shells, every remote connection mode, or every provider: no style/transport/provider-general contract was changed. Existing light and dark evidence already covers the two theme families.

Code-derived path and branch inventory (read before assessing the supplied matrix; source links use the pushed commit because the reviewed merge is local):

- [piModelErrorFailure and regexes](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/packages/provider-pi/src/server/adapter.ts#L95): /usage[ _]limit/i selects class. Only case-sensitive “Try again in ~[1-9]… min” with at most six digits supplies reset. Nonmatching, ~0, missing/empty and unparseable strings collapse to C or U. Text length is bounded downstream; exact minute count does not add a layout class beyond the overflow fixture N.
- [message_end and retry dispatch](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/packages/provider-pi/src/server/adapter.ts#L1995): no active turn/non-assistant/non-error/existing failure bypass classification. Retry success clears failure; failed auto_retry_end overrides with classified finalError and retryable=false. auto_retry_start remains provider_error. Interrupted/native-aborted finalization suppresses terminal failure. These are protocol/state controls, not additional changed render layouts. Finalize emits provider-session raw error, error turn item and turn.terminal; that is why both banner and session-error transitions matter.
- [shared client derivation](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/packages/client-runtime/src/state/threadExecution.ts#L288) supplies resumable run, latest runtime and hidden current limit rows. Historical rows return after another run executes; root failure vs child failure and current vs historical are distinct. Latest current limit rows are hidden regardless of retry count. Unchanged interrupted/recovered/compaction/validation cases need focused tests rather than decorative captures.
- [ChatView caller](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/ChatView.tsx#L2521) is the sole usageLimitRecoveryBannerItem caller. It supplies run identity, errorCode, resetAt, stoppedAt, settled flag, pending questions/approvals, recovery, snoozedUntil, timestamp format and metadata callback. [UsageLimitRecoveryBanner](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/chat/UsageLimitRecoveryBanner.tsx#L37) hides actions when settled/pending/no-reset/reset<=stoppedAt; matching recovery identity selects Resumes/Cancel; owned snooze selects Wake now; a later snooze postpones display. Deadline disables new snooze, pending mutation shows Saving, errors toast. These unchanged guard/error mechanics do not require deliberately creating impossible Pi histories or new independent harness cases; record the reachable successful transitions A/S. A forced pending/rejection case would be labelled harness evidence, not real Pi proof.
- [ComposerBannerStack / NoticeText](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/chat/ComposerBannerStack.tsx#L191) renders title/description/actions, measures overflow, moves hidden description behind hover/tap popover at narrow widths, wraps actions and orders multiple banner groups. U/T prove attached empty-composer states. N covers narrow overflow, A/S long labels, M content/send states. Multiple unrelated banners and custom theme variants are baseline compositor paths, not changes to this adapter. Web locally hosted/app-hosted and Electron use this same renderer; no separate changed Electron branch exists.
- [historical web row](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/chat/TimelineWorkEntryRow.tsx#L209) changes red failure plus raw text to amber warning and optional reset in local timezone. [Timeline grouping](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/chat/MessagesTimeline.logic.ts#L985) retains usage-limited tool groups. Retry count labels exist in session-logic but the warning row overrides them. D proves timed history; M proves no-reset history; W covers nonempty work.
- [sidebar resolver](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/Sidebar.logic.ts#L887), [hover card](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/sidebar/SidebarThreadRow.tsx#L350), and [notifications](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/ThreadNotificationCoordinator.tsx#L130) expose changed status outside the active chat. Hidden/snoozed/settled/active/permission/focus gates are unchanged; B captures one visible unsnoozed thread, then S covers snooze visibility.
- [native recovery card](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/mobile/src/features/threads/UsageLimitRecoveryCard.tsx#L21) is called by ThreadDetailScreen. It uses a distinct dated reset string, generic no-reset paragraph, native links, pending/inline error states and recovery-owned snooze. Native historical error rows, ThreadComposer play/send logic and thread-list Limited state are separate implementations; PN0/PNT cover them. Android's surrounding composer-height/keyboard path is distinct baseline platform layout; this change adds no native platform conditional.
- [Settings recovery switches](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/web/src/components/settings/SettingsPanels.tsx#L1654) and native ThreadSettingsSections feed the existing server settings; the [worker](https://github.com/pingdotgg/t3code/blob/15aa7d018f32bbb335977b4c75acb9833a387789/apps/server/src/orchestration-v2/UsageLimitRecoveryWorker.ts#L17) guards failed usage_limit, finite reset later than failure, run/recovery identity, archive/settle/pending, global auto/snooze, reset deadline and independent snooze. D exercises automatic resume; S must exercise the independent snooze branch. No new keybinding, palette command, RPC schema, provider model picker, usage-meter window, scheduled-task UI or agent tool is added. Existing transports/services receive the same changed fields; duplicating local/remote/tunnel captures would not add a distinct render fixture.
- Special ChatGPT/workspace notice, manage-usage/admin buttons and copied/error states are **unreachable from the changed Pi helper**, which passes no error code and yields code:null. Do not seed those into this PR's matrix. Other Pi model providers can match this generic text classifier, but unsupported wording remains C; no provider-picker coverage is implied by this error change.

Existing capture inspection and claim check:

All 20 linked PNGs (ten before/after stills, ten diff masks) and both MP4s were downloaded from the PR. All 12 manifest checksums match. Dimensions match: desktop stills 1280×889 for declared 1440×1000 CSS; narrow stills 780×1688 for declared 390×844 CSS; videos 2880×2000. Themes visually match dark U/R/D/narrow and light T-desktop. DPR 2, browser version, exact CSS viewport and SHA provenance come from the capture manifest/procedure, not intrinsic PNG proof. No capture image embeds its build SHA. The linked logs consistently name the specified before/after commits, and git independently verifies the adapter identity.

The selected stills have settled text, no loading spinner/ghosted overlay, no clipped banner/action text, and ordinary intentional empty conversation space after suppressing the duplicated current failure. The truncated narrow thread title is identical on both sides. D minimap markers are coherent with the extra continuation/reply, not residual text.

Independently regenerated Diff0 and DiffT masks match **every pixel** of all ten supplied masks, without alignment or masking. Changed pixels (Diff0 / >5 threshold): U 23,357 / 22,801; T desktop 32,318 / 23,008; T narrow 84,281 / 83,219; R 24,016 / 23,376; D settled 17,030 / 15,770. Differences stay on the removed failure/timestamp, added notice/actions/play control, composer shadow, or D continuation/history/minimap. No unexplained difference region was found. Producer's settled-validation JSON reports pixel-identical twins at least 500 ms apart; those twins are not linked, so temporal equality cannot be independently recalculated from the selected PNGs alone.

Every “what else changed” bullet was checked: U's removed red row/unknown-reset banner/play; T's reset 11:36/actions/play and faint Diff0 shadow; TN's removed row, two-line notice and info icon; R's removed 1/1 heading and absent replacement; D's armed Resumes 9:31/Cancel/Snooze, historical “Usage limit reached. Retry after 9:31 AM (UTC).”, server-attributed continuation, success reply and minimap; the raw session-error return near +93 s. All match, with the additional initial D-after transient noted above. “No other visible differences” is supportable for these selected fixture pairs, not for unrepresented inputs/surfaces such as W/B/native.

Videos decode fully. Durations independently match 122.264433 s before and 94.207467 s after. After contains 22 encoded painted frames; every one was inspected. Before transition frames and periodic waiting/caret frames were inspected through the end (200 encoded frames). The last after frame at +94.174 s is clean and recovered; main still has the failed run at +122.231 s. Runtime transcript reports resetAt 09:31:02.032, auto continuation starts 09:31:02.274 and completes 09:31:02.728, consistent with the frames and stub log. Metadata says the after recorder was stopped later than its last painted output; that is consistent with the differing duration, but these are not synchronized visual-diff recordings. Supplementary D stills supply the clean static comparison.

Verification performed in this pass: local diff/caller/branch tracing; 39 linked artifact downloads; SHA-256 verification; PNG dimension/theme inspection; full video decode and timestamp/frame inspection; independent full-image RGB diff regeneration. No tests were rerun, server started, worktree changed, PR comment posted, or source pushed. The supplied focused test log reports 5 passing tests; the full 122-test/check:changed results are PR claims, not newly executed checks in this audit. Work stayed within the 45-minute budget.

FRICTION:
- tool | Python urllib artifact requests returned HTTP 403; curl downloaded every artifact successfully | use curl for this host.
- tool | Pillow is unavailable | use installed ImageMagick and Python standard library for pixel comparisons; no package installation needed.
- brief | requested full provider-pi identity check differs in status.ts after merged main; adapter identity is intact | state that adapter.ts/adapter.test.ts are identical rather than the whole directory.

AI-assisted; verified with the tests and tools listed above.

VERDICT: INSUFFICIENT N, A, S, M, W, B, PN0, PNT
