Skip to content

refactor(runtime): name every mobile-session close outcome so the tombstone decision is explicit (STA-4718) - #15346

Merged
nwparker merged 1 commit into
mainfrom
nwparker/sta-4718-close-outcome-constructors
Aug 18, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/sta-4718-close-outcome-constructors

Conversation

@nwparker

@nwparker nwparker commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

When you close a terminal tab from your phone or from the web, the desktop app has three possible answers:

  1. "Done — I closed it myself."
  2. "I asked the desktop window to close it, and I have no idea whether it actually did."
  3. "No, I'm not doing that."

Answer 1 had a proper name in the code. Answers 2 and 3 were anonymous — just bare little objects scattered across about eight places. Nothing said which of the three any given spot meant.

That matters because answer 1 does an extra bit of bookkeeping: it writes down "this tab is gone, stop showing it to that phone." Answers 2 and 3 deliberately don't. But since they had no name, nobody could tell whether that missing bookkeeping was a careful decision or a slip — and someone filed a bug report saying it was a slip.

It wasn't. Doing it there would have caused a real bug: the desktop is allowed to say "no" to a close (say, you cancelled the "this tab is still running something, really close it?" box). If we'd already written down "that tab is gone," your still-running terminal would disappear from your phone permanently, with no way to bring it back.

So this PR doesn't make the change the bug report asked for. It gives all three answers names, so nobody has to guess again.

