diff --git a/tools/ui/src/app.css b/tools/ui/src/app.css index 53464de8c..bf1a071a9 100644 --- a/tools/ui/src/app.css +++ b/tools/ui/src/app.css @@ -282,3 +282,22 @@ .mermaidTooltip { display: none !important; } + +/* + * While the page sticks to its bottom, the pin after the messages is its only + * scroll anchor, so the browser keeps the bottom in place as content above it + * grows or realizes its size. Browsers without overflow-anchor (WebKit) never + * get the attribute, see createStickToBottom. + */ +[data-stick-to-bottom] * { + overflow-anchor: none; +} + +[data-stick-to-bottom] *:has(.stick-to-bottom-pin), +[data-stick-to-bottom] .stick-to-bottom-pin { + overflow-anchor: auto; +} + +.stick-to-bottom-pin { + height: 1px; +} diff --git a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockExecShellCommand.svelte b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockExecShellCommand.svelte index de7205443..665238a29 100644 --- a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockExecShellCommand.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockExecShellCommand.svelte @@ -334,10 +334,6 @@ } } - .terminal-output { - overscroll-behavior: contain; - } - .terminal-output.is-clamped { max-height: 28rem; overflow-y: auto; diff --git a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageReasoningBlock.svelte b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageReasoningBlock.svelte index 31a63259d..ae460258e 100644 --- a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageReasoningBlock.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageReasoningBlock.svelte @@ -155,7 +155,6 @@ .reasoning-content.is-streaming { max-height: 28rem; overflow-y: auto; - overscroll-behavior: contain; scrollbar-gutter: stable; padding-right: 0.25rem; } diff --git a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte index 790aa95c9..f67b610ea 100644 --- a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte @@ -2,6 +2,7 @@ import ChatScreenActionScrollDown from './ChatScreenActionScrollDown.svelte'; import ChatScreenDialogsAndAlerts from './ChatScreenDialogsAndAlerts.svelte'; import ChatScreenGreeting from './ChatScreenGreeting.svelte'; + import { disableScrollHandling, onNavigate } from '$app/navigation'; import { page } from '$app/state'; import { ChatMessages, @@ -65,6 +66,7 @@ // The page is the scroller: it follows new content while the reader sits at // the bottom, and otherwise leaves their position alone. const stick = createStickToBottom(); + let stickPin: HTMLDivElement | undefined = $state(); const activeModel = useChatScreenActiveModel(); const fileUpload = useChatScreenFileUpload({ activeModelId: () => activeModel.activeModelId, @@ -126,6 +128,17 @@ let lastScrolledConversationId: string | null = null; + // Every navigation lands anew: this screen outlives its routes, so coming + // back to the conversation it last landed on (via the new-chat screen) + // must not count as already landed. The landing also owns the scroll + // position, so SvelteKit's own reset (or restore, on back/forward) would + // only move the previous conversation, still on screen, to its top. + onNavigate((navigation) => { + lastScrolledConversationId = null; + + if (navigation.to?.params?.id) disableScrollHandling(); + }); + // Lands at the bottom of a conversation the first time its messages render. // The resize observer keeps it there while the list settles (lazy rows, // syntax highlighting, images) without polling for a stable height. @@ -204,7 +217,7 @@ }); $effect(() => { - stick.attach(document.documentElement, document.body); + stick.attach(document.documentElement, document.body, stickPin); return () => stick.detach(); }); @@ -235,6 +248,9 @@ /> {/if} + + +