Skip to content

fix(mobile): after closing a tab, return to the previous one - #15220

Closed
apple-ouyang wants to merge 6 commits into
stablyai:mainfrom
apple-ouyang:fix/mobile-close-tab-successor
Closed

apple-ouyang wants to merge 6 commits into
stablyai:mainfrom
apple-ouyang:fix/mobile-close-tab-successor

Conversation

@apple-ouyang

@apple-ouyang apple-ouyang commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

If you close a document on your phone, Orca used to jump to the leftmost tab. It now goes back to the tab you were looking at before, and only if you never visited another tab does it land on the newest remaining one.

What Changed

  • Remember phone-side tab visits even when activation is caller-only.
  • After Close, activate the previous visit if it is still open; otherwise the most recently added remaining tab.
  • Stop using tabs[0] as the snapshot / headless-close fallback.

Why

Phone switches do not update host recentTabIds. Close also cleared the local active tab, so the next snapshot picked the leftmost remaining tab.

Linked Issue

Fixes #15219

Visual Proof

N/A — mobile session tab selection after close. Covered by unit tests for previous-visit vs newest-remaining successor selection.

Testing

  • vitest run --config config/vitest.config.ts src/shared/session-tab-close-successor.test.ts src/main/runtime/mobile-session-terminal-retirement.test.ts src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.test.ts — 18 passed.
  • mobile/ session suite (vitest run src/session/) — 191 files / 1828 tests, all passing after the origin/main merge (route-parity fingerprint pins re-recorded against the merged tree).
  • pnpm run typecheck — clean; oxlint + oxfmt clean on the touched files.
  • Regression scenarios pinned in tests: closing/retiring the active tab picks the newer cross-group visit over the repaired group sibling (never the leftmost), and an emptied visit history falls back to the per-group history while dropping stale ids from the stored snapshot.

@coderabbitai

coderabbitai Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added shared MRU tracking and successor-selection utilities for session tabs. Updated mobile session state, startup, snapshot application, switching, and close actions to track recent tabs and select MRU successors. Updated runtime close and active-surface fallback paths to use recent group history. Added tests for utility behavior and active versus background mobile tab closes.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk

Merge Risk: 🔵 Low · up to f0a1b

After a workspace graph sync, closing a tab can return to an older tab instead of the most recently visited one. Preserve global tab history during hydration before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to f0a1b

Closing a tab now follows recent visits, but a device’s visit order also enters shared session state. Other paired devices may learn that order on a later update, although their active tab remains independently selected. No authorization bypass is evident.

Retained concerns

  • Low · security · inferred: Caller-only tab activation writes visit order into a shared worktree snapshot. A later snapshot can expose that order to another paired client even though active-tab selection is projected per client.

Security review details

Security Blast Radius

  • inferred — The identified metadata exposure is bounded to clients receiving the same worktree’s session snapshots; the inspected paths do not show access to another worktree or added terminal-kill authority.

Security Findings and Attack Paths

  • inferred — A paired client receiving a later worktree snapshot can infer another caller’s tab-visit order from recentTabIds. Caller-only activation does not immediately broadcast its active selection to all clients, limiting the exposure.

Trust Boundaries and Controls

  • observed — The existing close handler checks client-projected tab visibility and destructive-mutation capability before invoking runtime close. Runtime activation resolves the requested tab from its authoritative snapshot before activation.

Resilience and Maintainability Implications

  • observed — Retirement prunes closed top-level IDs before choosing and persisting a successor, reducing the chance that repeated publication retains a retired tab as the active selection.

