# Proof audit, pass 1 — capture matrix

PR [#28494](https://git.cafe/pingdotgg/t3code/pull/28494), also mirrored as [#28494](https://github.com/pingdotgg/t3code/pull/28494). Audited head [0d27d85](https://github.com/pingdotgg/t3code/commit/0d27d85707dcdc7de5d0fcd05f5649b030203899), branch `fix/issue-28467`, against merge-base [71646e0](https://github.com/pingdotgg/t3code/commit/71646e031a6dc9db1309b3b9dff597f872cf2fd3). Both the worktree and current PR metadata matched the supplied head. Read-only audit in `/home/ubuntu/wt/pr-28494`. This document contains a capture plan, not captures or executed fixture setup.

## Findings

1. **All affected rows still need captures.** The diff changes two server files (implementation and tests), with no component or contract edits. Code inspection establishes the expected action states, not their appearance or behavior in a running client. The seven new GitManager tests cover real git graphs with a fake forge; they do not establish real GitHub lookup or client rendering. Tests were not rerun in this pass.
2. **Use an actual merged PR for the primary end-to-end proof.** At this head, GitHub change-request lists are direct GraphQL HTTP requests; `gh` may supply authentication. A shim returning JSON for `gh pr list` will not drive this server. A bare local remote alone is also insufficient: an unknown forge returns no change requests. A disposable GitHub repository with real squash merges is realistic. `gh auth status` on this VM reported an active GitHub login; repository creation/write permission and the app's selected credential were not verified. No remote repository was created by this audit.
3. **Deleted and kept branches have different expected controls.** Deleted: web and iOS quick action changes from enabled “Push & create PR” to disabled “Push.” Kept: web changes from enabled “Create PR” to disabled “Commit,” but iOS already has disabled “Commit” before the patch: its Create PR **overview row** changes. Android has no ThreadGitControls quick menu at this head; use its Git overview sheet/inspector.
4. **The PR body's dirty-worktree statement needs qualification.** Dirty quick actions remain available, but the Create PR menu/overview entry becomes disabled at an already-merged HEAD; web's standalone Push entry can become disabled too when the remote branch is gone. Capture the dirty row rather than claiming the entire UI is unchanged.
5. **Avoid a false negative caused by hidden controls.** A full-density card and an established thread with history reliably expose the action row. Compact/essential cards hide idle actions. A draft or zero-turn thread also hides Git actions AND Changes when the quick action becomes disabled and resolved line totals are zero. A normal text squash fixture commonly retains historical nonzero Changes totals, so do not assume a clean tree implies zero totals.
6. **A true upstream delta intentionally remains.** The patch zeroes `aheadOfDefaultCount` for an eligible PR, but preserves `aheadCount` whenever an upstream exists, and always preserves `behindCount`. The primary control may therefore remain Sync ref, Pull, or even Push & create PR in unusual upstream configurations. The fix does not promise to disable all actions just because PR.state is merged.

## What is actually changed

Let `A=aheadCount`, `B=behindCount`, `D=aheadOfDefaultCount`, `U=hasUpstream`, and `H=the PR's recorded head SHA`. A merged PR qualifies only if its base is the resolved default branch and current HEAD is exactly H. Default resolution first uses the remote-status default (origin/HEAD), then the primary remote's symbolic HEAD; if no default can be resolved, only bases named main/master qualify. A failed HEAD resolution does not qualify.

Eligible result: `D := 0`, and `A := 0` only if `U=false`; B, PR metadata, local dirty state, changed files and Changes totals are preserved. H remains server-internal, including in the last-known-PR fallback; it is not added to the wire contract. Open, closed, missing PR, missing H, another base, later commit, detached HEAD and non-repository paths do not gain the adjustment. A successful lookup returning no PR replaces the remembered record; fallback is for an error, not for an empty list.

## Render/caller inventory

| Path | Callers and changing inputs | Conditional render/interaction coverage |
|---|---|---|
| Server remote status | `readRemoteStatus` is called through its 1-second result cache or directly by `GitManager.remoteStatus`; `GitManager.status` merges local + remote. GitWorkflowService routes git repos to those methods. VcsStatusBroadcaster reads remote status on subscription/refresh/PR refresh and publishes it over `subscribeVcsStatus`; `vcs.refreshStatus` drives an explicit refresh. | No new render component. Error/no-repo returns and branch-null skip lookup. PR lookups prefer open matches, otherwise latest matching update; unpublished branches can skip lookup. Last-known fallback is keyed by cwd/branch and checked against head context/remote identity. Changing remotes or receiving an empty successful lookup is not a fixture for “warm lookup error.” |
| Web + Electron desktop | `ChatView` → `ThreadDetailsPanel` (sole production caller) → memoized `GitActionsControl`. Inputs: gitCwd, activeThreadRef (environment/thread), draftId, compact, showActions, showChanges, onOpenChanges. It reads the scoped live VcsStatusResult and write permission. | No cwd → null; nonrepo → Initialize Git; absent status → disabled Commit / no menu entries; status error appears in the options menu and defeats quiet-draft hiding. `showActions=false` hides actions; `showChanges=false` hides Changes. Quiet-draft gating depends on history, disabled quick action and line totals; compact hides idle quick actions. Progress replaces the primary row and hides the chevron; success temporarily replaces the primary row then expires after 10 seconds. Neither is a new UI from this diff. |
| Web action selection | `resolveQuickAction` and `buildMenuItems` in web's own GitActionsControl.logic, not the mobile implementation. | Dirty first: Commit if behind/no remote, Commit & push if open PR/default, otherwise Commit, push & PR. Clean without U: Publish repository if no remote; disabled Push if A=0 (disabled Commit for open PR); otherwise Push for open/default or Push & create PR. With U: A>0 & B>0 → disabled Sync ref; B>0 → Pull; A>0 → Push or Push & create PR; A=0 & D>0 & nondefault & no open PR → Create PR; otherwise disabled Commit. Menu Commit needs dirty; Push needs A>0 and B=0; Create PR needs D>0, B=0 and no open PR. Branch, remote, busy and permission gates apply. With a remote and no open PR there are three entries; an open PR removes web's Create entry; without remote web keeps only Commit (plus publish affordance). Missing D falls back to A for older payloads. |
| Web interactions | Enabled quick action runs the stacked action (or pull/publish); menu entries open commit/push/create flows. Disabled primary is an aria-disabled popover trigger that can show a hint; it is not a native disabled button. Opening options requests a status refresh. Disabled menu entries have hover popovers; detached/behind/error warnings may appear. | Record opening options, the post-refresh stable state, showing the relevant hint and trying the disabled affordance. A still cannot prove it dispatches no mutation. No need to execute the incorrect baseline Create PR action or create another PR. Dialog file selection, message editing, branch-confirmation and publish wizard are unchanged and not part of this fix's capture scope. |
| Layout, width, theme | ThreadDetailsCard chooses full/compact/essential from measured available HEIGHT, not a fixed viewport-width breakpoint. Full card is 280px wide; narrow layouts can use the details popover. ThreadDetailsItems visibility settings can hide gitActions/changes separately. | Dark/light use existing theme styles; no theme-dependent logic changed. Text clips/truncates in the existing panel. Use one narrow/light witness in addition to full/dark; do not multiply every server truth case by every theme. Long branch names affect surrounding BranchToolbar/native menu titles, not these action labels. Changes numbers and many file paths affect unchanged Changes/commit dialogs. |
| iOS quick menu | `ThreadRouteScreen` passes status to `useThreadHeaderOptions` → ThreadGitControls/header item hooks; review headers also reuse `useThreadGitMenuDefinition`. Native header and fallback toolbar menus use the same model. | Shared client-runtime `resolveQuickAction` has **no D-only Create PR branch**: clean A=0/U=true already gives disabled Commit unless an open PR gives View PR. Native menu includes a branch/status row, quick action, Review changes and More. Branch title is middle-truncated after 24 characters. Compact status changes “1 ahead” → “Clean” for a clean deleted branch. Draft (`hasThread=false`) disables Review/More, but does not use web's quiet-row hiding. |
| Mobile overview | `GitOverviewSheet` uses client-runtime `buildMenuItems`; reached via More / GitOverview route, or `ThreadRouteScreen`'s persistent git inspector on supported wider layouts. Route parameters are environmentId + threadId. | Commit/Push/Create PR are separate ListRows with disabled reasons as visible descriptions. Mobile Push also requires a clean tree (unlike web). Open PR gives View PR instead of Create. Status subtitle includes clean/changed file count, A/B, open PR. U is not displayed directly. Counts distinguish 1 vs N in enabled Push description; dirty count distinguishes file/files. No PR title/name list is newly rendered. B>0 adds Pull latest. Sheet/inspector, linked-PR rows and worktree-path section are independent unchanged layout factors. |
| Mobile platform | iOS sheet has native SheetHeader + pull-to-refresh; iOS inspector has native header; Android uses AndroidSheetHeader/anchored refresh menu and different padding; wider inspector has its own heading. ThreadGitControls.android is an explicit null/empty stub, so do not demand a nonexistent Android quick-menu screenshot. | Native opening, refreshing, disabled taps and sheet ↔ inspector navigation require recordings. Stable overview rows can additionally have stills, but stills do not replace the recording. |
| Other entry points/modes | No alternate GitActionsControl caller in Settings, CommandPalette or keybindings was found. Settings changes provider auth/visibility/appearance but does not duplicate this action decision. The review-header iOS menu above is an additional entry point. | All agent runtimes consume the same repository status; this patch is not provider-adapter-specific. Local/remote/relay/tunnel clients receive the same scoped contract. No origin/IPC/transport change requires a separate visual Cartesian product. A web recording proves the shared desktop component, not that an Electron executable was exercised. Native mobile requires its own evidence. |

## Fixture matrix

All fixtures use one clean feature commit H above initial base C, and a squash commit S on the default branch with the same tree as H but a different SHA. Defaults unless overridden: actual GitHub provider, nondefault named feature branch, `isRepo=true`, `hasPrimaryRemote=true`, write scope granted, not busy, established thread with a prior turn, full density, no inline success, no open PR, and `pr={state:merged, baseRef:main}` with recorded head H. Keep working directories separate per fixture. Counts below are raw-before → corrected-after at identical git/forge state. For normal text fixture the historical Changes total is expected to stay nonzero.

“Recording” means a before clip on the pinned base and an after clip on the pinned head, both labelled with row ID/client/revision. All required visible rows include a menu/hint/tap interaction; optional stills are supplements. “Same display” variants use an existing row's captures plus a real RPC transcript; they do not need duplicate screenshots.

| ID / named fixture | Data and expected counts | Before → after visible result | Diff affected? | Seed / proof mode |
|---|---|---|---|---|
| A1 — merged, remote branch deleted, clean | HEAD=H, base main, U=false, A=1→0, B=0, D=1→0, dirty=false. Preserve branch remote/merge config after pruning. | Web/iOS: enabled Push & create PR → disabled Push; web options Push + Create PR enabled → both disabled. Mobile overview Push + Create PR become disabled with “No local commits…” descriptions; iOS compact status loses “1 ahead” and says Clean; overview subtitle loses “1 ahead.” Commit remains disabled. | **Yes, core regression.** | Real GitHub recipe R1. **Recording.** Show primary, options/overview and disabled action attempts after refresh. |
| A2 — merged, remote branch kept, clean | HEAD=H, U=true tracking origin/feature at H, A=0, B=0, D=1→0. | Web Create PR → disabled Commit. Web/mobile overview Create PR enabled → disabled. Push remains disabled. iOS quick action is disabled Commit on BOTH revisions; do not expect a Create PR label there. | **Yes, core regression.** | Real GitHub R2. **Recording.** Same primary/menu/overview coverage. |
| A3 — merged, deleted branch, dirty | A1 plus one uncommitted text edit; one changed file, workingTree +1 line; HEAD still H. A=1→0, D=1→0. | Primary remains enabled Commit, push & PR. Web menu Commit enabled throughout, Push + Create PR enabled → disabled. Mobile Push is already disabled because dirty; Create PR becomes disabled. Dirty summaries lose ahead count but retain file count. | **Yes, menu/summary; primary is a guard.** | R1 then edit a tracked file without committing. **Recording.** Open options/overview; do not run the combined quick action. |
| A4 — merged, kept branch, dirty | A2 plus the same dirty edit. A=0, D=1→0. | Primary remains Commit, push & PR; Commit stays enabled and Push stays disabled; Create PR enabled → disabled. Mobile descriptions keep “1 file changed.” | **Yes, menu; distinct baseline from A3.** | R2 plus uncommitted edit. **Recording.** Can share the same clip sequence with A3. |
| A5 — merged head tracks default, diverged | Keep remote feature at H so own-branch PR lookup remains possible; set feature upstream to origin/main at S. A=1, B=1 unchanged, D=1→0. | Web disabled Sync ref / iOS Sync branch stays; Create PR stays disabled but its reason changes from behind upstream to no commits for PR. Both A and B remain visible in mobile summary; no false “all synced” claim. | **Yes, PR disabled reason; preserved divergence guard.** | R2 then set upstream to origin/main. Ensure feature remote ref remains. Disable automatic pull in isolated settings. **Recording** including disabled Create hint/description. |
| A6 — merged head has a real upstream ahead delta | HEAD=H; keep a same-name published remote branch but move its tip back to C after merge, retaining PR's recorded H; U=true, A=1, B=0, D=1→0. | Primary stays enabled Push & create PR; Push stays enabled; Create PR menu/overview row becomes disabled. This is the deliberate preservation of a real upstream delta, not proof that the core A1/A2 fix failed. | **Yes, menu only.** | R2 plus rewind only its feature remote ref, **only if a real forge read confirms the merged PR still reports H**; if that host updates headRefOid to the new branch tip, this row requires a **labelled provider+git integration harness**. Do not assume the post-merge API behavior. **Recording.** Do not click enabled Push/Create quick action. |
| A7 — merged head is behind its feature upstream | HEAD=H, remote feature advances to H+J after merge; U=true, A=0, B=1, D=1→0. | Primary remains Pull. Extra mobile Pull latest row and web behind warning remain. Create PR remains disabled; reason changes from behind upstream to no commits for PR. | **Yes, reason only.** | R2 plus a second fixture checkout pushing J to the feature branch; app checkout stays at H. Verify the merged PR still reports H; if that host changes headRefOid, use a **labelled integration harness**. Keep auto-pull off, or the fixture will cease to be at H. **Recording.** No pull operation. |
| A8 — merged, quiet draft/zero-turn thread, zero line totals | Use A1 or A2, but draftId set or server shell.latestRun=null; workingTree and branchChanges insertion/deletion totals zero, no error/progress. Binary-only PR is a realistic way to have a graph delta without line totals; verify via actual RPC. | Web's enabled baseline action and Changes row disappear once the corrected quick action is disabled. Do not misreport this as expected disabled Push/Commit on an established thread. Mobile has no corresponding quiet-row gate. | **Yes, conditional visibility.** | Real binary-only R1/R2 plus a fresh draft/empty thread, if RPC confirms zeros; otherwise **labelled UI harness** for this rendering-only gate, not a substitute for A1/A2 end-to-end evidence. **Recording** through draft/thread entry. |
| A9 — successful merged lookup, then lookup failure | Start A1; same server process first remembers PR+H, then provider lookup fails on refresh. No branch/remote/context switch. Raw A=1,D=1; fallback should keep corrected A=0,D=0. | Before patch stale merged PR can still accompany enabled Push & create PR; after patch disabled Push persists across failed lookup. No new user-facing forge-error banner is guaranteed: GitManager catches/logs this lookup failure. | **Yes, temporal fallback.** | **Labelled provider+real-git integration harness** R-H. Normal app has no “fail next PR lookup” setting. Record successful state → explicit failed refresh → persistent disabled state; retain correlated server failure log. Do not induce real rate limits or revoke shared credentials. **Recording required; stills insufficient.** |
| G1 — post-merge follow-up commit | A2 plus local new commit J: HEAD≠H, U=true, A=1,B=0,D=2; deleted variant U=false,A=D=2. | Push & create PR and standalone Push/Create PR remain enabled on both revisions; local work is not swallowed. | No adjustment; regression guard. | Real R2 then commit a new file (do not push). A static endpoint is optional; **recording** if showing the transition from A2 → new commit. Existing focused server test covers retained variant. |
| G2 — merged into nondefault release | Merge feature into release, leave default main at C; HEAD=H, PR.base=release, U=true, A=0,B=0,D=1. | Web Create PR and overview Create PR remain enabled; mobile quick Commit remains disabled. | No adjustment; base guard. | Real throwaway repo R2 with PR base release. No extra screenshot needed beyond A2's baseline appearance; preserve RPC+forge transcript. |
| G3 — default-resolution aliases | A1/A2 but main/master resolved with missing origin/HEAD; or only remote upstream whose symbolic HEAD is develop and PR.base=develop. H matches. | Same display as A1/A2 for equivalent A/U values. Renaming the default/remote must not leave Create PR enabled. | **Yes, same-display variants.** | Real R1/R2; for develop use only upstream remote and `upstream/HEAD→upstream/develop`. Keep actual forge default consistent. Capture A1/A2 once; add real status+default-ref transcript for these variants. The non-origin/develop case is in the new server tests. |
| G4 — PR missing head / failed HEAD resolution / base unresolved non-main | Merged PR but H absent; or commit resolver fails; or default unresolved and PR.base=develop. Counts retain raw values. | Equivalent unchanged A1/A2 baseline labels/menu. No new error view is introduced by these caught resolver failures. | No adjustment; metadata/resolution guard. | Missing H and resolver failures require **labelled provider/driver integration harness**; GraphQL normally requests headRefOid, and a broken git checkout is not a reliable UI seed. No duplicate still needed; runtime transcript distinguishes predicates. |
| G5 — no qualifying merged PR / detached / no remote / status unavailable | See orthogonal gates below; not used to claim the fix worked. | Existing UI branches described below remain. | No new behavior from diff. | Real app where possible; controlled loading/error/busy states require a labelled harness if deliberately frozen. No extra captures required for this server-only fix. |
| G6 — idle compact/essential or hidden action settings | Any A1/A2 state with compact=true; or showActions=false. Full card density determined by actual measured height. | Idle Git action row absent both revisions; Changes can remain unless quiet gate also applies. Progress/success can temporarily expose compact row. | Counts can change but no idle action visual delta. | Real resize/available-height adjustment and Thread Details visibility menu. One narrow/light full-density popover witness is in the capture minimum; compact/essential variants need no duplicate screenshot. **Recording** for any resize/toggle proof. |

## Orthogonal gates (inventory, not a demand to capture the whole unchanged UI)

- **Empty/loading/error:** null status means disabled Commit, empty action items; iOS menu says Checking status; mobile overview says Loading branch status…. Web query error adds the options error text and prevents quiet-draft hiding. A cold failed forge lookup with no remembered PR is different: GitManager returns pr=null with raw counts, not an A9 fallback. A successful empty lookup must not be labelled a lookup failure. These are existing UI shapes and should not be frozen as the primary proof fixture.
- **Open/closed/missing PR:** open PR bypasses this fix; dirty → Commit & push, clean ahead → Push; clean caught-up web quick is disabled Commit while mobile quick is View PR. Web drops its Create menu entry, mobile replaces it with View PR. Closed/null PR follows ordinary no-open-PR creation rules. For a genuinely unpublished branch, `isUnpublishedBranch` can avoid forge lookup entirely.
- **No branch / no repo / no remote:** detached HEAD skips PR lookup and disables primary, with the web detached warning; nonrepo gives Initialize Git on web and Git unavailable on the iOS quick menu; a repo without remote offers Publish repository on clean web and disabled Push on mobile, with only Commit in web's normal action list. None is evidence of merged-head suppression.
- **Busy/success/time:** active stacked action/pull progress hides normal primary, adds elapsed time, disables/hides the menu; success can replace the primary for 10 seconds, then the corrected idle state appears. Label such evidence **recording**, including the transition; don't capture the transient label as the settled result. No need to run an actual agent or stacked action just to produce these unchanged states.
- **Permissions:** a read-only environment overlays the web primary with “This connection cannot change source control” and disables menu mutations. Mobile overview presents the same explicit permission description. Choose a write-authorized connection for A1/A2 so permission denial cannot explain the disabled result. Keep branch/thread metadata permission separate from source-control-write.
- **Card/draft/history:** no cwd returns nothing; inactive project/no project name can prevent the sole caller from mounting the control; `showActions` and `showChanges` are independent visibility preferences. A dirty working tree retains commit affordances; no-turn alone does not hide an enabled action or a row with positive resolved Changes totals. A8 is the exact empty case.
- **Lengths:** A/D 0 vs positive is what the web action decision reads. Mobile also displays 1 vs N commit/file grammar; use one commit and one dirty file in the primary fixtures, and a 2-commit G1 transcript as a count guard. No account/list card exists in this diff. Long file paths/many files affect the unchanged commit dialog; long branch names affect existing truncation. They do not warrant separate fix captures.
- **Platforms/connections:** iOS native header vs fallback toolbar and review-header menu reuse the same quick-action model; the overview sheet vs wide inspector is a separate presentation. Android overview is applicable, its ThreadGitControls quick menu is not. Electron wraps this web control; direct server status over local, remote and tunnel connections has the same payload. Do not substitute a mobile-sized web viewport for native mobile.

## Minimum capture set (viewport × theme × row)

These are selected factor witnesses, not every Cartesian product. A single before clip and after clip per client may walk multiple row IDs; label the row transitions. Every changed display class A1–A9 is represented, with G3 aliases proven by transcripts instead of duplicate images.

| Client / logical viewport | Theme | Rows / surface | Required artifact |
|---|---|---|---|
| Web, 1440×1000 CSS px, full-density Thread Details card | Dark | A1, A2, A3, A4, A5, A6, A7, A8; normal primary + options + relevant disabled hints | Before/after **recordings**, can be one short walkthrough per revision. Include stable frames of A1 and A2 for the PR body. A6/A7 can use clearly labelled integration-harness segments if real throwaway remote cannot be owned; A8 rendering harness permitted only with label. |
| Web, 390×844 CSS px, full-density details popover | Light | A1 | Before/after **recording** opening the popover/options and showing disabled Push/Create PR; guards width, alternate container and disabled contrast. No need to repeat every row in this theme. |
| Web, 1440×1000 CSS px | Dark | A9 labelled integration harness | **Recording** of successful merged lookup then forced lookup error/refresh in same process; server trace must distinguish completed failure from a cached response. |
| Native iOS phone, approximately 393×852 pt (record actual device/physical pixels) | Dark | A1 native Git menu → More → overview; A2, A3/A4, A5 overview rows | Before/after **recordings**; capture A1's summary/quick action and A2's overview Create PR state, not a nonexistent iOS quick Create PR. One sequence can cover the dirty and divergence descriptions. |
| Native Android phone, approximately 412×915 dp | Light | A1 and A2 GitOverview sheet; A3 overview; refresh via Repository options | Before/after **recordings**; no Android ThreadGitControls menu expected. This tests the other native row renderer/platform header. |
| Native tablet/wide layout, approximately 1024×1366 logical units | Same theme as available native phone | A2 GitOverview as persistent inspector | One before/after **recording** opening the inspector and showing Create PR transition. iOS is sufficient to witness the native inspector wrapper; Android inspector is unchanged presentation, not another minimum Cartesian factor. |
| RPC/git transcript (not visual) | N/A | G1, G2, G3 default aliases; raw and final A/B/D/U; successful/failed forge context | Attach sanitized transcript alongside UI evidence. No new screen shape requires more screenshots. G4 is an optional labelled harness guard beyond the supplied tests, not required to prove the reported issue. |

If scope is constrained to the two reported clean-worktree regressions, the **issue-only core** is A1+A2 at full web size plus their native overview counterparts. That narrower set must not be described as coverage of dirty/menu, quiet-history or fallback behavior. This pass's full matrix explicitly includes those changed paths.

## How to seed the real app

### R1 / R2 — recommended real GitHub fixture

These are instructions for the authorized capturer, not operations performed by this read-only auditor.

1. Under a NEW timestamped directory below `/tmp/lead-28494-r2/`, make a disposable git repository and a GitHub repository that the capture account owns (or use a previously provisioned disposable repository). Select a source-control credential in the isolated T3 server that can read that repo. The app may use settings/env/App credentials ahead of gh; a shell login alone is not proof that the server uses it. Do not use the production T3 repository as a mutable fixture.
2. Push initial main C, set actual GitHub default main and local origin/HEAD→origin/main. Create a distinct feature name for each fixture, change one tracked text file, commit H, push `-u` so `branch.<feature>.remote` and `.merge` remain recorded, and create a real PR targeting main. Record original H. Squash-merge it **on GitHub**, producing S ≠ H; remain checked out on the original local feature H. A local `git merge --squash` alone does not mark a GitHub PR merged.
3. R2 keeps the published feature ref at H. R1 deletes only that remote feature ref and runs `git fetch --prune origin`. **Do not unset the local branch's tracking config.** The app must see no resolvable upstream, yet still know the branch had been published; removing both branch remote/merge config and the tracking ref can make `isUnpublishedBranch` skip PR lookup. Do not check out main or reset to S; either breaks HEAD=PR-head identity.
4. Verify raw git state: HEAD=H, default tip S, `git rev-list --count origin/main..HEAD`=1, clean porcelain for clean rows, and `git diff origin/main HEAD` empty. For R1, local branch config remains while its tracking ref is absent; for R2 the tracked feature ref resolves to H. Verify actual forge result: state merged, base main, headRefOid=H, matching feature name/repository. A newly-opened PR or a branch merely containing an old squash tree is not enough.
5. Start the isolated app with its own home/database; add this fixture path as a project and select an established thread rooted in that cwd/worktree. In web navigate `/<environmentId>/<threadId>` (the `_chat` file-route layout adds no path segment), then reveal Thread Details and ensure Git actions and Changes visibility settings are on. For native clients select that same environment/thread and open Git/More or the Git overview/inspector. The review header's iOS Git menu is the same model, not a separate fixture.
6. Keep automatic pull disabled for the divergence/behind fixtures. Use `vcs.refreshStatus` with `{cwd, localOnly:false}` through the app's authenticated environment RPC client and subscribe to `subscribeVcsStatus({cwd})`; the options chevron also requests refresh. Record the completed payload with A/B/D/U, PR state/base/ref, refName and dirty flag. A local-only refresh is not enough. Opening options can itself change a stale label, so record it through settlement, not a flash before the response.
7. Capture the same git/forge state on base and head in separate isolated server homes/processes. No client count injection for A1/A2. Pin the listed commits and use the same native client compatible with each server; the client contract is unchanged. Baseline expected labels above are derived from the **pinned base**, not the older baseline SHA mentioned in the PR body.

For a rebase-merge semantic variant, use a real rebase merge that produces a new base commit identity while retaining the PR's original H locally; confirm actual headRefOid and git counts. It collapses to A1/A2 visually and does not need duplicate captures. A regular merge whose raw counts are already zero is also the same corrected visual class and is not a useful failing-before fixture.

### R-H — labelled harness rows

The normal production app does not expose a setting to inject fake change requests or arbitrary VcsStatusResult. The existing GitManager tests replace the SourceControlProviderRegistry/provider while keeping a real bare git remote. A capturer can build an equivalent isolated integration bootstrap: production GitManager + real git + a fixture-scoped provider returning `{state:merged, baseRefName, headRefName, headSha:H, number, title, url, mergedAt, updatedAt}`. Scope it to the fixture cwd. For A9, let the first complete status lookup succeed, then fail provider lookup, invoke normal status invalidation/refresh, and preserve the same manager instance so lastKnownPrByBranchKey survives. Capture both the app and a sanitized failure trace. A warm UI receiving no new response is not proof of fallback.

Label every such segment **“Integration harness — real git, controlled forge response”**, including before/after. A bare remote plus a stubbed gh executable is insufficient at this head because the list read bypasses gh. A pure UI harness may instead feed exact before/after VcsStatusResult to the real control for A8/unchanged layout gates, but label it **“UI harness — injected status; does not execute GitManager”** and never use it as the sole server-regression proof. Do not rewrite GitHub DNS, capture or forward credentials, revoke shared auth, or intentionally exhaust a real API quota.

## Evidence acceptance / limits

- Each core recording must identify revision, fixture ID, client, viewport/theme and real-forge vs harness. Pair each with sanitized actual RPC/git/forge data, not hand-written expected JSON.
- Show the enabled baseline and corrected post-refresh disabled/hidden control at identical git state. Disabled state must not be caused by read-only scope, busy action, missing repo/status, compact density or another dialog.
- For attempts on a disabled control, show no stacked-action mutation/dialog follows; web's disabled-primary popover opening is expected. Avoid triggering the enabled baseline action, which can publish or create new PRs and destroy fixture identity.
- Existing historical Changes +N/−M is intentionally unchanged except A8's existing quiet-row gate hides the row. Do not “fix” or mask these totals for evidence.
- No captures, browser sessions, dev servers, emulators or leases were started in this audit. Worktree remained clean. No edits, pushes or forge comments. Artifacts remain under `/tmp/lead-28494-r2/audit1/`; no cleanup of lead-owned directories.

## Source anchors

[Changed remote-status and head-SHA fallback logic](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/server/src/git/GitManager.ts#L1258)

[Merged-head count adjustment](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/server/src/git/GitManager.ts#L1387)

[Unpublished branch guard and PR head matching](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/server/src/git/GitManager.ts#L1767)

[Added real-git / fake-forge fixtures](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/server/src/git/GitManager.test.ts#L1777)

[Web action decisions](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/web/src/components/GitActionsControl.logic.ts#L109)

[Web quiet-draft and data gates](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/web/src/components/GitActionsControl.tsx#L1034)

[Web primary and options rendering](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/web/src/components/GitActionsControl.tsx#L1485)

[Sole control caller and density/visibility props](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/web/src/components/chat/ThreadDetailsPanel.tsx#L157)

[Card density uses measured height](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/web/src/components/chat/threadDetailsCardLayout.ts#L8)

[Mobile action decisions and descriptions](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/packages/client-runtime/src/state/gitActions.ts#L52)

[iOS native/fallback menu model](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/mobile/src/features/threads/ThreadGitControls.tsx#L125)

[Android quick-control stubs](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/mobile/src/features/threads/ThreadGitControls.android.tsx#L10)

[Native overview rows and sheet/inspector variants](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/mobile/src/features/threads/git/GitOverviewSheet.tsx#L100)

[Overview summary count text](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/mobile/src/features/threads/git/gitSheetComponents.tsx#L29)

[Direct GitHub GraphQL list by head](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/packages/source-control-github/src/server/GitHubSourceControlProvider.ts#L539)

[GitHub GraphQL asks for original headRefOid](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/packages/source-control-github/src/server/sourceControlProvider.graphql#L6)

[gh supplies credentials, not PR list results](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/packages/source-control-github/src/server/GitHubCredentials.ts#L149)

[Unknown forge returns no change requests](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/server/src/sourceControl/SourceControlProviderRegistry.ts#L71)

[Status refresh invalidates lookup and publishes result](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/apps/server/src/vcs/VcsStatusBroadcaster.ts#L493)

[Status RPC names and typed payloads](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/packages/contracts/src/rpc.ts#L1437)

[Unchanged remote status contract](https://github.com/pingdotgg/t3code/blob/0d27d85707dcdc7de5d0fcd05f5649b030203899/packages/contracts/src/git.ts#L246)

FRICTION: none

VERDICT: INSUFFICIENT <all affected rows: captures pending>
