Repository navigation
Conversation
|
Too many files changed for review (1881 files, 100 file limit). |
There was a problem hiding this comment.
ℹ️ No critical issues — the migration is well-scoped and heavily tested. Two defense-in-depth gaps in the Android shell versus iOS, and one unthrottled terminal flow-control path, are worth closing before the release-candidate cutover.
Reviewed changes
- Hybrid shell + package store (iOS/Android) — content-addressed, host-scoped package staging, verification, atomic activation, and last-known-good recovery with path/symlink/hard-link boundary checks on both platforms.
- Capability bridge — schema-checked, versioned operation/capability grants with request/response/concurrency/rate limits, replay guard, and user-gesture gating for privacy-sensitive native actions.
- Desktop package serving — manifest + bounded chunk RPCs that re-verify hashes, path bounds, and file identity before serving.
- Hosted workspace adapters — terminal, session, file, source-control, review, task, account, agent-history, browser, and native-chat operations routed through the opaque workspace/repo/tab authorities.
ℹ️ Android WebView isolation is one layer thinner than iOS
The iOS shell neutralizes fetch, XMLHttpRequest, and WebSocket with a user script injected into all frames, installs a WKContentRuleList blocking http(s)/ws(s), and re-checks the final URL/MIME in decidePolicyFor navigationResponse. Android injects no equivalent script and has no response-side navigation gate, so its network boundary rests on two mechanisms: blockNetworkLoads = true plus the connect-src 'none' CSP header. On Android WebView, shouldInterceptRequest is not invoked for WebSocket handshakes, and blockNetworkLoads coverage of WebSocket is inconsistent across System WebView versions — so WebSocket egress is gated by CSP alone, a single layer that iOS backs with two more. The secondary gap is that Android's per-session origin is derived from sessionId.take(32) (see inline comment), which weakens the documented "storage cannot cross hosts" invariant. None of these is a demonstrated live exploit — CSP connect-src 'none' is present and applies to WebSocket — but this is exactly the boundary the release notes advertise as strict, and it is one regression or WebView change away from leaking on one platform while staying closed on the other.
Technical details
# Android WebView network-isolation parity with iOS
## Affected sites
- mobile/packages/expo-mobile-web-shell/android/src/main/java/expo/modules/mobilewebshell/MobileWebShellView.kt:76-108 — `init` sets `blockNetworkLoads = true` and CSP but injects no JS network-API blocker; compare iOS MobileWebShellView.swift:74-138 (blocker) and 386-392 (injection into all frames).
- mobile/packages/expo-mobile-web-shell/android/src/main/java/expo/modules/mobilewebshell/MobileWebShellView.kt:217-231 — no `navigationResponse`-side URL/MIME gate; compare iOS MobileWebShellView.swift:548-566.
## Required outcome
- Android should independently neutralize `fetch`/`XMLHttpRequest`/`WebSocket`/`serviceWorker` (a document-start user script, as iOS does) so WebSocket egress does not rest on CSP alone.
- Android should gate the final navigation response (URL + MIME) the way iOS does.
## Open questions for the human (optional)
- Is the `blockNetworkLoads` + CSP-only WebSocket posture on Android an accepted release risk, given the app-review claim that hosted content has no network path?ℹ️ Terminal snapshot requests are unthrottled under withheld acks
queueOutput triggers a full SnapshotRequest every time pendingOutputBytes exceeds the 256 KB outstanding window, and that path first supersedes (acking all outstanding host bytes) so the host immediately resumes sending. A page that withholds acks therefore turns ~5 bytes of injected input (e.g. yes\n) into a repeating cycle: buffer 256 KB → drop → request a full 2 MB snapshot → host freed → send more. Broker memory stays bounded, so this is a host-side CPU/bandwidth amplification rather than a memory-exhaustion bug, and it requires a hostile page — but the whole bridge exists to contain a hostile page, so the internal snapshot path should be throttled or deduplicated the same way the page-facing resync operation is rate-limited.
Technical details
# Throttle broker-initiated terminal snapshot requests
## Affected sites
- mobile/src/mobile-web/mobile-web-terminal-flow-control.ts:177-181 — flow-overflow supersedes + requests snapshot on every overflow.
- mobile/src/mobile-web/mobile-web-terminal-streams.ts:257-268 — requestSnapshot has no throttle or dedup.
## Required outcome
- Bound or coalesce broker-initiated snapshot requests per record (e.g. a minimum interval or a single in-flight snapshot), so withheld acks cannot amplify small inputs into a continuous snapshot/redelivery loop.
## Open questions for the human (optional)
- Does the host/relay side already throttle SnapshotRequest? If so this may be acceptable as-is; please confirm.DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found in the incremental changes.
Reviewed changes (delta since the prior pullfrog review at aa75216)
- Native-chat RPC message sanitizer — new
native-chat-rpc-message-sanitizer.tswindows transcripts to a recent slice and truncates oversized text/tool-result/tool-call blocks for mobile clients, wired throughnativeChat.readSessionandnativeChat.subscribe. - Transcript watcher refactor —
transcript-watch-engine.tssplit into extracted helpers (state/tail reads, runtime activation/disposal, reconciler, scheduler) to stay under the line budget. - Merged
main—.gitattributespatch rules, workflow/lockfile dependency bumps, browser screencast lifecycle test, tray-icon creation fallback, and SSH chunked-download fallback.
The sanitizer is correctly scoped to clientKind === 'mobile' (except image-ref, which is stripped on every transport), clamps limit to the documented window, and bounds tool-input recursion by depth, node count, item count, and a shared character budget. The watcher extraction is mechanical and covered by existing tests. None of the files touched by the prior review's findings (MobileWebShellView.kt, MobileWebOrigin.kt, mobile-web-terminal-flow-control.ts) changed, so those findings still stand.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
The Android staging-path hardening runs after mkdirs(), so a symlinked staging directory is still followed before it is rejected, and the new test asserting the target stays empty will fail.
Reviewed changes (delta since the prior pullfrog review)
- Android package-store boundary hardening — added
isMobileWebUnlinkedPath/requireMobileWebWritePathsymlink and hard-link checks to staging, asset write, and generation commit, with new boundary tests. - Android WebView lifecycle refactor — extracted
createWebView, tracked the debug-isolationScriptHandlerper session, guarded bridge-listener attach/detach, and added render-process-gone recovery plusonAttachedToWindowreattach/reload. - iOS load-state and RNG hardening — added
loadingSessionIdgating on load-state callbacks and maderandomIdentifierthrow onSecRandomCopyBytesfailure. - Capability gating and transport extraction —
getStatusnow advertises the mobile-web package capabilities only when the manifest and every asset verify on disk; terminal binary send extracted with close-on-send-failure; the package-capability hook now keys re-probing on connection id.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found in the new commits.
Reviewed changes (delta since b551c47)
- WebView reload guard — added a
documentLoadedflag soonAttachedToWindowno longer reloads an already-loaded document on view reattach; it now reloads only when the document is incomplete or the current URL is no longer the allowed document URL. - Host-screen type narrowing — replaced the
as unknown as HostScreenControllercast with a properHostScreenControllertype that narrowsrouterand drops internal refs from the exposedstate. - CDP fake relocation — moved
hosted-webview-cdp-test-fakes.tsto a.test.tssuite and updated the importing test.
The documentLoaded flag is reset on session change, deactivate, main-frame load error, and render-process-gone, and set on onPageFinished only for an allowed document URL, so the reattach decision tracks the actual document state. The type change is mechanical and the test-file move is inert.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found in the new commits.
Reviewed changes (delta since 0ae1dd2)
- Gzip chunk decode bound —
decodeGzipMobileWebPackageChunknow inflates into a fixedexpectedLength + 1buffer and rejects anything whose decoded length differs from the advertisedsourceByteLength, instead of lettinggunzipSyncallocate from the gzip ISIZE trailer. - Hosted resume and reconnect —
SessionScreenaccepts optionalreconnect/reconnectAttempts/lastConnectedAtprops, the hosted route wiresshell.client.navigationReconnect()as the retry action, and the hosted workspace-list route no longer overwrites the resume route viarememberRoute.
The gzip change is correct against fflate's implementation: with an out buffer, inflt disables its growth path (resize is false), out-of-bounds writes are dropped, and subarray clamps to the buffer, so over-expansion yields byteLength === expectedLength + 1 and is rejected without an attacker-controlled allocation. The reconnect prop plumbing is mechanical and covered by the updated source-assertion tests.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
585b290 to
1f4401d
Compare
There was a problem hiding this comment.
Important
The credential-recovery routing this commit restores is unreachable in production — the loader passed to the resolver never supplies credentialStatus — and the new optional notice field on a .strict() route schema is not wire-safe for older desktop pages. Both are detailed inline.
Reviewed changes
- Notification credential recovery —
credentialStatusByHostIdthreading,credentialRecoveryon navigation targets, andnotificationCredentialRecoveryRoutehandling in_layout.tsx. worktree-missingnotice plumbing — carried through workspace-list intents, home navigation, the retired-route redirect, the hosted route restorer, and the bridge route schema.- Session fencing on host switch — a new
sessionHostIdon the package-session state and three guard sites inhybrid.tsxprevent the previous host's session from binding to a new host's broker or WebView. - Desktop PR-check scope —
mobile/app/h/now triggers Linux/Windows package checks, plus plan-doc follow-up notes.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at 1f4401d)
- Merged
origin/main— 28 upstream commits (jira user-field negotiation, agent-hooks/reminted pane keys, orchestration request-show, browser annotation tray, dashboard bucket counts, terminal fast-paths, CI dependency speedups, and mobile task/agent refactors) brought onto the branch. - Resolved the single merge conflict in
src/main/runtime/rpc/dispatcher.ts— the branch's earliermeta()inlining was superseded bymain'sRpcStreamingDispatcherextraction; the resolution keepsmain's structure (private meta()restored,dispatch()callingthis.meta(),dispatchStreaming()delegating toRpcStreamingDispatcher).
I compared the resolved dispatcher.ts against main's version (251c9a0f7d): it is byte-for-byte main, which is the correct resolution — the branch's only dispatcher change was the cosmetic meta() inlining, and main's RpcStreamingDispatcher preserves the binary-stream fields (pairedDeviceId, pairing, sendBinary, registerBinaryStreamHandler, registerBinaryMessageHandler) in both the non-streaming and streaming handler contexts. No PR-authored mobile-web code is touched by this delta; the open Pullfrog threads (notification-routing.ts, bridge-contract.ts, MobileWebPackageStore.kt, MobileWebOrigin.kt) are unchanged by it and still stand.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at 66a007b)
- Merged
origin/main— brought in two upstream commits:91cc834fix(remote): preserve standing host reconnect intentandb892f05cperf(packaging): ship one native runtime per target. - Resolved the single merge conflict in
config/electron-builder.config.cjs— the branch's mobile-web additions (mobileWebExtraResource, theverifyPackagedMobileWebimport andafterPackcall) are preserved alongsidemain's per-target speech packaging (macSpeechNativeResource/linuxSpeechNativeResource/winSpeechNativeResourceextraResources, the!node_modules/sherpa-onnx*filesexclusion, and the removal ofsherpa-onnxfromasarUnpackandserve-simfrom macextraResources).
I compared the resolved file against both merge parents: every PR-authored line is intact and every main-side speech-packaging change is applied, so the resolution is the correct union. The electron-builder config and speech tests pass (38/38). No PR-authored mobile-web code changed in this delta; the open IMPORTANT findings (staging-dir symlink check ordering, credential-recovery loader, notice strict-schema) and the three informational findings are anchored in files this delta does not touch, so they still stand.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at 57f7371)
- Merged
origin/main— one upstream commit,3d65466perf(agent-hooks): coalesce Codex transcript poll timers, brought onto the branch via a clean merge (no conflicts). - Extracted the Codex subagent poll timer into a shared scheduler —
src/shared/codex-subagent-poll-scheduler.tsnow owns the coalesced deadline logic;src/main/agent-hooks/server.tsandsrc/relay/agent-hook-result-retry-scheduler.tsboth delegate to it.
This is a clean fast-forward of upstream main; no PR-authored mobile-web code is touched by the merge, and the scheduler refactor is base-branch content already reviewed on its own PR. The open IMPORTANT findings (staging-dir symlink check ordering, credential-recovery loader, notice strict-schema) and the three informational findings are anchored in files this delta does not touch, so they still stand.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at f068ec8)
- Merged
origin/main— 57 upstream commits brought onto the branch: docs-site publishing, WSL/Codex account drain, daemon boot/PTY recovery budgets, SSH reconnect and pending-kill replay, a broad performance pass (mobile session/home/terminal selectors, renderer selectors, relay PTY source-credit accounting), and packaging source-map pruning. - Terminal theme comparison now structural —
mobileSessionTabEqual(terminal case) andterminalRecordsEqualnow delegate tomobileTerminalThemesEqual, adoptingmain'sperf(mobile): compare terminal themes without serialization(#17455) in place of the branch's earlierJSON.stringifyequality.
The merge is clean — no conflict markers, no PR-authored mobile-web/hybrid-shell files touched, and the comparator swap is equivalent-or-better (key-order independent, null/undefined-safe, covered by the vi-fenced no-serialization test). The open Pullfrog findings (beginStage symlink ordering, credential-recovery loader, notice strict-schema) are anchored in files this merge does not touch and so remain unaddressed.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at ff6485c)
- Merged
origin/main— six upstream commits:63d60e0targeted SSH session-tab refresh,db84894paired-terminal focus isolation,aabcc573remote-control outage publication,68de1beGitLab lint gates, and the5fe3772/894ed75native-chat structured-result preserve-then-revert pair (net no-op). - Resolved
src/main/runtime/orca-runtime.ts— the merge adoptedmain'srefreshMobileSessionPtyInventoryfast path from#17486(targeted polls short-circuit the aggregate census, resolve the owning execution host, and returnnullfor runtime-owned workspaces). The branch's copy of this function was the pre-#17486base, so no PR-authored logic was dropped; every other PR addition in the file (mobileWebPackageIsAvailable,notifySshRelayUnavailable,resolveNativeChatTranscriptBinding,submitHostedReview, etc.) remains intact. terminal.createcaller fencing — the legacy RPC now forcesfocus/activateto false andfocusedpresentation tobackgroundfor any paired viewer (clientKind !== undefined), leaving caller-local selection to the session-tab RPC flow.
The merge is clean — no conflict markers, and none of the 47 changed files lives under mobile/ or in the hybrid-shell/package-store surface. The open IMPORTANT findings (beginStage symlink ordering, credential-recovery loader, notice strict-schema) and the three informational findings are anchored in files this merge does not touch, so they remain unaddressed.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found in the incremental changes.
Reviewed changes (delta since the prior pullfrog review at 45d2d4b)
- Remote hook-installer seeding fixture —
docker-ssh-native-chat-fixture.tsgainedseedDockerSshNativeChatAgent(a/usr/local/bin/claudeexit 0shim plus/root/.claude/settings.json) andwaitForDockerSshNativeChatAgentHook, which polls until the remote managed-hook installer lays down an executable/root/.orca/agent-hooks/claude-hook.sh. - Spec wiring —
ssh-docker-native-chat-transcript.spec.tsseeds the fake agent before the SSH connection is established and waits for the managed hook after the terminal manager is ready, so the test now exercises SSH-startup hook installation instead of only invoking the hook script directly with a synthetic payload.
The seed lines up with the installer contract — claude's detectCmd is claude (tui-agent-config.ts), detection resolves from PATH presence, and the shared hook installs to $HOME/.orca/agent-hooks/claude-hook.sh with mode 755 — and the synchronous Atomics.wait polling cadence matches the existing waitForSsh and blockDockerSshRelayTargetTcpForwarding helpers in this fixture. The MOBILE_WEB_PRODUCTION_GRANTS import split is mechanical and correct (the symbol lives in mobile-web-production-grants). The delta is test-only, gated behind ORCA_E2E_SSH_DOCKER=1, and does not touch any file anchored by the still-open prior findings.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
The origin/main merge left a duplicate note field in two type literals in mobile/src/tasks/source-workspace-create.ts — both CreateWorkspaceFromComposerArgs and the createWorkItemWorkspace parameter type now declare note: string | undefined twice, so pnpm --dir mobile typecheck fails with Duplicate identifier 'note' (TS2300). Everything else in this delta is a clean upstream merge.
Reviewed changes
- Merged
origin/main— 34 upstream commits (terminal/PTY, native-chat/Codex structured sessions, browser viewport, git worktree/ref probes, process-table, docs, and CI) brought onto the branch in one merge commit. - Mobile worktree display-name provenance —
mobile/src/tasks/*adoptedmain'sdisplayNameKind(user/generated) stamping and host-returneddisplayNamehandling from8f15f217a2(Preserve user-set workspace names across branch changes), wired throughuse-mobile-tasks-workspace-create-actions.tsxandworkspace-create-params.ts.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes
- Resolved the merge type conflict — removed the duplicate
note: string | undefinedfrom bothCreateWorkspaceFromComposerArgsand thecreateWorkItemWorkspaceparameter type inmobile/src/tasks/source-workspace-create.ts, fixing the TS2300 duplicate-identifier failure flagged on57ef1fe.
Approval is withheld only because earlier Pullfrog findings (the Android beginStage symlink ordering, the credential-recovery loader, and the notice strict-schema concern) remain open on files this delta does not touch.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — the rollout-default flip is clean. One test lost its teeth and the PR summary is now stale.
Reviewed changes (delta since the prior pullfrog review at a9d48c6)
- Introduced a build-time architecture selector —
mobileNativeBaselineModenow resolves native for release builds and hybrid for development by default, overridable throughEXPO_PUBLIC_ORCA_MOBILE_ARCHITECTURE=native|hybrid; the E2E baseline flag stays development-only. - Re-routed native-mode navigation —
mobileHomeDestination,mobileHostWorkspaceEntry,navigateFromMobileHome, andmobileOnboardingDestinationnow threadnativeBaselineEnabled, so native builds navigate to/h/...routes instead of/hybrid. - Added a native-mode
/hybridguard —_layout.tsxredirects a native build that lands on/hybridback to the native host entry, and notification taps now push throughmobileHomeDestination. - Updated the rollout docs — the architecture doc, migration plan, and
mobile/README.mdnow describe native-by-default release builds with hybrid as an explicit opt-in candidate.
ℹ️ PR summary still claims the hybrid route is the production default
The hand-written ## Summary in the PR body says "Make the hybrid workspace route the production default", but this commit makes ordinary release builds default to native and turns hybrid into an opt-in build mode. The architecture docs were updated to match, but the PR description was not. Worth reconciling so the advertised rollout story agrees with the code.
Technical details
# Update PR description to match the native-default rollout
## Affected sites
- PR #13386 body, "Summary" section — still states hybrid is the production default.
## Required outcome
- Reword the summary bullet to state that release builds default to native and hybrid is opt-in via `EXPO_PUBLIC_ORCA_MOBILE_ARCHITECTURE=hybrid`, consistent with `docs/reference/mobile-hybrid-webview-architecture.md`.ℹ️ Nitpicks
MOBILE_ARCHITECTURE(mobile/src/mobile-web/mobile-native-baseline-mode.ts) is exported but has no consumers — either wire it into a caller or drop it until it is needed.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at cffec8a)
- Restored the branch's native session surface —
e9f7d494re-inlinedmobile/app/h/[hostId]/session/[worktreeId].tsx(back to its ~4,984-line monolith, byte-identical tocffec8a) and deleted theuse-mobile-session-*hooks andMobileSession*components thatorigin/mainhad introduced, consolidatingterminal-webview-htmlfragments back into a single file (also byte-identical). - Passed worktree-prefetch git options through
orca-runtime.ts—bdf3e57d22now forwardsgetWorktreeCreatePrefetchGitOptions(store, repo)intoprefetchWorktreeCreateBase, matching the existing prefetch handler. - Merged
origin/maintwice —a49d3d4f25and39530bba3dbrought in the renderer TaskPage/Terminal/StatusBar splits and other upstream changes; the merge conflict resolutions preserved the branch's session surface and itsorca-runtime.tsadditions (mobileWebPackageIsAvailable,notifySshRelayUnavailable,resolveNativeChatTranscriptBinding,submitHostedReviewall remain intact).
ℹ️ Reverting main's session-surface split also reverts its tool-output guard
The merge resolution in e9f7d494 chooses the branch's own session surface over origin/main's split, and in doing so it discards main's mobileNativeChatStreamPreview guard and its regression test (PR #17782, "stop rendering tool output as the agent's streaming reply"). On main that guard drops lastAssistantMessageIsToolOutput statuses so a tool's stdout/error published as lastAssistantMessage does not render as an un-collapsed assistant streaming bubble; the branch's use-mobile-native-chat-controller.ts uses the unguarded nativeChatAgentWorking ? nativeChatStatus?.lastAssistantMessage : undefined, and the branch's status contract (MobileWebNativeChatAgentStatusSchema) does not carry lastAssistantMessageIsToolOutput at all. This was already the case at the previous review head, so it is not a new regression — but it means merging this PR will delete main's #17782 fix and its test, so please confirm that is the intended long-term shape for this surface.
Technical details
# Preserve or explicitly waive #17782 on the branch's session surface
## Affected sites
- mobile/src/session/use-mobile-native-chat-controller.ts:161 — unguarded streaming-preview ternary.
- mobile/src/session/mobile-native-chat-streaming-gate.ts — `mobileNativeChatStreamPreview` deleted, with its regression test.
- src/shared/mobile-web/native-chat-operation-contract.ts:29-41 — `MobileWebNativeChatAgentStatusSchema` lacks `lastAssistantMessageIsToolOutput`.
## Required outcome
- Confirm the branch deliberately supersedes main's session surface; if so, port the tool-output suppression (and its test) into the branch's controller, and plumb `lastAssistantMessageIsToolOutput` through the mobile-web status contract.
## Open questions for the human (optional)
- Will the branch re-adopt main's session-surface split before merge, or is the inline monolith the permanent shape?DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta — a clean
origin/mainmerge with a deliberate, behavior-preserving conflict resolution. One divergence observation below.
Reviewed changes
- Merged
origin/main(40 commits) — the512ec1a0datip down to41015f9393: therefactor(main): split backend services and startupfile-split pass (plus filesystem/preload/renderer/keybindings/agent-hooks/browser-manager/rate-limits/updater splits), WSL/git path fixes, a browser-cookie keychainrunProcessfix, and an observer stale-runtime-capture fix. - Resolved the single conflict in
src/main/index.tsin the branch's favor — the branch kept its 3837-line monolithic entry point (with/* eslint-disable max-lines */), byte-identical to39530bba3d, rejecting main's5b4e7edb50split ofindex.tsinto a 65-line shim plusstartup/main-process-*.tsmodules.
The merge is clean: pnpm tc:node passes, no conflict markers remain, no duplicate imports/declarations in index.ts, and none of the PR-authored surfaces (mobile/, src/shared/mobile-web/, the mobile-web-package-* RPC files, or the mobile-web additions in orca-runtime.ts) are touched by the delta. The observer semantics are unaffected: the monolith reads the module-level runtime lazily (runtime?.touchMobileSessionTabsForWorktree, index.ts:2644-2685), which is already equivalent to main's post-fix state.runtime?., so de84a9f999 has no analog gap here.
ℹ️ Branch keeps the monolithic entry point and orphanes main's startup split
The index.ts conflict resolution keeps the 3837-line monolith while main's 5b4e7edb50 split has already landed, leaving ~25 newly-split modules (src/main/startup/main-process-{preflight,ipc-bootstrap,ready*,quit}.ts, main-window-{actions,controller}.ts, main-process-state.ts, main-process-observers.ts, synthetic-title-runtime.ts, gpu-lifecycle.ts, and more) with zero importers — dead code on the branch. This is the same divergence the prior pullfrog reviews flagged for the mobile session surface (the monolith as intended long-term shape); this window extends it to the main-process entry point. It is not a bug today — typecheck passes and the monolith is behaviorally equivalent — but main keeps fixing the split modules the branch neither imports nor inherits (de84a9f999 is the current example), so the two shapes drift further with each merge. Worth the author confirming that the monolith is the intended permanent shape here too, since main is unlikely to stop splitting this file.
Technical details
# Confirm the monolith is intentional for the main-process entry point
## Affected sites
- src/main/index.ts:1 — 3837-line monolith with a max-lines disable; main's split shim is rejected.
- src/main/startup/main-process-*.ts, main-window-*.ts, synthetic-title-runtime.ts, gpu-lifecycle.ts — orphaned (0 importers).
## Required outcome
- Either adopt main's `index.ts` split (dropping the monolith) or explicitly acknowledge the monolith as the permanent shape so the orphaned modules can be removed rather than left as dead code on the branch.
## Open questions for the human (optional)
- Will the branch re-adopt main's startup split before merge, or is the inline monolith permanent for the entry point as well?DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at e74ac4505)
- Android document-start network API blocker —
MobileWebNetworkApiBlocker.ktneutralizesfetch/XMLHttpRequest/WebSocket/serviceWorkervia a document-start script, wired intoMobileWebShellView.kt(session set, render-process-gone recovery), closing the Android/iOS network-isolation gap from the first review. beginStagesymlink check reordered —MobileWebPackageStore.ktnow validates thehostRoot/stagingRootunlinked-path invariant beforestageRoot.mkdirs(), resolving the prior IMPORTANT finding (thread retired this run).- Table-driven capability dispatch — broker requests resolve grants through a single operation-keyed index and dispatch through capability-keyed arm tables (
mobile-web-capability-execution-arms.ts,mobile-web-production-grant-table.ts), with census tests pinning all 226 operations to an arm. - User-gesture gating consolidated — the gesture requirement is hoisted into one module (
mobile-web-user-gesture-requirement.ts); nativealertnow requires a recent (witnessed, not consumed) gesture. - Broker teardown on view restart —
use-mobile-web-capability-broker.tsretires the prior page's broker before a fresh document posts. - Re-adopted
main's splits — the session route and RPC client split are restored frommain, and the Tasks route is restored as a hook composition, reversing the branch-divergence shape flagged in reviews 14–16. - Merged
origin/main— AppImage CLI rework, renderer task-page split, preload bridge renames, and agent-status store slices (base-branch content).
Approval is withheld only because earlier Pullfrog findings remain open on files this delta does not touch: the credential-recovery loader (review 5063035925) and the notice .strict() wire-safety concern (review 5063035925), plus the MobileWebOrigin.kt take(32) and terminal snapshot-amplification notes.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes
- Stripped PR #10179 residue from the branch — removed the bounded-resource protections that a separate PR introduced (mobile inbound/outbound memory budgets, frame queues, JSON-text admission, connection-log byte limits, transcript message retention/record buffers, SSH
readDirBounded/markdown-listing, relay directory/markdown readers,RuntimeSshRelayRecoveryGenerations,agentSubagentsEqual) and restored the affected files tomain's shape. Verified againstorigin/main: none of these files or APIs exist onmain, so this is drift cleanup, not a regression — the branch's diff is now ~86 files smaller. - Dropped the bounded PR file-content cache — reverted the mobile Tasks PR-diff cache to the monolith's plain
useStatepair and restored the cache-hit/loading/try-catch load flow, matchingmain's Tasks shape. - Removed the inert iOS app-bound navigation flag —
limitsNavigationsToAppBoundDomainswas a no-op because the app declares noWKAppBoundDomainskey; the navigation delegate remains the enforcement fence (covered bymobile-web-native-transport-source.test.tsand the new app-bound probe). - Added iOS-only E2E probes —
hosted-ios-user-gesture-window-probe.mjsandhosted-ios-app-bound-navigation-probe.mjsexercise the gesture-window gating and the delegate's external-navigation refusal; both are macOS-simulator-gated and inert in CI.
Approval remains withheld only because the two open IMPORTANT findings are still unaddressed on files this delta does not touch: the credential-recovery dead-code loader (review 5063035925) and the notice .strict() wire-safety concern (review 5063035925).
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior review at 7520516)
- Removed the recent-user-gesture window —
d156826131deletes the entire user-gesture gate added in themr-p1-securitygroup (use-mobile-web-user-gesture-authority.ts,mobile-web-user-gesture-requirement.ts,mobile-web-user-gesture.ts, the census/operations tests, and thehosted-ios-user-gesture-window-probe.mjs), replacing it withuse-mobile-web-app-foreground-authority.ts, which only forwardsAppStateforeground/background transitions tobroker.updateAppForegroundState(cancelling speech on background). The rationale is documented in the architecture doc and plan index: a scroll armed the window, so it gated nothing on the first-party page, and peer hybrid frameworks do not gate bridge calls on gestures. - Merged
origin/main— 12 upstream commits (worktreebase-ref/divergence probes, daemon adoption telemetry, prepared-checkout hit-rate fixes, stale-cleanup signals, PTY owner-unverifiable pane preservation, Dependabot bumps) plus regenerated lockfiles.
The gesture-window removal is a defense-in-depth simplification rather than a regression: the gate was already bypassable (any user scroll armed it), and the substantive protections — the production grant table, openExternal's scheme allowlist, the OS permission prompts for clipboard/picker/mic/camera, and the navigation-delegate/network-blocker fence — all remain in place and covered by tests. No dangling references to the deleted modules remain (verified). The two open IMPORTANT findings (credential-recovery loader, notice .strict() wire-safety) are in files this delta does not touch.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at 3f26541)
- Retired the resolved-worktree snapshot on repo-catalog changes —
notifyReposChangedForRemoteClients()now callsinvalidateResolvedWorktreeCache()before emittingreposChanged, so a remote repo added while the 1s-TTL snapshot is warm is visible to the next worktree-scoped resolution instead of staying invisible for the whole TTL. - Added a regression test — warms the snapshot, registers a late SSH repo, then asserts
listManagedWorktrees()includes the newly-added host's worktree afternotifyReposChangedForRemoteClients().
The fix lands at the correct funnel: repos:addRemote → notifyReposChanged(mainWindow) → setRepoRemoteClientNotifier(runtime) → notifyReposChangedForRemoteClients(), and invalidateResolvedWorktreeCache() (resolvedWorktrees.invalidateResolved()) is exactly the sibling notifyWorktreesChangedForRemoteClients already performs. The test fails without the fix (the warm snapshot is still inside its TTL and omits /remote/worktree), so it is real coverage, not theatre.
Approval remains withheld only because earlier Pullfrog findings are still open on files this delta does not touch: the credential-recovery dead-code loader and the notice .strict() wire-safety concern (both from review 5063035925), plus the MobileWebOrigin.kt take(32) and terminal snapshot-amplification notes.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at 1bcd613)
- Retired the synthetic-resume evidence gate — the Android and iOS agent-history journeys dropped the "reject untrusted synthetic resume" step and its
synthetic: 'gesture rejected'assertion, following the removal of the user-gesture window in the prior delta (the tap now goes straight to the native-touch resume,native: 'queued'). - Fixed the simulator long-press —
longPressHostedIosPointnow holds the touch across twogesturecommands (begin+move, thenmove+endafter a 900 ms hold) instead of batching 60 move frames into a single command that lands as a tap, with afinallythat always lifts the touch and an optionalsettledLabelwait. - Added native-journey A/B capture tooling — new
run-hosted-ios-native-journey-capture.mjs,hosted-ios-native-journey-labels.mjs, anddiff-hosted-ios-native-journey-capture.mjswalk six native journeys, diffing settled accessibility-label captures between two client builds (with volatile status-bar labels filtered out). - Re-read the host-origin landmark after the route animates in —
captureNativeRoutewaits, delays, then re-reads the title so the screenshot's landmark position matches the settled screen. - Added an
ORCA_E2E_MOBILE_METRO_DIRoverride —start-emulator.mjsand the launcher can now serve Metro from a second checkout for old-client A/B runs.
The delta is test/E2E tooling only (15 files, all under mobile/scripts/ and their .test.ts counterparts); no production surface changed. Approval remains withheld only because earlier Pullfrog findings are still open on files this delta does not touch: the credential-recovery dead-code loader and the notice .strict() wire-safety concern (both from review 5063035925), plus the MobileWebOrigin.kt take(32) and terminal snapshot-amplification notes.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at d3f97a4f)
- Session-origin
reviewOpenparity assertion — the hosted iOS Source Control journey now resolves the session-origin changed-file tap into either the Session diff route or the headless host outcome (renderer_unavailable), and asserts whichever it is matches what the native app did on the same host instead of hardcoding a failure. - Native baseline capture —
captureNativeSourceControlReviewBaselinesnow recordssessionOriginReviewOpen(captureNativeSessionOriginReviewOpen→readNativeReviewOpenOutcome→returnNativeFromSessionOriginSourceControl), so the hosted journey has a same-host native answer to compare against. - Reverted the route-settle landmark re-read — dropped the double
waitForHostedIosAccessibilityControl+ 500 ms settle delay incaptureNativeRoute, back to a single read before the screenshot. - Test updates — the two unit suites add real coverage: one asserts the exact
reviewOpenshape (headless + native + bridge error + visible message), the other asserts the divergence path rejects.
All five files are E2E tooling under mobile/scripts/ plus their .test.ts counterparts; no production surface changed. The divergence test exercises the parity check (it would pass with the assertion removed), so it is real coverage, not theatre. None of the still-open findings — the credential-recovery loader and the notice .strict() wire-safety concern (both 5063035925), plus MobileWebOrigin.kt take(32) and the terminal snapshot-amplification note — are touched by this delta, so approval remains withheld for them.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at fc4266d)
- SSH loss-of-contact keeps the native-chat grant —
resolveFreshMobileWebNativeChatBindingnow revokes only on a vanished tab, a different terminal handle, or a tab rebound to a different provider session; a terminal whoseagentStatuswas stripped by an unreachable host keeps its grant, so the read surfaces a host failure instead of a false "session gone". - Last-known session identity on the runtime —
resolveNativeChatTranscriptBindingremembers each handle's last live{ agent, providerSession }inlastKnownNativeChatSessions, falls back to it while the terminal still exists, and deletes it onceresolveTerminalContextreturns null (terminal genuinely gone), so the remembered identity cannot outlive the terminal. - Screenshot parity diagnostics —
hosted-ios-screenshot-parity.mjsnow reports bothnativeandhostedlandmarks alongside the delta when the landmark budget fails. - Regression tests — a client-side operations test pins
host_error+ retained grant, and a runtime spec pins the remembered identity surviving loss of contact and being cleared when the terminal disappears.
The fix matches ssh-execution-boundary.md's live/unverifiable/exited verdict: loss of contact no longer reads as session death on either side of the bridge. Both regression tests are red-then-green (the client test would reject not_found on the old code, and the runtime spec would return null after stripping), so they are real coverage. Approval remains withheld only because the still-open findings — the credential-recovery loader and the notice .strict() wire-safety concern (both 5063035925), plus MobileWebOrigin.kt take(32) and the terminal snapshot-amplification note — are in files this delta does not touch.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes (delta since the prior pullfrog review at 96bc6da)
- Merged
origin/main— ~155 upstream commits brought onto the branch: SSH lease/PTY recovery and orphan reattach, execution-host dispatch routing for git/filesystem providers, gh/glab process-tree reaping at deadline, relay streaming/flow-control, a broad renderer performance pass, the reviewed Linux packaging stack (build:linuxnow runsbuild-linux-local.mjs), native-chat structured Codex launches, the new agent dashboard, and more. - Resolved six merge conflicts — in
config/scripts/electron-builder-config.test.mjs,config/scripts/pr-code-change-scope.mjs,config/scripts/pr-code-change-scope.test.mjs,config/scripts/run-ssh-docker-e2e.mjs,src/main/git/command-runner/gh-exec-file.ts, andsrc/main/providers/ssh-filesystem-provider.ts. Each resolution preserves the branch's mobile-web content (DESKTOP_RELEVANT_MOBILE_PREFIXESand its scope test, thehosted-mobile-webview-sshcomment,stdinin the gh exec, theisMethodNotFoundErrorimport) while adoptingmain's changes — verified. - Carried the mobile-web afterPack fixtures into
main's relocated spec —9afe68a650movedcreateMobileWebResourceFixture/createPackagedCliResourceFixtureout ofelectron-builder-config.test.mjsand intoelectron-builder-runtime-resources.test.mjs, replacing the relocated spec's inline CLI fixture with the shared helpers soverifyPackagedMobileWebstays exercised during the afterPack test.
The electron-builder.config.cjs afterPack now runs both stampPackagedCliVersion (main) and verifyPackagedMobileWeb (branch), and commonExtraResources keeps mobileWebExtraResource — the correct union. I ran electron-builder-runtime-resources.test.mjs, electron-builder-config.test.mjs, and pr-code-change-scope.test.mjs: 70 tests pass. Approval remains withheld only for the still-open findings from review 5063035925 (credential-recovery dead-code loader, notice .strict() wire-safety) plus the MobileWebOrigin.kt take(32) and terminal snapshot-amplification notes — none of which this delta touches.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes
- Mirrored
main's host-label state into the hybrid host screen hooks —useHybridHostScreenStatenow carriesrepoHostIdByRepoId/hostLabelById/hostPlatform, anduseHybridHostScreenControllerresets them on host switch and wraps itsdisplayWorktreesmemo inapplyWorktreeHostContextLabels, matching the native controller so the sharedHostScreenViewtypechecks.
The three maps stay empty in the hybrid (use-hybrid-host-repo-metadata never populates them — the hosted bridge publishes no repo→host or host labels yet), so applyWorktreeHostContextLabels is a no-op for the single-host hosted list and rows keep today's presentation. That is a documented gap, not a regression.
Approval remains withheld only because earlier Pullfrog findings are still open on files this delta does not touch: the credential-recovery dead-code loader and the notice .strict() wire-safety concern (both from review 5063035925), plus the MobileWebOrigin.kt take(32) and terminal snapshot-amplification notes.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta.
Reviewed changes
- Settled the post-merge CI reds — the two hosted-mobile-webview SSH e2e runners now resolve pnpm via
resolvePnpmCliInvocation()(instead of naming the Windowspnpm.cmdshim) and threadshellthroughspawnSync; the gh stdin contract moved fromrunner-command-exec.test.ts(whoseexecFilemock is no longer wired) intogh-exec-file-deadline.test.ts(spawn mock); and theorca-runtime-mobile-file-path-search.test.tsfixture gainedexecutionHostId: 'local'.
The change is test/CI-script-only with no production surface. I verified pnpm-cli-invocation.mjs exports the { command, prefixArgs, shell } shape both call sites consume, and the three touched test files pass (40 tests).
Approval remains withheld only because earlier Pullfrog findings are still open on files this delta does not touch: the credential-recovery dead-code loader and the notice .strict() wire-safety concern (both from review 5063035925), plus the MobileWebOrigin.kt take(32) and terminal snapshot-amplification notes.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
The desktop's conflict arrived as a retryable host_error: the shell mapped every runtime_error the same way, and the hosted source-control adapter discarded every error's code and message. The shell now maps the runtime conflict to the bridge's conflict code, the adapter preserves a bridge error's code and message, and the review clients attach one readable message so the existing screens show it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
A rejected scope, search, or refresh request kept the previous rows under the new filter, and a rejected preview was cached as empty so reopening it never retried. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
…view Tapping an artifact path in the hosted UI returned silently. The tap now routes a webArtifact source into the existing preview screen, which reads it through the artifact chunk RPC with the same bounded chunk reader the image preview uses, and projects text through the existing file path. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
The review-conflict roundtrip test drives shell fixtures, so it lives with the other roundtrip tests under mobile/src; from src/mobile-web it dragged the whole shell tree into the page tsconfig. Also merges a duplicate import and formats two drifted files. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
db53205 to
5d4609c
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since the prior pullfrog review at c59ebc2)
- Cancelled download keeps its stage —
downloadMobileWebPackageno longer aborts the staging tree when the request is aborted, so a superseded attempt cannot delete a newer attempt's in-flight stage for the same host/build; abandoned stages are swept by the package store's startupdiscardAllStagedGenerations(). - Stale review writes surface as a distinct conflict —
mobileWebBridgeErrorCodeForHostRpcmapsruntime_errorwith messageconflictto the non-retryableconflictbridge code, andhostedSourceControlResponsepreserves aMobileWebBridgeClientError's code/message so the screen shows "refresh the review" instead of a generic retryable host error. - Failed agent-history refresh replaces stale rows — the hosted route and shared list now flip to an explicit error on a failed refresh/search/scope change, and preview loads retry on reopen instead of caching an empty result.
- Hosted terminal artifacts open through the existing preview route — a new
webArtifactsource reads viamobileWeb.terminal.artifactChunk, which re-resolves the terminal's file grant per chunk (tab must be active; path must resolve inside the addressed worktree) and bounds reads to the text/raster byte caps.
The abort change is correct: commitGeneration and the store's init-time discardAllStagedGenerations() both remove abandoned stages, and the newer attempt overwrites the same per-host/build stage slot, so nothing leaks. The conflict mapping matches the desktop review methods' new Error('conflict') and conflict is already a valid non-retryable bridge code. The artifact preview host-side boundary re-validates the tab and worktree on every chunk, so a hostile page cannot widen its read grant. All four commits carry red-then-green tests.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
✨ 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 |
pr-code-change-scope keeps the branch's streamed stdin helper (main adopted the same approach inline); the lockfile is regenerated over the merged manifest. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since the prior pullfrog review at 1da15a64)
- Merged a duplicate preview-request import —
web-host-file-preview-operations.tsfolds the standaloneMobileFilePreviewSourcetype import into the existingmobile-file-preview-requestgroup import, satisfying the import plugin audit (+5/−2, cosmetic). - Merged
origin/main(13 commits) — Codex structured-journal image/notice translation and compaction, native-chat notice-row and tool-batch rendering, xterm scrollback-decoration eviction by identity, sidebar collapsed-workspace reveal without clearing filters, orchestration per-PTY mailbox pointer release, dependency security bumps (DOMPurify, react-i18next, xmldom, cloud HTTP/WS), and the CI changed-path streaming read.
The merge is clean: no PR-authored mobile-web/hybrid-shell file is touched by it, and the single conflict-adjacent file (pr-code-change-scope.mjs) keeps the branch's streaming readChangedPaths — which already reads stdin past the 64 KB pipe buffer, preserving #19409's fix — alongside the branch's DESKTOP_RELEVANT_MOBILE_PREFIXES scope additions.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

