Skip to content

fix(native-chat): read a queued message in full - #26004

Merged
brennanb2025 merged 16 commits into
mainfrom
brennanb2025/queued-card-full-text-and-edit
Oct 9, 2026
Merged

brennanb2025 merged 16 commits into
mainfrom
brennanb2025/queued-card-full-text-and-edit

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​211 $\color{#cf222e}{\Huge{\mathbf{−}}}$​11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​200
Prod 15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​346 $\color{#cf222e}{\Huge{\mathbf{−}}}$​134 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​212

ELI5

When the agent is busy, messages for it (yours, or orchestration mail from another agent) wait as small cards above the chat box. A card showed only one cut-off line. A teammate got an orchestration card she had never seen before and wrote: "I can't see the entire steer message, so I dare not submit it."

Now a cut-off card has a small arrow that opens the whole message in place.

A second bug from the same report is not fixed here: "Edit message" on a card makes the message vanish while a question is open, because Edit moves the text into the hidden chat box and deletes the card. That is being fixed separately by editing the queued message in place, in its own pull request.

What Changed

Reading a long queued message (desktop and phone)

  • Before: one cut-off line. The only way to read the rest was to hover for the browser's tooltip.
  • After (desktop): when the line is actually cut off, a chevron button "Show full message" appears beside the card's actions. It opens the whole text below the card's row, across the card's full width, with line breaks kept and its own scroll area past about 15 lines. "Show less" folds it back. This is the same way the transcript's "Awaiting user input" row opens its question. (An earlier revision opened the text beside the buttons. In a narrow chat it wrapped a few characters per line, which live QA caught.) It is a normal button, so the keyboard reaches it, and the hover tooltip is still there. A card that fits on its line shows no chevron, because the line is measured, not guessed.
  • After (phone): a card still shows two lines. When the text is longer, a quiet "Show more" line appears under it, and a tap opens the whole message in a capped scroll box. "Show less" folds it. A card that fits shows no "Show more".

The clip measurement used by the transcript's "Awaiting user input" row moved into a small shared hook, useNativeChatClippedLine, which the queued card now reuses.

Why

  • Intended difference: the expand control. The common pattern for a queued row is one clipped line with no way to expand it, at most a hover tooltip. In Orca a queued card is often orchestration mail written by another agent, so the person has never read it, and they must be able to read it before pressing Steer. The teammate's report above is the observed failure: hover alone did not get her there. The control is the existing ghost icon-button idiom with no new tokens, and it appears only when the line is actually clipped.

Linked Issue

N/A (reported directly by a teammate)

Visual Proof

Before and after are the same scenario, run in an isolated dev build. A Codex chat is running a turn, and orchestration mail arrives, so it waits as a queued card.

Reading the card

Before (main) After (this PR)
Before: the queued card is clipped to "You hav…" with no way to open it After: the clipped card has a chevron button
One clipped line; only a hover tooltip. "Show full message" opens the whole text below the row: After: opened card shows the whole message across its width

Testing

  • I manually tested these changes locally

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

  • New desktop tests:

    • A clipped card opens to its whole text (line breaks kept, not truncated) and folds back. The toggle is a focusable native button.
    • A card that fits shows no toggle.
  • New phone tests: a clipped card opens on tap and folds back, and a card that fits is plain text.

  • Ablation: with the old card, list and phone component restored, the new expander tests fail (no "Show full message" button); with this change they pass.

  • Live QA (macOS, isolated dev builds of main and this branch, driven through the Chrome DevTools Protocol). Orchestration mail arrived while a stand-in Codex turn ran, and waited as a queued card. The screenshots above come from that run (taken before this PR was narrowed to the expander; the expander code is unchanged since). Also confirmed: a keyboard Enter on "Show full message" opens it, and the opened text's parent is the card itself, spanning its width. Not tested: the phone app on a device (covered by unit tests only), Linux, Windows and SSH.

  • Ran, after merging current main (ff4a51c872e): the queue list, queue hook (incl. resume/clear) tests (91) and the phone queue tests (23), all passing; web typecheck clean.

  • Also ran: web and mobile typecheck, the mobile tests-typecheck ratchet, the changed-code quality gate, React Doctor, and the localization checks. All pass. New strings are in all six locale catalogs.

AI Disclosure

Author: @BrennanKB5

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Security: no change.
  • Cross-platform: renderer and phone presentation only. Nothing is platform-specific.
  • SSH/remote: no wire or host change. A remote host's queued cards and prompts behave the same way.
  • Mobile: the phone card now opens long messages.
  • Backwards compatibility: no stored or wire data changes.
  • Folder workspaces: unaffected.

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)

… hiding it behind a prompt

A queued card showed one clipped line, so a long message (often another
agent's, never typed by the reader) could not be read before Steer or
Delete. A clipped card now opens in place to its whole text; the phone's
two-line card opens on tap.

Edit moved the card's text into the composer and deleted the card even
while a question or approval card stood in the composer's slot, so the
text vanished from the page. Edit now waits, saying why, until the
prompt is answered; the pane and the queue read one check for whether a
prompt holds the composer's slot.
…ser-slot check

The phone card measured nothing and made every card a button; it now lays
the text out unseen at its width and offers Show more only past two lines.
The Edit reason is no longer dimmed twice, and the shared composer-slot
check and the desktop toggle's keyboard reach have their own tests.
…e card's width

In a narrow chat the opened text sat beside the card's actions and wrapped
a few characters per line. It now opens under the row, indented to the
text, the way the transcript's awaiting-input row opens its question.
…ard-full-text-and-edit

# Conflicts:
#	src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.resume.test.tsx
@brennanb2025

brennanb2025 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Review summary

Head: bc47f089d0f. Main is merged in at 9fdd90ffa77, which includes #24586, #26028 and #22972. CI on this head is green: 16 passed, 0 failed. Test shard 2 lost its runner twice (shutdown signal partway through, no test failure) and passed on a full rerun.

  • The previous head's only red check was vitest-sqlite-runtime-boundary.test.ts, flagging main's own agent-launch-instant-tab.test.ts. Main fixed it in test(vitest): run agent-launch-instant-tab in the SQLite runtime project #26028, now merged in here.
  • After this merge: 85 test files (832 tests), including that boundary test and the host queued-card holds test, plus web and mobile typechecks, oxlint and the changed-code gate, all pass.

Three read-only review rounds ran on this branch. None found a P0, P1 or P2 issue.

Round 1 covered the whole change: correctness, styling rules, tests, remote/version compatibility, and the merge with the queue rework in #24586.

  • Verified:
    • promptHoldingComposerSlot gives exactly the old composer-visibility result.
    • Nothing else can reach Edit: no other caller and no shortcut. Cmd/Ctrl+Enter only steers.
    • A prompt this build cannot answer still leaves the composer shown and Edit enabled.
    • Nothing changes on the wire or in stored data.
    • All four new strings are in all six locale catalogs.
  • Follow-ups taken:
    • The phone card now measures whether its text is cut off. Before, every card was a button.
    • The disabled Edit reason is no longer dimmed twice.
    • The shared composer-slot check got its own unit test.
  • Not taken, with reasons:
    • A prompt arriving while an Edit is already in flight: the text returns when the composer reappears. Skipping the delete after the copy would show the text twice.
    • A wording edge when a newer-build approval comes first: the hidden composer in that case predates this change.
    • Focus loss after resizing an open card until it fits: rare, and only after a resize.

Round 2 covered the phone measurement: a hidden copy of the text laid out at the card's width, with onTextLayout counting lines.

  • Follow-up taken: the hidden copy no longer receives touches (pointerEvents="none").

Round 3 covered the layout change after live QA. At a narrow chat width, the opened text had been squeezed into a narrow column beside the buttons. It now opens below the row, across the card's width.

  • Follow-up taken: the toggle's aria-controls names the opened text only while it exists.

Merge with main.

  • Two components were at their line caps, so:
    • the queue hook now reads the pending prompts itself;
    • the pane derives both the composer's visibility and the approval card's focus from one value.
  • Two tests that came from main got the new controller field.
  • Web and mobile typechecks, the mobile tests-typecheck ratchet, oxlint on every changed file, the changed-code quality gate, and 64 native-chat test files (561 tests) all pass after the merge.

Merge with #24586 (main at 0c96550ee9a). The two changes agree:

Not verified: the phone change on a real device (unit tests only), and Linux, Windows and SSH. The live QA ran on macOS only.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 7, 2026 00:47
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Review in 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

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: a305752e-fbe0-4880-b25d-f2f7d8c2cca6
📥 Commits

Reviewing files that changed from the base of the PR and between 099c8aa and 9696ce4.

📒 Files selected for processing (13)
  • mobile/src/session/MobileNativeChatQueuedMessages.test.tsx
  • mobile/src/session/MobileNativeChatQueuedMessages.tsx
  • src/renderer/src/components/native-chat/NativeChatQueuedMessageCard.tsx
  • src/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session.ts
  • 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 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/en.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/ja.json

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


📝 Walkthrough

Walkthrough

Queued-message cards on mobile and desktop can expand clipped text and collapse it again. Pending questions or approvals that occupy the composer slot now prevent queued-message editing and display a translated reason. The changes also add a shared clipping hook and tests for text disclosure and edit holds.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 9696c

When an unanswerable prompt precedes an answerable one, the composer may be hidden while the answerable prompt is not shown. This narrow case warrants owner awareness before merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 52.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 17 files. (6 skipped:… 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 identifies the primary user-facing change: allowing users to read queued messages in full.
Description check Passed The description is detailed and covers the required change summary, rationale, visual proof, testing, AI disclosure, review notes, and compatibility considerations. It marks the linked issue as N/A ev…
Full details: Docstring Coverage

Explanation

Docstring coverage is 52.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 17 files. (6 skipped: 6 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · 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.

…ard-full-text-and-edit

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsx
#	src/renderer/src/components/native-chat/native-chat-submission-navigation.test.tsx
#	src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.resume.test.tsx
#	src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.test.tsx
#	src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.ts
#	src/renderer/src/components/native-chat/use-structured-agent-session.ts
#	src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.test.ts
…ard-full-text-and-edit

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx

@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


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e252a464-80f1-42dd-852d-58f0969e9067
📥 Commits

Reviewing files that changed from the base of the PR and between b47690e and bc47f08.

📒 Files selected for processing (7)
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • 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 skipped from review as they are similar to previous changes (6)
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json

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

const promptsUnanswerable = pendingPromptsAllUnanswerableHere(controller.prompts)
const composerShown = (prompt === null || promptsUnanswerable) && !readFailedFinally
// A prompt this build cannot answer holds no slot: the composer stays, and its card takes no focus.
const promptInSlot = promptHoldingComposerSlot(controller.prompts)

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 'promptHoldingComposerSlot|pendingPromptsAllUnanswerableHere' src/renderer/src/components/native-chat
rg -n -C 5 'controller\.prompts|prompts\s*=' src/renderer/src/components/native-chat
rg -n -C 5 'unanswerable|composer slot|promptHoldingComposerSlot' src/renderer/src/components/native-chat --glob '*test*'

Repository: stablyai/orca

Length of output: 27461


🏁 Script executed:

set -eu
rg -n -F -- 'function pendingStructuredSessionPrompts' src
rg -n -F -- 'pendingStructuredSessionPrompts(' src/renderer/src/components/native-chat
rg -n -F -- 'controller.prompts' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
sed -n '1,120p' src/renderer/src/components/native-chat/structured-agent-session-message-projection.ts
sed -n '155,230p' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
sed -n '340,390p' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
sed -n '1,90p' src/renderer/src/components/native-chat/native-chat-composer-slot.ts

Repository: stablyai/orca

Length of output: 8555


🏁 Script executed:

set -eu
sed -n '1,175p' src/renderer/src/components/native-chat/use-structured-agent-session.ts
rg -n -F -- 'journalItems:' src
rg -n -F -- 'pendingPromptsAllUnanswerableHere' shared src/renderer/src/components/native-chat
rg -n -F -- 'AgentJournalRenderItem' shared/agent-session-journal-types.ts
sed -n '1,180p' shared/agent-session-approval-subject.ts
sed -n '1,180p' shared/agent-session-journal-types.ts

Repository: stablyai/orca

Length of output: 21013


🏁 Script executed:

set -eu
sed -n '1,180p' src/shared/agent-session-approval-subject.ts
sed -n '1,220p' src/shared/agent-session-journal-types.ts
sed -n '1,220p' src/renderer/src/components/native-chat/use-structured-agent-session-transport-state.ts
sed -n '1,130p' src/main/native-chat/agent-session-wire/structured-agent-session-transition.ts
rg -n -C 4 -F -- 'journalItems' src/main/native-chat/agent-session-wire src/main/native-chat/agent-session-timeline | head -200

Repository: stablyai/orca

Length of output: 28970


🏁 Script executed:

set -eu
rg -n -F -- 'function chatApprovalFromJournal' src
rg -n -F -- 'chatApprovalFromJournal' src/renderer/src/components/native-chat
rg -n -C 8 -F -- 'const approval = prompt?.body.kind' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
rg -n -C 8 -F -- 'NativeChatApprovalCard' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx

Repository: stablyai/orca

Length of output: 3848


🏁 Script executed:

set -eu
sed -n '35,72p' src/renderer/src/components/native-chat/native-chat-interactive-prompt.ts
sed -n '330,410p' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx

Repository: stablyai/orca

Length of output: 5395


Render the first answerable prompt consistently.

pendingStructuredSessionPrompts preserves journal order, while promptHoldingComposerSlot hides the composer when any prompt is answerable but still classifies the first prompt. If an unanswerable approval precedes a question, the session renders only the approval card and the question cannot be answered until the approval is resolved or cancelled. Select the first answerable prompt for both slot state and rendering, while preserving the existing first-prompt fallback when all prompts are unanswerable.

…ard-full-text-and-edit

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

Copy link
Copy Markdown
Contributor Author

Status: re-synced with main, CI green

Head: 099c8aadc3b. Main was merged in twice today, up to 5de53910aa1; there was no product change on this branch.

  • One conflict in NativeChatStructuredSession.tsx. Main had replaced the host-outage notice component with useNativeChatHostOutageNotice and added an availability notice; both are kept. Both prompt cards (approval and question) now take focus from this PR's single promptHoldingComposerSlot rule. Main's promptsUnanswerable check is no longer used, because that rule already covers it.
  • Local checks:
    • The PR's 7 renderer test files pass (97 tests).
    • orca-ci-checks passes 22 of 23. The one failure is the five mobile no-cycle warnings, which main already has; this PR does not touch those files.
  • CI on 099c8aadc3b: all 16 checks pass. The previous push failed static analysis only on main's own structured-agent-session-host.ts line limit, which fix(lint): move knownAgentIds out of the session host #26648 has since fixed on main.

No behaviour change since the earlier review summary.

…ard-full-text-and-edit

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatQueuedMessageCard.tsx
#	src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.ts
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Status: main merged in again after #25704, CI green

Head: 9696ce405e7.

#25704 (/compact waits in line) landed and touched the same queued-card code. Both changes are kept:

  • Card actions
    • From main: no actions on a card that is still sending; no Send on a command card while the agent works; no Edit on a command card; and the 'retry once the agent finishes' caption.
    • From this PR: the full-message expander, and Edit disabled with its reason while a prompt holds the composer's slot.
  • Queue controller
    • From main: pending sends show as sending cards.
    • From this PR: promptHoldingComposerSlot decides whether Edit may move text. Edit now refuses for a command card or while a prompt is in the composer's slot.

Checks

  • 10 queue/card test files pass (150 tests).
  • orca-ci-checks passes 22 of 23. The only failure is main's existing mobile no-cycle warnings.
  • CI: 16/16 pass, mergeable.

…ard-full-text-and-edit

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatQueuedMessageCard.tsx
@brennanb2025 brennanb2025 changed the title fix(native-chat): read a queued message in full, and keep Edit from hiding it behind a question fix(native-chat): read a queued message in full Oct 8, 2026
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Status: narrowed to the full-message expander, CI green

Head: 13181256b9d.

At the author's request this PR now only adds reading a queued message in full: the desktop chevron, and the phone's Show more. The Edit change is removed (greyed-out Edit while a prompt is open, promptHoldingComposerSlot, its tests and its two strings). That bug will be fixed properly by editing the queued message in place, in a separate PR.

Checks

  • Queue list, hook and phone queue tests pass (49 + 91 + 23).
  • Web typecheck is clean.
  • CI: 16/16 pass, mergeable.
  • The PR body is updated to match.

@brennanb2025
brennanb2025 merged commit 56cdd67 into main Oct 9, 2026
32 checks passed
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