Skip to content

feat(native-chat): one queue header that becomes the pause row; drop the composer Resume and the send dialog - #28654

Merged
brennanb2025 merged 8 commits into
mainfrom
brennanb2025/queue-paused-header
Oct 15, 2026
Merged

brennanb2025 merged 8 commits into
mainfrom
brennanb2025/queue-paused-header

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 18 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​325 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1048 $\color{#cf222e}{\Huge{\mathbf{−}}}$​723
Prod 26 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​383 $\color{#cf222e}{\Huge{\mathbf{−}}}$​544 $\color{#cf222e}{\Huge{\mathbf{−}}}$​161

ELI5

Messages you send while the agent works wait as cards above the chat box. When you press Stop, those cards pause. Today a paused queue shows its controls in three places at once: a row reading "Queue paused because you interrupted" with Resume, the chat box's round Send button turning into a second Resume, and a "Send message?" pop-up if you type a new message. With nothing paused, the cards have no heading at all.

Now there is one heading row above the cards. It reads Queued and the number of cards. After a Stop it reads Paused and the number, with Clear all and Resume beside it. A small arrow folds the cards away. The Send button always stays Send, and a message you type into a paused queue just sends; the paused cards follow it.

What Changed

Before (desktop and phone):

  • No heading over the cards unless the queue was paused.
  • Paused: a row reading "Queue paused because you interrupted" with Resume.
  • Desktop only: with nothing typed and the agent idle, the chat box's round Send button became a second Resume (a play triangle).
  • Desktop only: typing a message into a paused, idle queue opened a "Send message?" dialog asking whether to clear the waiting cards first ("Clear queue" / "Send message").

After:

  • One heading row is always the first row of the card box: "Queued" + count + a fold arrow.
  • While the host publishes the Stop pause, the same row reads "Paused" + count + Clear all + Resume + fold arrow. The "Stopped" note in the transcript already says why, so the row no longer repeats the long sentence.
  • The fold arrow hides only the cards. The heading, count, Clear all and Resume stay. Folding is local to that view, is not saved anywhere, and the list opens again once the queue empties. On desktop the cards stay mounted while folded, so an edit in progress keeps its typing.
  • The chat box's button is Send when idle and Stop while working. It never becomes Resume.
  • No "Send message?" dialog. A message sent into a paused, idle queue goes straight to the agent. The host already lifts the pause when that turn is accepted, so the paused cards then follow in order.
  • Clear all deletes every waiting card through the existing per-card delete, one at a time, stopping at the first failure (one failure, one notice). No new host method.

Mechanism:

  • Desktop: NativeChatQueuedMessageList renders a NativeChatQueueHeader row in place of the old pause row. The queue controller drops queueResume and queueHold (the composer's copies of Resume and the dialog's clear) and exposes clear, the same sequential delete the dialog used.
  • Removed: native-chat-composer-primary-action.ts (the Send→Resume swap), use-native-chat-held-queue-composer-send.ts and NativeChatQueueSendConfirmDialog.tsx (the dialog), their transport and composer wiring, the sentFrom parameter that only the dialog's deferred send used, their tests and their strings in every locale.
  • Phone: MobileNativeChatQueuedMessages gets the same header row (each control keeps a 44 pt touch target), with Clear all over the existing Delete. The phone never had the composer Resume or the dialog.
  • New strings: Queued, Clear all, and the fold arrow's Collapse / Expand tooltip, translated in every desktop locale. "Paused", "Resume" and "Queued messages" reuse existing strings.

Why

  • One place for the queue's state and its two actions. The old layout said "paused" in up to three places and gave two Resume buttons for one action.
  • The dialog asked a question no one needs answered: sending already lifts the pause and the cards follow. Someone who wants the cards gone now has Clear all on the row, before or after sending.
  • Reusing what exists: the header reads the pause the host already publishes, Resume calls the existing agentSession.queuedMessagesResume, and Clear all reuses the per-card delete. The change removes far more code than it adds.
  • Considered and rejected: a neutral "Queued" title with a separate pause row under it (two rows describing one list); keeping the full pause sentence in the row (truncates at chat width and repeats the transcript's "Stopped" note).

Mixed versions, remote hosts, folder workspaces, phone. Client presentation only: no wire, host or capability change.

  • New client, old host: the header reads the same published cards and pause; Resume and Delete are methods older hosts already have.
  • Old client, new host: nothing changes for it; it keeps its own layout (old desktops keep the composer Resume and dialog until they update).
  • SSH, WSL and folder workspaces: the host owns the queue and the pause; the header only renders what it publishes, so nothing differs by host or workspace kind.
  • Queueing your own messages is still off (agent-session.queued-messages.v1 stays dark), so today the header shows over cards that already reach the queue (other agents' messages, cards kept across a restart). It is ready for the switch-on.

Differences from the common pattern.

  • Intended: the count and the fold arrow stay in the paused row, so the row is both the queue's title and its pause bar. One row instead of two; nothing is said twice.
  • Intended: after a reopen or /clear, the header reads "Queued", not "Paused". The host only publishes a pause for a person's Stop; the cards' own Send and any new message release the others, as before.
  • Intended: a folded list shows no summary of a card that needs attention. The count stays visible, and folding is the person's own choice that resets once the queue empties.

Linked Issue

N/A (maintainer PR)

Visual Proof

Hidden, isolated dev build on macOS, a stand-in Codex agent, queueing switched on locally for the run only (not committed). Before = the base commit, after = this branch.

While the agent works, two messages waiting

Before After
before queued after queued

Folded with the arrow (after): after collapsed

After Stop, settled. Before: the pause sentence with Resume, and the chat box's button is a second Resume (play triangle). After: "Paused 2" with Clear all and Resume; the chat box's button stays Send.

Before After
before paused after paused

Typing "C" + Enter into the paused queue. Before: the "Send message?" dialog. After: C goes straight to the agent; A and B wait under "Queued 2" and follow it.

Before After
before dialog after send

Clear all (after): paused D and E, then Clear all; the box is gone and focus returns to the chat box.

Paused After Clear all
after paused again after cleared

Testing

  • Electron QA (macOS), driven by Codex gpt-5.6-terra (medium) over Playwright CDP on a hidden isolated rig (ORCA_BACKGROUND_LAUNCH=1, isolated HOME / CODEX_HOME / CLAUDE_CONFIG_DIR / user data, checked with ps eww), stand-in Codex agent, queueing capability added locally for the run only:

    • Before: no heading over the cards; after Stop the pause sentence + Resume and the chat box's button reads Resume (play icon); sending C opened "Send message?".
    • After: "Queued 2" heading; the arrow folds only the cards (aria-expanded true → false → true); after Stop "Paused 2" + Clear all + Resume, chat box button Send (disabled while empty, arrow icon); sending C opened no dialog, and the agent log shows C, then A, then B. Clear all over paused D and E removed both, D and E never reached the agent, focus returned to the chat box.
  • Phone UI QA: BLOCKED. No Orca Mobile development client built from this branch is installed in a simulator on this machine, and a sibling phone PR's emulator run was blocked by the emulator typing the dev-client URL wrongly. The phone header is covered by component tests (Paused / Queued text and count, Clear all one card at a time stopping at the first failure, fold, 44 pt targets, no row with no cards).

  • Targeted vitest: NativeChatQueuedMessageList (+ inline-edit), the queue controller (+ resume), NativeChatComposerActions, NativeChatStructuredSession, submission navigation, submit reveal, composer transport, queue gating, queued turn order, the cross-process structured-agent-session-queued-card-holds contract (Node runner), and the phone MobileNativeChatQueuedMessages, composer tray, queued-card and feed tests — all pass. (The tray test's background-tasks toggle lookup is now scoped to that strip, since the queue header also has a fold toggle.)

  • pnpm tc:web and the phone typecheck pass. pnpm tc:node fails only at structured-agent-session-message-options.ts:30 (missing now), untouched here and on main (fix in fix(i18n): translate 21 new strings to es/fr/ja/ko/zh #28629).

  • Remote CI: static analysis and typecheck, Mobile verify, the mobile bundle and Node test shards 2 and 4 pass; shards 1 and 3 were still queued when this was written. Shard 5 fails only src/main/runtime/orca-runtime-files-artifact-facade.test.ts (openMobileFile arity 4 vs 3): that file is not on this branch's base and this PR touches nothing under src/main/runtime; it comes from two crossing main changes (Typecheck file access and watchers #27582's facade test, feat(cli): open files at a requested line and column #28240's new openMobileFile argument) in CI's merge with current main.

  • orca-ci-checks (local): everything this PR touches passes (lint, changed-code quality, React doctor, ratchets, catalogs). Remaining failures are untouched main issues: the now error above, the Bun typecheck runner's missing script, three stale es.json keys (AgentsPane.setupOnly, setupOnlyHint, AgentCombobox.setupOnly), and the native no-cycle audit in mobile/src/transport/* and mobile/src/terminal/document/*, which this PR does not import or change.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Tests that pinned the removed composer Resume and dialog were deleted; the list, controller and phone tests were rewritten for the header, and the cross-process card-holds contract no longer projects a composer Resume or dialog.

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

No new I/O, timers, polling or stored state. The fold state is one local boolean per view.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • 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 (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…mposer Resume or send dialog

The queued cards now sit under one header row on desktop and phone: "Queued"
and the count normally, and "Paused" with the count, Clear all and Resume
while the host holds the queue after a Stop. A chevron folds only the cards;
the choice is local to the view and resets when the queue empties.

The composer's Send no longer turns into a second Resume, and a message sent
into a paused queue no longer asks "Send message?": it sends, its accepted
turn lifts the pause, and the cards follow it. Clear all deletes every card
through the existing per-card delete. No wire or host change.
@brennanb2025

brennanb2025 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Status: ready for review. Desktop QA passed all four cases (main compared with this PR), phone QA passed all six cases, and the one final sync with main is merged; CI is green on it (16 passed, 16 skipped).

What this PR changes for a user

Before (main): when you stop the agent while follow-up messages are queued, the queue shows "Queue paused because you interrupted" with a Resume button, the empty composer's send button also turns into Resume, and sending a new message opens a "Send message?" dialog asking whether to clear the queue.

After (this PR): the queue has one collapsible header row with a count: "Queued 2" while the agent works, "Paused 2" with Clear all and Resume after Stop. The composer keeps its ordinary Send button, and sending a new message while paused just sends it, with no dialog. Clear all deletes the cards that are shown at that moment, using the same per-card delete as the trash button.

Desktop QA (rendered, hidden test windows, main vs this PR at cdd1529)

Case Main This PR Result
1. New workspace with a setup script: tabs, active tab, setup output, sidebar status Setup + Codex Chat (active); setup ran to completion Identical Pass, no difference
2. Paused queue look Pause sentence + Resume, no count, no fold; composer button becomes Resume; sending opens the dialog "Queued 2" while working; "Paused 2" + Clear all + Resume + fold after Stop; fold hides and restores the cards; composer keeps Send; sending starts a turn with no dialog and the queued cards stay Pass, all differences intended
3. Clear all with each delete delayed 20 s Only the dialog's "Clear queue"; deletes run one at a time; with a forced failure, later cards were never tried and no error appeared Two presses 118 ms apart sent one batch (each card deleted once, in parallel); a card queued while the deletes were pending survived; with forced failures, one error toast for the batch and the cards stay Pass
4. Send and Steer while a Clear is pending Send did nothing; the draft stayed in the box Send started the new message right away; Steer on a card queued during the Clear worked Pass

The agent in the test was a stand-in program that speaks the real Codex protocol; turns, Stop and Steer go through the real Orca host. The slow and failing deletes were injected at the app's own request handler, the same way on both builds. Queueing is switched off in the product today, so both builds were run with the queue capability turned on for the test only (not committed).

Why the earlier run doesn't count. The previous desktop run on this PR was invalid: while it was being set up, a test terminal started the real Codex with the real user home. On macOS, Orca opens terminals through a login shell, which resets the home folder and adds the system Homebrew folder (where the real codex lives) to the path, so isolating the home and path at app launch wasn't enough. This run closed both: terminals skipped the login shell, the system shell startup files were turned off, and every agent name resolved to a stand-in or to nothing. A check before each launch refused to start otherwise. Inside the running app, the setup script recorded the throwaway home and the stand-in Codex. A watcher sampled every test process every 2 seconds across all three launches: none had the real home and no real agent binary started. The real agent config files had the same hashes before and after.

Phone QA (iPhone simulator, this PR at cdd1529)

A development build of the phone app on its own iPhone simulator, paired over the local network with a hidden, isolated desktop test copy (same safety setup as the desktop run: stand-in agent, throwaway home folders, no login shell, and a gate that refused to launch if any real agent was reachable). Grok drove the phone through the simulator's accessibility tree; screenshots are the simulator's own screen captures.

Case What the phone showed Result
Queued header while working "Queued 2" with a fold chevron above the two cards; no Clear all or Resume; composer keeps Send Pass
Paused header after Stop "Paused 2" with Clear all, Resume and the fold; no "Queue paused because you interrupted" sentence; empty composer shows an ordinary (disabled) Send, not Resume. The three header controls are about 44 pt tall Pass
Fold Folding hides the cards and keeps "Paused 2", Clear all and Resume; unfolding brings the cards back in order Pass
Send while paused Sending "QA D" showed no dialog; it started a new turn, the two cards stayed, and the header became "Queued 2" Pass
Resume After another Stop, Resume started the first waiting card; header became "Queued 1" Pass
Clear all One tap removed both cards and the header with no dialog or error; the next message sent normally Pass

The real agent config files on the test machine have no trace of this run (the two files that changed during it were being written by other sessions on that machine, as in the desktop run), and the process watcher saw no rig process with the real home and no real agent binary.

Things a reviewer might not expect

On this PR (not regressions):

  • Clear all shows no progress while the host is slow. With a 20 s delete delay the header and cards look unchanged, and a second press seems to do nothing (it correctly joins the first). Main's dialog Clear showed no progress either.
  • A failed Clear shows one short generic toast ("Orca couldn't confirm what happened. Check the chat.") for about 8 s, with no lasting mark on the cards. The wording came from the injected error code, so a real failure may read differently. Main showed nothing for the same failure.

On both builds, outside this change:

  • On the first Codex send in a fresh profile, a "Orca now runs Codex without its shared server" toast appears and stays over the corner of the composer.
  • The setup trust prompt is titled "Run setup script from project?" but its button says "Run hooks".
  • On the phone, a stopped turn shows "Cancellation requested." under "Interrupted after …"; the desktop shows only the "Interrupted after …" line. Both come from the existing host stop note, not from this change.

About the phone run:

  • Once, before the first message was sent, the phone app dropped back to the iPhone home screen and lost the unsent draft; reopening it reconnected to the same chat. The test machine was heavily loaded at the time (load average around 500 to 700), so this is unconfirmed as an app bug.

Not covered

  • A Clear where some deletes succeed and others fail (the desktop injection failed all of them).
  • The phone run used this PR only; main's phone layout was not captured side by side.
  • No latency, CPU or memory numbers.

Earlier checks and the final sync

Three review rounds and a final narrow check at cdd1529 found no blocking issue, and CI on cdd1529 passed (16 passed, 16 skipped). The one final sync with main is merge commit e7bb8b3; it had no conflicts, and the PR still changes the same files against main as before the sync. CI on e7bb8b3: green (16 passed, 16 skipped).

Desktop screenshots (whole window, uncropped)

Paused queue after Stop:

Main This PR
Main: queue paused sentence with Resume PR: Paused 2 header with Clear all, Resume and fold

Sending a new message while paused:

Main This PR
Main: Send message dialog asking to clear the queue PR: new message sent, no dialog, queued cards kept

Phone screenshots (this PR, simulator screen captures)

Paused 2 Folded Send while paused After Clear all
Phone: Paused 2 header with Clear all and Resume Phone: folded queue keeps the header Phone: QA D sent with no dialog, header Queued 2 Phone: queue and header gone after Clear all

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 023705b1-ef76-46c4-ae58-6dfede0b83d5
📥 Commits

Reviewing files that changed from the base of the PR and between e7bb8b3 and 35e7415.

📒 Files selected for processing: 10
  • mobile/src/session/MobileNativeChatOverlay.tsx
  • src/renderer/src/components/native-chat/NativeChatComposerActions.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx
  • src/renderer/src/i18n/en-runtime-required.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
💤 Files with no reviewable changes: 1
  • src/renderer/src/components/native-chat/NativeChatComposerActions.test.tsx
🚧 Files skipped from review as they are similar to previous changes: 6
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/ko.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

📝 Walkthrough

The queue controllers add bulk-clear actions, and the mobile and desktop queue views add collapsible headers with queue counts and paused-queue actions. The composer no longer uses the held-queue Resume action or the send-confirmation flow. The structured composer send function reads the current draft when sending. Tests, fixtures, and locale strings are updated for these changes.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 35e74

The queue changes preserve retryable items after failed clears and keep a newly sent turn ahead of the paused queue. No concrete merge-blocking risk remains.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 41.94% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 26 files. (7 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary UI change: a unified queue header and removal of composer Resume and send-confirmation behavior.
Description check Passed The description follows the repository template and provides a clear user-facing summary, mechanism, rationale, linked-issue exception, visual proof, testing details, known failures, compatibility not…
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 41.94% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 26 files. (7 skipped: 7 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

…used-header

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatComposerActions.test.tsx
#	src/renderer/src/i18n/en-runtime-required.json
@brennanb2025
brennanb2025 merged commit e2f2277 into main Oct 15, 2026
32 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/queue-paused-header branch October 15, 2026 23:09
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