What you might notice: nothing at all. Every one of those eight places produces exactly the same result as before, down to the order of the fields.


Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​63 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​63
Prod 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​88 $\color{#cf222e}{\Huge{\mathbf{−}}}$​42 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​46

Relates to STA-4718. Follow-up from #13387.

The ticket as filed is invalid — please read before reviewing

STA-4718 asks that two renderer-mediated close branches in closeMobileSessionTab (orca-runtime.ts, the two return { closed: true } sites) be routed through finishCommittedClose() so they record selection tombstones. Doing that would introduce a bug. This PR deliberately does not do it.

The omission is load-bearing. Both branches call this.notifier.closeTerminal(...), which is declared void — fire-and-forget — and lands in the renderer as closeTerminalTab(tabId, { skipRunningProcessConfirm: true }). That renderer call can legitimately decline to close:

  • no resolvable worktree route (terminal-tab-actions.ts:85-88), or
  • a pinned tab with confirmClosePinnedTab on, which opens a modal the user can cancel (terminal-tab-actions.ts:90-112).

The host never learns the outcome. And a tombstone is not a passive "don't re-select" hint — projectWithoutClosedClientSessionTabs filters the tab out of every snapshot published to that client, and the only thing that clears one is activate() on that tab id, which a client cannot do for a tab it can no longer see. So tombstoning an uncommitted close would permanently hide a live terminal from a paired client, with no recovery path.

This is confirmed by history: 0e96b82e449 introduced finishCommittedClose(), converted the other branches to it, and deliberately left these two alone in the same diff — adding expect(forgetTabs).not.toHaveBeenCalled() to pin the behaviour. One of its sub-commits is literally "fix(mobile): avoid tombstones for uncommitted closes".

The residual symptom the ticket describes — a stale snapshot briefly re-selecting the tab — is real but self-healing, and on the delegated path it is correct: if the renderer refused, the tab really is still there.

What this PR fixes instead

The real defect is narrower and is why the ticket got filed against correct code: closeMobileSessionTab has three distinct outcome classes — host-committed, delegated-to-renderer (outcome unknown), and refused — but only the committed one had a named constructor. The other two were bare object literals across ~8 return sites. Nothing at the type level or in review distinguished "I chose not to tombstone" from "I forgot to".

This adds named constructors for all three, so every return site declares its class and the deliberate no-tombstone decision is self-documenting.

Behaviour preservation

Strictly behaviour-preserving; the existing tests are the guard and all pass unmodified.

  • All 8 return sites produce byte-identical objects, including the four distinct snapshotRepublished conditionalities (missing-intent=true, unknown-liveness=Boolean(snapshot), stale-publication=Boolean(snapshot), stale-terminal=true, live-host-pty=!addressedDeadLeaf, retirement-owner=key absent). refusedMobileSessionTabClose spreads the key only when truthy, so no snapshotRepublished: false ever reaches the wire.
  • RuntimeMobileSessionTabCloseResult is serialized to mixed-version clients over RPC, so the brand is type-only (declare const … unique symbol, never assigned) — src/shared/runtime-types.ts has zero diff.
  • All four expect(forgetTabs).not.toHaveBeenCalled() assertions intact and unweakened.

Tests

1183 pass in orca-runtime.test.ts, plus a new test asserting the delegated path still records no tombstone, phrased around the outcome class rather than the line number.

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds branded mobile session tab-close outcomes with constructors for committed, delegated, and refused closures. Runtime close paths now use these constructors and preserve refusal reasons and snapshot-republication state. Committed closures tombstone selected tabs, while delegated closures leave selection state unchanged. Tests cover delegated terminal closure, renderer notification, PTY retention, and session-tab retention.

Merge Risk: ⚪ Minimal · up to c0e17

This PR makes mobile-session close outcomes explicit without changing wire behavior or tombstone decisions. The delegated and refused paths remain protected from incorrect tombstones, and no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the rationale, behavior preservation, linked issue, and tests, but omits most required template sections and checklist items. Add the required ELI5, What Changed, Why, Visual Proof or N/A, Testing checklist, AI Disclosure, Review, Notes, Checklist, and Author sections.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the runtime refactor and the explicit naming of mobile-session close outcomes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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.

🧹 Nitpick comments (1)
src/main/runtime/mobile-session-tab-close-outcome.ts (1)

4-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the rationale comments.

These comments explain a valid non-obvious rule, but the blocks are not concise. Keep one brief comment for each tombstone rule. Move the full rationale to design documentation.

As per coding guidelines, comments must be concise, non-obvious, and brief; prefer one line and do not explain obvious behavior or walk through code.

Proposed comment-only cleanup
-// Why: a selection tombstone ...
-// ...full rationale...
+// Only host-committed closes tombstone client selection.

-// Why: the required brand ...
-// ...full rationale...
+// The private brand requires named outcome constructors.

-// Why: the host tore the tab down ...
-// ...full rationale...
+// Host-committed closes may tombstone client selection.

-// Why: the close was handed to the renderer ...
-// ...full rationale...
+// Delegated closes do not tombstone because the outcome is unknown.

-// Why: `snapshotRepublished` tells the client ...
+// Include this flag only when the refusal republishes a snapshot.

Also applies to: 12-17, 26-28, 38-41, 46-47

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c726e68-2918-4cc5-a83f-03c2bedd771c

📥 Commits

Reviewing files that changed from the base of the PR and between 2a760e3 and c0e175d.

📒 Files selected for processing (3)
  • src/main/runtime/mobile-session-tab-close-outcome.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/orca-runtime.ts

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

@nwparker

Copy link
Copy Markdown
Contributor Author

Cross-platform verification

Run on real hosts via orca-cli, not emulated.

Linux — Ubuntu 24.04 (kernel 7.0.0), node v26.1.0, pnpm 10.24.0, over SSH.
Windows — awin, Git Bash (MINGW64), node v24.18.0, WSL Ubuntu-24.04 + Sta4593-Federated both running.

Scope: src/main/runtime/orca-runtime.test.ts

host result
Linux Test Files 1 passed / Tests 1183 passed | 1 skipped
Windows fails to collect — identical on origin/main

On Windows this suite does not collect at all (Tests no tests) on both origin/main and this branch, so it is pre-existing and unrelated to this change. Verified by running both refs back to back:

--- BASELINE origin/main ---   FAIL src/main/runtime/orca-runtime.test.ts   Tests  no tests
--- OURS ---                   FAIL src/main/runtime/orca-runtime.test.ts   Tests  no tests

Linux gives full coverage of the change: all 1183 tests pass, including the four expect(forgetTabs).not.toHaveBeenCalled() assertions that pin the deliberate no-tombstone behaviour.

@nwparker
nwparker merged commit ccb2305 into main Aug 18, 2026
47 checks passed
@nwparker
nwparker deleted the nwparker/sta-4718-close-outcome-constructors branch August 18, 2026 22:17
paidaxingyo666 pushed a commit to paidaxingyo666/Manta that referenced this pull request Aug 21, 2026
dallascrilley pushed a commit to dallascrilley/orca that referenced this pull request Aug 27, 2026
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 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.

1 participant