Fix mobile browser frames after reconnect (STA-4128) - #16122
brennanb2025 wants to merge 4 commits into
Conversation
Co-authored-by: blade035 <blade035@hotmail.com>
📝 WalkthroughWalkthroughThe change adds the experimental Merge Risk: 🟡 Moderate · up to A reconnect can still display a stale or unready mobile browser frame if an older image callback arrives after a newer frame replaces it, undermining the freeze fix. The migration test also does not verify that binary frames reach the replacement session, so the PR is not merge-ready until the callback fencing and targeted test assertions are addressed. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Full details: Description checkExplanation The description is detailed and follows the required structure. It explains the user impact, implementation, rationale, linked issue, testing, limitations, and scope. It explicitly documents that visual proof and full local lint, test, and build runs are unavailable or deferred to CI. ✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
config/reliability-gates.jsonc (1)
3-3: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winBump the file-level
updatedAtto cover the newly added evidence date.The file's top-level
updatedAtis"2026-08-20"(Line 3), but the newmobile-browser.frame-continuitygate'sevidenceRunsentry is dated"2026-08-23"(Line 3251), three days later than the declared update timestamp. IfupdatedAtis meant to reflect the most recent change to this manifest, it is now stale relative to content this same change introduces.📅 Suggested fix
"schemaVersion": 1, - "updatedAt": "2026-08-20", + "updatedAt": "2026-08-23",Also applies to: 3251-3258
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 92d7099c-c131-462f-844a-5a477c55fdc7
📒 Files selected for processing (5)
config/reliability-gates.jsoncmobile/src/browser/MobileBrowserPane.tsxmobile/src/browser/mobile-browser-frameless-stream.test.tsxmobile/src/transport/rpc-client-terminal-reconnect.test.tsmobile/src/transport/stable-logical-rpc-client.test.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review.
|
From the #14115 author — your decoder-wedge diagnosis also explains why the subscription-restart approach appeared to work: remounting the pane's logical subscription at the reconnect boundary implicitly remounts the hidden |
…stener The shape-only assertion passed on a no-op callback, which is the dropped-frame bug this migration path exists to fix. Also pin what a superseded frame's late onLoad promotes.
# Conflicts: # mobile/src/browser/MobileBrowserPane.tsx
|
Thanks a lot for the co-author credit 🙏 Glad my earlier attempt (#14115) could inform the official fix — happy to help verify the reconnect behavior on the mobile browser build if useful, I hit this flow daily. |
|
Closing as superseded by #22392 (now on main), which fixes the same freeze at its root. The pane no longer re-points a still-decoding hidden layer, which is what lost the onLoad and sent every later frame into the dead slot. Each layer now tracks the source its Image holds, and a load only counts if it matches that source, so late callbacks can't flip the wrong frame. #22694 also restarts the stream on every return to the app. Thanks @BrennanKB5! Your analysis of the double-buffer freeze matched what we reproduced on an Android emulator. If you still see a frozen pane after a reconnect on current main, please reopen with the repro. |
Author: @BrennanKB5
ELI5
When mobile reconnects while the next browser screenshot is still loading offscreen, that hidden image can stop reporting that it loaded. Orca now replaces only that stuck hidden image and keeps the last good screenshot visible until a fresh frame is ready.
In plainer words: you have a web page open in a browser tab on Orca Mobile. Your phone drops the
connection for a moment — a lift, a cell handoff, switching between Wi-Fi and data — and comes back.
If the drop lands in the split second a new screenshot of the page is being decoded, that decode
never finishes and never reports back. Every screenshot after it goes into the same dead slot, so the
picture on your screen freezes on the last frame for the rest of that tab's life. The page is
still live on the other side; you just never see it change again, and the only way out is closing and
reopening the tab.
Now Orca notices that one stuck slot, replaces just it, and keeps the last good picture on screen
until a fresh one is ready — so there is no blank flash either.
User-facing before / after
When it bites
Orca Mobile users with a browser tab open, whenever the connection drops and returns — cellular
handoffs, Wi-Fi/data switches, backgrounding and reopening the app — and only when the drop happens
to land in the moment a screenshot is decoding.
What Changed
connectedboundary, remount only an unresolved hidden decoder and fence lateonLoad/onErrorcallbacks by decoder epoch.browser.screencastsubscription.mobile-browser.frame-continuityreliability gate.Why
Current main already replays retained subscriptions after a physical socket reconnect and attaches them to the replacement session during logical relay/direct migration. The remaining freeze is in the React Native double buffer: if the hidden
ImagemissesonLoad, every later frame continues updating that hidden layer while the last decoded layer remains visible forever.This supersedes #14115:
The implementation commit credits @blade035 as co-author for the community contribution that informed this fix.
Linked Issue
Fixes #14274
Linear: STA-4128
Visual Proof
Blocked. The iOS simulator loaded this worktree's mobile app, paired to the isolated development runtime, opened the target worktree, and opened a live browser tab; however, the unsigned local runtime exposes no Relay path and there is no safely scoped emulator control for a physical socket drop while preserving the mounted pane. No screenshot is attached because it would not prove the transition, and Android/live-network validation remains uncollected.
Testing
I manually tested these changes locally — partial iOS smoke only; the target transition is blocked as described above
Automated tests added/updated
Pre-fix acceptance: failed at the hidden native Image identity assertion.
Candidate acceptance: passed.
Candidate with only the reconnect repair disabled: failed again at the same identity assertion.
cd mobile && pnpm vitest run --config vitest.config.ts src/browser/mobile-browser-frameless-stream.test.tsx src/transport/rpc-client-terminal-reconnect.test.ts src/transport/stable-logical-rpc-client.test.ts— 25 passed.pnpm vitest run --config config/vitest.config.ts config/scripts/check-reliability-gates.test.mjs— 24 passed.pnpm typecheck— passed.node config/scripts/check-changed-code-quality.mjs— passed with 0 findings.node config/scripts/check-react-doctor-changed.mjs— passed with 0 issues.pnpm check:max-lines-ratchetandpnpm check:reliability-gates— passed.oxfmt check and
git diff --check— passed.AI Disclosure
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Checklist
pnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass — scoped lint/typecheck/tests pass; full test/build left to CI