Repository navigation
fix(native-chat): show "Sending…" on a message until the host confirms it - #24606
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review. 📝 WalkthroughWalkthroughUnconfirmed outbox entries can be automatically resent under their existing operation ID when no matching journal submission exists and the entry has not been retried or outlived by Stop. The session displays “Sending…” while delivery remains unresolved. When journal submissions report an unknown outcome, the session displays a notice with a Retry option. Tests cover reopened entries, live and recovered unknown outcomes, Stop history, and probe timing. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to This update reorganizes the delivery probe tests into their own suite and adds them to the reliability gate. No concrete risk to user-facing behavior was found, and the change is ready to merge. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The inspected changes preserve message identity and the controls that stop automatic retries after a host response, user retry, or Stop. No new security issue was established. Confidence remains limited because recovery across different runtime targets and the exact before-and-after behavior were not fully verified. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 38.24% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 34 functions across 22 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve 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. Comment |
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes
- A provably-unsent send returns to an empty composer —
disposeStructuredAgentSessionSendRefusalnow drops an entry the host never recorded (structuredAgentSessionRefusalProvesUnsent) when an empty composer shows the chat, hands its text/images back through the existing Stop restore path (restoreWithdrawn.unrecorded, renamed frombyStop), and puts the refusal words on the chat's error line once. Launch prompts never return. - Settlement rules extracted into
structured-agent-session-outbox-settlement.ts— the proof (structuredAgentSessionRefusalProvesUnsent), the lost-outcome predicate (structuredAgentSessionSubmissionOutcomeLost), andstructuredAgentSessionEntryConfirmingByItself, now read by the drain, the unconfirmed probe, and the delivery notices. - In-doubt sends no longer flash as failed — the probe and the notices suppress a message Orca confirms under its own id; reconcile drops an entry whose journal row is a recovered
unknownand sayssendOutcomeLostonce. - Copy and tests — new
sendOutcomeLostsentence across the copy module,en-runtime-required.json, and all six locale catalogs; new/updated tests for the composer return, the lost outcome, and the no-notice confirmation.
I verified the proof extraction is behavior-identical to the old inline condition, that retained reads the un-staged wire entry (so a first attempt is distinguished from the staged stored entry), and that the reconcile lost-drop cannot lose the message from the transcript: it only fires when the recovered-unknown submission is present in submissions, whose non-rejected items the journal projection renders. The one gap I found is in the new emptiness check.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…its own A send whose answer was lost (for example after reopening the chat mid-send) showed "Message delivery is unconfirmed." with a Retry for the moment before the automatic resend under the same message id confirmed it. One rule now decides both: the resend runs, and the notice stays quiet, only while the send's answer is lost, the user never retried it, it was not still going out when the user pressed Stop, and the host has no record of it yet. Once the host has any record of it, the line and Retry are exactly as before.
af8b9a9 to
4959afa
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
e8f1426f-a017-41f5-be70-f094f9285f4d
📒 Files selected for processing (6)
src/renderer/src/components/native-chat/NativeChatStructuredSession.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsxsrc/renderer/src/components/native-chat/structured-agent-session-delivery-notices.test.tssrc/renderer/src/components/native-chat/structured-agent-session-delivery-notices.tssrc/renderer/src/components/native-chat/use-structured-agent-session-outbox-unconfirmed-probe.tssrc/shared/structured-agent-session-outbox-unconfirmed-resend.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.
| if (structuredAgentSessionEntryResendsUnconfirmed(entry, submissions)) { | ||
| continue |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
# Locate the probe owner and check whether a visible session can retain a detached owner.
rg -n -C 8 \
'useStructuredAgentSessionOutboxUnconfirmedProbe\(|owner\.attached|attached:|isVisible' \
src/renderer/src/components/native-chat \
-g '*.ts' -g '*.tsx'Repository: stablyai/orca
Length of output: 45667
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- probe ---'
cat -n src/renderer/src/components/native-chat/use-structured-agent-session-outbox-unconfirmed-probe.ts | sed -n '1,180p'
printf '%s\n' '--- outbox hook ---'
cat -n src/renderer/src/components/native-chat/use-structured-agent-session-outbox.ts | sed -n '1,290p'
printf '%s\n' '--- notices ---'
cat -n src/renderer/src/components/native-chat/structured-agent-session-delivery-notices.ts | sed -n '1,230p'
printf '%s\n' '--- owner references ---'
rg -n -C 6 'create.*owner|owner\s*=|attached\s*:|attached\b|useStructuredAgentSessionOutbox\(' src/renderer/src/components/native-chat -g '*.ts' -g '*.tsx' | sed -n '1,500p'
printf '%s\n' '--- notice call sites ---'
rg -n -C 8 'structuredAgentSessionDeliveryNotices|deliveryNotices|structuredAgentSessionEntryResendsUnconfirmed' src/renderer/src/components/native-chat -g '*.ts' -g '*.tsx' | sed -n '1,500p'Repository: stablyai/orca
Length of output: 42517
🏁 Script executed:
set -eu
echo '--- probe ---'
cat -n src/renderer/src/components/native-chat/use-structured-agent-session-outbox-unconfirmed-probe.ts | sed -n '1,180p'
echo '--- outbox hook ---'
cat -n src/renderer/src/components/native-chat/use-structured-agent-session-outbox.ts | sed -n '1,290p'
echo '--- notices ---'
cat -n src/renderer/src/components/native-chat/structured-agent-session-delivery-notices.ts | sed -n '1,230p'
echo '--- owner references ---'
rg -n -C 6 'create.*owner|owner\s*=|attached\s*:|attached\b|useStructuredAgentSessionOutbox\(' src/renderer/src/components/native-chat -g '*.ts' -g '*.tsx' | sed -n '1,500p'
echo '--- notice call sites ---'
rg -n -C 8 'structuredAgentSessionDeliveryNotices|deliveryNotices|structuredAgentSessionEntryResendsUnconfirmed' src/renderer/src/components/native-chat -g '*.ts' -g '*.tsx' | sed -n '1,500p'Repository: stablyai/orca
Length of output: 42578
🏁 Script executed:
set -eu
echo '--- owner hook ---'
cat -n src/renderer/src/runtime/structured-agent-session-accepted-send-capability.ts | sed -n '1,280p'
echo '--- owner hook call sites ---'
rg -n -C 12 'useStructuredAgentSessionOutboxOwnerChange|useStructuredAgentSessionOutbox\(' src/renderer/src -g '*.ts' -g '*.tsx' | sed -n '1,700p'
echo '--- structured session outbox/controller wiring ---'
rg -n -C 12 'useStructuredAgentSessionOutbox|structuredAgentSessionDeliveryNotices|isVisible|fence' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx src/renderer/src/components/native-chat -g 'use-structured-agent-session-controller*.ts' -g '*controller*.ts' -g 'NativeChatStructuredSession.tsx' | sed -n '1,700p'Repository: stablyai/orca
Length of output: 42414
🏁 Script executed:
set -eu
echo '--- structured session hook ---'
cat -n src/renderer/src/components/native-chat/use-structured-agent-session.ts | sed -n '1,190p'
echo '--- fence state definitions and producers ---'
rg -n -C 10 'fence\s*[:=]|transportState|useStructuredAgentSessionTransport|fenceRef' src/renderer/src/components/native-chat src/renderer/src/runtime -g '*.ts' -g '*.tsx' | sed -n '1,900p'
echo '--- visibility retention boundary ---'
rg -n -C 12 'NativeChatStructuredSession|useStructuredAgentSession\(|enabled: isVisible|isVisible' src/renderer/src/components/native-chat/NativeChatResolvedView.tsx src/renderer/src/components/native-chat -g '*.ts' -g '*.tsx' | sed -n '1,800p'Repository: stablyai/orca
Length of output: 42250
Keep the notice and Retry while the owner is detached.
When transportState.fence is null, owner.attached is false. The probe returns without scheduling a resend, but this branch still suppresses the notice because the resend predicate matches. A visible session can retain and render that outbox entry while the fence is null. Gate this suppression on owner attachment so the notice and Retry remain available until the probe can run.
There was a problem hiding this comment.
ℹ️ No critical issues — one behavioral edge worth confirming.
Reviewed changes
- Shared resend rule extracted — new
src/shared/structured-agent-session-outbox-unconfirmed-resend.tsholdsstructuredAgentSessionEntryResendsUnconfirmed(in doubt, never retried, not outlived by a Stop, no journal row yet). - Probe reads the rule —
use-structured-agent-session-outbox-unconfirmed-probe.tsderivesprobeIdfrom it and drops the separateprobeSettledcheck; verified behavior-identical (the extrastate === 'unconfirmed'conjunct is implied byblocker, and the submission check only moves intoprobeId). - Notices go quiet while resending —
structured-agent-session-delivery-notices.tsskips any entry the rule matches. - Journal rows reach the notices for in-doubt sends —
NativeChatStructuredSession.tsxnow passescontroller.submissionswhen the outbox has anunconfirmedentry, not only when one was rejected (readsRows). - Tests — new coverage in
structured-agent-session-delivery-notices.test.tsandNativeChatStructuredSessionDelivery.test.tsx; existing unconfirmed fixtures were updated to keep their notice under the new suppression.
I traced the predicate against the base inline probe condition and the reconcile/dispatch paths. The one gap is that the suppression is broader than the "Orca is resending it" condition it is named for.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Review summaryThe problem. In native chat, if you reopened a chat (or the connection dropped) while a message was still sending, that message briefly showed a red "Message delivery is unconfirmed." line and a Retry button. Orca was already resending it on its own and confirmed it a moment later, so the line flashed and the Retry was never needed. What changes for you. That message now looks like any other message in the chat while Orca resends it, with no line and no Retry. As soon as the agent's host has any record of the message, the chat behaves exactly as before. One rule now decides both when Orca resends such a message and when the line is hidden, so the line can only be hidden while a resend is actually responsible for the message. What the review changed. The first version of this PR also tried two more things. Review found both were wrong, so they were removed and the PR now ships only the fix above:
Deferred (not in this PR).
What was verified.
Not verified.
|
…led-send-returns-to-composer
A send whose answer was lost was resent quietly under the same id but looked like any delivered message. Every message still in the outbox with no failure to show now says "Sending…", muted, in place of its time, until the journal holds a row for it. Rows that say a message did not go through keep only that line and its Retry. The notices read the outbox through the same reconcile as the transcript, so a row that lands clears the marker in one step.
…the time's slot A message the user retried, a second message in doubt, or one requeued over a rejected row had a journal row that did not hold it, so it showed nothing and looked sent. "Sending…" now stays until the row is pending or accepted. The marker took the place of the whole meta row, so the copy button went away while sending and the row jumped when it cleared. The row now stays mounted: copy keeps its hover reveal and "Sending…" sits where the time goes.
Follow-up: "Sending…" until the host confirms a messageThe problem with the earlier version. It removed the flashing "Message delivery is unconfirmed." line, but left a message whose answer was lost looking exactly like a delivered one while Orca resent it in the background. If the answers kept getting lost, the message looked sent but never was. What changes for you now. Any message Orca hasn't seen confirmed shows a muted "Sending…" where its time goes: while it's queued, while it's on its way, and while Orca is quietly resending it. It clears once, when the host records the message, and then the time and copy button show on hover as before. No red line and no Retry for those states; a message that actually failed keeps only its reason and Retry. This follows the common pattern of a quiet pending marker in place of the time. What review changed in this round.
Deferred.
Verified.
Not verified.
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Sending…replaces the flashed "unconfirmed" line — an outbox message the host has not confirmed now shows a mutedSending…where its time normally is, with no Retry, instead of the red "Message delivery is unconfirmed." line that flashed on every reopen mid-send.- Shared resend rule — new
structured-agent-session-outbox-unconfirmed-resend.tsholdsstructuredAgentSessionEntryResendsUnconfirmed, read by both the auto-resend probe and the notices; the probe's separateprobeSettledcheck folds into it and the behavior is unchanged. - Notices read reconciled entries —
structuredAgentSessionDeliveryNoticesnow reconciles the outbox throughreconcileStructuredAgentSessionOutboxWithQueue(the same call the transcript uses) so a row that just landed is answered before the outbox commits it and the marker never flashes the red line on its way out. - Journal rows reach the notices whenever the outbox is non-empty —
NativeChatStructuredSessiongates onoutbox.length > 0rather thanhasRejected;hasRejectedstill gatesstartFailures. - User-row meta restructured —
NativeChatDeliveryNoticeis now a two-shape union;UserMessageMetakeepsSending…in the time's slot visible without hover, preserves the copy button's hover reveal, and keeps the row's height when the marker clears. - Copy + tests —
components.native-chat.messageSendingadded to all six catalogs; new unit and chat-level coverage for the lost-answer send, reopen mid-send, Stop-outlived, immediate rejection, second-in-doubt, and requeued-after-rejection cases.
I traced every branch of the notice decision against the admission/reconcile paths and the probe's resend condition, and the result matches the PR's before/after table (including that a pending/accepted row clears the marker while an unknown/rejected row does not). The new tests distinguish the marker-clearing rule from an any-row rule rather than asserting a tautology. I also ran the changed-code quality gate, the design-system lint on the two changed components, the web typecheck, and the notices (39), message-row (16), delivery-session (20), projection (6), outbox-hook (21), relaunch-hold (14), and start-failure (3) suites — all green.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…led-send-returns-to-composer # Conflicts: # src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Merge of
origin/mainreconciled into the delivery tests —0f5fd45mergesmain; the only conflict wasNativeChatStructuredSessionDelivery.test.tsx, resolved by keeping this branch'sSending…tests and adopting main'suseProbeClock/advanceProbeClockfake-timer helpers for the tests main had converted (main PR #25187). The branch's new unconfirmed/Stop tests still use realsetTimeoutwaits, and main's send-then-click-Retry version of the force-retry test was replaced by the branch's seeded-outbox version — the click path remains covered by the retry-head tests. - Production unchanged — every non-test file in the PR is byte-identical to the previously reviewed head (
059e79e); the notice decision, the shared resend predicate, the two-shapeNativeChatDeliveryNotice, and theoutbox.length > 0journal-row gate are all as previously traced.
I re-read the full PR diff, confirmed the merge brought no production change (git diff 059e79e 0f5fd45 on the six prod source files is empty), and ran NativeChatStructuredSessionDelivery.test.tsx (20/20 green, ~15s).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
NativeChatStructuredSessionDelivery.test.tsx went over the 800-line lint limit after main was merged. The eight tests for the automatic probe of an unconfirmed message move unchanged to NativeChatStructuredSessionDelivery.probe.test.tsx, which uses the shared structured-session test harness for its mocks. The outbox seeding and probe clock helpers move into that harness so both files share them. The at-most-once reliability gate now lists the new file, with a fresh evidence run.
|
no API key found — Pullfrog needs at least one LLM provider API key (e.g. To fix: add the key as a GitHub Actions secret (referenced from your workflow's Open repo secrets → · Configure model → · Setup docs → · Ask in Discord → |
…led-send-returns-to-composer Main now lets the host's row own a message it recorded and then rejected. The "Sending…" marker follows that: the notices read the outbox through the same reconcile as the transcript, now with the loaded rows, so a message whose row was rejected reads as not sent in the host's words and never as sending. The delivery-notice hook passes the journal's rows whenever the outbox holds a message, so the marker still clears on a pending or accepted row.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Rebased the notice path onto main's rewrite — the merge of
origin/main(which landed #24710'sreconcileStructuredAgentSessionOutboxWithQueueand rejected-in-place projection) moved the branch's journal-row gating out ofNativeChatStructuredSession.tsxand intouse-structured-agent-session-delivery-notices.ts; equivalent behavior, nowjournalRows/loadedItemsinside the hook. Sending…gated on the same rule —structuredAgentSessionDeliveryNoticesnow reconciles its entries before deciding, keeps the failure branch for arejected/held/blocked-head message, and emits{ sending: true }only when nopending/acceptedsubmission exists for it.structuredAgentSessionEntryResendsUnconfirmedcontinues to gate the auto-resend cases.- Test split — the delivery probe tests moved to their own
NativeChatStructuredSessionDelivery.probe.test.tsx,config/reliability-gates.jsonclists it, and the notices tests were updated to main's rule (a requeued message over a rejected row shows as not sent, neverSending…).
I traced the reconcile/gating interaction against the base and re-ran the changed suites at this head: notices (41), message-row (16), delivery (12) + probe (8), hook (3), unsent-message (5), and send-disposition (27) — all green. The two prior inline threads are resolved.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…ected message Main's #24710 draws a message Orca recorded and then rejected where the host placed it, as "Not sent", from the host's own history. The host side (the rejection moves the message to its place; a failed start writes its rejections and its row in one transaction) is taken as written. On the client, C2's settlement stays the only place an outbox entry ends: - #24710's reconcile module is folded into C2's: an entry whose rejected row is not loaded yet stays, never sent again, and draws the message until that row loads (or the host's window for its id ends). Nothing new is stored; the held submission says it was rejected. A batch that settles nothing writes nothing. - One "not sent" surface: main's rule hides a rejected message a live card holds or a later copy of the same text superseded, in the rows and the notices alike, on the desktop and the phone. The notice stays C2's muted one, and notices are kept as the same objects across unchanged batches. - No Retry: a message refused before it was recorded still goes back to the conversation's draft with the reason, as before. - A rejected /compact stays in the chat (#24918's rule), since the command's own reply says nothing once the journal shows it. - The phone and the desktop both draw these rows; only the host's outline, which older clients read, leaves them out.
Brings main through d3afb5c, which includes #24606 as it landed: in the native chat that is the version C2 already merged, so those files keep C2's resolution, and the locales keep C2's messageNotConfirmed line beside main's new keys. From #24918: - Not-sent rows come back at their journal places, so the phone draws them where the host recorded them. - A command's reply stays silent only once the loaded journal draws it as not sent (structuredAgentSessionJournalShowsRejection), so a pending command a Stop then withdraws still gets its reply. - The phone's echo matching no longer double-counts not-sent rows. - The delivery-line tone test is taken in C2's terms: a recorded rejection reads muted from its row or from this client's copy, and a message on its way reads only as sending. #24918's `notSent`/Retry notice shape stays out.

ELI5
In Orca's native chat, a message you send waits in a local queue until the agent's host confirms it has the message. If that confirmation gets lost (the connection drops, or you reopen the chat while a message is still sending), Orca sends it again on its own, under the same message id, until the host answers. Today the chat shows a red "Message delivery is unconfirmed." line with a Retry button while that happens, which flashes on every reopen mid-send and asks you to do something Orca is already doing. An earlier version of this PR removed the flash but left the message looking exactly like a delivered one until the resend settled it.
Now every message Orca hasn't seen confirmed shows a quiet grey "Sending…" under it, where its time normally appears, from the moment you send it until the host's record shows it has the message (pending or delivered). There's no red text and no Retry, because Orca is already sending it. Messages that really did fail still show why, as on main: one the host recorded and then rejected shows as not sent, with the host's reason and no Retry, and one the host never recorded keeps its Retry. That includes a send made while the host can't be reached at all: it fails to connect, Orca doesn't resend it, and it shows "Your message was not sent." with Retry, exactly as today.
What Changed
The problem. Every send waits in a local queue (the outbox) until the host that runs the agent answers. When the answer is lost, Orca resends the message automatically, under the same id, with a growing wait between tries (up to 16 seconds), until the host's record for it (its journal row) appears. The host records a message id only once, so the resend can't deliver it twice. Two things went wrong around this:
Before / after (what you see)
Mechanism.
structured-agent-session-delivery-notices.tsdecides what each queued message's row says. A message in a failure state (rejected, held for Retry, or stopped in front of the queue with no automatic resend) keeps exactly its line and Retry. Every other message now gets a "sending" notice until its journal row says the host has it (pending or accepted). A row with an unknown outcome does not count, so a message you retried or a second message in doubt never looks delivered meanwhile. A rejected row makes the message the host's: main shows it as not sent from that row (or from the queue's copy until the row loads), never as sending. A message whose answer was lost and that Orca is resending on its own counts as sending, not failed; the rule for that,structuredAgentSessionEntryResendsUnconfirmed(new,src/shared/structured-agent-session-outbox-unconfirmed-resend.ts), is the automatic resend's own condition, whichuse-structured-agent-session-outbox-unconfirmed-probe.tsnow reads from there with no change in behaviour.reconcileStructuredAgentSessionOutboxWithQueue, with the loaded rows, as the transcript passes them). Without it, a resent message whose journal row had just arrived showed the red line for one render before the queue caught up, so the marker would have gone "Sending…" → red line → nothing. With it, the marker changes once.NativeChatDeliveryNotice(inNativeChatMessageRow.tsx) now has two shapes: sending, or a failure line with optional Retry. The user row's time and copy button sit in a small row under the bubble that appears on hover. While the message is sending, that row stays visible with "Sending…" in the time's place, muted, and the copy button keeps its own hover reveal, so the row keeps its height when the marker clears. Everything is derived from the queue and journal rows Orca already holds; nothing new is stored.use-structured-agent-session-delivery-notices.ts(the hook main added to build the notices) gives them the journal's rows whenever the queue is non-empty, as well as whenever a message is shown as not sent (main's rule), since every queued message now needs to know whether its row exists. The rows' list only changes when a batch carries new rows, not on every streamed update. The loaded rows are passed only while the queue holds a message and a rejection exists, the one case the reconcile reads them, so a streaming turn rebuilds no notice otherwise.components.native-chat.messageSending("Sending…"), in all six locale catalogs, using each catalog's existing translation of the question card's "Sending…".No wire, host or saved-field change. Messages saved by an older build are read the same way.
Why
The common pattern for a chat client that quietly resends unsent messages under the same id is a muted "Pending" in place of the message's time until the server acknowledges it: no red text and no Retry. The resend stays quiet, and an unconfirmed message never passes for a delivered one. This PR follows that, and keeps the red line and Retry only for messages that actually failed or that only the user can move.
Alternatives considered:
Differences from the common pattern
Linked Issue
No issue.
Visual Proof
Captured on a separate test Mac in hidden, isolated test builds with a stand-in agent. Before = main at
1dc6157614c; after = this PR at059e79e4188, before main's #24710 merged in. None of the captured states involves a rejected message, which #24710 owns. The app's saved outbox was seeded where a state needed it, and a page observer recorded when each label appeared. The test Mac was heavily loaded by other work, so the timings below are upper bounds.No new pop-up, label or line appears in the after build other than "Sending…". Changed from the earlier set: these replace the screenshots taken at
4959afa60b7, before the marker existed. Not captured: a real host outage (a send that can't reach the host shows "Your message was not sent." with Retry, unchanged in both builds; the test setup has no remote host), and a message whose host record has an unknown outcome at the front of the queue (unchanged; covered by unit tests).Testing
I manually tested these changes locally
Automated tests added/updated, or explained why not below
structured-agent-session-delivery-notices.test.ts: a message whose answer was lost and that has no journal row says only that it is sending, with no Retry; so do a message on its way, one waiting its turn, and one behind the message the queue stopped on. So do a message while the Retry you pressed is queued or on its way (including after a host restart), and the second of two messages in doubt. A message put back in the queue over a rejected row, alone or behind a stopped one, says it was not sent in the host's words with no Retry, never "Sending…", whether the queue's copy or the loaded row draws it. Each stops saying it once the journal holds a pending or accepted row for it, and a resent message whose row just arrived never shows the red line on the way. A message rejected before the host recorded it, held for Retry, unconfirmed with a host record, retried by you, or outlived by a Stop says only its reason, with Retry, and never "Sending…".NativeChatStructuredSessionDelivery.test.tsx: a new send whose answer is lost shows "Sending…" from the moment it is sent, with no line or Retry, is resent with the same request, and the marker goes when it is confirmed; a chat reopened mid-send shows "Sending…" until the resend settles it; with a host record of unknown outcome, or after a Stop, it shows the line and Retry and no "Sending…".NativeChatMessageRow.test.tsx: "Sending…" is muted, sits in the time's place, is visible without hover, and has no Retry; the copy button is there with its hover reveal; once confirmed, the same row holds the time again.Ablations: with the four changed source files put back to the previous head (
4959afa60b7merged with main) and the new tests kept, 8 tests fail, including the lost-answer send showing no "Sending…" in both the unit and the chat-level test. Without the reconcile step in the notices, 2 tests fail, each showing the red "unconfirmed" line on a resent message whose row had just arrived. With the marker ending at any journal row (the rule atb5cc3c83695), 6 tests fail: the three Retry cases, both second-in-doubt cases and the requeued one. With the row atb5cc3c83695, where the marker replaced the whole row, 2 tests fail: the copy button is missing while sending, and the row is not kept.Ran 29 outbox, delivery-notice, send-disposition, message-row, message-list and structured-chat test files (317/317 passed), plus the 8 locale catalog test files (53/53).
After merging main's fix(native-chat): show a message Orca accepted and then failed to deliver as "Not sent" in the chat #24710: ran 49 files (the 29 above, the split-out resend suite, and every native-chat and shared projection/outbox test file fix(native-chat): show a message Orca accepted and then failed to deliver as "Not sent" in the chat #24710 added or changed): 410/410 passed. With the notices hook passing the journal's rows only while a message is rejected (main's rule alone), 4 chat-level tests fail: a message reopened mid-send, or one the host recorded with an unknown outcome, never shows its "unconfirmed" line and Retry once that row arrives.
oxlint, the changed-code quality gate (including React Doctor and the design system) and the localization checks pass. The web and node typechecks ran through the machine's typecheck queue after the merge; the only errors are "cannot find module" for
stream-json/stream-chain, a dependency main added that is missing from the local install, in files this PR does not touch.AI Disclosure
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Renderer-only. The same on macOS, Linux, Windows and SSH hosts. The "Sending…" marker covers a send whose answer was lost and a chat reopened mid-send; it is not an outage safety net. When the host can't be reached, a send fails to connect, Orca does not resend it, and its message shows "Your message was not sent." with Retry, as on main. A later follow-up changes that so the text goes back to the composer instead. The outage cue is the host-scoped notice from the separate open PR #24898, which says in the chat that the host is reconnecting or offline. Each PR is complete on its own: with or without #24898, every message in Orca's send queue shows either why it did not go through or "Sending…" until the host's record says it has the message (pending or delivered). One new string in all six locale catalogs. Mobile is untouched.
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)Author: @BrennanKB5