Skip to content

feat(native-chat): a Jump to top button beside Jump to latest - #25702

Open
brennanb2025 wants to merge 24 commits into
mainfrom
brennanb2025/native-chat-jump-to-top
Open

brennanb2025 wants to merge 24 commits into
mainfrom
brennanb2025/native-chat-jump-to-top

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 43 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2051 $\color{#cf222e}{\Huge{\mathbf{−}}}$​76 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1975
Prod 37 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​738 $\color{#cf222e}{\Huge{\mathbf{−}}}$​160 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​578

ELI5

In a long desktop chat, there's no quick way back to the very first message: you scroll up, wait for older messages to load, and repeat. This adds an up-arrow button next to "Jump to latest" that takes you straight to the first message.

What Changed

Before: reaching the start of a long conversation meant scrolling up repeatedly while older messages loaded in pages.

After: while you're scrolled away from the end and the start is out of view, an up-arrow button (tooltip "Jump to top") appears beside "Jump to latest". Clicking it loads any older history that isn't loaded yet, with a spinner on the button while it loads, then lands on the first message. Scrolling, sending, pressing Jump to latest, picking a rail item or switching away cancels the jump. Reduced-motion settings turn off the spinner animation.

Mechanism: it reuses the existing history jump that the side rail uses, with a new "start of conversation" target. It's a small NativeChatJumpToTop button placed to the left of "Jump to latest" (which landed in #25759); "Jump to latest" keeps its look and stays centred.

Why

Asked for on #23797. Split out of #24514 so the scrolling behaviour (#24514), the "Jump to latest" restyle (#25759, merged) and this button can each be reviewed and landed on their own. Trade-off to weigh before merging: on a very long or remote session, jumping to the start loads every older page, which can take time and memory.

Stacked on #24514. Until it merges, this PR's diff includes it. Review only the last commit, 938056d2fae. After #24514 lands, this PR's diff shrinks to just this button.

Linked Issue

Fixes #23797 (together with #24514 and #25759)

Visual Proof

Kelvin's recording on #24514 shows both buttons (it was made before the split): https://github.com/user-attachments/assets/474af3c7-710b-47fa-80c9-1be87206f2dc

Testing

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

Kelvin's Jump to top from #24514, rebuilt on the reworked #24514. Each behaviour has a test that was checked to fail with it removed: shows only when scrolled away and the start is out of view; pages in older history and lands on the first message; spinner while loading; the reader scrolling or sending cancels it. 358 native-chat and structured-session test files (3,438 tests), the web typecheck, lint, the changed-code quality gate and the localization checks pass. Not yet seen in the running app.

Differs from the earlier version: opening a row no longer cancels the jump, because #24514 no longer tracks opened rows. The earlier "hand focus back to the transcript when the focused button hides" is dropped, matching the merged "Jump to latest".

Notes

Renderer-only. Same behaviour on macOS, Linux and Windows. On SSH or remote sessions, loading every older page goes over the connection, which is the trade-off noted above.

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)

Co-authored by Kelvin (@AmoabaKelvin), who built the feature in #24514.

AmoabaKelvin and others added 6 commits October 4, 2026 22:46
…umps to latest and top

Sending while scrolled up left the reader parked in old history, and the way back was a faint button. A send now resumes following, opening a row to read it stops following until it is closed, and Jump to latest and Jump to top sit above the composer. Refs #23797.
…o scrolled away

An answer's reveal is now held from the click and dropped if the reader acts before the host accepts. In the terminal lane, an answer with no terminal to write to reveals nothing.
The terminal lane's reveal now uses the same check the send does, and the send-site guard test ignores comments.
Jump to top moves to its own PR so this one lands the send reveal and
follow behaviour on their own.
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Walkthrough

The changes update native-chat transcript scrolling, add navigation to the conversation start, and connect composer submissions and structured-session actions to latest-message reveals. They also add queued-send and resume results, accessibility labels, translations, and tests for reader navigation, history paging, and submission behavior.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to 93805

In a very long conversation, Jump to top may stop short. A successful Resume may also move the transcript after the reader has navigated elsewhere. Both are recoverable navigation issues; the change is mergeable with owner awareness or follow-up.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 365d6

The changes affect conversation navigation and when accepted actions return the reader to the latest messages. The reviewed paths preserve existing message and command checks. No new security issue was demonstrated, but coverage is incomplete.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected new reveal operations affect conversation presentation and reader-follow state, not command execution authority. The structured wrapper still invokes existing controller methods; no broader tenant, service, or data-store exposure was established by these changes.

Trust Boundaries and Controls

  • observed — The existing structured mutation layer names the originating session, supplies an operation ID and expected runtime fence, fingerprints the payload, and can drop stale results. The new response wrapper continues through that controller path. These client controls are counterevidence to a bypass, not a substitute for verifying host authorization.

Resilience and Maintainability Implications

  • observed — The inspected live-transcript paging path joins concurrent requests within a transcript epoch and discards results after session, transport-owner, visibility, or epoch changes. Failed reads preserve the loaded transcript. Structured paging selects a read owner by session and target, but that owner's complete transition implementation remains unverified in this pass.
🚥 Pre-merge checks | ✅ 4 | ❓ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ❓ Inconclusive Docstring coverage is 37.66% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 77 functions across 80 files. (8 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 [#23797] The PR adds a Jump to top control that pages older history and navigates to the first message. It exposes busy state and supports cancellation when the reader scrolls, sends, or leaves the pa…
Out of Scope Changes check ✅ Passed The summarized whole-PR changes support [#23797] scrolling, transcript navigation, send-triggered reveal, accessibility, or their tests and translations. The unrelated changes visible in the increment…
Title check ✅ Passed The title clearly identifies the primary change: adding a “Jump to top” button beside “Jump to latest” in native chat.
Description check ✅ Passed The description explains the user-facing change, its purpose, implementation, linked issue, visual proof, testing, and compatibility considerations. Some checklist items remain unchecked, and the Revi…
Full details: Docstring Coverage

Explanation

Docstring coverage is 37.66% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 77 functions across 80 files. (8 skipped: 6 unsupported, 2 over the file limit.)

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

@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: 3


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 06781e25-b243-4f37-b73c-338787bc72c8
📥 Commits

Reviewing files that changed from the base of the PR and between 990b0ed and dff63cb.

📒 Files selected for processing (66)
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/NativeChatJumpControls.test.tsx
  • src/renderer/src/components/native-chat/NativeChatJumpControls.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.growth-windowing.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.interrupted-turn-fold.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.message-rail-windowing.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.older-history.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.provider-retry-runs.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.queued-turn-order.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.rail-history-jump.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.stream-render.perf.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.subagent-sections.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.task-list-frames.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.tool-stream-cost.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.turn-fold.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.turn-history.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.turn-indicator.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.turn-membership.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.turn-timing.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.unsent-message.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.windowing.test.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.reveal-latest.test.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.launch-lifecycle.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • src/renderer/src/components/native-chat/NativeChatSubagentRun.test.tsx
  • src/renderer/src/components/native-chat/NativeChatSubagentRun.tsx
  • src/renderer/src/components/native-chat/native-chat-app-root-test-render.tsx
  • src/renderer/src/components/native-chat/native-chat-autoscroll.test.ts
  • src/renderer/src/components/native-chat/native-chat-autoscroll.ts
  • src/renderer/src/components/native-chat/native-chat-composer-types.ts
  • src/renderer/src/components/native-chat/native-chat-disclosure-store.ts
  • src/renderer/src/components/native-chat/native-chat-reader-scroll-input.ts
  • src/renderer/src/components/native-chat/native-chat-send-reveal-ratchet.test.ts
  • src/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-composer-submit.ts
  • src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.ts
  • src/renderer/src/components/native-chat/use-native-chat-pty-composer-send.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-pty-composer-send.ts
  • src/renderer/src/components/native-chat/use-native-chat-rail-history-jump.test.ts
  • src/renderer/src/components/native-chat/use-native-chat-rail-history-jump.ts
  • src/renderer/src/components/native-chat/use-native-chat-reader-opens.test.ts
  • src/renderer/src/components/native-chat/use-native-chat-reader-opens.ts
  • src/renderer/src/components/native-chat/use-native-chat-reveal-latest.ts
  • src/renderer/src/components/native-chat/use-native-chat-session-option-command.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts
  • src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts
  • src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts
  • src/renderer/src/components/native-chat/use-native-chat-transcript-window.ts
  • src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.test.ts
  • src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.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

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

Comment on lines +119 to +147
const { sendAnswer, sendRaw, cancelPending, cancelAsk, cancel } = useNativeChatInteractiveSend(
terminalTabId,
paneKey,
targetPtyId,
agent
)
// Every send this pane makes brings the latest into view, wherever the reader had scrolled.
const { messageListRef, revealLatest } = useNativeChatRevealLatest()
const interactiveSend = useMemo<NativeChatInteractiveSend>(
() => ({
// Only what is written moves the reader: no terminal, or an empty answer, writes nothing.
sendAnswer: (prompt, selections, onDeliverySettled) => {
if (targetPtyId && hasAskAnswer(prompt, selections)) {
revealLatest()
}
return sendAnswer(prompt, selections, onDeliverySettled)
},
sendRaw: (raw) => {
if (targetPtyId) {
revealLatest()
}
sendRaw(raw)
},
cancelPending,
cancelAsk,
cancel
}),
[cancel, cancelAsk, cancelPending, revealLatest, sendAnswer, sendRaw, targetPtyId]
)

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:

sed -n '106,154p' src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
sed -n '55,185p' src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts

Repository: stablyai/orca

Length of output: 7474


🏁 Script executed:

printf '%s\n' '--- PR diff ---'
git diff --unified=35 990b0ed5cdd8f541f3b63fbcea068b4ef6097eae dff63cb8576f4e013f018b014213b891bfbf946a -- src/renderer/src/components/native-chat/NativeChatResolvedView.tsx

printf '%s\n' '--- send symbols and callsites ---'
rg -n -F -- 'function sendRuntimePtyInput' src/renderer/src
rg -n -F -- 'sendRuntimePtyInput(' src/renderer/src
rg -n -F -- 'function sendNativeChatMessage' src/renderer/src
rg -n -F -- 'sendNativeChatMessage(' src/renderer/src/components/native-chat
rg -n -F -- 'sendNativeChatAskAnswer' src/renderer/src/components/native-chat
rg -n -F -- 'interactiveSend.sendAnswer' src/renderer/src/components/native-chat
rg -n -F -- 'interactiveSend.sendRaw' src/renderer/src/components/native-chat

printf '%s\n' '--- helper paths ---'
rg --files src/renderer/src | rg 'native-chat|pty|runtime'

Repository: stablyai/orca

Length of output: 41344


🏁 Script executed:

printf '%s\n' '--- runtime PTY send contract ---'
sed -n '190,325p' src/renderer/src/runtime/runtime-terminal-inspection.ts
printf '%s\n' '--- chat runtime send ---'
sed -n '1,165p' src/renderer/src/components/native-chat/native-chat-runtime-send.ts
printf '%s\n' '--- answer send callsites ---'
sed -n '1,245p' src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts
rg -n -F -- 'send.sendAnswer' src/renderer/src/components/native-chat
rg -n -F -- 'send.sendRaw' src/renderer/src/components/native-chat
printf '%s\n' '--- interactive card ---'
rg --files src/renderer/src/components/native-chat | rg 'NativeChatInteractiveCard'
printf '%s\n' '--- reveal helper ---'
sed -n '1,200p' src/renderer/src/components/native-chat/use-native-chat-reveal-latest.ts

Repository: stablyai/orca

Length of output: 19842


🏁 Script executed:

printf '%s\n' '--- runtime PTY send contract ---'
sed -n '190,325p' src/renderer/src/runtime/runtime-terminal-inspection.ts
printf '%s\n' '--- chat runtime send ---'
sed -n '1,165p' src/renderer/src/components/native-chat/native-chat-runtime-send.ts
printf '%s\n' '--- answer send callsites ---'
sed -n '1,245p' src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts
rg -n -F -- 'send.sendAnswer' src/renderer/src/components/native-chat
rg -n -F -- 'send.sendRaw' src/renderer/src/components/native-chat
printf '%s\n' '--- interactive card source files ---'
rg --files src/renderer/src/components/native-chat | rg 'NativeChatInteractiveCard'
printf '%s\n' '--- reveal helper ---'
sed -n '1,200p' src/renderer/src/components/native-chat/use-native-chat-reveal-latest.ts

Repository: stablyai/orca

Length of output: 19855


🏁 Script executed:

printf '%s\n' '--- interactive card complete source ---'
sed -n '1,300p' src/renderer/src/components/native-chat/NativeChatInteractiveCard.tsx
printf '%s\n' '--- ask-answer delivery implementation ---'
rg -n -F -- 'function sendNativeChatAskAnswer' src/renderer/src/components/native-chat/native-chat-runtime-send.ts
sed -n '245,460p' src/renderer/src/components/native-chat/native-chat-runtime-send.ts
printf '%s\n' '--- observed send rejection path ---'
sed -n '1,260p' src/renderer/src/components/native-chat/native-chat-observed-send.ts
printf '%s\n' '--- interactive card send-result handling ---'
rg -n -F -- 'onDeliverySettled' src/renderer/src/components/native-chat

Repository: stablyai/orca

Length of output: 11733


🏁 Script executed:

printf '%s\n' '--- reveal hold implementation and usages ---'
rg -n -F -- 'holdRevealLatest' src/renderer/src/components/native-chat
rg -n -F -- 'useNativeChatMessageListHandle' src/renderer/src/components/native-chat
printf '%s\n' '--- message list reveal behavior ---'
rg -n -F -- 'untilReaderActs' src/renderer/src/components/native-chat
printf '%s\n' '--- full stepped answer sender ---'
sed -n '205,275p' src/renderer/src/components/native-chat/native-chat-runtime-send.ts
printf '%s\n' '--- stepped mode selection contract ---'
rg -n -F -- 'function shouldStepNativeChatAskAnswer' src/renderer/src
rg -n -F -- 'shouldStepNativeChatAskAnswer' src/renderer/src/components/native-chat/native-chat-interactive-prompt.ts src/shared/native-chat-agent-support.ts
printf '%s\n' '--- focused delivery tests ---'
sed -n '275,345p' src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx

Repository: stablyai/orca

Length of output: 8007


🏁 Script executed:

printf '%s\n' '--- reader action latch ---'
sed -n '170,220p' src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts
printf '%s\n' '--- existing deferred reveal caller ---'
sed -n '1,100p' src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.ts
printf '%s\n' '--- reveal behavior tests ---'
sed -n '165,215p' src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx
printf '%s\n' '--- stepped agents ---'
sed -n '1,55p' src/shared/native-chat-agent-support.ts

Repository: stablyai/orca

Length of output: 8737


🏁 Script executed:

printf '%s\n' '--- reveal wrapper ---'
nl -ba src/renderer/src/components/native-chat/NativeChatResolvedView.tsx | sed -n '117,145p'
printf '%s\n' '--- interactive sender ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts | sed -n '74,150p'
printf '%s\n' '--- question card failure handling ---'
nl -ba src/renderer/src/components/native-chat/NativeChatInteractiveCard.tsx | sed -n '60,135p'
printf '%s\n' '--- verified answer writes ---'
nl -ba src/renderer/src/components/native-chat/native-chat-runtime-send.ts | sed -n '226,272p'
printf '%s\n' '--- verified PTY rejection contract ---'
nl -ba src/renderer/src/runtime/runtime-terminal-inspection.ts | sed -n '279,325p'
printf '%s\n' '--- established deferred reveal pattern ---'
nl -ba src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.ts | sed -n '44,67p'

Repository: stablyai/orca

Length of output: 13633


Defer verified answer reveals until delivery succeeds.

For stepped question answers, a PTY write can be rejected after this wrapper calls revealLatest(). The card then stays available for retry, but the reader has already jumped to latest. Hold the reveal until delivery succeeds; keep the current immediate behavior for answers without verified delivery.

Suggested fix
-  const { messageListRef, revealLatest } = useNativeChatRevealLatest()
+  const { messageListRef, revealLatest, holdRevealLatest } = useNativeChatRevealLatest()
...
       sendAnswer: (prompt, selections, onDeliverySettled) => {
-        if (targetPtyId && hasAskAnswer(prompt, selections)) {
-          revealLatest()
+        if (!targetPtyId || !hasAskAnswer(prompt, selections)) {
+          return sendAnswer(prompt, selections, onDeliverySettled)
         }
-        return sendAnswer(prompt, selections, onDeliverySettled)
+        const reveal = holdRevealLatest()
+        const result = sendAnswer(prompt, selections, (delivered) => {
+          if (delivered) {
+            reveal()
+          }
+          onDeliverySettled?.(delivered)
+        })
+        if (!result.waitsForVerifiedDelivery) {
+          reveal()
+        }
+        return result
       },
...
-    [cancel, cancelAsk, cancelPending, revealLatest, sendAnswer, sendRaw, targetPtyId]
+    [cancel, cancelAsk, cancelPending, holdRevealLatest, revealLatest, sendAnswer, sendRaw, targetPtyId]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { sendAnswer, sendRaw, cancelPending, cancelAsk, cancel } = useNativeChatInteractiveSend(
terminalTabId,
paneKey,
targetPtyId,
agent
)
// Every send this pane makes brings the latest into view, wherever the reader had scrolled.
const { messageListRef, revealLatest } = useNativeChatRevealLatest()
const interactiveSend = useMemo<NativeChatInteractiveSend>(
() => ({
// Only what is written moves the reader: no terminal, or an empty answer, writes nothing.
sendAnswer: (prompt, selections, onDeliverySettled) => {
if (targetPtyId && hasAskAnswer(prompt, selections)) {
revealLatest()
}
return sendAnswer(prompt, selections, onDeliverySettled)
},
sendRaw: (raw) => {
if (targetPtyId) {
revealLatest()
}
sendRaw(raw)
},
cancelPending,
cancelAsk,
cancel
}),
[cancel, cancelAsk, cancelPending, revealLatest, sendAnswer, sendRaw, targetPtyId]
)
const { sendAnswer, sendRaw, cancelPending, cancelAsk, cancel } = useNativeChatInteractiveSend(
terminalTabId,
paneKey,
targetPtyId,
agent
)
// Every send this pane makes brings the latest into view, wherever the reader had scrolled.
const { messageListRef, revealLatest, holdRevealLatest } = useNativeChatRevealLatest()
const interactiveSend = useMemo<NativeChatInteractiveSend>(
() => ({
// Only what is written moves the reader: no terminal, or an empty answer, writes nothing.
sendAnswer: (prompt, selections, onDeliverySettled) => {
if (!targetPtyId || !hasAskAnswer(prompt, selections)) {
return sendAnswer(prompt, selections, onDeliverySettled)
}
const reveal = holdRevealLatest()
const result = sendAnswer(prompt, selections, (delivered) => {
if (delivered) {
reveal()
}
onDeliverySettled?.(delivered)
})
if (!result.waitsForVerifiedDelivery) {
reveal()
}
return result
},
sendRaw: (raw) => {
if (targetPtyId) {
revealLatest()
}
sendRaw(raw)
},
cancelPending,
cancelAsk,
cancel
}),
[cancel, cancelAsk, cancelPending, holdRevealLatest, revealLatest, sendAnswer, sendRaw, targetPtyId]
)

Comment on lines +99 to 101
if (pages >= NATIVE_CHAT_RAIL_JUMP_MAX_PAGES) {
return
}

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

The page cap ends a "Jump to top" without scrolling to the top.

For a start jump, the loop returns when pages reaches NATIVE_CHAT_RAIL_JUMP_MAX_PAGES and never calls jumpToStart. The failure path at Line 107 lands on the top of the loaded history, but this cap path does not. The spinner stops and the reader stays where they were. Call jumpToStart here when the target is HISTORY_START.

Proposed fix
         if (pages >= NATIVE_CHAT_RAIL_JUMP_MAX_PAGES) {
+          if (target === HISTORY_START) {
+            committedRef.current.jumpToStart()
+          }
           return
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (pages >= NATIVE_CHAT_RAIL_JUMP_MAX_PAGES) {
return
}
if (pages >= NATIVE_CHAT_RAIL_JUMP_MAX_PAGES) {
if (target === HISTORY_START) {
committedRef.current.jumpToStart()
}
return
}

emitNativeChatMessageSent({ agent, runtime: structuredTransport.runtime })
// An accepted command can still be one chat sessions don't run; nothing landed then.
if (!error) {
structuredTransport.onSubmitted?.()

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:

sed -n '35,110p' src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts
sed -n '65,112p' src/renderer/src/components/native-chat/use-native-chat-composer-submit.ts
sed -n '75,137p' src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts
cat src/renderer/src/components/native-chat/use-native-chat-reveal-latest.ts

Repository: stablyai/orca

Length of output: 7717


🏁 Script executed:

printf '%s\n' '--- relevant sources/tests ---'
rg -n -F -- 'holdRevealLatest' src/renderer/src/components/native-chat src/renderer/src
rg -n -F -- 'onSubmitted' src/renderer/src/components/native-chat
rg -n -F -- 'useNativeChatMessageListHandle' src/renderer/src
rg -n -F -- 'setObjective' src/renderer/src/components/native-chat
rg -n -F -- 'use-native-chat-structured-composer-send' src/renderer
rg -n -F -- 'use-native-chat-session-option-command' src/renderer
printf '%s\n' '--- structured/composer hooks ---'
sed -n '1,170p' src/renderer/src/components/native-chat/use-native-chat-composer-submit.ts
sed -n '1,190p' src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts
printf '%s\n' '--- callback owners ---'
rg -n -C 7 -- 'onSubmitted:|onSubmitted=' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
printf '%s\n' '--- reveal related files ---'
rg --files src/renderer/src/components/native-chat | rg 'message-list|reveal|structured-composer|session-option|composer-submit|NativeChatStructuredSession|NativeChatResolvedView'
printf '%s\n' '--- PR diff for affected hooks/callbacks ---'
git diff --unified=5 990b0ed5cdd8f541f3b63fbcea068b4ef6097eae dff63cb8576f4e013f018b014213b891bfbf946a -- src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts src/renderer/src/components/native-chat/use-native-chat-composer-submit.ts src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.tsx

Repository: stablyai/orca

Length of output: 41833


🏁 Script executed:

printf '%s\n' '--- submit reveal contract ---'
sed -n '1,180p' src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.ts
sed -n '1,120p' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
printf '%s\n' '--- composer prop and callback flow ---'
sed -n '1,125p' src/renderer/src/components/native-chat/native-chat-composer-types.ts
sed -n '1,360p' src/renderer/src/components/native-chat/NativeChatComposer.tsx
printf '%s\n' '--- resolved view callback context ---'
sed -n '95,145p' src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
sed -n '410,450p' src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
printf '%s\n' '--- message-list reader cancellation ---'
sed -n '260,325p' src/renderer/src/components/native-chat/NativeChatMessageList.tsx
rg -n -C 12 -- 'untilReaderActs|reader acts|revealLatest' src/renderer/src/components/native-chat/native-chat-message-list* src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx
printf '%s\n' '--- targeted tests ---'
sed -n '1,240p' src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.test.ts
sed -n '1,230p' src/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsx
sed -n '1,150p' src/renderer/src/components/native-chat/use-native-chat-session-option-command.test.tsx
sed -n '1,165p' src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.test.tsx
sed -n '620,675p' src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx
sed -n '1,135p' src/renderer/src/components/native-chat/NativeChatResolvedView.reveal-latest.test.tsx

Repository: stablyai/orca

Length of output: 42290


🏁 Script executed:

printf '%s\n' '--- message-list cancellation contract and tests ---'
rg -n -C 20 -- 'untilReaderActs|holdRevealLatest|scrolls away|reader.*acts|row' src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx src/renderer/src/components/native-chat/NativeChatMessageList.tsx src/renderer/src/components/native-chat
printf '%s\n' '--- transcript scroll binding ---'
rg -n -- 'untilReaderActs|beginNavigation' src/renderer/src/components/native-chat
printf '%s\n' '--- full composer submit callback routing ---'
rg -n -C 5 -- 'useNativeChatSessionOptionCommand|useNativeChatStructuredComposerSend|useNativeChatComposerSubmit|onSubmitted' src/renderer/src/components/native-chat/NativeChatComposer.tsx
printf '%s\n' '--- structured dispatch implementation ---'
sed -n '1,240p' src/renderer/src/components/native-chat/native-chat-structured-composer-dispatch.ts
sed -n '1,240p' src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts
printf '%s\n' '--- session option command and runtime send bindings ---'
sed -n '1,175p' src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts
rg -n -C 6 -- 'function sendNativeChatMessageVerified|const sendNativeChatMessageVerified|function typeNativeChatCommand|const typeNativeChatCommand' src/renderer/src/components/native-chat
printf '%s\n' '--- relevant asynchronous source tests ---'
sed -n '170,220p' src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx
sed -n '70,130p' src/renderer/src/components/native-chat/use-native-chat-session-option-command.test.tsx
sed -n '170,205p' src/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsx
printf '%s\n' '--- reader event interactions ---'
rg -n -C 8 -- 'row.*open|open.*row|reader|scroll' src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx src/renderer/src/components/native-chat/NativeChatMessageList.tsx

Repository: stablyai/orca

Length of output: 45668


🏁 Script executed:

printf '%s\n' '--- structured dispatch ---'
nl -ba src/renderer/src/components/native-chat/native-chat-structured-composer-dispatch.ts | sed -n '1,180p'
nl -ba src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts | sed -n '42,93p'
printf '%s\n' '--- goal submit ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-composer-submit.ts | sed -n '70,112p'
nl -ba src/renderer/src/components/native-chat/native-chat-composer-types.ts | sed -n '20,88p'
printf '%s\n' '--- terminal option operation ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts | sed -n '53,125p'
rg -n -- 'sendNativeChatMessageVerified|typeNativeChatCommand' src/renderer/src/components/native-chat/native-chat-runtime-send.ts src/renderer/src/components/native-chat/native-chat-pty-send-queue.ts src/renderer/src/components/native-chat
printf '%s\n' '--- reveal host wiring ---'
nl -ba src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx | sed -n '200,230p'
nl -ba src/renderer/src/components/native-chat/NativeChatResolvedView.tsx | sed -n '425,442p'
nl -ba src/renderer/src/components/native-chat/use-native-chat-reveal-latest.ts | sed -n '1,48p'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.tsx | sed -n '300,315p'
printf '%s\n' '--- transcript reader action contract ---'
rg -n -- 'untilReaderActs|beginNavigation' src/renderer/src/components/native-chat
printf '%s\n' '--- decisive list tests ---'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx | sed -n '173,200p'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx | sed -n '300,380p'
printf '%s\n' '--- async path tests ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsx | sed -n '178,198p'
nl -ba src/renderer/src/components/native-chat/use-native-chat-session-option-command.test.tsx | sed -n '35,75p'
nl -ba src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.test.tsx | sed -n '100,130p'

Repository: stablyai/orca

Length of output: 34881


🏁 Script executed:

printf '%s\n' '--- structured command dispatch contract ---'
rg -n -- 'dispatchStructuredAgentSessionComposerCommand|runConversationCommand|setStructuredOption|setThreadGoalObjective' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx src/renderer/src/components/native-chat/use-structured-agent-session.ts src/shared/structured-agent-session-composer.ts
nl -ba src/shared/structured-agent-session-composer.ts | sed -n '1,220p'
nl -ba src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx | sed -n '150,216p'
printf '%s\n' '--- concrete goal setter bindings ---'
rg -n -- 'setObjective|setThreadGoalObjective' src/renderer/src/components/native-chat/use-structured-agent-session.ts src/renderer/src/components/native-chat
printf '%s\n' '--- terminal send implementations ---'
nl -ba src/renderer/src/components/native-chat/native-chat-runtime-send.ts | sed -n '115,195p'
printf '%s\n' '--- held reveal cancellation and row actions ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts | sed -n '180,225p'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.tsx | sed -n '390,440p'
rg -n -- 'onReaderScroll|onToggle|toggle.*Tool|ToolRun|expanded' src/renderer/src/components/native-chat/NativeChatMessageList.tsx src/renderer/src/components/native-chat/NativeChatMessageRow.tsx src/renderer/src/components/native-chat

Repository: stablyai/orca

Length of output: 41988


🏁 Script executed:

printf '%s\n' '--- held reveal cancellation implementation ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts | sed -n '188,218p'
nl -ba src/renderer/src/components/native-chat/use-native-chat-reader-opens.ts | sed -n '1,82p'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.tsx | sed -n '255,318p'
printf '%s\n' '--- goal controller implementation/binding ---'
rg -n -- 'threadGoal|change:|kind: .set.' src/renderer/src/components/native-chat/use-structured-agent-session.ts src/renderer/src/components/native-chat/use-structured-agent-session-thread-goal.ts src/renderer/src/components/native-chat
printf '%s\n' '--- terminal send operation ---'
nl -ba src/renderer/src/components/native-chat/native-chat-runtime-send.ts | sed -n '122,195p'
printf '%s\n' '--- composer dispatch interfaces and plain-send binding ---'
nl -ba src/renderer/src/components/native-chat/native-chat-composer-types.ts | sed -n '15,42p'
nl -ba src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx | sed -n '184,224p'
rg -n -- 'function sendThroughRelaunch|const sendThroughRelaunch|sendThroughRelaunch' src/renderer/src/components/native-chat/use-native-chat-provisional-launch.ts

Repository: stablyai/orca

Length of output: 25776


🏁 Script executed:

printf '%s\n' '--- reader action counter updates ---'
rg -n -C 4 -- 'readerActsRef|readerOpened|readerClosed|onReaderScroll' src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts src/renderer/src/components/native-chat/NativeChatMessageList.tsx src/renderer/src/components/native-chat/use-native-chat-reader-opens.ts
printf '%s\n' '--- host async bindings ---'
nl -ba src/renderer/src/components/native-chat/use-structured-agent-session-thread-goal.ts | sed -n '1,65p'
nl -ba src/renderer/src/components/native-chat/use-structured-agent-session.ts | sed -n '185,210p'
nl -ba src/renderer/src/components/native-chat/use-structured-agent-session.ts | sed -n '285,315p'
rg -n -C 4 -- 'runConversationCommand|setStructuredOption' src/renderer/src/components/native-chat/use-structured-agent-session.ts src/renderer/src/components/native-chat/use-structured-agent-session-*

Repository: stablyai/orca

Length of output: 39061


Hold reveals for deferred composer operations.

Host-owned structured commands and goal changes can wait for host acceptance. Session-option dispatch waits for terminal acceptance. Each path calls revealLatest directly after success. If the reader scrolls or opens a row while an operation waits, the later reveal can jump away from that navigation. Pass holdRevealLatest through the composer contracts. Capture its returned callback when each deferred operation starts, and invoke it only after acceptance. Keep the ordinary structured-message fallback on its synchronous acceptance path.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes — this PR is stacked on #24514 (still open), so the 66-file diff is largely that PR's delta; per the PR body I reviewed the authored top commit, dff63cb857 (34 files, +354/−80), which re-adds the "Jump to top" control that #24514 deliberately held back. git diff 0ec79785e6 dff63cb857 is empty, confirming the commit is a byte-identical restore of what b5974c11a8 removed.

  • New "Jump to top" button (NativeChatJumpControls.tsx) — an up-arrow beside "Jump to latest", shown only when showLatest && startOutOfView; inert/aria-hidden while hidden, an aria-busy reduced-motion-safe spinner while paging, and focus handed to the transcript when it hides. Wrapped in a Tooltip (hence the app-root TooltipProvider).
  • Conversation-start history jump (use-native-chat-rail-history-jump.ts) — target generalized to string | HISTORY_START; startFromBeginning() pages older history until a page cannot advance, then lands on the top of what loaded via jumpToStart(); surfaced as startPending / startFromBeginning. The normal rail path is unchanged apart from an added abort check after nextCommit.
  • Scroll primitive (use-native-chat-transcript-scroll.ts) — awayFromTop (scrollTop > clientHeight) and scrollToTop(), which detaches follow and restores offset 0 (deferred to reveal when the pane is hidden).
  • Wiring (NativeChatMessageList.tsx) — passes showOlderHistory || awayFromTop and the rail-jump handle to the controls; "Jump to latest" still aborts a jump first.
  • Tests + helper — new native-chat-app-root-test-render.tsx renders the chat suites under the app's TooltipProvider, and new cases pin focus hand-back, aria-busy/spinner, the visibility threshold, paging to the start, abort, and the failed-page landing.
  • i18n — components.native-chat.jumpToTop added to en, es, fr, ja, ko, zh.

Verification this run: pnpm tc:web clean; focused suites pass (use-native-chat-rail-history-jump.test.ts 16, NativeChatJumpControls.test.tsx 3, NativeChatMessageList.rail-history-jump.test.tsx 16, NativeChatMessageList.reader-navigation.test.tsx 12). I traced every real loadEarlier path and confirmed the start-jump loop cannot spin: it returns applied only when the window actually grew.

Two notes, neither blocking and neither needing a new thread: the NATIVE_CHAT_RAIL_JUMP_MAX_PAGES path returns without landing on a start jump (already covered by the open CodeRabbit thread, and unreachable below 1000 pages), and the two other CodeRabbit threads target NativeChatResolvedView.tsx / use-native-chat-structured-composer-send.ts, which are #24514 files rather than this commit.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

…ange

The solid, fading Jump to latest button moves to the UI PR (#25702) so this one carries only the scroll behaviour.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/native-chat-jump-to-top branch from dff63cb to ce78e3f Compare October 6, 2026 00:14
@brennanb2025 brennanb2025 changed the title feat(native-chat): jump to the start of a long conversation feat(native-chat): a solid, fading Jump to latest and a Jump to top button Oct 6, 2026
# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx
#	src/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsx
@brennanb2025
brennanb2025 force-pushed the brennanb2025/native-chat-jump-to-top branch from ce78e3f to 365d6b8 Compare October 6, 2026 02:19
@brennanb2025 brennanb2025 changed the title feat(native-chat): a solid, fading Jump to latest and a Jump to top button feat(native-chat): a Jump to top button beside Jump to latest Oct 6, 2026
brennanb2025 and others added 13 commits October 5, 2026 20:50
…layout

Replace open-row lifetime tracking with bounded position preservation, and tie delayed reveals to the originating reader and session. Keep queued drafts and picker actions from navigating the transcript.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
A refused queue Resume moved readers before the host answered, and hiding during disclosure settlement could leave a reopened view at the tail without following new output. Resume now reveals its originating reader only after success, and a restored pane derives following from its actual distance to the end.

Merge current main while preserving its chat layout and the existing reader ownership and submission mechanisms.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
The rail test rejected the retained-position takeover before a message jump. Check its offset and the selected row target, then verify the jump stays spent and a later diff can reveal.

Merge main to resolve the transcript styling conflict, preserve incoming typography and remove obsolete test props.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Pending reader takeover could keep an earlier end target once a duplicate 250 ms gate expired. Let the existing pending-frame check keep the reader's actual offset until that frame ends.

Move unchanged interactive reveal and approval projection into their existing modules so both view files meet the line limit.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Integrate the current main branch so the corrected protocol lint can let
required checks run. Preserve the chat scrolling change and both locale
updates, and format two existing test calls.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatJumpToLatest.tsx
#	src/renderer/src/components/native-chat/NativeChatMessageList.tsx
#	src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx
#	src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
#	src/renderer/src/components/native-chat/use-structured-agent-session.ts
… at the press

Opening or closing a row used to stop the transcript from following the
newest output. A reader at the live end who expanded a tool run then lost
the stream. Sends that wait on the host (answers, commands, goals, option
changes) only brought the latest into view after the host replied, so the
reader watched nothing happen at the press.

Now following stops only on a reader gesture: an upward wheel or scroll key
when there is content above, a scrollbar press, a touch drag once it has
carried the view off the end, or a press on content while already away from
the end. Opening, closing, layout changes and the app's own scrolls never
stop it, and returning to the end resumes it. Every local send reveals the
latest at the press. A message that waits as a queued card, a message from
another device, and Stop leave a reader who scrolled up where they are.
Queue Resume still reveals only after the host lifts the pause, and only in
the pane that pressed it while that pane is shown.

Removes the disclosure position hold, the reader-opens wiring, the held
reveal and its reader generation tracking. The queued-card decision moves
into the outbox send so the session hook stays under the line limit. Also
restores the transcript label import and the approval card's verified send
that the merge with main dropped.
# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
#	src/renderer/src/components/native-chat/use-structured-agent-session-outbox.ts
#	src/renderer/src/components/native-chat/use-structured-agent-session.ts
brennanb2025 and others added 3 commits October 6, 2026 12:56
Reaching the start of a long conversation meant scrolling up again and again
while older messages loaded in pages. Now, while the reader is scrolled away
from the end and the start of the conversation is out of view, a round
up-arrow button labelled "Jump to top" sits beside "Jump to latest". Pressing
it loads any older history still unloaded, with a spinner on the button
meanwhile, then lands on the first message. Scrolling the transcript, sending
a message, or starting another jump (a rail pick, a diff reveal, "Jump to
latest") cancels it.

The walk to the start reuses the rail's existing "page older history until the
target loads" loop, with the start of the conversation as a target that has no
slot to wait for. "Jump to latest" keeps its look; only its positioning moved
to the list, which now lays the two buttons out together with "Jump to latest"
still centered.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
@brennanb2025
brennanb2025 force-pushed the brennanb2025/native-chat-jump-to-top branch from 365d6b8 to 938056d Compare October 6, 2026 20:14

@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: 4841541c-8281-446b-88ba-6bb1a5ed952b
📥 Commits

Reviewing files that changed from the base of the PR and between 365d6b8 and 938056d.

📒 Files selected for processing (58)
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/NativeChatJumpToLatest.tsx
  • src/renderer/src/components/native-chat/NativeChatJumpToTop.test.tsx
  • src/renderer/src/components/native-chat/NativeChatJumpToTop.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.growth-windowing.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.live-reasoning.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.message-rail-windowing.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.rail-history-jump.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.stopping-turn-bar.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.unloaded-live-turn.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageRail.tsx
  • src/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsx
  • src/renderer/src/components/native-chat/NativeChatQueuedMessageList.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.reveal-latest.test.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.launch-lifecycle.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.stopping.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • src/renderer/src/components/native-chat/native-chat-autoscroll.test.ts
  • src/renderer/src/components/native-chat/native-chat-autoscroll.ts
  • src/renderer/src/components/native-chat/native-chat-composer-types.ts
  • src/renderer/src/components/native-chat/native-chat-reader-scroll-input.test.tsx
  • src/renderer/src/components/native-chat/native-chat-reader-scroll-input.ts
  • src/renderer/src/components/native-chat/native-chat-root-key-routing.ts
  • src/renderer/src/components/native-chat/native-chat-structured-composer-dispatch.ts
  • src/renderer/src/components/native-chat/native-chat-submission-navigation.test.tsx
  • src/renderer/src/components/native-chat/native-chat-windowing-test-harness.tsx
  • src/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-composer-submit.ts
  • src/renderer/src/components/native-chat/use-native-chat-local-command-answer.ts
  • src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.ts
  • src/renderer/src/components/native-chat/use-native-chat-rail-history-jump.test.ts
  • src/renderer/src/components/native-chat/use-native-chat-reveal-latest.ts
  • src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts
  • src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts
  • src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts
  • src/renderer/src/components/native-chat/use-native-chat-transcript-window.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-command-send-gate.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.queue-delivery.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.ts
  • 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.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
  • src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.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
  • src/shared/structured-agent-session-composer.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/renderer/src/i18n/locales/zh.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/es.json
  • src/renderer/src/i18n/locales/fr.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.

Comment on lines +62 to +72
if (resumed) {
revealLatest()
}
return resumed
},
steerNewest: () => {
const steered = queuedMessages.steerNewest()
if (steered) {
revealLatest()
}
return steered

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:

nl -ba src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.ts | sed -n '1,100p'
nl -ba src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.ts | sed -n '145,185p'
nl -ba src/renderer/src/components/native-chat/use-native-chat-reveal-latest.ts | sed -n '50,85p'

Repository: stablyai/orca

Length of output: 6375


🏁 Script executed:

git diff --no-ext-diff --unified=50 81a1968bc656c0faa3731859a64f29f3adbb0ed1 938056d2fae5bf9182c56e4144311a6631d4e3f9 -- src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.ts
printf '\\n--- bound uses and list handle ---\\n'
rg -n -F -- 'useStructuredNativeChatSubmitReveal' src/renderer/src
rg -n -F -- 'useNativeChatMessageListHandle' src/renderer/src
rg -n -F -- 'queuedMessages.resume' src/renderer/src
printf '\\n--- files mentioning resume and navigation ---\\n'
rg -n -i 'resum(e|ing)|revealLatest|scroll' src/renderer/src/components/native-chat

Repository: stablyai/orca

Length of output: 42062


🤖 get_repo_knowledge executed:

get_repo_knowledge stablyai/orca /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions

Length of output: 47246


🏁 Script executed:

printf '%s\\n' '--- resume/navigation test assertions ---'
nl -ba src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.resume.test.tsx | sed -n '140,275p'
nl -ba src/renderer/src/components/native-chat/native-chat-submission-navigation.test.tsx | sed -n '380,455p'
printf '%s\\n' '--- production pane wiring ---'
nl -ba src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx | sed -n '100,190p'
printf '%s\\n' '--- transcript scroll contract ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts | sed -n '1,240p'
printf '%s\\n' '--- message-list reveal and row actions ---'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.tsx | sed -n '275,370p'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageRow.tsx | sed -n '100,150p'
rg -n -i 'defer|hold|reader(action|takeover)|leavesEnd|userScroll|revealLatest' src/renderer/src/components/native-chat --glob '!*.test.*'

Repository: stablyai/orca

Length of output: 42222


🏁 Script executed:

printf '%s\n' '--- resume/navigation test assertions ---'
nl -ba src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.resume.test.tsx | sed -n '140,275p'
nl -ba src/renderer/src/components/native-chat/native-chat-submission-navigation.test.tsx | sed -n '380,455p'
printf '%s\n' '--- production pane wiring ---'
nl -ba src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx | sed -n '100,190p'
printf '%s\n' '--- transcript scroll contract ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts | sed -n '1,240p'
printf '%s\n' '--- message-list reveal and row actions ---'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.tsx | sed -n '275,370p'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageRow.tsx | sed -n '100,150p'
rg -n -i 'defer|hold|reader(action|takeover)|leavesEnd|userScroll|revealLatest' src/renderer/src/components/native-chat --glob '!*.test.*'

Repository: stablyai/orca

Length of output: 41699


🏁 Script executed:

printf '%s\n' '--- queue Resume UI ---'
nl -ba src/renderer/src/components/native-chat/NativeChatQueuedMessageList.tsx | sed -n '1,175p'
printf '%s\n' '--- structured session wiring ---'
nl -ba src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx | sed -n '180,390p'
printf '%s\n' '--- message-list navigation wiring ---'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.tsx | sed -n '225,335p'
printf '%s\n' '--- reader gestures and row selection handlers ---'
nl -ba src/renderer/src/components/native-chat/native-chat-reader-scroll-input.ts | sed -n '70,170p'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageRow.tsx | sed -n '125,145p'

Repository: stablyai/orca

Length of output: 26783


Preserve navigation made while Resume is pending.

If the reader scrolls or jumps to a row while queuedMessages.resume() is pending, a successful result still calls revealLatest(). That call maps to scrollToBottom(), which resumes following and moves the visible pane to the end. Track reader navigation across the await and skip the reveal if the reader moved.

brennanb2025 added a commit that referenced this pull request Oct 6, 2026
… reply (#24514)

* fix(native-chat): sending brings the latest into view, with visible jumps to latest and top

Sending while scrolled up left the reader parked in old history, and the way back was a faint button. A send now resumes following, opening a row to read it stops following until it is closed, and Jump to latest and Jump to top sit above the composer. Refs #23797.

* fix(native-chat): a late or unsent answer no longer moves a reader who scrolled away

An answer's reveal is now held from the click and dropped if the reader acts before the host accepts. In the terminal lane, an answer with no terminal to write to reveals nothing.

* fix(native-chat): an empty answer no longer moves the reader

The terminal lane's reveal now uses the same check the send does, and the send-site guard test ignores comments.

* fix(native-chat): preserve upstream retry and question cancellation

* refactor(native-chat): leave Jump to top out of this change

Jump to top moves to its own PR so this one lands the send reveal and
follow behaviour on their own.

* refactor(native-chat): keep Jump to latest's existing look in this change

The solid, fading Jump to latest button moves to the UI PR (#25702) so this one carries only the scroll behaviour.

* Preserve native chat reader intent across submissions and disclosure layout

Replace open-row lifetime tracking with bounded position preservation, and tie delayed reveals to the originating reader and session. Keep queued drafts and picker actions from navigating the transcript.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>

* fix(native-chat): retain reader takeover until its frame ends

Pending reader takeover could keep an earlier end target once a duplicate 250 ms gate expired. Let the existing pending-frame check keep the reader's actual offset until that frame ends.

Move unchanged interactive reveal and approval projection into their existing modules so both view files meet the line limit.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>

* fix(native-chat): only a scroll gesture stops following; sends reveal at the press

Opening or closing a row used to stop the transcript from following the
newest output. A reader at the live end who expanded a tool run then lost
the stream. Sends that wait on the host (answers, commands, goals, option
changes) only brought the latest into view after the host replied, so the
reader watched nothing happen at the press.

Now following stops only on a reader gesture: an upward wheel or scroll key
when there is content above, a scrollbar press, a touch drag once it has
carried the view off the end, or a press on content while already away from
the end. Opening, closing, layout changes and the app's own scrolls never
stop it, and returning to the end resumes it. Every local send reveals the
latest at the press. A message that waits as a queued card, a message from
another device, and Stop leave a reader who scrolled up where they are.
Queue Resume still reveals only after the host lifts the pause, and only in
the pane that pressed it while that pane is shown.

Removes the disclosure position hold, the reader-opens wiring, the held
reveal and its reader generation tracking. The queued-card decision moves
into the outbox send so the session hook stays under the line limit. Also
restores the transcript label import and the approval card's verified send
that the merge with main dropped.

* chore: leave an unrelated fixture as main has it

* refactor(native-chat): share the terminal send paths' common options in the composer

* test(native-chat): drop a field the main merge declared twice

* test(native-chat): the pane's steer still steers the queue, and also reveals

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>

This branch has not been deployed

No deployments
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.

[Feature]: Improve desktop chat UI scrolling behavior

3 participants