Skip to content

fix(native-chat): make Claude /resume reachable from Orca Mobile (STA-4617) - #16684

Closed
brennanb2025 wants to merge 9 commits into
mainfrom
brennanb2025/sta-4617-claude-mobile-sync
Closed

brennanb2025 wants to merge 9 commits into
mainfrom
brennanb2025/sta-4617-claude-mobile-sync

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​664 $\color{#cf222e}{\Huge{\mathbf{−}}}$​9 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​655
Prod 17 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​295 $\color{#cf222e}{\Huge{\mathbf{−}}}$​71 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​224

ELI5

On the phone, Claude's /resume was missing from the / menu, so there was no way to pick an earlier conversation. And even when you typed it out, Claude answers /resume by drawing its own session picker in the terminal — which the chat view is covering — so the command looked like it did nothing. Now /resume is in the menu, and using it reveals the terminal where the picker actually is. Desktop had the same dead end, so it gets the same reveal.

User-facing before / after

Before After
On your phone, you open the / command menu in a Claude chat and look for /resume It isn't there. There is no way to reach an earlier conversation from the phone /resume is listed alongside the other Claude commands
On your phone, you type /resume yourself and send it Nothing visibly happens. Claude did open its session picker, but it drew it in the terminal, which the chat view is covering Orca switches you to the terminal, where the picker is waiting
The send's acknowledgement gets lost on a flaky mobile link Same dead end, with no way to tell whether it went through Orca still reveals the terminal — you see either the picker or your untouched prompt
On the desktop, you run /resume from the native chat view Same invisible picker Orca reveals the terminal for you
You had already switched to the terminal yourself while the command was in flight You could get yanked back into the chat view, hiding the picker again You are left where you are
Any other slash command — Unchanged; only commands that answer with the agent's own picker reveal the terminal

When it bites

Anyone trying to return to an earlier Claude conversation — on Orca Mobile it was impossible, and on
the desktop it looked like the command did nothing.

What Changed

Shared catalog

  • src/shared/native-chat-slash-commands.ts: add resume to the Claude catalog (claude + openclaude), and mark the commands whose answer is the agent's own interactive picker (resume for Claude and Codex) with opensAgentPicker. New nativeChatSlashCommandOpensAgentPicker(draft, commands) matches the leading token only, exactly like classifyNativeChatSend.

Mobile

  • mobile-native-chat-send-classification.ts: mobileNativeChatSendOpensAgentPicker(agent, text), resolving the agent's verified catalog.
  • use-mobile-native-chat-message-send.ts: optional onAgentPicker, fired for a control send whose command opens a picker — on accepted and on unknown. An ack-lost send usually did land (the hook's own comment says so, from a cellular-relay report), so the picker is open and invisible on exactly the links this bug came from. Revealing is self-correcting: the user sees the picker or their untouched prompt. Session-option dispatches (recordControlSend: false) keep their own decision.
  • use-mobile-session-view-mode.ts: new idempotent showTabTerminalView, factored out of toggleTabChatView so both derive "currently chat" from the same live ref. An absolute set that already holds returns without re-persisting.
  • use-mobile-native-chat-controller.ts: the existing handleAgentPicker (the seam /model already uses) now calls showTabTerminalView and is passed to the send hook.

Desktop

  • use-native-chat-slash-command-dispatched.ts (new): the chat view's handler for a verified slash command that just went out — the local Ran /x marker, plus the reveal. NativeChatView uses it in place of its inline onSlashCommand.
  • switchNativeChatToTerminal is now an absolute set (setTabViewMode(tabId, 'terminal')), not a pass through toggleNativeChatForLeaf. Once the reveal waits on the send, it fires from a callback captured before that wait, and the toggle's closure holds the old effectiveChatViewMode / chatLeafId — so a user who reached the terminal themselves meanwhile (or a leaf that took its own exit route) was flipped back into chat with a null chat leaf, hiding the picker. Same hazard the mobile half was hardened against, same remedy. It no longer reads either value, so there is no stale state left to be wrong about — the hazard is gone by construction rather than guarded, which is why this one has no test of its own while mobile's showTabTerminalView does.
  • The session-option dispatch hands over its own settle signal too. Its report fires while the model-switch observer is still awaited, so a future catalog entry marked opensAgentPicker would have unmounted the composer mid-dispatch and aborted the confirmation.
  • onSlashCommand now carries the send handle's settled promise, and the reveal waits on it. Revealing synchronously destroys the send it is revealing: the view switch unmounts the chat subtree in the same commit, and useNativeChatSendLifecycle's unmount cleanup cancels every tracked handle — Claude's delayed Enter never fires and onCancelUnsubmitted Ctrl+U's /resume off the TUI line, while Codex (typed key-by-key) loses it mid-word. That second one would have been a regression on main, where Codex /resume is already in the catalog. The same commit also discarded the composer's own setDraft(''), so the command reappeared in the box. The marker still lands immediately — it is feedback for the user's action, not for the writes.

Why

The Claude catalog is the single source both the / autocomplete menu and send classification read, so an omitted resume is literally "resume unavailable" on a phone: the menu never lists it and a typed /resume classifies as unknown-token.

Discoverability alone is not enough, and would have made things worse on desktop — advertising a command that leads to an invisible picker is the dead end this PR exists to remove. The reveal belongs to the view, because only the view knows the chat surface is covering the TUI the picker draws in. Mobile already had that mechanism (onAgentPicker → terminal view) built for Codex /model; desktop had the same seam (onSwitchToTerminal) wired only into session options. Both now route picker-opening slash commands through it rather than growing a second mechanism. Putting the desktop half on onSlashCommand covers both of its dispatch paths (typed send and / menu) and works for agents like openclaude that have slash commands but no session-option catalog.

showTabTerminalView exists because the reveal fires after an async send: a toggle guarded by a render snapshot could flip a user who had already switched to the terminal back into chat — hiding the picker and persisting that override.

Linked Issue

STA-4617 (GitHub #15067).

Scope note: this PR covers the /resume half of that ticket only. The transcript-sync half — a Claude session on an SSH host showing an empty chat on mobile — is fixed by #15672 (STA-3846), which reroutes nativeChat.readSession / subscribe to read on the execution host; that change is not duplicated here.

Visual Proof

No new chrome. /resume renders as an ordinary row in the existing / suggestion list, and the reveal is the same transition the long-press "switch to terminal view" action already performs. Proven at render level instead: MobileNativeChatComposer.test.ts mounts the real composer with agent: 'claude' and /res, and asserts the rendered menu contains /resume and its description row.

Mobile emulator QA was not run: exercising this end to end needs a paired host with a live Claude session (the native-chat pairing rig), which is out of proportion to the change. Every seam it touches is covered by failing-first tests below.

Testing

  • Automated tests added, and proven non-vacuous
  • Manual mobile-emulator / Electron run (see Visual Proof)

Non-vacuity, measured on this HEAD. All five fix points were neutered at once — resume removed from the Claude catalog, the mobile reveal short-circuited, the mobile reveal reverted to a toggle, showTabTerminalView's already-holds guard deleted, and the desktop reveal short-circuited. 17 tests failed:

  • mobile src/session/: 9 failed / 1,220 passed
  • root src/shared/native-chat-slash-commands + src/renderer/.../native-chat/: 8 failed / 784 passed

Restoring all five makes every one pass. The temporary mutation is not in this branch.

The settle deferral has its own arm: reverting the reveal to a synchronous call and dropping the handle hand-off from both desktop dispatch paths fails 4 tests (holds the reveal until the send settles, the two composer hand-off tests, and the picker-dispatch hand-off test).

Suites run on this HEAD:

  • Root Vitest src/shared/ + src/renderer/src/components/native-chat/ + src/renderer/src/components/terminal-pane/: 1,033 files / 10,637 passed (4 files, 66 tests skipped)
  • Mobile Vitest, CI's exact invocation (cd mobile && npx vitest run, no path filter): 467 files / 3,768 passed / 3 skipped
  • pnpm run typecheck (node + cli + web) — clean
  • cd mobile && npx tsc --noEmit — clean
  • oxlint on the changed trees — clean; oxfmt --write applied
  • node config/scripts/check-changed-code-quality.mjs — 0 findings (native, type-aware, React Doctor) across 15 changed files
  • pnpm run check:max-lines-ratchet — OK, no new bypasses (no max-lines suppression added; the desktop reveal was extracted to its own module rather than growing NativeChatComposer.tsx past its cap)
  • git diff --check — clean; mobile/pnpm-lock.yaml unchanged (patchedDependencies still present)

Rebase note

Rebased onto main after the first CI run: the branch had forked 273 commits earlier, and CI tests refs/pull/N/merge. The merged tree had since moved the mobile chat send's pre-body input clear into its own real transport write, which the new controller-test describe did not stub — its sends came back rejected and the reveal assertions failed in CI while passing locally on the stale base. Fixed by giving that describe the same accepted-terminal.send client stub the file's other blocks already use. The conflict in use-mobile-native-chat-message-send.ts was resolved by deriving revealsAgentPicker next to the classification main now computes earlier.

static analysis also fails on the rebased head, at Verify localization catalog — a pre-existing main break, not this branch. Evidence: the branch touches no i18n file (git diff --name-only <merge-base>..HEAD -- src/renderer/src/i18n/ is empty), the failure reproduces locally on this branch's merged tree and names only components.skills.* keys from #16682, and two unrelated PRs from another author fail the identical step (nwparker/minidump-module-cap-minimal, nwparker/image-viewer-definite-box-minimal).

Job-level CI outcome on head 7bdd4f91d3 (the tree before the last commit): Mobile Checks green, and PR Checks 44 success / 1 skipped / 2 failures — the two being static analysis above and verify, which is only its aggregate. All 32 test shards (node 24 and 26), typecheck, Git compatibility, xterm patch sync, shell contracts, package, package (windows), and e2e passed.

Two failures on that first (stale-base) run are not from this branch and reproduce on main: static analysis → verify:localization-catalog on ja.json (from #16682's skills i18n; this branch touches no i18n file), and package (windows) → an EPERM temp-dir rm in config/scripts/rebuild-native-deps.test.mjs.

AI Disclosure

Internal contributor.

Review

Remote wire compatibility

No wire change. The catalog and the picker predicate are client-side data and pure functions; no RPC params, stream frames, or host-published payloads are touched. A mixed-version pair is unaffected — an older client keeps the old menu, and the send itself is the same terminal.send it always was.

Execution host / SSH / WSL / Windows

/resume is delivered by the same send path as any other chat send, so the execution host still owns the write and the agent's picker draws on the host that runs the agent. Nothing here reads a transcript, resolves a path, or infers process state, so there is no new host-ownership or path-separator surface. The unknown-outcome reveal is deliberately aligned with the existing rule that loss of contact is not evidence the write failed.

Folder workspaces

The change keys off the tab's agent and its chat/terminal view mode, never off a worktree or git identity, so folder workspaces behave identically.

Known trade-off

Adding resume to the Claude catalog means a user-authored Claude skill named resume is now noise-filtered like any built-in instead of being re-surfaced as a user bubble by surfaceSkillInvocationUserTurns. That is inherent to catalog membership and already true of the entries beside it (init, review, compact are at least as plausible as skill names); it is not a new mechanism. Not addressed here — separating built-in from skill envelopes is its own change.

Agent / provider contracts

The picker flag is per-command catalog data, so Grok (no verified catalog) and any agent without a marked command are unaffected; the predicate returns false for an empty catalog, a null agent, an unknown token, and a leading-whitespace draft. openclaude is covered on both platforms even though it has no session-option catalog. Codex /model keeps its existing session-option path — the two reveals cannot double-fire today, since no command is both a marked slash command and a session-option picker command, and the mobile reveal is now idempotent regardless.

Agent skill upstream boundary

  • Not applicable.

Checklist

  • This PR is small and focused
  • I explained what changed and why (including ELI5)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • Lint, typecheck, and the focused/mobile suites pass locally

@coderabbitai

coderabbitai Bot commented Aug 26, 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

The change adds agent-picker metadata to Claude and Codex /resume commands. Mobile classification detects exact picker commands for verified agents. The mobile send hook invokes an optional callback for accepted and unknown picker-command outcomes. The controller uses an idempotent terminal-view setter for the active tab. Renderer slash-command dispatch records command markers and reveals terminal view for verified picker commands. Tests cover catalogs, classification, callback behavior, view switching, dispatch, and menu rendering.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.91% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 22 functions across 25 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 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 and concisely identifies the primary change: making Claude /resume reachable from Orca Mobile. It matches the pull request scope.
Description check ✅ Passed The description is detailed, focused, and covers the change, rationale, linked issue, testing, compatibility considerations, and checklist. The visual proof section explains why screenshots or videos …
Full details: Description check

Explanation

The description is detailed, focused, and covers the change, rationale, linked issue, testing, compatibility considerations, and checklist. The visual proof section explains why screenshots or videos were not provided, although it does not use the template's exact N/A wording.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • 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.

…-4617)

Claude's slash catalog had no `resume` entry, so mobile's `/` menu never offered
it and a typed `/resume` classified as an unknown token. Even once dispatched,
Claude answers it with its own TUI picker, which the chat view cannot render —
so the command read as doing nothing at all.

Add `resume` to the Claude catalog, mark the picker-opening commands (Claude and
Codex `resume`) as such, and have an accepted mobile command send bring the
terminal view forward through the existing agent-picker seam.
…bile reveal

Three fixes from review of the first commit:

- Desktop advertised `/resume` in its `/` menu but never revealed the TUI the
  picker draws in, so the new entry led to the same dead end it removes on
  mobile. The chat view — the only layer that knows it is covering the terminal
  — now owns that decision, through a hook that also keeps the local `Ran /x`
  marker. It covers both desktop dispatch paths and agents with no
  session-option catalog behind them.
- The mobile reveal toggled the view from a render-snapshot guard while the
  toggle itself reads live state. A send resolving after the user switched to
  the terminal would flip them back into chat, hiding the picker and pinning
  the tab there. Added an idempotent `showTabTerminalView` and used it.
- An ack-lost send returned before the reveal. Those usually did land, so the
  picker was open and invisible on exactly the flaky links this bug came from.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/sta-4617-claude-mobile-sync branch from ba0f648 to 71a68c5 Compare August 26, 2026 22:23
@coderabbitai

coderabbitai Bot commented Aug 26, 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.

Revealing the terminal synchronously destroyed the send that triggered it.
Switching views unmounts the chat subtree in the same commit, and the send
lifecycle's unmount cleanup cancels every tracked handle: Claude's delayed
Enter never fired and `onCancelUnsubmitted` wiped `/resume` off the TUI line,
while Codex — typed key-by-key — lost it mid-word. That last one was a
regression, since Codex `/resume` was already in the catalog. The same commit
also discarded the composer's own draft clear, so the command reappeared in
the box on the way back.

The send handle now rides along with the dispatch report, and the reveal waits
on it. The local `Ran /x` marker still lands immediately — it is feedback for
the user's own action, not for the writes.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/sta-4617-claude-mobile-sync branch from 483655d to 7bdd4f9 Compare August 26, 2026 22:36

@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)
mobile/src/session/use-mobile-native-chat-controller.test.ts (1)

796-798: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce the new test comments to the required context. The test names already describe the behavior.

As per coding guidelines: "**/*.{ts,tsx,js,jsx}: DO NOT: be verbose, explain the obvious, walk through the code."

Also applies to: 829-830, 859-860, 884-885

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e92c278-2ec3-48d2-90a2-3552bde4fe8e

📥 Commits

Reviewing files that changed from the base of the PR and between 81f89a7 and 483655da032a4695c3d23674a48d3bc7cff16fc4.

📒 Files selected for processing (20)
  • mobile/src/session/MobileNativeChatComposer.test.ts
  • mobile/src/session/mobile-native-chat-send-classification.test.ts
  • mobile/src/session/mobile-native-chat-send-classification.ts
  • mobile/src/session/use-mobile-native-chat-controller.test.ts
  • mobile/src/session/use-mobile-native-chat-controller.ts
  • mobile/src/session/use-mobile-native-chat-message-send.test.ts
  • mobile/src/session/use-mobile-native-chat-message-send.ts
  • mobile/src/session/use-mobile-session-view-mode.test.ts
  • mobile/src/session/use-mobile-session-view-mode.ts
  • src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/NativeChatView.tsx
  • src/renderer/src/components/native-chat/native-chat-composer-types.ts
  • src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.ts
  • src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts
  • src/renderer/src/components/native-chat/use-native-chat-slash-command-dispatched.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-slash-command-dispatched.ts
  • src/shared/native-chat-slash-commands.test.ts
  • src/shared/native-chat-slash-commands.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • mobile/src/session/use-mobile-session-view-mode.test.ts
  • src/renderer/src/components/native-chat/use-native-chat-slash-command-dispatched.ts
  • mobile/src/session/MobileNativeChatComposer.test.ts
  • mobile/src/session/mobile-native-chat-send-classification.test.ts
  • src/shared/native-chat-slash-commands.test.ts
  • src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
  • mobile/src/session/use-mobile-native-chat-message-send.ts
  • mobile/src/session/use-mobile-session-view-mode.ts
  • mobile/src/session/mobile-native-chat-send-classification.ts
  • mobile/src/session/use-mobile-native-chat-message-send.test.ts
  • src/renderer/src/components/native-chat/NativeChatView.tsx
  • mobile/src/session/use-mobile-native-chat-controller.ts
  • src/shared/native-chat-slash-commands.ts

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

…ggle

`switchNativeChatToTerminal` routed through `toggleNativeChatForLeaf`, whose
closure captures `effectiveChatViewMode` and `chatLeafId`. Now that the reveal
waits on the send, it fires from a callback captured before that wait — so a
user who reached the terminal themselves in the meantime (or a leaf that took
its own exit route) was flipped back into chat with a null chat leaf, hiding the
picker the reveal existed to show. Same hazard the mobile half was hardened
against, and the same remedy: set the view, never flip it. The function no
longer reads either value, so there is no stale state left to be wrong about.

Also hand the session-option dispatch its own settle signal. Its report fires
while the model-switch observer is still being awaited, so a future catalog
entry marked `opensAgentPicker` would have unmounted the composer mid-dispatch
and aborted the confirmation.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Open review findings (not fixed in this PR)

A fourth adversarial review pass on 506bf27261 cleared the core design and both stale-snapshot fixes, but raised five items I did not fix. Recording them so they are not lost — the first is the one that matters.

1. MEDIUM — a send queued behind the revealing one is destroyed (introduced here). The reveal waits for /resume's own settled, but enqueueNativeChatPtySend serializes per PTY. If the user sends an ordinary message before /resume settles, that second handle is still queued when the reveal unmounts the view — cancelPendingSends() cancels it and onOptimisticSendCanceled removes its bubble. The message is silently dropped: never written, no error, draft not restored. The revealing send is hardened against this; its successors are not. A correct fix likely gates the reveal on the pty queue being idle, or reveals without unmounting.

2. LOW — the reveal also fires for a cancelled send. handle.cancel() resolves settled (it never rejects and does not distinguish cancel from completion). Focusing a different pane inside the ~500ms delayed-Enter window cancels /resume and Ctrl+U's it off the line, yet the reveal still runs — dumping the user into the terminal with no picker and no command. A not-cancelled check before revealing would gate it.

3. LOW — telemetry regression. switchNativeChatToTerminal moved from toggleTabViewMode (which emits native_chat_toggled) to setTabViewMode (which does not). The correctness reason for the absolute set stands, but the emit needs to come along, or chat-exit is undercounted — including for the pre-existing Codex /model reveal.

4. LOW — the skill-collision case is worse on mobile than the trade-off noted above. A user-authored Claude skill named resume triggers the reveal, and mobile's showTabTerminalView persists a 'terminal' override — pinning that tab to terminal until the user toggles back, while the skill's output lands in the transcript they were moved away from. The composer already models this (skillCollision); the picker predicate ignores it.

5. LOW / scope — other Codex commands have the same dead end. permissions, skills, mention, theme, personality, subagents, plugins, keymap, statusline are all advertised in the / menu, all answer with an interactive TUI picker, and none are routed through session options. Pre-existing, but the new flag makes them cheap to mark.

Given #1 is a regression this PR introduces, I would not merge as-is — #1 and #2 want fixing first, and #3 is a one-liner.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Independent review verification — CHANGES REQUESTED

Reviewed exact head 506bf27261674466ad28753d2effea5222cce5a2 against STA-4617, the cumulative diff, PR discussion, and current checks.

P2 — revealing /resume can silently discard the next queued chat send

The new reveal waits only for the /resume handle to settle, then unmounts native chat:

  • use-native-chat-slash-command-dispatched.ts:44-46 calls the terminal reveal as soon as that one promise settles.
  • NativeChatComposer.tsx:285-299 tracks a later ordinary send independently.
  • use-native-chat-send-lifecycle.ts:20-30,52-54 cancels every still-tracked handle on unmount and removes its optimistic bubble.

Because PTY sends serialize, a normal message submitted while /resume is still settling can remain queued behind it. The reveal then cancels that successor before it writes, removes the bubble, and provides no rejection/error/draft restoration. This is a user-data-loss regression introduced by this head; the reveal needs to wait for the queue to be idle, avoid unmounting the send owner, or preserve/reject queued successors visibly.

The focused tests are non-vacuous for catalog classification, accepted/unknown picker routing, idempotent view selection, and the revealing command itself, but no test covers a second send queued behind the revealing command. That is the missing regression case.

STA-4617 acceptance and compatibility audit

  • The /resume catalog/discoverability and healthy-delivery picker path work on desktop and mobile.
  • The Claude transcript-sync half of STA-4617 is not in this head. It remains in open PR fix(native-chat): read SSH worktree transcripts on the execution host #15672; neither its routing commit nor its current head is an ancestor of this PR. The full issue therefore cannot be considered fixed by fix(native-chat): make Claude /resume reachable from Orca Mobile (STA-4617) #16684 alone.
  • This diff adds no RPC field, stream opcode, or host-side execution behavior. Execution-host ownership and mixed-version wire behavior are unchanged; the cross-version wire check is green. SSH/WSL/Windows/folder-workspace and non-GitHub provider contracts are low-risk for this focused catalog/view-routing change. The SSH transcript path still depends on fix(native-chat): read SSH worktree transcripts on the execution host #15672.
  • Current CI failures are unrelated to this diff: localization catalog validation fails on existing Japanese Agent terms, and Node 26 shard 9 hit the unrelated worktree-git-common-watch.test.ts watcher flake (23/23 passed locally on rerun). The aggregate verify failure follows localization.

Verification

  • Root focused tests: 5 files, 51 tests passed.
  • Mobile focused tests: 5 files, 105 tests passed.
  • Mobile typecheck passed.
  • Changed-file oxlint passed for root and mobile; git diff --check passed.
  • Electron Playwright/CDP: Claude /resume autocomplete and Resume Session picker passed.
  • iOS simulator via orca emulator: healthy-delivery retest passed and the runtime rendered-screen read independently confirmed the picker. One earlier attempt revealed the terminal with an untouched prompt and no picker/write; QA evidence is posted separately and is not represented as an unconditional pass.

Verdict: NOT CLEAN. The P2 queued-successor loss must be fixed before merge; the full STA-4617 transcript requirement also remains dependent on unmerged #15672.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

QA evidence — exact head 506bf27261674466ad28753d2effea5222cce5a2

All interaction used the permitted surfaces: Playwright over Electron CDP for desktop, and orca emulator for iOS mobile interaction. Simulator screenshots were captured with simctl; no desktop-control or OS input automation was used.

Electron — PASS on healthy delivery

Autocomplete exposes Claude /resume:

Electron before: Claude resume autocomplete

Sending it reveals the real Claude Resume Session picker:

Electron after: Claude Resume Session picker

Orca Mobile iOS simulator — PASS on controlled retest, with an intermittent failure observed

The first run showed the new autocomplete entry:

Mobile first run before: resume autocomplete

On that first send, mobile switched to terminal view but the Claude prompt remained untouched and no picker appeared; a read-only runtime screen check independently showed the same unchanged prompt:

Mobile first run after: terminal revealed without picker

I then repeated the flow on the same paired simulator, host, workspace, tab, and exact PR head. The command was entered again:

Mobile retest before: resume command entered

The retest succeeded: mobile revealed the Resume Session picker, and orca terminal read --screen independently confirmed the same picker on terminal term_70485fff-0b91-440d-9034-47a1cd247509:

Mobile retest after: Resume Session picker

Truthful result: the discoverability and healthy-delivery picker flow are exercisable and passed on both Electron and mobile. Mobile is not claimed as an unconditional pass because one observed attempt took the changed unknown-delivery reveal behavior and surfaced an untouched prompt without a picker or warning; the immediate controlled retest passed. Separately, the review verdict remains blocked by the proven desktop queued-successor cancellation regression.

@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: Pro Plus

Run ID: c6a8ed82-6130-4f2a-ad15-320b57daa862

📥 Commits

Reviewing files that changed from the base of the PR and between 483655da032a4695c3d23674a48d3bc7cff16fc4 and 03a704a.

📒 Files selected for processing (7)
  • src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/native-chat-runtime-send.ts
  • src/renderer/src/components/native-chat/native-chat-send-settlement.ts
  • src/renderer/src/components/native-chat/use-native-chat-session-option-command.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts
  • src/renderer/src/components/terminal-pane/TerminalPane.tsx

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

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review verification — exact head 38445ab994faca4050ec7cde5aa5932b3bc676c6

Re-reviewed the cumulative PR diff and all current discussion findings after the queued-send fix.

Fixes verified

  • Picker reveals now wait for the entire per-PTY send queue, including commands selected from autocomplete; a normal send queued behind /resume is no longer canceled when chat unmounts.
  • Send handles expose cancellation state and stale reveal callbacks suppress terminal switching after cancellation.
  • Absolute terminal reveal restores native_chat_toggled telemetry when transitioning from chat to terminal.

The focused desktop/shared suite passes (52 tests), tsc --noEmit -p config/tsconfig.tc.web.json passes, changed-file oxlint passes, and git diff --check passes. The branch is pushed at the exact SHA above; no merge was performed.

The Claude transcript-sync portion of STA-4617 remains outside this PR and still depends on the separate transcript-sync change; this review does not claim that portion is merged here.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

QA / evidence — exact head 38445ab994faca4050ec7cde5aa5932b3bc676c6

Deterministic code-level verification passed, but rendered QA is a truthful blocker in this workspace:

  • Electron CDP launch could not produce a target; the dev process exited immediately while the host filesystem had only ~117 MiB free (ENOSPC).
  • Mobile interaction was not attempted with orca emulator; the mobile dependency setup could not complete because the same filesystem ran out of space, and the mobile Vitest setup lacks the Expo dependency in this checkout.

Therefore no screenshot or rendered-screen evidence is claimed for this exact head in this run. The remaining gate is to rerun Electron via Playwright CDP and mobile via orca emulator after restoring disk/dependencies; no merge was performed.

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

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/src/components/native-chat/NativeChatComposer.test.tsx (1)

42-47: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mirror the cancellation contract in the test mock.

The production helper forwards cancelled, but this mock drops that argument. The mocked send handle also lacks cancelled, so these tests cannot detect a cancellation-propagation regression.

Proposed mock update
-  sendHandle: { cancel: vi.fn(), settleAfterMs: 500, settled: Promise.resolve() },
+  sendHandle: {
+    cancel: vi.fn(),
+    cancelled: vi.fn(() => false),
+    settleAfterMs: 500,
+    settled: Promise.resolve()
+  },

   notifyNativeChatSlashCommand: (
-    onSlashCommand: ((command: string, settled?: Promise<void>) => void) | undefined,
+    onSlashCommand:
+      | ((command: string, settled?: Promise<void>, cancelled?: () => boolean) => void)
+      | undefined,
     command: string,
-    settled?: Promise<void>
-  ) => onSlashCommand?.(command, settled)
+    settled?: Promise<void>,
+    cancelled?: () => boolean
+  ) => onSlashCommand?.(command, settled, cancelled)

Also applies to: 88-93


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da50646a-d1e2-4b7a-b660-b0865d535d5f

📥 Commits

Reviewing files that changed from the base of the PR and between 03a704a and 38445ab.

📒 Files selected for processing (10)
  • src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/native-chat-composer-types.ts
  • src/renderer/src/components/native-chat/native-chat-pty-send-queue.ts
  • src/renderer/src/components/native-chat/native-chat-runtime-send.ts
  • src/renderer/src/components/native-chat/native-chat-send-settlement.ts
  • src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.ts
  • src/renderer/src/components/native-chat/use-native-chat-slash-command-dispatched.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-slash-command-dispatched.ts
  • src/renderer/src/components/terminal-pane/TerminalPane.tsx

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

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Exact-head review verification — d866a785a0117e3607121240b2ab5beb4df171b3 (live PR head verified before and after push).

Fixed the remaining in-scope defects: /resume reveal now awaits the complete per-PTY send queue, terminal-toggle telemetry uses the resolved active leaf agent in mixed splits, and desktop/mobile transitions remain absolute so stale async callbacks cannot toggle back to chat. Verification passed: desktop/shared 6 files / 53 tests; mobile 5 files / 105 tests; pnpm tc; mobile typecheck; changed-code quality 0 findings across 28 branch files; max-lines ratchet; 98 reliability gates; formatting and git diff --check.

CI classification: the prior ja.json localization failure is base-identical (runs 33020488208 and 33021483710; main later corrected it in 0096e47850c37dc51a643af531c1078c469e0638), while the branch-caused max-lines failure was repaired by the focused type extraction. Final review-until-clean, review-code, and perf passes found no remaining proven in-scope issue; not merged.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Exact-head QA — d866a785a0117e3607121240b2ab5beb4df171b3.

Electron was exercised through Playwright CDP only against the repaired working tree (the app build label reported its checked-out base 38445ab994): Native Chat offered /resume, waited for dispatch settlement, revealed the Claude terminal, and rendered the real Resume session picker.

Electron Claude resume picker

Mobile iOS QA used orca emulator only. The paired app showed workspace pr-16684-review-r1, both Claude tabs, the live Claude transcript, and /resume autocomplete; dispatch then switched to terminal mode with the correct workspace/session tabs and controls. The terminal canvas was blank after the switch, so this mobile screenshot proves the transition and identity preservation, not picker rendering.

Mobile Claude terminal-mode transition

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review verification — exact head 6244468b7b1f2f7635e1a151bb6811f1e0873a44

Re-reviewed the live cumulative diff, all PR discussion and review threads, STA-4617, and the exact-head checks after the final lifecycle-race fix.

Findings verified

  • The picker reveal now follows the live per-PTY queue tail until it is truly idle, so a send appended while an earlier tail drains cannot be canceled by the chat-to-terminal transition.
  • A delayed reveal is suppressed after its originating chat view unmounts.
  • The earlier cancellation-state and telemetry fixes remain intact. All three review threads are resolved/outdated, and I found no remaining proven in-scope defect.
  • The change adds no RPC field, stream opcode, persistence schema, host-side execution behavior, path handling, or shell command. SSH/WSL/Windows/Linux/macOS, folder-workspace, mixed-version wire, and provider boundaries are unchanged.

Verification

  • Desktop/shared focused: 6 files, 55 tests passed.
  • Mobile focused: 5 files, 105 tests passed.
  • Mobile terminal-stream gate: 2 files, 23 tests passed.
  • Root and mobile typechecks passed; max-lines, formatting, and git diff --check passed.
  • Changed-code quality passed under Node 24 with zero new findings across 28 files.
  • Exact-head CI passed static analysis, typecheck, Git and cross-version wire compatibility, shell contracts, Windows packaging, changed e2e, and all relevant suites.

The red aggregate is external to this diff: Node 24/26 shard 10 both fail the same runtime-export parity test in an untouched file, with the correction isolated in #16773, and Linux packaging hit the unrelated Electron WebRTC GPU/DBus no result runner failure. The Claude transcript-sync half of STA-4617 also remains in separate PR #15672 and is not claimed fixed here.

Verdict: the PR implementation is clean and non-regressive at this exact head, but do not merge while required CI is red; re-evaluate after the shared parity fix lands and the Linux packaging check reruns green. No merge was performed.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

QA / evidence — exact head 6244468b7b1f2f7635e1a151bb6811f1e0873a44

Electron — PASS

Validation used Playwright over Electron CDP only. The running dev app identified itself as Orca: 6244468b7b from workspace pr-16684-review-r1 @ 6244468b7b.

Claude native-chat autocomplete exposes /resume — Resume a previous conversation:

Electron before: Claude resume autocomplete

Sending /resume switches the tab to terminal view and renders the real Claude Resume session picker; a read-only PTY main-buffer snapshot independently confirmed the picker and terminal view mode:

Electron after: Claude Resume session picker

Orca Mobile iOS — BLOCKED by unavailable host

Mobile interaction was limited to orca emulator as required. The installed app launched and rendered its workspace list, but every available host/workspace was unavailable or disconnected and the existing Metro session repeatedly failed to reach stale endpoints. Because no live host could accept /resume, the exact mobile picker flow could not be exercised at this final SHA; I did not substitute web, Electron, accessibility, or OS-level input automation for mobile proof.

Deterministic mobile coverage remains green: 5 focused files / 105 tests, 2 terminal-stream files / 23 tests, and mobile typecheck all passed. This comment records a truthful mobile runtime blocker, not an unconditional mobile pass.

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing this terminal-picker reveal implementation. Chat history can already resume into a structured chat through #18933 and #19176, and live terminal handoff was removed in #22783.

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