Skip to content

feat(mobile): migrate workspace UI to hybrid WebView - #13386

Open
Jinwoo-H wants to merge 532 commits into
mainfrom
OrcaWin/mobile-rearch
Open

Jinwoo-H wants to merge 532 commits into
mainfrom
OrcaWin/mobile-rearch

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 518 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​50084 $\color{#cf222e}{\Huge{\mathbf{−}}}$​2657 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​47427
Prod 1363 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​91672 $\color{#cf222e}{\Huge{\mathbf{−}}}$​14445 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​77227

Summary

Migrate the mobile workspace experience to a stable hybrid shell that loads the desktop-bundled mobile web application over Orca's authenticated connection.

  • Preserve the existing mobile screens and UX by rendering the same React Native components through React Native Web; this is an architecture migration, not a UI redesign.
  • Keep pairing, credentials, encrypted connectivity, recovery, notifications, deep links, camera, audio, clipboard, haptics, and file picking in the native shell.
  • Move host-specific workspace surfaces such as sessions, terminal, source control, files, diffs, tasks, accounts, and agent history behind versioned host-operation adapters and a narrow capability bridge.
  • Package the matching mobile web UI with each desktop release, cache it atomically on device, and automatically recover to the last known-good package.
  • Keep ordinary release builds on the existing native workspace UI; dedicated hybrid candidates opt in with EXPO_PUBLIC_ORCA_MOBILE_ARCHITECTURE=hybrid. Native capability changes still require store releases.

Architecture and migration details:

  • docs/reference/mobile-hybrid-webview-architecture.md
  • docs/reference/mobile-hybrid-webview-rollback.md
  • docs/reference/plans/2026-07-22-mobile-hybrid-webview-single-pr-migration.md
  • docs/reference/plans/2026-07-27-mobile-hybrid-webview-remaining-work.md

Screenshots

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, main merged in; CI 36/36 green, PR mergeable clean).

  • pnpm --dir mobile lint, typecheck, oxfmt --check
  • pnpm typecheck (all projects), typecheck:tsc:node, typecheck:mobile-web
  • pnpm --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)
  • Root scoped suites for every touched area (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 311ea8b
  • check:code-quality:changed, check:react-doctor:changed, plain oxlint (no max-lines disables or per-file bumps anywhere on the branch), native-plugins oxlint gate (--deny-warnings)
  • Reliability gate manifest (110 gates) and e2e gate contract (42 tests)
  • SSH Docker e2e lane, unfiltered: 19 passed
  • Hosted iOS simulator e2e: --security-only and --source-control-only green; all six native baselines green in the full lane
  • Four review sweeps (two Opus multi-lens sweeps, one Codex third-opinion sweep, one four-lane Opus audit of the 2026-09-06/07 long-lived-shell work) with every P0/P1/P2 finding fixed on the branch; all pullfrog review threads resolved
  • Four further four-lane Codex review rounds over the OTA code (desktop / shell / native / page; 60 → 21 → 13 → 2 touched files, converging), each fix re-judged by the lead before landing; a final Codex orchestration pass closed four of the eleven follow-ups the rounds raised (review conflict mapping, hosted artifact taps, agent-history stale rows, download staging race)
  • Device dogfood, Samsung SM-S921W: native (com.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)
  • Full hosted iOS lane: one open harness item, capture Source Control parity fails 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 identical
  • Hosted Android lane: never run on this branch
  • Production desktop/mobile builds

Native 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:

  • Old client: 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.
  • A/B: the same walk on the branch's native build, same runtime/workspace/PR, diffed by ordered accessibility labels plus screenshots. The only difference is three added 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.
  • Not reachable in the harness: native chat (the fixture creates a plain shell terminal). Covered by unit tests and the RPC surface check only.
  • Tooling: mobile/scripts/run-hosted-ios-native-journey-capture.mjs (--app, --out, --metro-dir) and diff-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 navigation event is ignored by main's client (if/else chain, no throw).

Intentional behavior changes

  • Hybrid only: the page-side "recent user gesture" window (5 s, armed by any touch including scroll) is removed. Peer hybrid frameworks do not gate bridge calls on gestures and a scroll armed it anyway. Probe evidence in the commit history.
  • Hybrid only: limitsNavigationsToAppBoundDomains removed from the iOS shell; with no WKAppBoundDomains key an A/B rebuild showed it inert. The navigation delegate is the fence; hosted-ios-app-bound-navigation-probe.mjs pins it.
  • Hybrid only, dogfood item: the hybrid Source Control page shows a generic "Source control action failed" where native names the host error (renderer_unavailable on a headless host). Recorded in e2e evidence on every run.
  • The "File too large for mobile preview" message is gone; it existed only for a byte cap in a PR file-content cache that main never had (see next section).

Reverted-PR residue and non-mobile drift

main reverted #10179 (memory bounding) but this branch forked before that and carried 90 files of it. All stripped: 32 files deleted, 8 restored to main verbatim, 24 hunk-stripped, plus the PR file-content cache. No #10179 identifier remains. Of the 138 other non-mobile files that differ from main, 124 are required by a named mobile importer; 6 slipped and were restored/deleted.

Two latent main bugs surfaced by the SSH Docker lane on this branch and fixed here, both with red-then-green regression tests:

  • repos:addRemote never retired the 1 s resolved-worktree snapshot, so a just-registered SSH worktree was invisible to skill installs for up to a second.
  • Relay teardown clears agent-status entries, and both native-chat binding resolvers read the missing provider session as "session gone" instead of "host unreachable" (contrary to 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 main and 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:

  • Hosted packages travel through the authenticated paired Orca connection; pairing credentials are not placed in URLs.
  • WebView navigation and native capability dispatch are restricted to the paired Orca application and explicit operation contracts.
  • CSP, manifest/token/type admission, package/path bounds, unpaired-surrogate rejection, cache symlink defenses, and atomic last-known-good rollback are covered by tests.
  • Page-owned persistence cannot replace native package authority, and privacy-sensitive bridge/log surfaces are redacted or bounded.
  • Terminal, filesystem, task, clipboard, picker, microphone, and notification operations remain capability-scoped rather than exposing a general native RPC surface.

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/hostSubscribe lane 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 hosted AsyncStorage, 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 main and are deferred to separate PRs there rather than widened into this one: the review-metadata lost-update race (native worktree.set carries no expected revision either), the capability probe that retries status.get forever 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-rearch was changed.

@Jinwoo-H
Jinwoo-H marked this pull request as ready for review August 31, 2026 00:46
@greptile-apps

greptile-apps Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

Too many files changed for review (1881 files, 100 file limit).

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.ts windows transcripts to a recent slice and truncates oversized text/tool-result/tool-call blocks for mobile clients, wired through nativeChat.readSession and nativeChat.subscribe.
  • Transcript watcher refactor — transcript-watch-engine.ts split into extracted helpers (state/tail reads, runtime activation/disposal, reconciler, scheduler) to stay under the line budget.
  • Merged main — .gitattributes patch 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 / requireMobileWebWritePath symlink and hard-link checks to staging, asset write, and generation commit, with new boundary tests.
  • Android WebView lifecycle refactor — extracted createWebView, tracked the debug-isolation ScriptHandler per session, guarded bridge-listener attach/detach, and added render-process-gone recovery plus onAttachedToWindow reattach/reload.
  • iOS load-state and RNG hardening — added loadingSessionId gating on load-state callbacks and made randomIdentifier throw on SecRandomCopyBytes failure.
  • Capability gating and transport extraction — getStatus now 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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found in the new commits.

Reviewed changes (delta since b551c47)

  • WebView reload guard — added a documentLoaded flag so onAttachedToWindow no 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 HostScreenController cast with a proper HostScreenController type that narrows router and drops internal refs from the exposed state.
  • CDP fake relocation — moved hosted-webview-cdp-test-fakes.ts to a .test.ts suite 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found in the new commits.

Reviewed changes (delta since 0ae1dd2)

  • Gzip chunk decode bound — decodeGzipMobileWebPackageChunk now inflates into a fixed expectedLength + 1 buffer and rejects anything whose decoded length differs from the advertised sourceByteLength, instead of letting gunzipSync allocate from the gzip ISIZE trailer.
  • Hosted resume and reconnect — SessionScreen accepts optional reconnect / reconnectAttempts / lastConnectedAt props, the hosted route wires shell.client.navigationReconnect() as the retry action, and the hosted workspace-list route no longer overwrites the resume route via rememberRoute.

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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@Jinwoo-H
Jinwoo-H force-pushed the OrcaWin/mobile-rearch branch from 585b290 to 1f4401d Compare August 31, 2026 03:41

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 — credentialStatusByHostId threading, credentialRecovery on navigation targets, and notificationCredentialRecoveryRoute handling in _layout.tsx.
  • worktree-missing notice 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 sessionHostId on the package-session state and three guard sites in hybrid.tsx prevent 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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 earlier meta() inlining was superseded by main's RpcStreamingDispatcher extraction; the resolution keeps main's structure (private meta() restored, dispatch() calling this.meta(), dispatchStreaming() delegating to RpcStreamingDispatcher).

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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes (delta since the prior pullfrog review at 66a007b)

  • Merged origin/main — brought in two upstream commits: 91cc834 fix(remote): preserve standing host reconnect intent and b892f05c perf(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, the verifyPackagedMobileWeb import and afterPack call) are preserved alongside main's per-target speech packaging (macSpeechNativeResource/linuxSpeechNativeResource/winSpeechNativeResource extraResources, the !node_modules/sherpa-onnx* files exclusion, and the removal of sherpa-onnx from asarUnpack and serve-sim from mac extraResources).

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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes (delta since the prior pullfrog review at 57f7371)

  • Merged origin/main — one upstream commit, 3d65466 perf(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.ts now owns the coalesced deadline logic; src/main/agent-hooks/server.ts and src/relay/agent-hook-result-retry-scheduler.ts both 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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) and terminalRecordsEqual now delegate to mobileTerminalThemesEqual, adopting main's perf(mobile): compare terminal themes without serialization (#17455) in place of the branch's earlier JSON.stringify equality.

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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes (delta since the prior pullfrog review at ff6485c)

  • Merged origin/main — six upstream commits: 63d60e0 targeted SSH session-tab refresh, db84894 paired-terminal focus isolation, aabcc573 remote-control outage publication, 68de1be GitLab lint gates, and the 5fe3772/894ed75 native-chat structured-result preserve-then-revert pair (net no-op).
  • Resolved src/main/runtime/orca-runtime.ts — the merge adopted main's refreshMobileSessionPtyInventory fast path from #17486 (targeted polls short-circuit the aggregate census, resolve the owning execution host, and return null for runtime-owned workspaces). The branch's copy of this function was the pre-#17486 base, so no PR-authored logic was dropped; every other PR addition in the file (mobileWebPackageIsAvailable, notifySshRelayUnavailable, resolveNativeChatTranscriptBinding, submitHostedReview, etc.) remains intact.
  • terminal.create caller fencing — the legacy RPC now forces focus/activate to false and focused presentation to background for 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.ts gained seedDockerSshNativeChatAgent (a /usr/local/bin/claude exit 0 shim plus /root/.claude/settings.json) and waitForDockerSshNativeChatAgentHook, 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.ts seeds 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/* adopted main's displayNameKind (user / generated) stamping and host-returned displayName handling from 8f15f217a2 (Preserve user-set workspace names across branch changes), wired through use-mobile-tasks-workspace-create-actions.tsx and workspace-create-params.ts.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes

  • Resolved the merge type conflict — removed the duplicate note: string | undefined from both CreateWorkspaceFromComposerArgs and the createWorkItemWorkspace parameter type in mobile/src/tasks/source-workspace-create.ts, fixing the TS2300 duplicate-identifier failure flagged on 57ef1fe.

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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 — mobileNativeBaselineMode now resolves native for release builds and hybrid for development by default, overridable through EXPO_PUBLIC_ORCA_MOBILE_ARCHITECTURE=native|hybrid; the E2E baseline flag stays development-only.
  • Re-routed native-mode navigation — mobileHomeDestination, mobileHostWorkspaceEntry, navigateFromMobileHome, and mobileOnboardingDestination now thread nativeBaselineEnabled, so native builds navigate to /h/... routes instead of /hybrid.
  • Added a native-mode /hybrid guard — _layout.tsx redirects a native build that lands on /hybrid back to the native host entry, and notification taps now push through mobileHomeDestination.
  • Updated the rollout docs — the architecture doc, migration plan, and mobile/README.md now 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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes (delta since the prior pullfrog review at cffec8a)

  • Restored the branch's native session surface — e9f7d494 re-inlined mobile/app/h/[hostId]/session/[worktreeId].tsx (back to its ~4,984-line monolith, byte-identical to cffec8a) and deleted the use-mobile-session-* hooks and MobileSession* components that origin/main had introduced, consolidating terminal-webview-html fragments back into a single file (also byte-identical).
  • Passed worktree-prefetch git options through orca-runtime.ts — bdf3e57d22 now forwards getWorktreeCreatePrefetchGitOptions(store, repo) into prefetchWorktreeCreateBase, matching the existing prefetch handler.
  • Merged origin/main twice — a49d3d4f25 and 39530bba3d brought in the renderer TaskPage/Terminal/StatusBar splits and other upstream changes; the merge conflict resolutions preserved the branch's session surface and its orca-runtime.ts additions (mobileWebPackageIsAvailable, notifySshRelayUnavailable, resolveNativeChatTranscriptBinding, submitHostedReview all 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?

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta — a clean origin/main merge with a deliberate, behavior-preserving conflict resolution. One divergence observation below.

Reviewed changes

  • Merged origin/main (40 commits) — the 512ec1a0da tip down to 41015f9393: the refactor(main): split backend services and startup file-split pass (plus filesystem/preload/renderer/keybindings/agent-hooks/browser-manager/rate-limits/updater splits), WSL/git path fixes, a browser-cookie keychain runProcess fix, and an observer stale-runtime-capture fix.
  • Resolved the single conflict in src/main/index.ts in the branch's favor — the branch kept its 3837-line monolithic entry point (with /* eslint-disable max-lines */), byte-identical to 39530bba3d, rejecting main's 5b4e7edb50 split of index.ts into a 65-line shim plus startup/main-process-*.ts modules.

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?

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes (delta since the prior pullfrog review at e74ac4505)

  • Android document-start network API blocker — MobileWebNetworkApiBlocker.kt neutralizes fetch/XMLHttpRequest/WebSocket/serviceWorker via a document-start script, wired into MobileWebShellView.kt (session set, render-process-gone recovery), closing the Android/iOS network-isolation gap from the first review.
  • beginStage symlink check reordered — MobileWebPackageStore.kt now validates the hostRoot/stagingRoot unlinked-path invariant before stageRoot.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); native alert now requires a recent (witnessed, not consumed) gesture.
  • Broker teardown on view restart — use-mobile-web-capability-broker.ts retires the prior page's broker before a fresh document posts.
  • Re-adopted main's splits — the session route and RPC client split are restored from main, 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 to main's shape. Verified against origin/main: none of these files or APIs exist on main, 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 useState pair and restored the cache-hit/loading/try-catch load flow, matching main's Tasks shape.
  • Removed the inert iOS app-bound navigation flag — limitsNavigationsToAppBoundDomains was a no-op because the app declares no WKAppBoundDomains key; the navigation delegate remains the enforcement fence (covered by mobile-web-native-transport-source.test.ts and the new app-bound probe).
  • Added iOS-only E2E probes — hosted-ios-user-gesture-window-probe.mjs and hosted-ios-app-bound-navigation-probe.mjs exercise 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).

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes (delta since the prior review at 7520516)

  • Removed the recent-user-gesture window — d156826131 deletes the entire user-gesture gate added in the mr-p1-security group (use-mobile-web-user-gesture-authority.ts, mobile-web-user-gesture-requirement.ts, mobile-web-user-gesture.ts, the census/operations tests, and the hosted-ios-user-gesture-window-probe.mjs), replacing it with use-mobile-web-app-foreground-authority.ts, which only forwards AppState foreground/background transitions to broker.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 (worktree base-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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 calls invalidateResolvedWorktreeCache() before emitting reposChanged, 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 after notifyReposChangedForRemoteClients().

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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 — longPressHostedIosPoint now holds the touch across two gesture commands (begin+move, then move+end after a 900 ms hold) instead of batching 60 move frames into a single command that lands as a tap, with a finally that always lifts the touch and an optional settledLabel wait.
  • Added native-journey A/B capture tooling — new run-hosted-ios-native-journey-capture.mjs, hosted-ios-native-journey-labels.mjs, and diff-hosted-ios-native-journey-capture.mjs walk 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 — captureNativeRoute waits, delays, then re-reads the title so the screenshot's landmark position matches the settled screen.
  • Added an ORCA_E2E_MOBILE_METRO_DIR override — start-emulator.mjs and 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes (delta since the prior pullfrog review at d3f97a4f)

  • Session-origin reviewOpen parity 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 — captureNativeSourceControlReviewBaselines now records sessionOriginReviewOpen (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 in captureNativeRoute, back to a single read before the screenshot.
  • Test updates — the two unit suites add real coverage: one asserts the exact reviewOpen shape (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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 — resolveFreshMobileWebNativeChatBinding now revokes only on a vanished tab, a different terminal handle, or a tab rebound to a different provider session; a terminal whose agentStatus was 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 — resolveNativeChatTranscriptBinding remembers each handle's last live { agent, providerSession } in lastKnownNativeChatSessions, falls back to it while the terminal still exists, and deletes it once resolveTerminalContext returns null (terminal genuinely gone), so the remembered identity cannot outlive the terminal.
  • Screenshot parity diagnostics — hosted-ios-screenshot-parity.mjs now reports both native and hosted landmarks 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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:linux now runs build-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, and src/main/providers/ssh-filesystem-provider.ts. Each resolution preserves the branch's mobile-web content (DESKTOP_RELEVANT_MOBILE_PREFIXES and its scope test, the hosted-mobile-webview-ssh comment, stdin in the gh exec, the isMethodNotFoundError import) while adopting main's changes — verified.
  • Carried the mobile-web afterPack fixtures into main's relocated spec — 9afe68a650 moved createMobileWebResourceFixture/createPackagedCliResourceFixture out of electron-builder-config.test.mjs and into electron-builder-runtime-resources.test.mjs, replacing the relocated spec's inline CLI fixture with the shared helpers so verifyPackagedMobileWeb stays 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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta.

Reviewed changes

  • Mirrored main's host-label state into the hybrid host screen hooks — useHybridHostScreenState now carries repoHostIdByRepoId / hostLabelById / hostPlatform, and useHybridHostScreenController resets them on host switch and wraps its displayWorktrees memo in applyWorktreeHostContextLabels, matching the native controller so the shared HostScreenView typechecks.

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.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 Windows pnpm.cmd shim) and thread shell through spawnSync; the gh stdin contract moved from runner-command-exec.test.ts (whose execFile mock is no longer wired) into gh-exec-file-deadline.test.ts (spawn mock); and the orca-runtime-mobile-file-path-search.test.ts fixture gained executionHostId: '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.

Pullfrog  | Fix it ➔ | View workflow run | Using 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
@Jinwoo-H
Jinwoo-H force-pushed the OrcaWin/mobile-rearch branch from db53205 to 5d4609c Compare September 8, 2026 00:50

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes (delta since the prior pullfrog review at c59ebc2)

  • Cancelled download keeps its stage — downloadMobileWebPackage no 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 startup discardAllStagedGenerations().
  • Stale review writes surface as a distinct conflict — mobileWebBridgeErrorCodeForHostRpc maps runtime_error with message conflict to the non-retryable conflict bridge code, and hostedSourceControlResponse preserves a MobileWebBridgeClientError'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 webArtifact source reads via mobileWeb.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.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.ts folds the standalone MobileFilePreviewSource type import into the existing mobile-file-preview-request group 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.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

This was referenced Sep 19, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant