Skip to content

feat(native-chat): one notice card above the message box for the chat's own errors - #25835

Merged
brennanb2025 merged 1 commit into
mainfrom
brennanb2025/native-chat-notice-card
Oct 7, 2026
Merged

brennanb2025 merged 1 commit into
mainfrom
brennanb2025/native-chat-notice-card

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 10 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​304 $\color{#cf222e}{\Huge{\mathbf{−}}}$​12 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​292
Prod 23 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​429 $\color{#cf222e}{\Huge{\mathbf{−}}}$​99 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​330

Built on main. It was first stacked on #25207, which closed in favour of #25959 (desktop sends kept in memory). #25959's "why your text came back" reason reaches the screen through the same chat error line this PR turns into a row, so the two don't depend on each other. Whichever lands second rebases.

ELI5

When something Orca itself does goes wrong in a native chat (the chat can't start, a slash command is refused, a send or paste fails), Orca said so with loose lines of plain red text floating between the conversation and the message box. They had no icon, no structure, and sometimes the raw internal error text was the whole message.

Now those messages sit in one tidy card attached to the top of the message box, one row per message. Each row has an icon, Orca's words, and its button: Retry for a chat that failed to start, × to clear an error you caused. If there's a raw system error behind it, that error is shown separately in a small monospace block with a copy button, under a plain sentence saying what didn't happen.

What Changed

The problem, as you saw it

  • "Chat could not be started." was a red text line with a small Retry beside it.
  • The chat's own line (for example "Couldn't save your message.") and an error from your last send or slash command shared one red paragraph. When both existed, only the first was shown, so the other was silently hidden.
  • When a send threw, the raw IPC error was the entire message, for example Error invoking remote method 'agentSession:send': Error: connect ECONNREFUSED ….
  • Paste and attachment notices sat inside the composer area with a broken-image icon, whatever the notice was about (for example "Worktree not ready").
  • Two of these strings were never translated: "Paste failed." and "Remove attachments before using a chat-session command."

After

Situation Before After
Chat failed to start Red text line, ghost Retry Row with an alert icon and an outlined Retry
Slash command refused (for example /model with an unknown model) Red text line Row with an alert icon and ×
Chat line and a composer error at the same time Only one shown Both shown, errors first
A send throws Raw IPC text as the message "Your message was not sent." (or "The command didn't run."), with the error text below in its own block and a copy button
Paste or attachment notice Muted line with a broken-image icon Row with a paperclip icon and ×
A question or approval card takes the message box's place Lines above it as before The same card, shown above the question or approval card

Mechanism

  • NativeChatComposerNotices is the card. It takes a list of notices, each with a kind (error or attachment), Orca's text, optional raw errorText, and an optional action or dismiss handler.
  • The structured session builds its notices in native-chat-structured-session-notices.ts: the failed or unconfirmed start (replacing NativeChatLaunchRetry, including main's rule that a launch-argument problem shows only its cause), the chat's own error line (main's rule for which error shows is kept as is), and the composer's error (held by useNativeChatComposerError). It passes them to the composer, which adds its own paste and attachment notice (use-native-chat-composer-notice.ts) and renders one card above the input.
  • The red error paragraph is removed from NativeChatStructuredSessionStatus.
  • The card is a list inside a live region that stays mounted, so a screen reader announces the first notice as well as later ones. After pressing ×, focus goes back to the message box.
  • setNativeChatPasteFailure keeps Orca's own "Clipboard text is too large…" sentence as the row text even when it comes back from the main process.
  • nativeChatNoticeFromError keeps a main-process (IPC-wrapped) error apart from Orca's words, with the wrapper stripped. An error this window raised itself, such as the paste size limit, is already Orca's sentence and stays as the row text.
  • The transport's onError gains an optional errorText argument for the raw part.

Why

  • One surface for one kind of thing. Orca's own failures are things you act on before your next send, so they belong next to the message box, said once each. The common pattern for this is a bordered notice area attached above the composer, one row per notice, with an icon, a short statement and an action, and longer detail kept out of the way.
  • Raw text apart, not instead. A system error is what you paste into a bug report, so it should be readable and copyable as written. It should never replace the sentence that tells you what didn't happen.
  • Alternatives considered:
    • Restyling each line in place would have kept four different components and the one-hides-the-other bug.
    • Moving these to toasts would hide failures you still need to act on, such as a chat that can't start.

Differences from the common pattern

  • Intended:
    • The raw error block and its copy button sit on the notice itself. The common pattern keeps raw detail in a separate expandable block or puts copy only on error toasts. Here both are on the row, because this is where the user is already looking when it fails.
    • All notices show as rows at once, rather than one notice with the rest collapsed behind it. It's rarely more than two, and collapsing them is exactly how one hid the other before.
  • Temporary: the host-connection notice from fix(native-chat): stop flashing a reconnecting line on a stream drop #24898, now on main (" is reconnecting…" / " is offline" with Reconnect), is still its own line just above. A follow-up PR makes it a row of this card, keeping its 2-second rule and its neutral/warning colours.

Linked Issue

N/A (maintainer).

Visual Proof

Captured in two isolated, hidden dev builds of Orca (their own profile and home folder). A stand-in codex refuses to run, so the chat's start fails; no real agent ran. Before = main 5b1bb78d14b; after = this PR on that main. The PR has since been rebased onto 988f87e743e; the only change in this area is that the error callback now goes through main's new composer-transport hook.

A chat that failed to start

Before After
before-S1.png after-S1.png

A failed start and a refused slash command at once

Before After
before-S3.png after-S3.png

After pressing × on the refusal: it goes, the failed start with Retry stays, and focus is back in the message box.

after-S2-dismissed.png

Not captured in the app, but covered by tests:

  • the raw-error block;
  • the paste notice;
  • the card above an approval card;
  • the chat's own error line beside a composer error.

The in-app case above (a failed start plus a refusal) was two separate lines before, so both already showed; the hiding happened between the chat's own error and a composer error.

Testing

  • I manually tested these changes locally (macOS, isolated dev build, screenshots above)
  • Automated tests added/updated
    • NativeChatComposerNotices.test.tsx: card order (errors first, nothing hidden), the live region, raw error in its own block with copy, Retry and dismiss only where given, the raw-vs-Orca-words split, and the paste size-limit sentence coming back from the main process.
    • use-native-chat-composer-notice.test.tsx: the composer's own notice and the chat's errors stay separate rows (distinct keys) and dismiss independently.
    • use-native-chat-structured-composer-send.test.tsx: a send that throws says "Your message was not sent." with the IPC error apart.
    • NativeChatStructuredSession.notice-card.test.tsx: the chat's own error and a composer error both show; a send's raw error is apart and dismissible; the card stays above an approval card.
    • native-chat-structured-session-notices.command-failure.test.tsx (moved from main's NativeChatLaunchRetry.command-failure.test.tsx): the launch-command failure is still worded the same in its row, with Retry.
    • Existing tests updated for the new props. The structured-session tests' composer stand-in, now in NativeChatStructuredSession.test-composer.tsx, draws the card, so those tests check what users see.
  • Local checks:
    • pnpm tc:web passes.
    • oxlint, the design-system lint, the changed-code quality gate (including React Doctor) and the localization checks pass.
    • 33 related test files (335 tests) pass.
  • Not tested: Linux, Windows, and SSH-hosted chats. The change is renderer-only and has no platform-specific code.

Notes

  • Renderer-only. There is no wire, host, saved-data or mobile change.
  • New strings ("Dismiss", "Copy error", "Paste failed.", the attachments-with-a-command refusal) are translated in all six catalogs.

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)

@brennanb2025
brennanb2025 force-pushed the brennanb2025/native-chat-notice-card branch 3 times, most recently from 374a792 to c653d41 Compare October 7, 2026 02:59
@brennanb2025
brennanb2025 changed the base branch from brennanb2025/chat-send-settles-once to main October 7, 2026 02:59
@brennanb2025
brennanb2025 force-pushed the brennanb2025/native-chat-notice-card branch 2 times, most recently from e420c5d to 27c4820 Compare October 7, 2026 03:43
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review and QA summary

The problem. Orca's own errors in a native chat were loose lines of plain text between the conversation and the message box, from four different places:

  • a red "Chat could not be started." line with a small Retry;
  • a red paragraph shared by the chat's own error and the error from your last send or slash command. When both existed, only one showed.
  • a muted paste/attachment notice inside the composer, always with a broken-image icon;
  • a send that threw, which showed the raw IPC error text as the whole message.

What changes for you. All of these are now rows of one bordered card attached above the message box, errors first:

  • Each row has an icon and Orca's words.
  • Retry appears only on a chat that failed to start.
  • × clears an error you caused; focus then goes back to the message box.
  • Text Orca didn't write (a host or system error) is shown apart in a small monospace block with a copy button, under a plain sentence saying what didn't happen.

There is no Retry for a message that wasn't sent.

Review. One full read-only review of the code against main. Findings, all fixed in this head:

  • P1: the send/command error and the paste notice shared the React key composer. Shown together, a row could be dropped or duplicated, which is the very "one hides another" problem this PR removes. They now have distinct keys, and a test proves both stay.
  • P2: Orca's own "Clipboard text is too large…" sentence fell into the raw-error block when it came back from the main process. It is now the row's text.
  • P2, screen readers:
    • the live region now stays mounted, so the first notice is announced too;
    • rows are a list, without nested alerts that could be read twice;
    • × has a hover label;
    • focus returns to the message box after ×.
  • P2, tests: added coverage for a send that throws, the paste cases, the composer merging its own notice with the chat's, and the card above an approval card.
  • Nits: Korean wording ("remove" rather than "delete" attachments), a redundant icon size. Smaller refactors were deferred.

QA. Two isolated, hidden dev builds (own profile and home folder; a stand-in codex that refuses to run, so no real agent ran), driven over CDP. Before = main, after = this PR. Covered: a failed start; a refused /model; both at once; × on the refusal, with focus back in the message box. Screenshots are in the PR body. The user's real Codex, Claude and Grok config files were hashed before and after, and were unchanged.

Not verified in the app: the raw-error block, the paste notice, and the card above an approval card. These are covered by tests. Linux, Windows and SSH-hosted chats weren't run; the change is renderer-only with no platform-specific code.

Related. #25959 (desktop sends in memory) shows its "why your text came back" sentence through the same chat error line, so it becomes a row here with no extra wiring. Whichever lands second rebases. #24898's host notice (" is offline / reconnecting") is still its own line; a follow-up makes it a row.

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

coderabbitai Bot commented Oct 7, 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: 3d178736-61d6-4f3c-a6fc-1bd4502c5f62
📥 Commits

Reviewing files that changed from the base of the PR and between 27c4820 and b6205ad.

📒 Files selected for processing (10)
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • src/renderer/src/components/native-chat/native-chat-composer-types.ts
  • src/renderer/src/components/native-chat/use-native-chat-structured-composer-transport.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/ko.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/zh.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

Walkthrough

The composer now accepts multiple typed notices. Notices can include separate error details, actions, and dismissal callbacks. Paste and structured-send failures use shared error conversion helpers. Structured sessions combine launch, session, and composer errors, then display notices in the composer or prompt slot. The status component no longer renders session or composer errors. English, Spanish, French, Japanese, Korean, and Chinese locales add related strings.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to b6205

This change adds composer and prompt-slot notices while preserving distinct rows and failed-launch retry access. No actionable merge-blocking risk was identified in the reviewed boundaries.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 26 functions across 25 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 main change: a notice card above the native-chat message box for chat errors. It is specific and relevant to the changeset.
Description check ✅ Passed The description is detailed and covers the user-facing change, implementation, rationale, linked-issue exception, visual proof, testing, and compatibility notes. It leaves some checklist items uncheck…
Full details: Docstring Coverage

Explanation

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

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

@brennanb2025
brennanb2025 force-pushed the brennanb2025/native-chat-notice-card branch from 27c4820 to 0ec9968 Compare October 7, 2026 05:41
…own errors

Orca's own errors in a native chat were loose lines between the transcript
and the message box: a red "Chat could not be started." line with Retry, a
red paragraph for the chat's line and for a send or slash-command error
(where the second was hidden whenever the first showed), and a muted
attachment notice inside the composer that always used a broken-image icon.
A send that threw showed the raw IPC error as the whole message.

They are now rows of one card attached above the message box, errors first:
each row has an icon, Orca's words, and its action (Retry for a failed start,
dismiss for errors the user caused). Error text Orca did not write, such as a
host or system error, is shown apart in a monospace block with a copy button,
under the words saying what didn't happen. The card also shows where a
question or approval card takes the composer's place.

Also translates "Paste failed." and the attachments-with-a-command refusal.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/native-chat-notice-card branch from 0ec9968 to b6205ad Compare October 7, 2026 05:54
@brennanb2025
brennanb2025 merged commit a3ecee0 into main Oct 7, 2026
51 of 53 checks passed
brennanb2025 added a commit that referenced this pull request Oct 7, 2026
Takes main's notice card (#25835) as main has it; the chat's send errors
reach it through controller.error. The outbox Stop-withdrawal module stays
deleted: #25217 only exported its Retry test for the projection's
awaitsRetry flag, and no in-memory send ever waits on a Retry. Main's
opening-send test is ported to optimistic messages, minus its two
waiting-on-Retry cases.

Also: a launch text handed back by a Stop before publish (or a failed
start) is guarded like the sender's hand-back, so a draft write that throws
is reported and never skips the Stop; the Stop-before-publish cases for a
notes launch, an already-sent launch and a throwing hand-back are tested;
the legacy outbox comment no longer names a build number.
Jinwoo-H added a commit that referenced this pull request Oct 7, 2026
…e test (#26097)

#25835 kept the onAcceptMention prop that #26017 replaced with
onChooseMentionFile and mentionFiles, so the web typecheck fails on main.
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