Repository navigation
feat(native-chat): a Jump to top button beside Jump to latest - #25702
brennanb2025 wants to merge 24 commits into
Conversation
…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.
📝 WalkthroughWalkthroughThe 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 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 ReviewSecurity architecture risk: 🔵 Low · up to 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 Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❓ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation 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 💡
🛠️ Fix failing CI checks 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
06781e25-b243-4f37-b73c-338787bc72c8
📒 Files selected for processing (66)
src/renderer/src/components/native-chat/NativeChatComposer.tsxsrc/renderer/src/components/native-chat/NativeChatJumpControls.test.tsxsrc/renderer/src/components/native-chat/NativeChatJumpControls.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.growth-windowing.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.interrupted-turn-fold.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.message-rail-windowing.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.older-history.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.provider-retry-runs.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.queued-turn-order.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.rail-history-jump.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.stream-render.perf.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.subagent-sections.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.task-list-frames.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.tool-stream-cost.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.turn-fold.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.turn-history.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.turn-indicator.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.turn-membership.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.turn-timing.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.unsent-message.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.windowing.test.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.reveal-latest.test.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.launch-lifecycle.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.tsxsrc/renderer/src/components/native-chat/NativeChatSubagentRun.test.tsxsrc/renderer/src/components/native-chat/NativeChatSubagentRun.tsxsrc/renderer/src/components/native-chat/native-chat-app-root-test-render.tsxsrc/renderer/src/components/native-chat/native-chat-autoscroll.test.tssrc/renderer/src/components/native-chat/native-chat-autoscroll.tssrc/renderer/src/components/native-chat/native-chat-composer-types.tssrc/renderer/src/components/native-chat/native-chat-disclosure-store.tssrc/renderer/src/components/native-chat/native-chat-reader-scroll-input.tssrc/renderer/src/components/native-chat/native-chat-send-reveal-ratchet.test.tssrc/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-composer-submit.tssrc/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.tssrc/renderer/src/components/native-chat/use-native-chat-pty-composer-send.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-pty-composer-send.tssrc/renderer/src/components/native-chat/use-native-chat-rail-history-jump.test.tssrc/renderer/src/components/native-chat/use-native-chat-rail-history-jump.tssrc/renderer/src/components/native-chat/use-native-chat-reader-opens.test.tssrc/renderer/src/components/native-chat/use-native-chat-reader-opens.tssrc/renderer/src/components/native-chat/use-native-chat-reveal-latest.tssrc/renderer/src/components/native-chat/use-native-chat-session-option-command.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-session-option-command.tssrc/renderer/src/components/native-chat/use-native-chat-structured-composer-send.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-structured-composer-send.tssrc/renderer/src/components/native-chat/use-native-chat-transcript-scroll.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-transcript-scroll.tssrc/renderer/src/components/native-chat/use-native-chat-transcript-window.tssrc/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.test.tssrc/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/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.
| 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] | ||
| ) |
There was a problem hiding this comment.
🎯 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.tsRepository: 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.tsRepository: 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.tsRepository: 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-chatRepository: 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.tsxRepository: 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.tsRepository: 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.
| 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] | |
| ) |
| if (pages >= NATIVE_CHAT_RAIL_JUMP_MAX_PAGES) { | ||
| return | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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?.() |
There was a problem hiding this comment.
🎯 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.tsRepository: 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.tsxRepository: 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.tsxRepository: 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.tsxRepository: 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-chatRepository: 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.tsRepository: 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.
There was a problem hiding this comment.
✅ 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 whenshowLatest && startOutOfView;inert/aria-hiddenwhile hidden, anaria-busyreduced-motion-safe spinner while paging, and focus handed to the transcript when it hides. Wrapped in aTooltip(hence the app-rootTooltipProvider). - Conversation-start history jump (
use-native-chat-rail-history-jump.ts) — target generalized tostring | HISTORY_START;startFromBeginning()pages older history until a page cannot advance, then lands on the top of what loaded viajumpToStart(); surfaced asstartPending/startFromBeginning. The normal rail path is unchanged apart from an added abort check afternextCommit. - Scroll primitive (
use-native-chat-transcript-scroll.ts) —awayFromTop(scrollTop > clientHeight) andscrollToTop(), which detaches follow and restores offset 0 (deferred to reveal when the pane is hidden). - Wiring (
NativeChatMessageList.tsx) — passesshowOlderHistory || awayFromTopand the rail-jump handle to the controls; "Jump to latest" still aborts a jump first. - Tests + helper — new
native-chat-app-root-test-render.tsxrenders the chat suites under the app'sTooltipProvider, 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.jumpToTopadded toen,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.
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.
dff63cb to
ce78e3f
Compare
# Conflicts: # src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx # src/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsx
ce78e3f to
365d6b8
Compare
…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
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>
365d6b8 to
938056d
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
4841541c-8281-446b-88ba-6bb1a5ed952b
📒 Files selected for processing (58)
src/renderer/src/components/native-chat/NativeChatComposer.tsxsrc/renderer/src/components/native-chat/NativeChatJumpToLatest.tsxsrc/renderer/src/components/native-chat/NativeChatJumpToTop.test.tsxsrc/renderer/src/components/native-chat/NativeChatJumpToTop.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.growth-windowing.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.live-reasoning.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.message-rail-windowing.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.rail-history-jump.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.reader-navigation.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.stopping-turn-bar.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.tsxsrc/renderer/src/components/native-chat/NativeChatMessageList.unloaded-live-turn.test.tsxsrc/renderer/src/components/native-chat/NativeChatMessageRail.tsxsrc/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsxsrc/renderer/src/components/native-chat/NativeChatQueuedMessageList.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.reveal-latest.test.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.launch-lifecycle.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.stopping.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.tsxsrc/renderer/src/components/native-chat/native-chat-autoscroll.test.tssrc/renderer/src/components/native-chat/native-chat-autoscroll.tssrc/renderer/src/components/native-chat/native-chat-composer-types.tssrc/renderer/src/components/native-chat/native-chat-reader-scroll-input.test.tsxsrc/renderer/src/components/native-chat/native-chat-reader-scroll-input.tssrc/renderer/src/components/native-chat/native-chat-root-key-routing.tssrc/renderer/src/components/native-chat/native-chat-structured-composer-dispatch.tssrc/renderer/src/components/native-chat/native-chat-submission-navigation.test.tsxsrc/renderer/src/components/native-chat/native-chat-windowing-test-harness.tsxsrc/renderer/src/components/native-chat/use-native-chat-composer-submit.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-composer-submit.tssrc/renderer/src/components/native-chat/use-native-chat-local-command-answer.tssrc/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.tssrc/renderer/src/components/native-chat/use-native-chat-rail-history-jump.test.tssrc/renderer/src/components/native-chat/use-native-chat-reveal-latest.tssrc/renderer/src/components/native-chat/use-native-chat-session-option-command.tssrc/renderer/src/components/native-chat/use-native-chat-structured-composer-send.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-structured-composer-send.tssrc/renderer/src/components/native-chat/use-native-chat-transcript-scroll.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-transcript-scroll.tssrc/renderer/src/components/native-chat/use-native-chat-transcript-window.tssrc/renderer/src/components/native-chat/use-structured-agent-session-command-send-gate.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-outbox.queue-delivery.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-outbox.tssrc/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.resume.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.tssrc/renderer/src/components/native-chat/use-structured-agent-session.tssrc/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.test.tssrc/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/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.
| if (resumed) { | ||
| revealLatest() | ||
| } | ||
| return resumed | ||
| }, | ||
| steerNewest: () => { | ||
| const steered = queuedMessages.steerNewest() | ||
| if (steered) { | ||
| revealLatest() | ||
| } | ||
| return steered |
There was a problem hiding this comment.
🎯 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-chatRepository: 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.
… 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>

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
NativeChatJumpToTopbutton 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
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
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)Co-authored by Kelvin (@AmoabaKelvin), who built the feature in #24514.