Skip to content

Fix restored mobile terminals and workspace visibility parity - #8789

Merged
Jinwoo-H merged 4 commits into
mainfrom
Jinwoo-H/mobile-restored-terminal-state-parity
Jul 15, 2026
Merged

Jinwoo-H merged 4 commits into
mainfrom
Jinwoo-H/mobile-restored-terminal-state-parity

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • restore pre-existing terminal output on Mobile when a parked desktop pane has registered a renderer serializer but its xterm instance has not hydrated yet
  • keep a mobile tab selection pending until a host publication confirms the mirrored desktop selection, while persisting terminal taps for headless hosts
  • make worktree.ps classify deferred startup workspaces, migrated/persisted legacy PTY bindings, provider-confirmed PTYs, and open-browser workspaces consistently with the desktop sidebar
  • retry idempotent activation across authenticated connection cutover and prevent subscription replays from continuously forcing account-usage refreshes
  • add privacy-safe, development-only terminal diagnostics for future real-device reports

Supersedes #8781.

Fixes #8788.

Related: #7400 has a similar missing-terminal symptom, but its stale structural mobileSessionTabs versioning root cause is separate and is not fixed by this PR.

Root causes

A parked desktop terminal could register a renderer serializer before xterm restored its buffer. The runtime treated that empty renderer snapshot as authoritative and never consulted retained provider history, so Mobile received a valid but empty terminal snapshot.

Mobile also cleared its pending local tab selection when it received a mobile-local: publication. That publication acknowledged Mobile's own state, not the mirrored desktop focus, allowing the next desktop snapshot to snap selection back.

For the workspace list, Mobile intentionally filters sleeping workspaces like desktop, but the runtime activity projection did not include deferred activeWorktreeIdsOnShutdown, browser-only activity, or legacy live PTYs whose persisted tab/layout binding was the only reliable worktree attribution. Persisted ownership also has to win after a workspace identity migration because a running daemon PTY keeps the old worktree id minted into its session id. Clicking a workspace on desktop mounted its pane and made the row appear, masking these projection gaps.

Separately, authenticated connection migration rejects in-flight requests by design, while account subscription replay used the same forced refresh lane as an explicit user refresh. Those behaviors caused dropped activation requests and repeated usage fetch cycles.

Screenshots

No visual change.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test — full root suite not run; focused runtime/account/usage coverage is listed below
  • pnpm build — no build-system or dependency changes
  • Added or updated high-quality tests that would catch regressions

Additional validation:

  • full Mobile suite: 1,690 passed, 2 skipped
  • final runtime service suite after review fix: 701 passed
  • focused post-rebase Mobile tests: 25 passed
  • focused runtime/account/usage tests: 22 passed
  • root and Mobile typechecks
  • root and Mobile lint
  • formatting and git diff --check
  • max-lines ratchet

AI Review Report

An independent regression review checked the final diff's terminal snapshot authority, output sequencing, pending mobile/desktop selection gates, diagnostic lifecycle, persisted PTY ownership, direct/relay cutover, SSH/runtime projection, and older-host behavior.

An earlier provider-preference design raised output-sequencing and serialization-cost concerns; the final implementation avoids that design and only falls back when the renderer snapshot is empty and retained provider content exists. The final review then found one workspace migration edge: persisted layout ownership was evaluated after the old worktree id frozen in a daemon PTY id. Follow-up commit eb3c2f64a reverses that precedence and adds the renamed-workspace regression test.

Cross-platform review found no new shortcuts, labels, shell commands, or platform-specific path assumptions. The ownership logic operates on persisted runtime ids and keeps existing native, WSL, SSH, and relay resolution paths intact.

Security Audit

  • no new dependencies, external commands, credential access, or filesystem path handling
  • activation retries are restricted to idempotent focus/activation operations
  • persisted PTY indexing rejects ambiguous duplicate ownership rather than guessing
  • terminal diagnostics are development/HMR-only, log identifier suffixes and state metadata, and never log terminal contents or RPC messages
  • account refresh changes retain existing authenticated RPC boundaries and only alter refresh scheduling/single-flight behavior

No security follow-up is required.

Notes

Real-device verification:

  • restart the desktop/dev process with several previously active workspaces and verify Mobile lists them before any workspace is clicked on desktop
  • open an older workspace with restored terminal scrollback and verify output appears without reload
  • switch between existing terminal tabs and verify Mobile stays on the selected tab and desktop mirrors it
  • leave Mobile open on the host/workspace list and verify the desktop usage meter settles instead of continuously refreshing
  • repeat direct with Relay disabled, then with Relay enabled if available

The terminal-history and workspace-projection fixes require the desktop changes. Tab-selection handling requires the Mobile changes. #7400 remains separate follow-up work.

Jinwoo-H and others added 2 commits July 14, 2026 21:41
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 932535b7-9a72-4717-9605-7d1dd7c7bc13

📥 Commits

Reviewing files that changed from the base of the PR and between eb3c2f6 and 9d51db1.

