# Proof audit, pass 1

Scope: [#32832](https://git.cafe/pingdotgg/t3code/pull/32832), requested head [418043f16e](https://git.cafe/pingdotgg/t3code/commit/418043f16ee8a52ed105c44c60ce2d29cdc9dfeb), against supplied merge-base [f12bb6229c](https://git.cafe/pingdotgg/t3code/commit/f12bb6229c007a12d24fd2e539222733778ac9ea). Read-only review of the supplied diff, source, installed updater implementation, and existing PR body. This document uses synthetic fixture data only. No runtime model identifiers or live release content are included.

## Findings

No missing proof row blocks this scoped change. The body covers the five affected provider-input cases below. No renderer capture is required: no component, renderer contract, style, interaction, or new renderer data shape changes in this diff. Stable begins using the same tagged-note array path Nightly already uses. Empty arrays, multiple groups, generated summaries, optional dates, item limits and omitted-group counts already belong to the renderer's input space.

One precision correction: empty notes select the pill's existing plain tooltip; they do not render an empty release-notes popover. `SidebarUpdateReleaseNotes` also has a defensive empty fallback, but its sole production caller gates mounting on a positive note count. This does not change the capture decision.

The track-switch row is present in the body's summary table but is not expanded in the raw transcript. That is sufficient for this notes-only proof audit: the updater's existing `current < release <= latest` selection explains the empty array when running newer than the stable target. It is not independent proof of track-switch permission, migration safety, or installer behavior; those paths do not change here.

The body records earlier revisions. The final transcript revision containing the preview filter has identical contents for both changed production modules at the requested head. The earlier full-changelog transcript predates only that filter. This makes its attribution, single-group and multiple-group rows applicable to this diff. Tests and runtime runs reported by the body were inspected, not re-executed in this read-only pass.

The forge currently reports a different head, [cef4a0e9cb](https://git.cafe/pingdotgg/t3code/commit/cef4a0e9cbe39ecc6e613697274d728e196ded72). This verdict is pinned to the requested revision, not an audit of that newer revision or its renderer context.

## Source and caller inventory

- Changed production modules: [DesktopUpdates.ts](https://git.cafe/pingdotgg/t3code/blob/418043f16ee8a52ed105c44c60ce2d29cdc9dfeb/apps/desktop/src/updates/DesktopUpdates.ts) configures `fullChangelog=true` once before checks; channel changes no longer reset it. [releaseNotes.ts](https://git.cafe/pingdotgg/t3code/blob/418043f16ee8a52ed105c44c60ce2d29cdc9dfeb/apps/desktop/src/updates/releaseNotes.ts) additionally excludes preview versions. Neither module renders anything.
- `normalizeDesktopUpdateReleaseNotes` has one production caller: `handleUpdateAvailable`, which supplies raw notes, offered version, selected channel and optional release date. The unchanged reducer places normalized notes/counts into `DesktopUpdateState`. Updates are sent to all Electron windows and read through `desktopBridge.getUpdateState/onUpdateState` into `useDesktopUpdateState`.
- The only note-content renderer is [SidebarUpdateReleaseNotes.tsx](https://git.cafe/pingdotgg/t3code/blob/418043f16ee8a52ed105c44c60ce2d29cdc9dfeb/apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx). Its sole production caller is [SidebarUpdatePill.tsx](https://git.cafe/pingdotgg/t3code/blob/418043f16ee8a52ed105c44c60ce2d29cdc9dfeb/apps/web/src/components/sidebar/SidebarUpdatePill.tsx), mounted by `SidebarChrome`. Props are the unchanged state, tooltip string and optional shell `openExternal` bridge. `ReleaseLink` is called for each release and for omitted release history.
- Sidebar, Settings > About, native application menu, startup recovery, background checking and channel-switch checks reach the same desktop service. Settings/channel-switch dialogs, Connections, boot recovery, downloaded toasts and native menu dialogs consume version/status/action data, not the note groups. Their layouts do not vary with this diff. No new command-palette or keybinding render path is introduced.
- Web without the desktop bridge has no local desktop updater state. Mobile uses separate app/server update paths and does not render these desktop note groups. A remote connection from the Electron client still displays updates for that local desktop through IPC; it does not substitute the remote server's notes. No provider-adapter, agent/MCP, scheduled-task or websocket contract changes apply.

## Fixture and capture matrix

All versions and note content below are synthetic. `S` is the unchanged sidebar state envelope: enabled, status available, channel latest, currentVersion 1.2.3, availableVersion 1.2.4, downloadedVersion null, no message/error, omittedReleaseCount 0. Each content row modifies only the stated fields. Feed rows use the real GitHubProvider path with synthetic Atom/YAML responses, then normalization. Body row numbers refer to its five-row runtime table.

“No new capture” is deliberate, not a claim that absent visual evidence exists. The minimum required viewport × theme × row capture set is **empty**. Transcript assertions have no viewport or theme. If a renderer interaction is optionally captured, use a **recording**, not a still. Distinct unchanged visual variants are named separately; unrelated axes are not multiplied into a full product.

| ID / named fixture | Data and visibly distinct result | Diff effect | Existing proof / status | How to seed | Capture requirement |
|---|---|---|---|---|---|
| F1 Stable absent, wrong-channel feed | Running 1.2.3; offered 1.2.4; feed only 1.2.5-nightly builds. Before: an incorrectly attributed group. After: `releaseNotes=[]`; existing version tooltip and download icon remain. | Direct bug fix | **Covered**, body row 1 and before/after raw transcript, plus new service regression case. | Provider harness with synthetic feed and latest YAML lacking `releaseNotes`. No user route can deterministically replace GitHub's feed. | Runtime transcript sufficient; no visual capture. Optional hover/focus inspection: **recording**. |
| F2 Stable present, one group | Same running/offered; feed includes 1.2.4 with one plain change. Before/after normalize to one offered-version group. | Stable raw value changes string to tagged array; normalized layout preserved | **Covered**, body row 2 and raw transcript, plus new service case. | Provider harness; for renderer use S with `[{version:'1.2.4',items:['Synthetic change'],totalItems:1}]`. | Runtime transcript sufficient; optional popover reveal: **recording**. |
| F3 Skipped stable versions | Running 1.2.2; offered 1.2.4; feed contains 1.2.4 and 1.2.3. After: two groups, second section margin/header and its own release link. | Newly reached on Stable; existing Nightly array renderer | **Covered**, body row 3 and raw transcript. | Provider harness; renderer S with the two corresponding groups. | Runtime transcript sufficient; optional reveal/scroll/link navigation: **recording**. |
| F4 Preview entry inside Stable range | Running 1.2.3; offered 1.2.4; feed includes a dated 1.2.4-preview build, Stable and Nightly entries. Keep only Stable notes. | Direct new filter | **Covered**, body row 4/raw transcript and amended channel-filter regression test with preview + Nightly + Stable entries. | Provider/normalizer harness. A dated preview is necessary to match the actual preview classifier. | Runtime transcript sufficient; no new visual state. |
| F5 Older Stable target after Nightly | Running 1.2.5-nightly.20261101.1; offered 1.2.4 on a guarded switch. Full-changelog selection produces no groups. UI result collapses to F1. | Stable now uses full-changelog range | **Covered**, body row 5 summary; no expanded raw pair. Existing semver-range source supports the stated output. | Provider harness with newer running semver. A real switch starts at Settings > About > update track, but its permission/install flow is unchanged and outside this notes assertion. | Notes transcript sufficient. Actual switch/recheck flow, if captured: **recording**. |
| U1 Hidden or disabled/local unavailable | No desktop bridge/state; disabled/unpackaged/unsupported install; managed denial. Hidden control or existing idle/unavailable tooltip. | Unchanged; configure returns before new setting if disabled | **Not required**; absent from body, no proof gap for this diff. | Actual web client without bridge; desktop disabled-policy fixture; renderer harness for exact status/denial variants. | No new capture. Optional tooltip reveal: **recording**. |
| U2 Loading/checking/up-to-date/check error | Null/initial state; status checking with reduced motion on/off and animation latch; up-to-date; error with message and canRetry. Notes are not the loading UI. | Unchanged | **Not required**; absent from body. | Start/check through desktop bridge; deterministic intermediate statuses require harness publishing. | No new capture. Check, retry and animation transitions: **recording**. |
| U3 Download progress and completion | Nonempty or empty notes retained while downloading; percent null/0/mid/100; downloaded target; retry after download/install error; not-applied warning. Non-available header uses tooltip. | Same normalized arrays flow through existing statuses | **Not required**; absent from body. These are existing state-machine/pill branches. | `downloadUpdate`/`installUpdate` bridge commands in isolated desktop; safe exact-state renderer harness preferred over installing an update. | No new capture. Download/retry/confirmation/restart sequences and progress: **recording**. |
| U4 Generated offered summary | Group version equals availableVersion; stats and/or intro. Offered generated group omits its section heading; intro wins over item list. Stats alone permits zero items. Optional footer is bold. | Parser/renderer unchanged; F1's old wrong content can have this shape | **Covered in part** by body row 1's before output; remaining summary variants **not required**. Existing normalizer tests cover summaries/highlights/footers. | Renderer harness S with stats-only, intro-plus-items, or stats + highlighted items + footer; normalizer synthetic generated summary with folded full changelog. | No new capture. Optional reveal: **recording**. |
| U5 Plain and highlighted item list | No intro; zero items yields no list; positive items map to bullets. Highlight regex splits `**Label.** Text` into strong label + text; ordinary items stay plain. Optional footer present/absent. | Unchanged | Plain list **covered** by body rows 2–4; other variants **not required**. Existing browser test renders highlight/footer. | Renderer harness group with plain/highlighted/duplicate items; generated stats-only group for empty list. | No new capture. Optional reveal: **recording**. |
| U6 Older generated group and dates | Group differs from offered: generated secondary heading shows version and optional age. Plain groups instead show What's changed / Changes in version. Offered date may be absent or ISO time; dated prereleases use day-based today/Nd labels; older Stable has no inferred date. | More Stable groups may be present; all headings/date branches already exist | Two plain groups **covered** by body row 3; generated/date variations **not required**. Existing date tests cover normalizer behavior. | Renderer harness with offered/older generated groups and releasedAt omitted, fixed ISO time, today and older YYYY-MM-DD; fixed clock for deterministic labels. | No new capture. Age-over-time behavior, if exercised: **recording**. |
| U7 Group cap and history link | 2–6 groups use repeated sections; >6 valid groups normalize to six + omittedReleaseCount. Count 1 uses release, >1 uses releases. Empty/invalid note entries do not count. Preview filtering happens before cap/count. | Existing cap now also reachable for Stable feed arrays | Multiple-group shape **covered** by F3; cap/count variants **not required** as new runtime captures. Existing normalizer case verifies six retained and one omitted; no cap code changes. | Normalizer harness with 7/8 valid stable groups, plus empty/preview entries; renderer harness with returned counts. | No new capture. History-link traversal/scroll: **recording**. |
| U8 Item count and truncation | Plain notes retain newest eight items, truncate long text at existing limit, and report totalItems. Link text: View release; 1 more change; N more changes; stats overrides all counts. | Unchanged | **Not required** beyond existing normalizer tests; no change to item parser/count/renderer. | Synthetic note containing 10 plain changes and a long item; renderer envelope variants for omitted count 0/1/2 and stats. | No new capture. Optional reveal/link traversal: **recording**. |
| U9 Links and failure | Each nonempty version produces release URL; missing/blank version can suppress it defensively. History URL for omitted groups. shell.openExternal success/false/throw or absent shell; failures toast. | Unchanged | Valid release links implicit in F2/F3 data; failure/defensive cases **not required**. | Renderer harness with shell stub and blank-version defensive fixture. No new arbitrary-note RPC exists. | No new capture. Click/keyboard link activation and resulting toast: **recording**. |
| U10 Popover vs tooltip and focus transitions | Notes positive + details + no active update run: hover/focus opens popover. Empty/no applicable details: tooltip. Press invokes update, not toggling notes; Tab enters links; Shift-Tab; Escape closes and suppresses immediate focus reopen; pointer close delay; notes arriving/disappearing while focused. | Inputs can select different existing branch; interactions unchanged | Empty/nonempty data **covered** by F1/F2. Existing browser cases verify focused arrival and disappearance; no new interaction proof required. | Renderer harness publishing S with [] then nonempty and reverse, with real pill; existing browser fixture demonstrates state publication. | No new capture. Any proof of these interactions/timing must be **recording**. |
| U11 Update everything override | Remote/provider updates alter tooltip and open confirmation; active run suppresses release-notes popover and uses capsule. Action pending/checking/download/run disable interaction via existing logic. | Unchanged; does not consume note contents | **Not required**; absent from body. | Real desktop with isolated remote/provider update fixture, or labelled pill harness configuring useUpdateEverything/run state. | No new capture. Confirmation/cancel/focus/run progress: **recording**. |
| U12 Width, height, theme and platform | Existing max width min(24rem,100vw-2rem), scroll limits from available height / min(28rem,100vh-6rem), wrapping and vertical scroll. Light/dark token colors unchanged. macOS/Windows/Linux share note normalization and web renderer; architecture warning is separate. | No responsive/theme/platform branch changed | **Not required**; absent from body. No Cartesian screenshot matrix needed. | Optional harness at 1280×800 and 480×600, light/dark; long six-group notes. Exact packaged platform environments require actual desktop clients, not claiming a web harness is one. | Minimum set remains empty. Resizing/scrolling/hover: **recording** if requested later. |
| U13 Generic feed / explicit YAML notes | Managed mirror/mock generic provider or GitHub YAML supplying releaseNotes skips Atom-derived computeReleaseNotes. String/array/malformed/missing inputs still normalize through existing validator; preview entries are now excluded. | Full-changelog flag has no generic-provider effect; preview removal covered by F4 | **Not required** as separate runtime row; same output classes and F4 filter, existing malformed-input tests. | Synthetic generic YAML through isolated mock updater, or direct normalizer harness. | No new capture. |

## Seeding limits and evidence classification

There is no production RPC that sets arbitrary release notes. A renderer-only driver must publish a synthetic `DesktopUpdateState` through a test bridge before mounting/subscribing, or use the existing browser test harness; label any such capture **renderer harness**. It proves presentation, not GitHub attribution. A provider harness must use the real provider plus normalizer to prove attribution, as the body's driver does. A generic mock feed alone cannot reproduce GitHubProvider's Atom fallback, so it cannot replace F1–F5 as provider proof.

No server, desktop client, provider turn, tests or capture tools were started during this audit. Only this scratch report was written and uploaded. No PR comments or source edits were made. Required additional captures: **none**. Required additional transcript rows: **none**. Packaged installation and GitHub Enterprise behavior remain outside the evidence's claims.

FRICTION: none

VERDICT: SUFFICIENT
