From 4c27f1753d4af6e4bdc1d5e2cf41b039fe1ee14d Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Tue, 14 Jul 2026 10:24:31 +0300 Subject: [PATCH] fix(chat): anchor prompt navigator to last turn at chat bottom (#2213) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(chat): anchor prompt navigator to last turn at chat bottom The scroll spy activates the last turn whose top edge crossed the reading line (100px below the container top). A final turn short enough to fit below that line could never become active — sitting at the very bottom of the chat kept the previous prompt highlighted on the navigator rail. When the container is scrolled to the bottom (within 8px), force the last turn active. * feat(chat): enable prompt navigator by default Users who already persisted an explicit preference keep their choice. --- .../ui/src/components/chat/lib/scroll/scrollSpy.ts | 10 +++++++++- packages/ui/src/stores/useUIStore.ts | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/chat/lib/scroll/scrollSpy.ts b/packages/ui/src/components/chat/lib/scroll/scrollSpy.ts index a4dd30e9..a68b3930 100644 --- a/packages/ui/src/components/chat/lib/scroll/scrollSpy.ts +++ b/packages/ui/src/components/chat/lib/scroll/scrollSpy.ts @@ -16,6 +16,11 @@ type ScrollSpyInput = { // stable while scrolling inside a long turn (no visibility-ratio flip-flop). const READ_LINE_OFFSET_PX = 100; +// When the container is scrolled to (or almost to) the bottom, the last turn +// is what the user is reading even if it is too short for its top edge to +// ever cross the reading line — force-activate it in that case. +const BOTTOM_ANCHOR_EPSILON_PX = 8; + const pickOffsetTurnId = (list: OffsetTurn[], cutoff: number): string | undefined => { if (list.length === 0) { return undefined; @@ -98,7 +103,10 @@ export const createScrollSpy = (input: ScrollSpyInput) => { refreshOffsets(); } - const next = pickOffsetTurnId(offsets, container.scrollTop + READ_LINE_OFFSET_PX); + const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; + const next = distanceFromBottom <= BOTTOM_ANCHOR_EPSILON_PX + ? offsets[offsets.length - 1]?.id + : pickOffsetTurnId(offsets, container.scrollTop + READ_LINE_OFFSET_PX); if (!next || next === active) { return; } diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index a525cdfd..e8479921 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -964,7 +964,7 @@ export const useUIStore = create()( userMessageRenderingMode: 'markdown', collapsibleUserMessages: true, stickyUserHeader: false, - promptNavigatorEnabled: false, + promptNavigatorEnabled: true, expandedEditorToolbar: false, showSplitAssistantMessageActions: false, allowPromptingSubagentSessions: false,