📒 Files selected for processing (3)
  • mobile/src/terminal/terminal-webview-html.ts
  • mobile/src/terminal/terminal-webview-init-surface.test.ts
  • mobile/src/terminal/terminal-webview-surface-swap-injected.ts

📝 Walkthrough

Walkthrough

The changes add cutover-aware mobile terminal and session-tab activation helpers, mirrored tab-selection confirmation, and structured development diagnostics for terminal streams, tab fetches, WebViews, and route state. Account subscription refreshes now use stale-aware rate-limit handling and prevent overlapping inactive-account fetches. Runtime behavior now falls back to provider terminal history, tracks host-sidebar activity from persisted session state and browser tabs, resolves PTY ownership through persisted bindings, and coordinates overlapping terminal WebView surface replacements.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% 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
Title check ✅ Passed The title matches the main change set: restoring mobile terminal/session behavior and workspace visibility parity.
Description check ✅ Passed The PR description covers summary, screenshots, testing, AI review, security, and notes, including the required platform review.
Linked Issues check ✅ Passed The changes address #8788 by aligning mobile workspace visibility with desktop activity classification, including deferred startup, legacy PTY, and browser-tab cases.
Out of Scope Changes check ✅ Passed The diff stays focused on the stated mobile restoration, workspace parity, cutover retry, refresh, and diagnostics work.
✨ 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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
mobile/app/h/[hostId]/session/[worktreeId].tsx (1)

2992-2999: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Avoid the duplicate activation RPC tab.id already identifies the pending terminal surface here, so leafId isn’t needed. The explicit call and the effect below both activate the same tab; keep one path.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 04e07080-0e1c-4722-af30-4d3a854eae04

📥 Commits

Reviewing files that changed from the base of the PR and between a03377f and eb3c2f6.

📒 Files selected for processing (16)
  • mobile/app/h/[hostId]/session/[worktreeId].tsx
  • mobile/src/session/mobile-session-route-helpers.ts
  • mobile/src/session/mobile-session-startup-source.test.ts
  • mobile/src/session/mobile-session-tab-activation.test.ts
  • mobile/src/session/mobile-session-tab-activation.ts
  • mobile/src/session/mobile-terminal-diagnostics.test.ts
  • mobile/src/session/mobile-terminal-diagnostics.ts
  • mobile/src/session/session-tab-snapshot-gate.test.ts
  • mobile/src/session/session-tab-snapshot-gate.ts
  • src/main/rate-limits/service.test.ts
  • src/main/rate-limits/service.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/remote-runtime-request-connection.integration.test.ts
  • src/main/runtime/rpc/methods/accounts.test.ts
  • src/main/runtime/rpc/methods/accounts.ts

Comment on lines +40 to +51
export function logMobileTerminalDiagnostic(
event: string,
details: MobileTerminalDiagnosticDetails = {}
): void {
// Why: lifecycle diagnostics are intentionally available for HMR repros,
// but high-frequency WebView events must not add production log overhead.
if (typeof __DEV__ !== 'undefined' && !__DEV__) {
return
}
// Keep this structured and content-free so users can safely share a filtered log.
console.log(MOBILE_TERMINAL_DIAGNOSTIC_TAG, event, details)
}

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.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Dev-only gate fails open when __DEV__ is undefined.

typeof __DEV__ !== 'undefined' && !__DEV__ only suppresses logging when __DEV__ is explicitly false; if __DEV__ is undefined in some execution context, this diagnostic module logs unconditionally — the opposite of "development-only." Metro always defines __DEV__ in the shipped app, so the shipped-app risk is low, but the guard should fail closed to actually guarantee the "privacy-safe, development-only" property stated for this feature.