Summary
Migrate the mobile workspace experience to a stable hybrid shell that loads the desktop-bundled mobile web application over Orca's authenticated connection.
EXPO_PUBLIC_ORCA_MOBILE_ARCHITECTURE=hybrid. Native capability changes still require store releases.Architecture and migration details:
docs/reference/mobile-hybrid-webview-architecture.mddocs/reference/mobile-hybrid-webview-rollback.mddocs/reference/plans/2026-07-22-mobile-hybrid-webview-single-pr-migration.mddocs/reference/plans/2026-07-27-mobile-hybrid-webview-remaining-work.mdScreenshots
No intentional visual change. The migration preserves the existing mobile UI; fresh device visual proof remains part of the release-candidate gates.
Testing
Current as of 5fe4bed (2026-09-07,
mainmerged in; CI 36/36 green, PR mergeable clean).pnpm --dir mobile lint,typecheck,oxfmt --checkpnpm typecheck(all projects),typecheck:tsc:node,typecheck:mobile-webpnpm --dir mobile test: 751 files, 5,122 passed, 3 skipped (file count down from 850 because the generic host lane retired the shell's per-method ledgers and stream caps along with their tests)src/main/runtime/rpc/methods,src/mobile-web,src/shared/mobile-web,src/main/runtime/runtime-rpc,src/main/git/command-runner): 294 files, 2,525 passed; the last full root run (8,130 files) was on 311ea8bcheck:code-quality:changed,check:react-doctor:changed, plainoxlint(nomax-linesdisables or per-file bumps anywhere on the branch), native-plugins oxlint gate (--deny-warnings)--security-onlyand--source-control-onlygreen; all six native baselines green in the full lanecom.stably.orca.mobile.three) and hybrid (com.stably.orca.mobile.ota) release APKs at 0.0.48 from cfc2856 both cold-launch clean, but they predate the generic-lane, resource-ledger and Codex-round changes and need a rebuild from the current head; adhoc desktop builds published from the branch (latest v1.4.198-adhoc.20260904232216)capture Source Control parityfails its screenshot-landmark budget (0.0475 vs 0.005) deterministically only in the full lane while the same check passes in isolation and both pixel budgets pass; the two screenshots are visually identicalNative app parity (users who never take the hybrid)
Release builds default to the native architecture (
mobile-native-baseline-mode.ts, unit-pinned). To verify the native app is functionally unchanged, two runs on the iOS simulator against this branch's desktop runtime:main's mobile app (f801a84) paired to this branch's runtime walked all twelve native stops (workspaces, Accounts, Tasks, session + terminal input dock, Files, file preview, workspace actions, Source Control, review, session actions, Agent History search + detail) with no error, no missing control, no wire rejection. Pairing deep link unchanged.accessibilityLabels on existing Tasks header buttons (Back, Refresh tasks, Create task). Screenshots identical. A whole-tree label census across both checkouts shows four additions and no removals.mobile/scripts/run-hosted-ios-native-journey-capture.mjs(--app,--out,--metro-dir) anddiff-hosted-ios-native-journey-capture.mjs.Wire: only additive RPC methods and capability strings; the one new terminal stream opcode is negotiated per stream; the new browser screencast
navigationevent is ignored bymain's client (if/else chain, no throw).Intentional behavior changes
limitsNavigationsToAppBoundDomainsremoved from the iOS shell; with noWKAppBoundDomainskey an A/B rebuild showed it inert. The navigation delegate is the fence;hosted-ios-app-bound-navigation-probe.mjspins it.renderer_unavailableon a headless host). Recorded in e2e evidence on every run.mainnever had (see next section).Reverted-PR residue and non-mobile drift
mainreverted #10179 (memory bounding) but this branch forked before that and carried 90 files of it. All stripped: 32 files deleted, 8 restored tomainverbatim, 24 hunk-stripped, plus the PR file-content cache. No #10179 identifier remains. Of the 138 other non-mobile files that differ frommain, 124 are required by a named mobile importer; 6 slipped and were restored/deleted.Two latent
mainbugs surfaced by the SSH Docker lane on this branch and fixed here, both with red-then-green regression tests:repos:addRemotenever retired the 1 s resolved-worktree snapshot, so a just-registered SSH worktree was invisible to skill installs for up to a second.ssh-execution-boundary.md). They now fall back to the last-known session while the terminal still exists.AI Review Report
The revival review rebased the 67-commit migration onto current
mainand checked the native/hosted operation boundaries, current mobile UI retention, folder and SSH behavior, terminal input delivery, native chat, source-control activation, notification routing, persistence, and task repository loading.It found and corrected stale compatibility expectations, a native chat file-link path that bypassed the shared tap handler, source-control dismissal before diff activation completed, and native-only task repo-list scoping introduced by newer
main. The final task flow retains current-main loading and refresh behavior while binding it to the hybrid adapter identity.Cross-platform review covered the shared macOS, Linux, and Windows desktop package-serving path plus iOS and Android shell behavior. No new platform-specific keyboard shortcut or path assumptions were introduced during the revival.
Security Audit
Reviewed the privileged boundary around hosted content and native capabilities:
Remaining security validation is release-level device testing against packaged iOS/Android builds and the App Store review environment.
Notes
Before cutover, the release candidate still needs packaged desktop validation, TestFlight and store-track Android APK device coverage, Tailscale/SSH testing, performance checks, crash/recovery exercises, and an actual App Store submission.
Landed 2026-09-06/07, the long-lived shell work: the page now reaches Desktop through a generic
workspace.hostRequest/hostSubscribelane whose allowlist (mobileWeb.host.catalog) lives in Desktop next to the page bundle, so a new host method, response field, stream or hosted screen ships with the Desktop and needs no APK. Session snapshot/feed/actions, native chat, file reads, Source Control reads/watch, terminal metadata and the Terminal/Voice/Notifications/About/Browser/Chat/diagnostics settings run on it. A four-lane Opus audit then removed the Desktop opaque-resource ledger the first cut had added (it defended against a hostile page, out of scope with a trusted Desktop, and wedged headless workspaces after ~128 tab actions), cached the host catalog per connection, made the grant the only source of the in-flight ceiling, batched hostedAsyncStorage, and clipped chat reads to the page contract on the Desktop. Decision record:docs/reference/plans/2026-09-07-long-lived-mobile-shell-decisions.md.Still shell translators that pin the APK (follow-ups, not blockers): task/provider, workspace inventory/creation/preferences, account, Source Control history/mutations/review, file write/open/markdown/artifacts, browser navigation, agent history; plus two inline-script CSP hashes in the Swift/Kotlin shell views.
Three Codex-round findings were verified to exist identically on
mainand are deferred to separate PRs there rather than widened into this one: the review-metadata lost-update race (nativeworktree.setcarries no expected revision either), the capability probe that retriesstatus.getforever without surfacing an error, and commit-message cancel being a no-op until the runtime registers the token after its preparation awaits.The branch was force-updated with lease after rebasing; no history outside
OrcaWin/mobile-rearchwas changed.