diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index 223c9add..a70fb054 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -2335,12 +2335,11 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo } // Handle ArrowUp/ArrowDown for message history navigation - // ArrowUp: only when cursor at start (position 0) or input is empty + // ArrowUp: only when input is empty (so pressing Up at start of text just moves cursor) // ArrowDown: also works when cursor at end (to cycle forward through history) const isAnyAutocompleteOpen = showCommandAutocomplete || showSkillAutocomplete || showSnippetAutocomplete || showFileMention; - const cursorAtStart = textareaRef.current?.selectionStart === 0 && textareaRef.current?.selectionEnd === 0; const cursorAtEnd = textareaRef.current?.selectionStart === message.length && textareaRef.current?.selectionEnd === message.length; - const canNavigateHistoryUp = !isAnyAutocompleteOpen && (message.length === 0 || cursorAtStart); + const canNavigateHistoryUp = !isAnyAutocompleteOpen && message.length === 0; const canNavigateHistoryDown = !isAnyAutocompleteOpen && (message.length === 0 || cursorAtEnd); // Markdown-aware auto-pairing (source mode), normal input only. diff --git a/packages/ui/src/components/chat/MessageList.tsx b/packages/ui/src/components/chat/MessageList.tsx index 755a61fd..0ae15742 100644 --- a/packages/ui/src/components/chat/MessageList.tsx +++ b/packages/ui/src/components/chat/MessageList.tsx @@ -1372,7 +1372,7 @@ const MessageList = React.forwardRef(({ scrollEl.scrollTop += prependedHeight; }); - const [virtualVersion, bumpVirtualVersion] = React.useReducer((v: number) => v + 1, 0); + const [historyVirtualRows, setHistoryVirtualRows] = React.useState(EMPTY_VIRTUAL_ROWS); const historyVirtualizer = useVirtualizer({ count: historyEntries.length, @@ -1383,7 +1383,9 @@ const MessageList = React.forwardRef(({ useAnimationFrameWithResizeObserver: true, overscan: MESSAGE_LIST_OVERSCAN, enabled: shouldVirtualizeHistory, - onChange: bumpVirtualVersion, + onChange: () => { + setHistoryVirtualRows(historyVirtualizer.getVirtualItems()); + }, }); React.useLayoutEffect(() => { @@ -1438,10 +1440,13 @@ const MessageList = React.forwardRef(({ }; }, []); - const historyVirtualRows = React.useMemo( - () => (shouldVirtualizeHistory ? historyVirtualizer.getVirtualItems() : EMPTY_VIRTUAL_ROWS), - [historyVirtualizer, shouldVirtualizeHistory, virtualVersion], - ); + // Sync virtual rows on initial mount and when virtualization toggles. + // Ongoing updates are handled by the virtualizer's onChange callback. + React.useLayoutEffect(() => { + setHistoryVirtualRows( + shouldVirtualizeHistory ? historyVirtualizer.getVirtualItems() : EMPTY_VIRTUAL_ROWS, + ); + }, [shouldVirtualizeHistory, historyVirtualizer]); const allEntries = React.useMemo(() => { return trailingStreamingEntry ? [...historyEntries, trailingStreamingEntry] : historyEntries;