🛡️ Proposed fix (fail closed instead of fail open)
-  if (typeof __DEV__ !== 'undefined' && !__DEV__) {
-    return
-  }
+  if (typeof __DEV__ === 'undefined' || !__DEV__) {
+    return
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function logMobileTerminalDiagnostic(
event: string,
details: MobileTerminalDiagnosticDetails = {}
): void {
// Why: lifecycle diagnostics are intentionally available for HMR repros,
// but high-frequency WebView events must not add production log overhead.
if (typeof __DEV__ !== 'undefined' && !__DEV__) {
return
}
// Keep this structured and content-free so users can safely share a filtered log.
console.log(MOBILE_TERMINAL_DIAGNOSTIC_TAG, event, details)
}
export function logMobileTerminalDiagnostic(
event: string,
details: MobileTerminalDiagnosticDetails = {}
): void {
// Why: lifecycle diagnostics are intentionally available for HMR repros,
// but high-frequency WebView events must not add production log overhead.
if (typeof __DEV__ === 'undefined' || !__DEV__) {
return
}
// Keep this structured and content-free so users can safely share a filtered log.
console.log(MOBILE_TERMINAL_DIAGNOSTIC_TAG, event, details)
}

for (const tab of tabs) {
bind(tab.ptyId, worktreeId)
bind(session?.remoteSessionIdsByTabId?.[tab.id], worktreeId)
const layout = session?.terminalLayoutsByTabId[tab.id]

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg "session(\?)?\.terminalLayoutsByTabId\[" src/main/runtime/orca-runtime.ts

Repository: stablyai/orca

Length of output: 284


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Show all terminalLayoutsByTabId uses with line numbers and nearby context.
rg -n -C 3 "terminalLayoutsByTabId" src/main/runtime/orca-runtime.ts

Repository: stablyai/orca

Length of output: 4488


Guard terminalLayoutsByTabId before indexing
Older persisted sessions can omit that map, so session?.terminalLayoutsByTabId[tab.id] can still throw. Use session?.terminalLayoutsByTabId?.[tab.id] here, and apply the same guard to the other direct access in this file.

Co-authored-by: Orca <help@stably.ai>
@Jinwoo-H
Jinwoo-H merged commit 42ee45f into main Jul 15, 2026
2 checks passed
@Jinwoo-H
Jinwoo-H deleted the Jinwoo-H/mobile-restored-terminal-state-parity branch July 15, 2026 02:36
AmethystLiang added a commit to bbingz/orca that referenced this pull request Jul 15, 2026
…ix) before landing host-endpoint-edit fixes
AmethystLiang added a commit to bbingz/orca that referenced this pull request Jul 15, 2026
Two independent review rounds found the same class of foot-gun: a
superseded mutator (updateHostEndpoint, then renameHost) left in
host-store.ts after the atomic updateHostNameAndEndpoint refactor, with
zero remaining callers. Either could be reintroduced by a future caller
and silently regress the non-atomic name/endpoint race the atomic
function was written to close, so both are removed.

Also covers reconnect-rejection and endpoint-only save paths that were
missing test coverage, and merges origin/main (stablyai#8789) so this lands
without reverting the mobile terminal restore fix.

Co-authored-by: Orca <help@stably.ai>
brennanb2025 added a commit to gsxdsm/orca-stably that referenced this pull request Jul 15, 2026
Reconcile mobile session route ([worktreeId].tsx) with stablyai#8789 (restored-terminal
+ workspace-visibility parity): keep the branch's native-chat coverage gates and
input-lease wiring while adopting main's terminal-diagnostics hooks
(terminalUnsubscribed, firstStreamEvent) and granular subscribe-skip logging.
AmethystLiang added a commit that referenced this pull request Jul 15, 2026
* feat(mobile): edit saved host endpoints

* fix(mobile): reject ambiguous numeric host addresses

* fix(mobile): label edit host inputs

* fix(mobile): make host edit save atomic and remove superseded mutators

Two independent review rounds found the same class of foot-gun: a
superseded mutator (updateHostEndpoint, then renameHost) left in
host-store.ts after the atomic updateHostNameAndEndpoint refactor, with
zero remaining callers. Either could be reintroduced by a future caller
and silently regress the non-atomic name/endpoint race the atomic
function was written to close, so both are removed.

Also covers reconnect-rejection and endpoint-only save paths that were
missing test coverage, and merges origin/main (#8789) so this lands
without reverting the mobile terminal restore fix.

Co-authored-by: Orca <help@stably.ai>

* Simplify save-race comment and reword host-removed error message

- Trims the redundant comment explaining the savingRef race guard down
  to one line.
- Changes the "no longer saved" load-error copy to "was removed" for
  clearer phrasing, updating the matching test expectation.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
…ai#8789)

* Fix mobile cutover activation and usage refresh loops

Co-authored-by: Orca <help@stably.ai>

* Fix restored mobile terminal state parity

Co-authored-by: Orca <help@stably.ai>

* Fix migrated PTY workspace attribution

Co-authored-by: Orca <help@stably.ai>

* Fix overlapping mobile terminal surface swaps

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
* feat(mobile): edit saved host endpoints

* fix(mobile): reject ambiguous numeric host addresses

* fix(mobile): label edit host inputs

* fix(mobile): make host edit save atomic and remove superseded mutators

Two independent review rounds found the same class of foot-gun: a
superseded mutator (updateHostEndpoint, then renameHost) left in
host-store.ts after the atomic updateHostNameAndEndpoint refactor, with
zero remaining callers. Either could be reintroduced by a future caller
and silently regress the non-atomic name/endpoint race the atomic
function was written to close, so both are removed.

Also covers reconnect-rejection and endpoint-only save paths that were
missing test coverage, and merges origin/main (stablyai#8789) so this lands
without reverting the mobile terminal restore fix.

Co-authored-by: Orca <help@stably.ai>

* Simplify save-race comment and reword host-removed error message

- Trims the redundant comment explaining the savingRef race guard down
  to one line.
- Changes the "no longer saved" load-error copy to "was removed" for
  clearer phrasing, updating the matching test expectation.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
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.

[Bug]: Mobile hides deferred-active workspaces until opened on desktop

1 participant