Hardening Proposals

  • proposed — If tab visits are intended to remain device-private, retain caller-only history in per-client state or omit it from snapshots projected to other clients.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 20 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue [#15219] requires previous-tab restoration, most-recently-added fallback, and no selection change for background closes. The PR records phone-side visits, including caller-only activations. It p…
Out of Scope Changes check ✅ Passed The changes remain within issue [#15219]. Shared successor utilities, runtime history propagation, stale-history pruning, close-path updates, and regression tests directly support the requested mobile…
Title check ✅ Passed The title clearly and concisely describes the main user-facing change: returning to the previous mobile tab after closing the current tab.
Description check ✅ Passed The description is mostly complete. It explains the user impact, implementation, motivation, linked issue, visual-proof status, and testing results. The template sections for Review, Notes, and Checkl…
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/mobile-close-tab-successor

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/src/session/mobile-session-last-tab-close.test.ts (1)

23-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Use behavior assertions for this regression.

These assertions only check that identifier strings exist in the route source. They can pass when the handler uses incorrect arguments or never reaches the branch. Add a behavior-level test that closes the active and background tabs and asserts active-state clearing and successor activation. If mounting the full screen is difficult, extract and test the pure close-selection decision.

Source basis: the PR objective requires regression coverage for previous-tab and fallback selection.

Also applies to: 28-35


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 55fb6c7f-f40f-41ec-8a59-48dc0a882757

📥 Commits

Reviewing files that changed from the base of the PR and between 13b10e0 and 810fa1e21255c7ad011e1bbe8bf338831784064f.

📒 Files selected for processing (6)
  • mobile/app/h/[hostId]/session/[worktreeId].tsx
  • mobile/src/session/mobile-session-last-tab-close.test.ts
  • src/main/runtime/mobile-session-terminal-retirement.ts
  • src/main/runtime/orca-runtime.ts
  • src/shared/session-tab-close-successor.test.ts
  • src/shared/session-tab-close-successor.ts

Included review availability: Your plan includes up to 10 reviews per rolling hour; 9 remain after this review.

Comment thread src/main/runtime/orca-runtime.ts Resolved
@apple-ouyang
apple-ouyang force-pushed the fix/mobile-close-tab-successor branch from 810fa1e to 5052aee Compare September 6, 2026 13:56
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 9be40f7d-9cc8-4315-9228-726acbce5cd6

📥 Commits

Reviewing files that changed from the base of the PR and between f952f1a and 5052aee50bfbf3b5f3431c4bdc2d3cd0e96cf23b.

📒 Files selected for processing (11)
  • mobile/src/session/active-session-tab.ts
  • mobile/src/session/mobile-session-last-tab-close.test.ts
  • mobile/src/session/use-mobile-session-close-actions.ts
  • mobile/src/session/use-mobile-session-screen-state.ts
  • mobile/src/session/use-mobile-session-startup.ts
  • mobile/src/session/use-mobile-session-tab-application.ts
  • mobile/src/session/use-mobile-session-tab-switching.ts
  • src/main/runtime/mobile-session-terminal-retirement.ts
  • src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.ts
  • src/shared/session-tab-close-successor.test.ts
  • src/shared/session-tab-close-successor.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/shared/session-tab-close-successor.test.ts
  • src/main/runtime/mobile-session-terminal-retirement.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 1f5596c1-0531-4e70-9caf-4f777fa7a46f

📥 Commits

Reviewing files that changed from the base of the PR and between 5052aee50bfbf3b5f3431c4bdc2d3cd0e96cf23b and 1da2dc99957132b9953db096acc5e1d6cc736f01.

📒 Files selected for processing (15)
  • mobile/src/session/active-session-tab.test.ts
  • mobile/src/session/active-session-tab.ts
  • mobile/src/session/mobile-session-route-parity.test.ts
  • mobile/src/session/mobile-session-route-types.ts
  • mobile/src/session/use-mobile-session-tab-application.ts
  • src/main/runtime/client-session-tab-closed-selection.ts
  • src/main/runtime/mobile-session-terminal-retirement.test.ts
  • src/main/runtime/mobile-session-terminal-retirement.ts
  • src/main/runtime/orca-runtime-apply-mobile-session-tab-navigation.ts
  • src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.test.ts
  • src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.ts
  • src/main/runtime/runtime-mobile-session-result-finalization.ts
  • src/shared/runtime-session-contracts.ts
  • src/shared/session-tab-close-successor.test.ts
  • src/shared/session-tab-close-successor.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • mobile/src/session/use-mobile-session-tab-application.ts
  • mobile/src/session/active-session-tab.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.

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

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Preserve recentTabIds during hydration… · orca-runtime-apply-mobile-session-tab-navigation.ts:48-55

src/main/runtime/orca-runtime-apply-mobile-session-tab-navigation.ts:48-55
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve recentTabIds during hydration rebuilds.

When navigation stores global history, a later graph sync can call hydrateHeadlessMobileSessionTabsFromWorkspaceSession. The rebuilt nextSnapshot omits recentTabIds, then replaces the existing snapshot. A later cross-group close therefore loses global MRU ordering and can select b-old from group history instead of a-new. Carry existing.recentTabIds into nextSnapshot.

Suggested fix
         : {}),
-        tabs: mergedTabs
+        tabs: mergedTabs,
+        ...(existing?.recentTabIds
+          ? { recentTabIds: [...existing.recentTabIds] }
+          : {})
       }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: a15e83ef-faeb-4e1f-b3e3-58f7c14a5f86

