# Proof audit pass 1 — task `issue17716`

Read-only code-derived plan, 2026-10-22. Audited `/home/ubuntu/wt/issue-17716`, branch `fix/issue-17716`, implementation [9349f9aaf8](https://git.cafe/pingdotgg/t3code/commit/9349f9aaf86350feb40c61fcb85ca7f07a0e2c92) against requested baseline [8acbefefc5](https://git.cafe/pingdotgg/t3code/commit/8acbefefc5). Only working-tree modification was the lead's temporary `apps/mobile/eas.json`; excluded. No code edits, tests, captures, servers, browser/device use, or issue/PR writes in this audit. The requested ghdrop artifact upload is the sole public write.

**Proof verdict: pending.** This is a fixture/evidence specification, not fresh execution proof. Real EAS iOS native static fixture captures remain required. Android is unavailable here and remains **UNVERIFIED**, including all Android rows below. The prior issue-comment capture from [70e5e384da](https://git.cafe/pingdotgg/t3code/commit/70e5e384da) is stale and cannot satisfy fresh before/after proof against this baseline and implementation.

## Render paths and scope

| Changed path | Required observation / caller distinction |
|---|---|
| `thread-pr-presentation.withThreadOpenPullRequest` | Null status stays null; checkout's open PR wins; otherwise current open or unsynced linked PR supplies only `status.pr`; closed/merged links do not. Other VCS fields stay checkout-derived. |
| `ThreadRouteScreen` → iOS thread Git header | Native Git status description and quick action use augmented status. Android uses its separate header actions instead of these iOS header items. |
| `useReviewHeaderPresentation` → iOS Review header | Separate changed caller, so inspect once. Android's `.android.ts` implementation returns `gitMenu: null`; do not invent an Android review Git-menu requirement. |
| `GitOverviewSheet`, both `sheet` and `inspector` | Own augmented status and View PR handler; renders existing links independently of capability. Reuse the same fixture for both presentations. |
| `ThreadHeader` → Android `AppBar` | New action comes from `thread.pullRequests`, for any linked state; one link opens its PR, multiple links/stack opens `ThreadPullRequests`. Usually in overflow. No new iOS direct PR header button is added. |
| `useLinkedPullRequestLines` → `GitOverviewSheet` and `ThreadPullRequestsSheet` | Both callers must retain already-present links when `threadPullRequests` is false/absent. Mutation controls retain their existing capability/scope gates. |

Web and desktop are unchanged; no browser-width or desktop capture requirement. No provider adapter, contracts, scheduler, MCP, or server behavior changed. One remote native connection to the isolated environment is sufficient; multiplying provider models or connection transports adds no distinct changed render path.

## Exact fixture data

Use synthetic data only. The lead's `/tmp/17716-fixture/seed.py` already seeds isolated `.t3/repro/userdata/statev2.sqlite` after migration, project ID `t3code`, stopped thread ID `remote-command-center`, project title `Linked PR fixture`, thread title `Open the linked pull request`, checkout `main`, `worktreePath: null`, no active provider thread, no running turns/tasks. Workspace is `<worktree>/.t3/repro/workspace`, initialized with one README commit and an origin pointing at synthetic [example/fixture](https://github.com/example/fixture). This does not create a public repository or PR.

Common linked record fields: `host: github.com`, `repository: example/fixture`, `source: manual`, `stack: null`, no `watch`, `linkedAt: 2026-10-21T12:00:00.000Z`. Common snapshot: `state: open`, `isDraft: false`, `baseBranch: main`, `updatedAt: null`, `syncedAt: 2026-10-21T12:00:00.000Z`. Omit optional avatars/checks/reviews/diff stats: they do not change this fix. URL and title/head overrides are exact below.

| Record | URL | Snapshot title | Head branch | Overrides |
|---|---|---|---|---|
| L42 | [#42](https://github.com/example/fixture/pull/42) | `Fork branch change` | `fork-feature` | Seed's original record. |
| L43 | [#43](https://github.com/example/fixture/pull/43) | `Second linked change with a deliberately long title for narrow native sheets` | `second-feature` | `linkedAt: 2026-10-21T12:01:00.000Z`; same snapshot `syncedAt` as common. |
| L44 | [#44](https://github.com/example/fixture/pull/44) | `Closed linked change` | `closed-feature` | Snapshot `state: closed`. |
| L45 | [#45](https://github.com/example/fixture/pull/45) | `Merged linked change` | `merged-feature` | Snapshot `state: merged`. |
| B3 | [#3](https://github.com/example/fixture/pull/3) | `Checkout branch change` | `main` | VCS `pr` object, not linked record: `number: 3`, `title` as shown, `url` as linked, `baseRef: release`, `headRef: main`, `state: open`, `isDraft: false`, `updatedAt: null`. |

Pin base VCS response S0: `isRepo: true`, `hasPrimaryRemote: true`, `isDefaultRef: true`, `refName: main`, `hasWorkingTreeChanges: false`, `workingTree: {files: [], insertions: 0, deletions: 0}`, `hasUpstream: false`, `aheadCount: 0`, `behindCount: 0`, `pr: null`. This matches the seed's clean, untracked local main; verify the actual response rather than assuming remote/default discovery succeeds. With an open link S0's quick action is **View PR** even without upstream. With no open PR its quick action is disabled **Push**; Git overview's **Create PR** is disabled. A test fixture with `aheadCount: 1` would instead show **Push** in the header even with a linked open PR: that is existing priority, not a failure.

| Fixture | Thread `pullRequests` | VCS | Purpose |
|---|---|---|---|
| F0 | `[]` | S0 | No-link negative control; no new Android PR action, no linked section, empty direct list route. |
| F1 | `[L42]` | S0 | Core reproduction: linked open PR on another branch; no checkout PR. |
| F2 | `[L42 with snapshot: null]` | S0 | Unsynced link counts as open for Git action; list shows `Waiting for host state`; Android accessibility says status pending. |
| F3c / F3m | `[L44]` / `[L45]` | S0 | Closed and merged collapsed for Git fallback behavior, retain two static states for distinct list glyph/color and Android action labels. Neither synthesizes an open PR. |
| F4 | `[L42]` | S0 with `pr: B3` | Git menus open checkout PR [#3](https://github.com/example/fixture/pull/3); Android linked badge opens [#42](https://github.com/example/fixture/pull/42). Intentional different sources. |
| F5 | `[L45, L44, L42, L43 with snapshot: null]` | S0 | Independent multiple links, two open/unsynced and two terminal; latest open link L43 selected for Git View PR. Dedicated list reports `2 open · 4 linked`, shows open chains first (L43, L42), then terminal chains (L45, L44 with stable tied timestamps/input order). |
| F6 | `[L42, L43 with snapshot.baseBranch: fork-feature]` | S0 | Derived two-layer stack; header action routes to list, top L43 is Git fallback, list shows base L42 then indented L43. Needed once because stack presentation differs from independent multiple links. |
| Fnull | `[L42]` | `null` | Code/test control only: no invented loaded Git status or enabled quick action. Not a timing capture demand. |

Capability/grant overlays (apply to F5 rather than multiplying every state):

| Overlay | Exact inputs | Expected visibility |
|---|---|---|
| C+ | Capabilities `threadPullRequests: true`, `threadPullRequestWatch: true`; normal isolated connection with `orchestration:operate` and `source-control:write`. | Links visible, Link/Unlink available, Watch available on open links. Do not execute mutations for this proof. |
| Ccap | Capabilities `threadPullRequests: false`, `threadPullRequestWatch: false`; keep the grants from C+. | Already-present links remain in both lists; no Link, Unlink, or Watch controls. Preserves specifically the formerly capability-hidden render branch. Omitted capability is equivalent to false for these gates. |
| Cscope | Capabilities true as C+; isolated read connection with `orchestration:read` and `filesystem:read`, without `orchestration:operate` or `source-control:write`. | Existing links and View PR remain readable; Link/Unlink/Watch and Git write actions unavailable. Scope denial is distinct from capability denial and must not be proved by combining both denials. |

## Minimal native evidence matrix

All rows are planned, not passed. Static rows require fresh matching before/after stills, baseline and implementation with identical fixture, theme and native geometry. Every interaction or temporal row is explicitly **RECORDING**; stills alone do not prove it. Reuse frames from recordings for static observations where clear.

| ID | Fixture / caller | Native size and theme | Assertion | Evidence / current status |
|---|---|---|---|---|
| S1 | F1 C+: iOS thread Git menu and Git overview sheet | Real EAS iOS phone portrait, approximately 390 logical pt wide; light **and** dark | Git menu status mentions linked PR; quick action and sheet say View PR; sheet shows L42. Baseline incorrectly lacks the fallback. | **STATIC before/after REQUIRED — pending EAS iOS.** Capture header/menu and sheet at rest. |
| S2 | F1 C+: iOS Review Git menu | Same iOS phone, one theme | Separate Review caller also says View PR, same linked number. Empty review content is acceptable; no fake running turn needed. | **STATIC before/after REQUIRED — pending.** Android review has no such menu. |
| S3 | F2, F3c, F3m and F0: iOS Git overview | Same phone, one theme | F2 View PR + waiting row; closed/merged remain visible but Create PR stays disabled under S0; F0 has no linked section. | **STATIC state strip — pending.** Closed/merged Git action branch equivalent; do not repeat every caller/theme. |
| S4 | F4: iOS Git menu/overview | Same phone, one theme | Existing checkout PR wins: View PR targets B3, without changing linked list L42. | **STATIC + code/test identity check — pending.** Destination interaction belongs to R1. |
| S5 | F5 Ccap then Cscope: both Git overview and dedicated linked PR sheet | Same iOS phone; F5 Ccap light and dark; Cscope one theme | Four rows retained, correct open count on dedicated sheet, no false empty state, no mutation controls. Cscope View PR still enabled. | **STATIC REQUIRED — pending.** C+ control reused from S1 and R2; list caller coverage cannot be collapsed to one screenshot. |
| S6 | F1 and F5 Ccap: split layout Git inspector + linked sheet | Real native iOS tablet / supported large native window, report actual app/content widths; light and dark using F5; F1 once | Inspector presentation retains View PR/links, native header layout switches without clipping; long title truncates inside row. | **STATIC — pending / width gap if no native wide device.** Do not substitute a desktop browser. |
| S7 | F1, F2, F3c, F3m, F0: Android thread header / overflow | Native portrait near 390 dp; one theme for state variations, F1 light and dark | New accessible Open action exists for each linked state; none for F0. Portrait overflow must remain reachable without crowding header. | **STATIC — UNVERIFIED: Android unavailable.** |
| R1 | F1 then F4: iOS thread quick View PR, Review quick View PR, Git sheet View PR | iOS phone; one theme | Open each changed caller, select View PR, confirm route repository/number (F1 L42; F4 B3), return/dismiss to thread. Reuse one continuous clip; F4 needs only one Git caller. | **RECORDING REQUIRED — pending.** Synthetic PR detail fetch failure does not invalidate correct route but cannot prove loaded remote PR content. |
| R2 | F5 Ccap/Cscope dedicated sheet, F6 stack; Android header single/multiple | iOS phone for sheets; Android portrait for new header path; one theme | Open list, show all rows, long-press one row to show only Copy/Open-in-browser under denied mutation, dismiss; select a row and show correct native destination. Android single opens PR directly; multiple/stack opens list first. | **RECORDING REQUIRED — iOS pending; Android UNVERIFIED.** Do not unlink, watch, or create anything. Stack can reuse the same clip; independent+stack are distinct labels/list shapes. |
| R3 | F1/F5: Android width/compact transitions | Native portrait header <600 dp, then actual header >=600 dp if attainable; normal and keyboard compact state | Open overflow before/after width change; PR action is never lost/duplicated; show reachable action with maximal existing Files/Terminal/Git siblings. Optional Return/Merge siblings can be enabled locally if accessible without building unrelated state. | **RECORDING REQUIRED — UNVERIFIED.** Threshold uses measured **header** width, not device pixels; at >2 actions direct count is 1 narrow / 3 wide. PR may still be overflow wide. |
| R4 | F1 → F2/F0 or F4 thread selection while iOS remains mounted | iOS phone, one theme | Switch between separately preseeded threads; native Git menu reflects selected link/checkout PR and callback, no stale number/destination; return/close menu. | **RECORDING REQUIRED — pending.** No wall-clock performance assertion. Seed copies rather than live SQL mutation. |

No loading spinner/performance/timing claim is needed by this patch. Any added refresh, reconnect, rotation, animation, or "updates immediately" claim must be a recording row, not an extra still. Null status can be checked in focused logic without orchestrating transient network timing.

## Width/theme interpretation and gaps

The explicit brief requests 1440 and 390, light/dark. Native units are iOS points / Android dp; screenshot pixel width (e.g. a scaled phone image) is not the app width. Use an actual supported EAS iPhone width near 390 and report the exact model, orientation, logical dimensions, scale and OS. If it is 393/402 instead, disclose the exact-390 gap; do not resize a screenshot and claim native coverage. Use a supported iPad/large native window for the wide branch. If exact 1440 logical width is not supported/available, mark **1440 unverified**, report the actual wide dimensions, and cover the split layout branch there. A 1440px desktop viewport proves neither native header nor sheet behavior.

Layout split threshold is ordinarily width >=720 and height >=600; Duo/native hinge metrics have separate geometry rules, not part of the smallest bug fix. Android action overflow threshold is header width >=600, so landscape alone may not reach it and a split tablet's detail header may remain narrow. Record actual header width. One compact and one native split geometry cover the relevant layout branches; no width sweep, every-phone-model matrix, or web/desktop tests. Light/dark need representative header/menu and row/sheet captures, not a Cartesian product of every fixture. Older iOS toolbar fallback is a conditional gap if the available EAS OS only exercises glass headers; do not call it verified through the modern toolbar.

## Seeding and capture handoff (lead executes later)

1. With only the isolated fixture server stopped, ensure `.t3/repro/userdata/statev2.sqlite` has the matching migrations. Copy/migrate from a read-only live snapshot into sandbox first; never start against live home. Run the existing seed only with root `<worktree>/.t3/repro`. It upserts two projection records; it does not create a replayable orchestration event history or the full fixture matrix.
2. Preserve F1, then generate named stopped-thread copies `remote-command-center-f0`, `-f2`, `-f3c`, `-f3m`, `-f4`, `-f5`, `-f6`; adjust row thread IDs, payload `id`, lineage `rootThreadId`, and title consistently. Keep the same synthetic project/workspace. F1 retains the requested original thread ID. Set all active provider IDs null; leave turns/tasks stopped. Make F4's checkout status branch-specific via the isolated response fixture; SQL `pullRequests` cannot supply VCS B3 by itself.
3. For deterministic snapshots and B3, provide isolated static service responses for VCS status and PR reads/sync. Pin S0/B3 and the specified snapshots, prevent real forge sync from replacing them, and expose Ccap via isolated server-config response plus Cscope through a genuine read-only isolated session. The current seed does **not** supply these response/capability fixtures; this is a remaining preparation step, not an existing verified mechanism. Use a temporary fixture boundary outside committed product code. No public forge mutation or fake link creation on a real repository is needed.
4. Check the client actually received the arrays/capabilities and selected project/thread before capturing. Route assertions may stop at synthetic PR destination; if clean rendered PR details are desired, add static detail responses rather than hitting nonexistent public PRs. Keep settings/theme local and do not upload secrets, pairing URLs or copied live payloads.
5. Lead performs authorized real EAS iOS static-fixture verification using the native client workflow; pin source SHA and native build/runtime identity. Build/update a missing native client when necessary. Use the same native OS/model, seed and geometry for baseline and implementation; Metro refresh/native reload and capability/session changes must actually be applied before a capture is labeled. This audit did not start that workflow.
6. Collect S1/S2/S5 minimum iOS evidence first, then remaining collapsed state/width checks and recording rows. Mark unsupported widths/Android/fallback OS explicitly unverified. Store proof outside the repository, label before/after and SHA, upload only synthetic evidence. Do not use the stale prior capture as the new baseline. No PR creation or comment write is authorized by this audit.

Focused logic coverage already exists in `apps/mobile/src/state/use-thread-pr.test.ts` for open fallback, unsynced link, checkout PR precedence, terminal-link exclusion and open-over-merged selection. This pass inspected it but did not run it. Null-status preservation, status non-PR field preservation, and both capability-independent list callers should be established by code review/focused checks or native fixture proof as appropriate; no broad UI test suite requested for a small fix.

## Friction

| category | what | prevention |
|---|---|---|
| Evidence freshness | Prior capture predates the requested comparison and current implementation. | Pair fresh baseline/implementation evidence; record source SHA and fixture IDs. |
| Fixture completeness | Existing seed only supplies F1 projection data, not checkout B3, denied capabilities, deterministic sync, or other states. | Prepare isolated response fixtures plus stopped-thread copies; verify actual wire data before capture. |
| Native availability | Android is unavailable; exact 390/1440 logical native sizes and older iOS header path may also be unavailable. | Report each concrete gap; use supported native sizes and never substitute desktop browser widths. |
| Header discovery | Android new action may be overflowed; narrow/wide follows measured header width. | Inspect More actions; record actual header dimensions and action route. |
| Permissions | Capability denial and missing operate grants are different render gates. | Test Ccap and Cscope separately against already-present arrays in both list callers. |
| State ownership | Synthetic projection seed is not event-sourced replay data; remote sync can overwrite snapshots. | Seed stopped isolated state only, pin read responses, recreate sandbox rather than touching live history. |
| Scope | Temporary EAS config, remote fixture fetch errors, or unrelated rich PR visuals could expand this fix. | Exclude temporary config, prove route/visibility with static data, keep changes scoped to this bug. |

No concrete vulnerability was identified in this proof audit. No subject-based hard stop was applied.
