Skip to content

fix(mobile): background-mount never-mounted terminals on subscribe so they don't render blank (STA-1840)#8811

Merged
Jinwoo-H merged 12 commits into
mainfrom
brennanb2025/fix-mobile-terminal-blank
Jul 16, 2026
Merged

fix(mobile): background-mount never-mounted terminals on subscribe so they don't render blank (STA-1840)#8811
Jinwoo-H merged 12 commits into
mainfrom
brennanb2025/fix-mobile-terminal-blank

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Problem (STA-1840 / regression #8597)

Mobile terminals can render blank—or show only a stale retained snapshot—when the phone opens a workspace whose terminal pane the desktop never mounted this session. The desktop does not follow the phone into that workspace, so nothing attaches the live PTY stream.

This affects never-visited workspaces, cold-activation-deferred tabs from #8597, and other panes with a persisted terminal identity but no renderer attachment.

ELI5

Orca remembers your terminal tabs, but it normally reconnects a terminal only when the desktop displays it. If you open a different workspace from your phone before the desktop has visited it, the phone can ask for a remembered terminal whose connection is still “asleep.” That is why the terminal can look blank or frozen even though the shell still exists.

This change lets the phone wake up exactly that one terminal in the background. It does not switch the desktop to another workspace or start every saved terminal.

For users, the difference is simple:

  • Before: opening the terminal on mobile can show a blank screen or old output that never updates.
  • After: the current prompt and live output appear, and the terminal accepts input normally.
  • On the desktop: nothing visibly moves or changes; it stays on the workspace the user was already viewing.

Root cause

A mobile terminal.subscribe can resolve a persisted PTY through a synthetic pty:<ptyId> handle even though the renderer graph has no leaf for it. On current main, provider-owned snapshot fallback (#8768) may still return retained output, but without a headless runtime model the PTY is not live-attached; the phone can therefore remain blank or frozen on stale output.

Fix

When a mobile subscribe has no connected leaf, or resolves a PTY with no headless runtime model, the runtime asks the renderer to background-mount the owning terminal tab. The subscribe installs its live-data listener before this request, so attach output is buffered and delivered without navigating the desktop.

Synthetic handles carry the ptyId; the renderer resolves it through the persisted tab model (tab.ptyId or a split layout binding). Mount planning is deliberately bounded:

  • a known terminal mounts exactly one tab;
  • an already-mounted tab is an idempotent no-op;
  • a stale/unresolvable ptyId mounts nothing;
  • there is no whole-worktree fallback.

That last rule preserves #8597's activation performance: a bad mapping cannot instantiate every saved xterm/PTY pane in a large hidden workspace.

User validation

Validated on an iOS simulator with two desktop workspaces:

  • Without the fix, opening the non-active workspace rendered a blank Terminal 3.
  • With the fix, the same terminal rendered the live prompt brennanbenson ~/orca/demo-project [main] $.
  • Temporary diagnostics (removed before commit) showed the target workspace's mounted-pane count transition from 0 to 1 after subscribe, and the mounted pane was the terminal opened on the phone.

Also validated through a disposable Linux SSH target using the real encrypted mobile WebSocket runtime:

  • The mobile subscription started before desktop relaunch and initially received an empty snapshot.
  • Relaunch recovered the existing remote PTY and delivered live output without navigating the desktop away from its local workspace.
  • The recovery kept exactly one logical terminal and one remote shell, with no duplicate output or remote Git changes.

Reliability contract

  • Invariant (terminal-session.mobile-live-attachment): a mobile subscription to a persisted terminal either attaches that terminal's live PTY without desktop navigation or safely remains on its existing fallback; it never mounts unrelated tabs.
  • Failure source: STA-1840, with the cold-activation regression lineage in perf(terminal): defer cold worktree activation tab mounts until first reveal #8597 and current-main provider snapshot interaction from Fix restored terminals rendering blank on mobile #8768.
  • Oracle: the iOS A/B proves visible live output; unit tests prove missing-model recovery, real-tab and synthetic-handle routing, split-layout resolution, cleanup on subscription close, and exact one-tab/zero-tab mount counts at 200 saved tabs. Live SSH restart coverage proves the same recovery path preserves and reconnects one remote PTY.
  • Gate: the existing terminal-runtime.mobile-stream-budget gate passes, plus the PR-specific focused tests below. Accepted gap: there is not yet a dedicated manifest entry or automated real-daemon iOS end-to-end gate for this exact navigation path.
  • Provider/platform coverage: local daemon + mobile/macOS and SSH relay + Linux ARM64 are live-covered. The request and resolver are provider/path agnostic and add no local filesystem or subprocess assumptions; WSL, remote-runtime, Linux/Windows desktop, and other SSH host platforms receive unit/shared-path coverage only.
  • Performance budget: no polling, timers, provider fanout, session inventory scan, or subprocess work was added. The only scan is the target worktree's persisted terminal tabs on the exceptional missing-model subscribe path; the executable 200-tab test proves it produces at most one pane mount.
  • Diagnostics: existing terminal/provider snapshot diagnostics distinguish retained snapshot state from a live headless model. Accepted gap: no new production breadcrumb is retained specifically for the mount IPC.
  • Residual gap: if persisted ownership is stale and cannot resolve the ptyId, recovery remains a safe no-op rather than risking a workspace-wide mount.

Validation

  • pnpm run typecheck — passed on merged current main.
  • pnpm run lint — passed, including reliability manifest and max-lines ratchet.
  • Focused terminal/runtime/renderer suite — 6 files, 99 tests passed.
  • terminal-runtime.mobile-stream-budget command — 3 files, 40 tests passed.
  • Full local suite under Node 26 — 29,916 passed; 30 unrelated environment failures (28 missing Node 26 window.localStorage, 2 inherited Git credential env duplication). CI uses the repository-required Node 24 runtime and is the authoritative full-suite gate.

Refs: STA-1840, #8597, #8768

… they don't render blank (STA-1840)

A mobile terminal.subscribe to a tab the desktop never mounted this session —
a workspace the desktop isn't currently showing, a cold-activation-deferred tab
(#8597), or a cold-parked tab — has no attached PTY, so the runtime has no
headless emulator and the initial snapshot is empty. The mobile terminal then
renders blank (repro: on the phone, scroll to an old workspace whose terminals
the desktop unmounted; they stay blank).

When a mobile subscribe hits an empty snapshot (or resolves no PTY), the runtime
now asks the renderer to background-mount that tab so the PTY attaches and the
live data stream — already subscribed on that path — delivers its output.

A never-mounted workspace has no renderer-graph leaf, so its terminals are
surfaced to mobile via synthetic pty:<ptyId> handles that carry no real UI
tabId. The runtime passes the ptyId through the mount request and the renderer
resolves the owning tab from the persisted tab model (resolveTerminalTabIdForPtyId),
falling back to a whole-worktree background mount if it cannot. The request is
idempotent for already-mounted tabs and never unmounts a live pane, so the worst
case is the same blank as before — no regression.

Validated on an iOS simulator with a two-workspace desktop: opening the
workspace the desktop was not showing rendered a fully blank terminal without
the fix and the live shell prompt with it.

Tests: runtime mount-request gating (real-tab / pty-form / no-ptyId / unknown),
subscribe-handler empty-snapshot-vs-content, and the ptyId->tabId resolver; all
608 RPC method tests pass.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds runtime-to-renderer terminal tab mount requests through a new IPC event. Mobile terminal subscriptions invoke the request when no PTY leaf or initial terminal content is available. The preload and web APIs expose the subscription, while the renderer resolves tabs from direct or split-layout PTY ownership and triggers background mounting. Tests cover request payloads, failure handling, empty snapshots, existing content, PTY-to-tab resolution, and updated IPC mocks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the mobile background-mount fix for blank terminal subscribes.
Description check ✅ Passed The description covers the problem, fix, validation, review, and security sections, but it omits the requested screenshots section.

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.

@Jinwoo-H Jinwoo-H merged commit 1ba71d2 into main Jul 16, 2026
2 checks passed
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.

2 participants