Skip to content

fix(mobile): match and place pending chat echoes with desktop's rule - #33970

Closed
AmoabaKelvin wants to merge 19 commits into
mainfrom
mobile-chat-pending-echo
Closed

AmoabaKelvin wants to merge 19 commits into
mainfrom
mobile-chat-pending-echo

Conversation

@AmoabaKelvin

@AmoabaKelvin AmoabaKelvin commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator
Files Added Deleted Net
Test 21 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​787 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1570 $\color{#cf222e}{\Huge{\mathbf{−}}}$​783
Prod 51 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​832 $\color{#cf222e}{\Huge{\mathbf{−}}}$​984 $\color{#cf222e}{\Huge{\mathbf{−}}}$​152

Stacked on #33969: review the commits after a7eaef8.

ELI5

After you send a message in a terminal chat, the phone shows a placeholder bubble until the agent records it. The phone now decides when that bubble is done with desktop's rule. A bubble that is never matched now sits at the bottom, as on desktop.

What Changed

  • Echo matching (native-chat Fix update checker 406 error and add menu feedback #8). Before: the phone counted matching user rows across the whole loaded window, with its own glue matcher. Paging in an older identical "yes" could clear a new echo before its row landed. If an earlier send never landed, a combined row could clear the sends after it. Now: the phone matches only rows after the point each message was sent, anchored at the first open send, with pasted image paths stripped. This is desktop's rule.
    • native-chat-pending-occurrence.ts moved as-is to src/shared/.
    • The rule part of native-chat-pending.ts is now src/shared/native-chat-pending-sends.ts, including the 8-send cap as NATIVE_CHAT_PENDING_SEND_LIMIT. The cache and launch-prompt code stay in the renderer.
    • The mobile matcher, counters, GLUE_SLIDE_BUDGET and the baseline module are deleted. A small adapter in mobile-native-chat-pending-echo.ts maps phone sends to NativeChatPendingSend.
  • Unmatched echo placement (Fix excessive re-renders causing UI lag #26). Before: an echo the TUI never recorded stayed where it was sent (A1, hi, A2). Now: transcript rows, then the streaming bubble, then unmatched echoes (A1, A2, hi), the order desktop's messageSortRank produces. The phone appends its three groups in that order rather than re-sorting rows the host already ordered, so compareMessages is not lifted.

Why

Desktop logic wins in the mobile/desktop parity audit. The phone's whole-window count could clear an echo early. One boundary-based rule fixes that and keeps both apps aligned.

Linked Issue

Part of the mobile/desktop parity audit

Visual Proof

No device captures for this fleet. The change adopts desktop's existing behavior: messagesAfterPendingBoundary and the occurrence rule in native-chat-pending-occurrence.ts, and pending rows' sort rank in native-chat-session-assembler.ts.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

The desktop pending tests moved with the rule. Mobile tests that pinned the old matcher are deleted or rewritten. The "A never lands" case now expects desktop's result. New regression tests cover glued photo captions and folded image boundaries; both fail without their fix. Focused root tests (shared and renderer pending, delivery, assembler, subagent sections) and mobile src/session pass.

AI Disclosure

Review

Codex review, 3 rounds, all findings fixed:

  • Restored the last loaded row as the send point while reconnecting.
  • A photo caption glued to the next send no longer leaves that send stuck.
  • Folded image rows keep preview binding.
  • The preview echo type now carries sentAt.
  • Matching uses image-normalized rows like desktop.
  • Open sends are capped at 8 like desktop.

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

Mobile behavior removed (desktop wins):

  • An unmatched echo no longer stays where it was sent; it sits at the bottom.
  • Matching no longer counts the whole loaded window.
  • If an earlier send never lands, a combined row no longer clears the sends after it.
  • An echo is hidden once its row lands and pruned when the agent replies, instead of being removed at once.
  • Sends made while the transcript is still loading no longer re-point at the first loaded row; they fall back to send time, as on desktop.
  • At most 8 open echoes per chat; the oldest is dropped.

Desktop: no behavior change. Code moved; pendingSendsAsMessages is split so the phone can call visibleNativeChatPendingSends.

Compatibility: phone-local state only, no wire change. As on desktop over SSH, a send made while loading compares the phone's clock with host row times.

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)

@AmoabaKelvin
AmoabaKelvin force-pushed the mobile-chat-pending-echo branch 2 times, most recently from af83f38 to 1ede9ce Compare October 27, 2026 01:20
@AmoabaKelvin
AmoabaKelvin marked this pull request as ready for review October 27, 2026 01:21
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Walkthrough

The change centralizes pending-send reconciliation and chat presentation logic in shared modules. Mobile send tracking now uses transcript boundaries and shared matching helpers, and pending messages are appended after folded messages. Mobile and renderer components use shared helpers for message copying, tool rows and runs, tool output, task counts, and diff formatting. Agent selection and transcript-readability checks also use shared availability helpers. The structured background-task view now receives foreground commands derived from session state.

Priority: Normal

Merge Risk: Moderate · up to 6cf4b

Resolve the stale-identity concern and the two pending-message reconciliation defects before merging; photo previews can attach to older messages, and an echoed send can remain pending.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surv… 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 change: aligning mobile pending chat echo matching and placement with desktop behavior.
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.
Description check Passed The description is detailed and covers the required ELI5, changes, rationale, testing, review, notes, and checklist sections. It identifies the parity audit but does not provide an actual linked issue…
Full details: Description check

Explanation

The description is detailed and covers the required ELI5, changes, rationale, testing, review, notes, and checklist sections. It identifies the parity audit but does not provide an actual linked issue under “Fixes #”. The Visual Proof section also does not use the exact N/A wording required for changes without visual impact.

Full details: Docstring Coverage

Explanation

Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 39 functions across 50 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

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
  • 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.

@AmoabaKelvin
AmoabaKelvin force-pushed the mobile-chat-pending-echo branch 7 times, most recently from bd4711f to 90f0284 Compare October 27, 2026 05:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d4ea09ad-a8d4-4ed5-bfaa-99a5961b2550
Commits

Reviewing files that changed from the base of the PR and between 8d72873 and 90f0284.

Files selected for processing: 72
  • config/flaky-test-pattern-baseline.txt
  • mobile/src/session/MobileNativeChatDiffCard.tsx
  • mobile/src/session/MobileNativeChatFullContent.tsx
  • mobile/src/session/MobileNativeChatMessage.tsx
  • mobile/src/session/MobileNativeChatMessageActionsSheet.tsx
  • mobile/src/session/MobileNativeChatToolLine.tsx
  • mobile/src/session/MobileNativeChatToolRun.tsx
  • mobile/src/session/MobileNativeChatView.streaming-rows.test.ts
  • mobile/src/session/MobileNativeChatView.tsx
  • mobile/src/session/mobile-native-chat-merged-snapshot-parity.test.ts
  • mobile/src/session/mobile-native-chat-message-plain-text.test.ts
  • mobile/src/session/mobile-native-chat-message-plain-text.ts
  • mobile/src/session/mobile-native-chat-pending-baseline.test.ts
  • mobile/src/session/mobile-native-chat-pending-baseline.ts
  • mobile/src/session/mobile-native-chat-render-data.test.ts
  • mobile/src/session/mobile-native-chat-render-data.ts
  • mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.test.ts
  • mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.ts
  • mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-glued-pending.test.ts
  • mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.ts
  • mobile/src/session/native-chat/input/MobileNativeChatTaskList.tsx
  • mobile/src/session/native-chat/mobile-native-chat-eligibility.test.ts
  • mobile/src/session/native-chat/mobile-native-chat-eligibility.ts
  • mobile/src/session/native-chat/mobile-native-chat-pending-echo.test.ts
  • mobile/src/session/native-chat/mobile-native-chat-pending-echo.ts
  • mobile/src/session/native-chat/mobile-native-chat-pending-pasted-content.test.ts
  • mobile/src/session/native-chat/mobile-native-chat-pending-retirement.test.ts
  • mobile/src/session/native-chat/mobile-native-chat-pending-retirement.ts
  • mobile/src/session/native-chat/use-mobile-native-chat-unconfirmed-sends.ts
  • mobile/src/session/use-mobile-native-chat-controller.test.ts
  • mobile/src/session/use-mobile-native-chat-readability.ts
  • mobile/src/session/use-mobile-native-chat-turn-disclosure-opening-send.test.tsx
  • mobile/src/session/use-mobile-structured-background-tasks.ts
  • mobile/src/session/use-mobile-structured-native-chat-send-bridge.test.ts
  • src/renderer/src/components/native-chat/NativeChatDiffCard.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageRow.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
  • src/renderer/src/components/native-chat/NativeChatTaskList.tsx
  • src/renderer/src/components/native-chat/NativeChatToolLine.tsx
  • src/renderer/src/components/native-chat/NativeChatToolOutput.tsx
  • src/renderer/src/components/native-chat/NativeChatToolRun.tsx
  • src/renderer/src/components/native-chat/native-chat-message-copy-text.ts
  • src/renderer/src/components/native-chat/native-chat-pane-resolution.ts
  • src/renderer/src/components/native-chat/native-chat-pending.test.ts
  • src/renderer/src/components/native-chat/native-chat-pending.ts
  • src/renderer/src/components/native-chat/native-chat-session-assembler.ts
  • src/renderer/src/components/native-chat/native-chat-stopped-before-start-row.ts
  • src/renderer/src/components/native-chat/native-chat-tool-line-label.ts
  • src/renderer/src/components/native-chat/use-native-chat-pending-delivery.ts
  • src/renderer/src/components/native-chat/use-native-chat-toggle-shortcut.surface-owner.test.ts
  • src/renderer/src/components/native-chat/use-native-chat-toggle-shortcut.ts
  • src/renderer/src/components/status-bar/workspace-space-format.ts
  • src/renderer/src/components/tab-bar/tab-bar-item-surface.tsx
  • src/renderer/src/components/terminal-pane/use-terminal-pane-chat-state.ts
  • src/renderer/src/i18n/en-runtime-required.json
  • src/shared/format-bytes.ts
  • src/shared/native-chat-availability.test.ts
  • src/shared/native-chat-availability.ts
  • src/shared/native-chat-message-copy-text.test.ts
  • src/shared/native-chat-message-copy-text.ts
  • src/shared/native-chat-pending-occurrence.test.ts
  • src/shared/native-chat-pending-occurrence.ts
  • src/shared/native-chat-pending-pasted-content.test.ts
  • src/shared/native-chat-pending-sends.test.ts
  • src/shared/native-chat-pending-sends.ts
  • src/shared/native-chat-stopped-before-start.ts
  • src/shared/native-chat/input/native-chat-task-list.ts
  • src/shared/native-chat/tools/native-chat-edit-model.ts
  • src/shared/native-chat/tools/native-chat-tool-activity.ts
  • src/shared/native-chat/tools/native-chat-tool-line-view.ts
  • src/shared/native-chat/tools/native-chat-tool-output.ts
  • src/shared/native-chat/tools/native-chat-tool-run-header.ts
Files with no reviewable changes: 12
  • mobile/src/session/mobile-native-chat-message-plain-text.ts
  • mobile/src/session/mobile-native-chat-message-plain-text.test.ts
  • mobile/src/session/mobile-native-chat-pending-baseline.ts
  • mobile/src/session/native-chat/mobile-native-chat-pending-pasted-content.test.ts
  • mobile/src/session/mobile-native-chat-pending-baseline.test.ts
  • mobile/src/session/mobile-native-chat-merged-snapshot-parity.test.ts
  • mobile/src/session/use-mobile-native-chat-turn-disclosure-opening-send.test.tsx
  • mobile/src/session/native-chat/mobile-native-chat-pending-retirement.test.ts
  • config/flaky-test-pattern-baseline.txt
  • mobile/src/session/native-chat/mobile-native-chat-pending-echo.test.ts
  • mobile/src/session/native-chat/mobile-native-chat-pending-retirement.ts
  • mobile/src/session/mobile-native-chat-render-data.test.ts

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

Comment on lines +176 to +180
const tailIndex = foldedBoundaryIndex(messages, messageIndexById, entry.afterMessageId)
// Same boundary-relative occurrence as the shared pending rule, so older identical rows cannot bind.
const candidate = candidates.filter(
(message) => tailIndex === undefined || (messageIndexById.get(message.id) ?? -1) > tailIndex
)[nativeChatPendingOccurrence(entry, 0) - 1]

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.

Minor · Functional Correctness · Quick win

Apply the shared timestamp fallback to image-preview matching.

foldedBoundaryIndex returns -1 when afterMessageId is null. It returns undefined when the boundary row is paged out. In both cases the filter at Line 179 keeps every candidate. The shared rule in messagesAfterPendingBoundary behaves differently. For a null boundary without afterEmptyTranscriptSessionId, it keeps only rows after the send timestamp. For a missing boundary id, it also falls back to the send timestamp.

Trigger: the user sends a captioned photo while the transcript is hydrating. captureSendOrigin records afterMessageId: null because the tail is empty. Settled history then arrives with an older identical look[Image #1] prompt. findLandedImagePreviewEchoes binds the new photo's preview URI to the old row. retireLandedMobileNativeChatPending then strips images from the pending echo. The shared prunePendingSends still keeps the caption, because its timestamp filter excludes the old row. Result: the old turn shows the new thumbnail, and the new echo has no photo.

Fix: widen PendingImagePreviewEcho to include the boundary timestamp fields. Then apply the timestamp fallback when no index boundary applies. Callers pass MobileNativeChatPendingMessage, which already has these fields. The coding guideline says "Before writing new logic ... check whether an existing implementation already does the job." A better long-term fix is to export the timestamp predicate from native-chat-pending-sends.ts and reuse it here.

🐛 Proposed fix
 export type PendingImagePreviewEcho = Pick<
   NativeChatPendingSend,
-  'id' | 'text' | 'sentAt' | 'afterMessageId' | 'matchingOccurrence'
+  | 'id'
+  | 'text'
+  | 'sentAt'
+  | 'afterMessageId'
+  | 'afterMessageTimestamp'
+  | 'afterEmptyTranscriptSessionId'
+  | 'matchingOccurrence'
+  | 'matchingAfterTimestamp'
 > & { images?: string[] }
     const tailIndex = foldedBoundaryIndex(messages, messageIndexById, entry.afterMessageId)
+    const usesIndexBoundary =
+      entry.afterMessageId === undefined ||
+      (entry.afterMessageId === null && entry.afterEmptyTranscriptSessionId !== undefined) ||
+      (entry.afterMessageId !== null && tailIndex !== undefined)
+    const boundaryTime = nativeChatPendingMatchingAfter(entry)
     // Same boundary-relative occurrence as the shared pending rule, so older identical rows cannot bind.
-    const candidate = candidates.filter(
-      (message) => tailIndex === undefined || (messageIndexById.get(message.id) ?? -1) > tailIndex
-    )[nativeChatPendingOccurrence(entry, 0) - 1]
+    const candidate = candidates.filter((message) => {
+      if (usesIndexBoundary) {
+        return (messageIndexById.get(message.id) ?? -1) > (tailIndex ?? -1)
+      }
+      if (message.timestamp === null) {
+        return true
+      }
+      return entry.afterMessageTimestamp == null
+        ? message.timestamp >= boundaryTime
+        : message.timestamp > boundaryTime
+    })[nativeChatPendingOccurrence(entry, 0) - 1]

Source: Coding guidelines

launchAgent: tab.launchAgent
})
// Frames can omit live status, so the remembered agent fills in only after desktop's evidence.
const agent = evidenceAgent ?? remembered?.agent ?? null

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.

Major · Functional Correctness · Quick win

Supported by static analysis

Script executed:

sed -n '40,100p' mobile/src/session/native-chat/mobile-native-chat-eligibility.ts
git diff 1fdf89685e82d034e9af740277821067c65906ba 90f0284c213c70ace7d7311428a1ef13b5f5ea31 -- mobile/src/session/native-chat/mobile-native-chat-eligibility.ts mobile/src/session/native-chat/mobile-native-chat-eligibility.test.ts
rg -n 'agentConfirmed' mobile/src

Repository: stablyai/orca

Length of output: 11648


Script executed:

set -o pipefail
printf '%s\n' '--- mobile confirmation consumer ---'
sed -n '1,180p' mobile/src/session/use-mobile-native-chat-active-resolution.ts
printf '%s\n' '--- agentConfirmed consumers ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -e 'activeChatAgentConfirmed' -e 'agentConfirmed' mobile/src src/renderer/src src/shared || test "$?" -eq 1
printf '%s\n' '--- desktop resolver ---'
sed -n '1,240p' src/renderer/src/components/native-chat/native-chat-pane-resolution.ts
printf '%s\n' '--- shared availability ---'
sed -n '1,240p' src/shared/native-chat-availability.ts
printf '%s\n' '--- mobile native-chat session/controller candidates ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -e 'activeChatAgentConfirmed' -e 'sendTerminal' -e 'write' -e 'terminal.write' mobile/src/session mobile/src | head -200

Repository: stablyai/orca

Length of output: 36288


Script executed:

sed -n '1,180p' mobile/src/session/use-mobile-native-chat-active-resolution.ts
rg -n -F --glob '*.ts' --glob '*.tsx' -e 'activeChatAgentConfirmed' -e 'agentConfirmed' mobile/src src/renderer/src src/shared || test "$?" -eq 1
sed -n '1,240p' src/renderer/src/components/native-chat/native-chat-pane-resolution.ts
sed -n '1,240p' src/shared/native-chat-availability.ts

Repository: stablyai/orca

Length of output: 10207


Script executed:

set -o pipefail
printf '%s\n' '--- mobile native-chat controller ---'
sed -n '1,230p' mobile/src/session/use-mobile-native-chat-controller.ts
printf '%s\n' '--- mobile native-chat message send ---'
sed -n '1,260p' mobile/src/session/use-mobile-native-chat-message-send.ts
printf '%s\n' '--- terminal write lock ---'
sed -n '1,240p' mobile/src/session/native-chat/mobile-native-chat-terminal-write-lock.ts
printf '%s\n' '--- native chat write operation ---'
sed -n '1,150p' mobile/src/session/mobile-session-write-operations.ts

Repository: stablyai/orca

Length of output: 25171


Keep the remembered identity ahead of a conflicting launch hint.

When live status is absent, nativeChatAvailabilityAgent selects the launch hint before the remembered identity. The resolver then marks the launch agent as confirmed and discards the remembered session.

useMobileNativeChatController enables terminal chat writes when activeChatAgentConfirmed is true. The message sender uses the same terminal handle while its agent reference contains the launch agent. A statusless frame can therefore send through the current terminal with the wrong agent identity.

Desktop uses the same shared precedence, but that does not remove the mobile path's explicit contract to retain identity until a new PTY handle replaces it. Preserve the remembered agent and session for a conflicting launch hint, and keep agentConfirmed false until live evidence replaces it. Launch-only tabs without a remembered identity may retain their existing behavior.

@AmoabaKelvin
AmoabaKelvin force-pushed the mobile-chat-pending-echo branch from 90f0284 to 6cf4b1c Compare October 27, 2026 13:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5abe7f31-d690-465b-ae37-43814acb8b67
Commits

Reviewing files that changed from the base of the PR and between 90f0284 and 6cf4b1c.

Files selected for processing: 12
  • mobile/src/session/MobileNativeChatDiffCard.tsx
  • mobile/src/session/MobileNativeChatFullContent.tsx
  • mobile/src/session/MobileNativeChatView.streaming-rows.test.ts
  • mobile/src/session/mobile-native-chat-message-plain-text.ts
  • mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-glued-pending.test.ts
  • mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.ts
  • mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx
  • mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx
  • src/renderer/src/components/native-chat/native-chat-message-copy-text.ts
  • src/renderer/src/components/native-chat/native-chat-pending.ts
  • src/renderer/src/i18n/en-runtime-required.json
  • src/shared/native-chat-message-copy-text.ts
Files with no reviewable changes: 1
  • mobile/src/session/mobile-native-chat-message-plain-text.ts
Files skipped from review as they are similar to previous changes: 1
  • src/renderer/src/i18n/en-runtime-required.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.

Comment on lines +240 to +242
const landedImagePreviews = transcriptSettled
? findLandedImagePreviewEchoes(messages, pending)
: []

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.

Major · Data Integrity & Integration · Heavy lift

Do not bind a hydration-time photo to older history.

If a user sends a photo while the transcript is empty and unsettled, captureSendOrigin records no row boundary. When an older photo with the same caption arrives during hydration, findLandedImagePreviewEchoes can select it without checking sentAt. For an image-only send, retirement then removes the pending photo and attaches its local preview to the older row. Apply the send-time fallback to preview candidates when no authoritative boundary exists. (raw.githubusercontent.com)

? rebaseMobileNativeChatPendingBaselines(messages, current)
: current
const next = retireLandedMobileNativeChatPending(messages, rebased, landedImagePendingIds)
const next = retireLandedMobileNativeChatPending(echoRows, current, landedImagePendingIds)

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.

Major · Functional Correctness · Heavy lift

Preserve source-row send boundaries through image folding.

If the raw tail at send time is an image-source row, normalization can fold that row into a later prompt and remove its ID from echoRows. Shared retirement and visibility then fall back from the captured row boundary to timestamps. A following send glued into that prompt can remain pending when the prompt and source have the same timestamp, even after an assistant reply. Preserve the source-row boundary when matching folded rows, including sends represented in the folded prompt. (raw.githubusercontent.com)

Mobile now uses the shared detected-then-launch precedence and transcript-readability check; the phone's remembered agent only fills in after them.
Copy now leaves out roster and background-task fallback twins and wordless provider frames.
Mobile reuses it instead of its own matcher.
…hed ones

Uses desktop's rule: rows before the send no longer retire an echo, a send that
never lands no longer lets later glued sends retire, and an unmatched echo sits
below later turns.
… at eight

Same input and bound as desktop; each open send rescans the transcript.
@AmoabaKelvin

Copy link
Copy Markdown
Collaborator Author

Folded into #33977 to land the stack in one pass.

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