# Mobile PR refresh headless runtime proof

Reviewed head: b76d4e808595268e56ba4bac84b0e370a3af6ba9.
Base: 1451e7cb23034d29774338608368a15d8845df36.

This is a controlled runtime fixture, not a native mobile recording. Device access returned: "Agent device access is turned off for this environment."

The scratch harness, outside the repository, imports the actual usePullToRefresh hook and executeAtomQuery implementation. It mounts them with React DOM, the actual RegistryContext, Effect atoms, and AtomRegistry. It parses the actual PullRequestRouteScreen refresh closure and pullRequestTarget function from each revision and executes those closures with controlled command/section dependencies. It does not reproduce those closures by hand or assert their source text.

The upstream/cache boundary is a fixture: held checks begin pending, upstream changes to success, and invalidation replaces the held value. It is not a live GitHub call, a running server integration, or the full React Native route renderer. The real hook, atom execution, spinner state, cancellation, and extracted route logic run.

Before (actual base hook and route closure): 1 scenario passed, 6 failed. The route did not call invalidation; detail and dependent reads ran immediately. The unchanged zero-argument hook path passed.

After (actual reviewed head): all 7 scenarios passed:

- Full selected environment/reference, including explicit host, reaches invalidation before dependent/detail reads. Held pending checks become success without a TTL advance.
- Slow invalidation starts and retains the spinner; repeated pulls deduplicate. The spinner remains active while detail is delayed and clears after detail completion.
- Rejected invalidation still refreshes stack, activity, files, and detail; guide remains conditional, tested both enabled and disabled.
- Switching selection during pending invalidation prevents obsolete dependent/detail reads.
- Unmount during pending invalidation prevents late dependent/detail reads.
- Old completion cannot clear a newer atom's spinner.
- Existing zero-argument hook invocation still refreshes without invalidating.

Commands, from /home/ubuntu/wt/pull-request-05 with PATH extended:

    node_modules/.bin/vp test run --config /tmp/pull-request-05/19177-runtime/vite.config.ts --reporter=verbose
    PROOF_BASE=1 node_modules/.bin/vp test run --config /tmp/pull-request-05/19177-runtime/vite.config.ts --reporter=verbose

The base hook is copied directly with git show origin/main:apps/mobile/src/state/use-pull-to-refresh.ts and selected by a scratch Vite alias. The worktree is never switched or edited for this comparison.

Additional existing server-cache behavior checks: 11 passed, 195 excluded by name filter. They exercise real PullRequestService cache sharing, held reads, explicit invalidation, and rate-limit behavior against provider fixtures. These tests already pass on main and support the service contract; they do not prove mobile wiring.

Existing mobile/shared focused baseline and head: 95 passed each; 38 excluded by name filter. No committed tests added. Mobile typecheck and formatting pass; scoped lint has only three existing warnings.

Not verified: iOS, Android, macOS, Windows, Electron desktop, full native screen rendering/gesture, or a real PR check transition through an authenticated host. A human should still open a PR with changing checks and pull on iOS/Android. No screenshot is claimed.
