diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 08fad258f6..b45ab7f9de 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -29,7 +29,7 @@ import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge" import { Button } from "@opencode-ai/ui/button" import { showToast } from "@opencode-ai/ui/toast" import { checksum } from "@opencode-ai/core/util/encode" -import { useSearchParams } from "@solidjs/router" +import { useLocation, useSearchParams } from "@solidjs/router" import { NewSessionView, SessionHeader } from "@/components/session" import { useComments } from "@/context/comments" import { getSessionPrefetch, SESSION_PREFETCH_TTL } from "@/context/global-sync/session-prefetch" @@ -196,6 +196,7 @@ export default function Page() { const comments = useComments() const terminal = useTerminal() const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>() + const location = useLocation() const { params, sessionKey, tabs, view } = useSessionLayout() createEffect(() => { @@ -1719,6 +1720,9 @@ export default function Page() { onUserScroll={markUserScroll} onHistoryScroll={historyLoader.onScrollerScroll} onAutoScrollInteraction={autoScroll.handleInteraction} + shouldAnchorBottom={() => + !location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled() + } centered={centered()} setContentRef={(el) => { content = el diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 6c6eda46e8..f8134976a8 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -107,6 +107,7 @@ function sameKeys(a: readonly string[] | undefined, b: readonly string[] | undef } const timelineCacheLimit = 16 +const timelineFallbackItemSize = 60 const timelineCache = new Map() function readTimelineCache(id: string, keys: readonly string[]) { @@ -451,6 +452,7 @@ export function MessageTimeline(props: { onUserScroll: () => void onHistoryScroll: () => void onAutoScrollInteraction: (event: MouseEvent) => void + shouldAnchorBottom: () => boolean centered: boolean setContentRef: (el: HTMLDivElement) => void historyShift: boolean @@ -713,6 +715,18 @@ export function MessageTimeline(props: { let cacheRowKeys = timelineRowKeys() let virtualizerSessionKey = cacheSessionKey let virtualizerRowKeys = cacheRowKeys + let bottomAnchorSessionKey = "" + + const maybeAnchorBottom = () => { + const key = sessionKey() + if (bottomAnchorSessionKey === key) return + if (!virtualizer) return + const keys = timelineRowKeys() + if (keys.length === 0) return + bottomAnchorSessionKey = key + if (!props.shouldAnchorBottom()) return + virtualizer.scrollToIndex(keys.length - 1, { align: "end" }) + } createEffect( on( @@ -724,6 +738,7 @@ export function MessageTimeline(props: { if (virtualizer) { virtualizerSessionKey = cacheSessionKey virtualizerRowKeys = cacheRowKeys + maybeAnchorBottom() } }, { defer: true }, @@ -1669,6 +1684,7 @@ export function MessageTimeline(props: {