📥 Commits

Reviewing files that changed from the base of the PR and between 1da2dc99957132b9953db096acc5e1d6cc736f01 and f0a1bedd6fb1658b7766e743bf0658a9d8a2664d.

📒 Files selected for processing (5)
  • src/main/runtime/mobile-session-terminal-retirement.test.ts
  • src/main/runtime/mobile-session-terminal-retirement.ts
  • src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.test.ts
  • src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.ts
  • src/shared/session-tab-close-successor.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.ts
  • src/main/runtime/mobile-session-terminal-retirement.test.ts
  • src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.test.ts
  • src/main/runtime/mobile-session-terminal-retirement.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Closing a document on the phone cleared the active tab and the next snapshot
fell back to the leftmost remaining tab. Desktop already prefers the previously
viewed tab. Phone switches are caller-only, so that visit history never reached
the host.

Remember viewed tabs on the phone. On close, activate the previous visit if it
is still open; otherwise activate the most recently added remaining tab.

Example:
- Input: viewed Terminal, then Doc A, then Doc B; close Doc B
- Before: jumps to the leftmost tab
- After: returns to Doc A
Add an authoritative cross-group MRU (`recentTabIds`) to
RuntimeMobileSessionTabsSnapshot and its public result: recorded on
activation (including caller-only activations that never focus the host
window), pruned against surviving top-level tabs on close and projection,
and consulted before the per-group MRU merge in both successor paths
(closeHeadlessMobileTerminalTab and retireTerminalSurfacesFromSnapshot).
The per-group merge stays as the fallback for snapshots that predate the
field.

Also drop the leftmost-tab fallback in resolveActiveSessionTab (a
snapshot with no active tab now picks the most recently added remaining
tab) and prune mobile recent history only after orphaned dirty drafts
are appended, so closing another tab can still return to a draft.

Bump the mobile route parity snapshots for the hook extraction changes.
Regression tests for the review round: the cross-group close and
retirement paths must follow the global visit history (a-new wins over
the older b-old / first-group pick), the close path prunes recentTabIds,
resolveActiveSessionTab falls back to the most recently added tab when
the snapshot has no active tab, and pruneRecentTabIds drops stale
entries. Each was red against the pre-fix tree.
Closing the active tab must not resurrect an older group sibling over a
newer cross-group visit: chooseActiveSurface now keeps a surviving active
surface first, then consults the global visit history (per-group merge
for older snapshots), before the repaired group fallback.

Both snapshot filters now use pruneRecentTabIds and assign the result
explicitly, so an emptied history falls back to the group history
instead of silently selecting the last-added tab, and stale ids no
longer survive through conditional spreads.
…tory fallback

Regression coverage for the two late review findings: retiring the active
tab keeps the newer cross-group visit over the repaired group sibling,
and a history that filters to empty falls back to the group history (and
drops its stale ids) instead of the last-added tab.
@nwparker

nwparker commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Closing as superseded — the linked issue (#15219) has been resolved on main by #31365 (52f6960751). Thanks for the contribution!

@nwparker nwparker closed this Nov 5, 2026
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]: Closing a document on mobile jumps to the leftmost tab

2 participants