From a0636fcd50ebf9f9bddb54718fbb319b7df6dee2 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:01:00 -0600 Subject: fix(app): auto-scroll ux --- packages/app/src/pages/session.tsx | 85 ++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 40 deletions(-) (limited to 'packages/app/src') diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index e794b8b8f..670e8ff02 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -840,13 +840,27 @@ export default function Page() { const autoScroll = createAutoScroll({ working: () => true, + overflowAnchor: "auto", }) + // When the user returns to the bottom, treat the active message as "latest". + createEffect( + on( + autoScroll.userScrolled, + (scrolled) => { + if (scrolled) return + setStore("messageId", undefined) + }, + { defer: true }, + ), + ) + createEffect( on( isWorking, (working, prev) => { if (!working || prev) return + if (autoScroll.userScrolled()) return autoScroll.forceScrollToBottom() }, { defer: true }, @@ -990,58 +1004,33 @@ export default function Page() { const a = el.getBoundingClientRect() const b = root.getBoundingClientRect() - const top = a.top - b.top + root.scrollTop - root.scrollTo({ top, behavior }) + const offset = (info()?.title ? 40 : 0) + 12 + const top = a.top - b.top + root.scrollTop - offset + root.scrollTo({ top: top > 0 ? top : 0, behavior }) return true } const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => { + // Navigating to a specific message should always pause auto-follow. + autoScroll.pause() setActiveMessage(message) + updateHash(message.id) const msgs = visibleUserMessages() const index = msgs.findIndex((m) => m.id === message.id) if (index !== -1 && index < store.turnStart) { setStore("turnStart", index) scheduleTurnBackfill() - - requestAnimationFrame(() => { - const el = document.getElementById(anchor(message.id)) - if (!el) { - requestAnimationFrame(() => { - const next = document.getElementById(anchor(message.id)) - if (!next) return - scrollToElement(next, behavior) - }) - return - } - scrollToElement(el, behavior) - }) - - updateHash(message.id) - return } - const el = document.getElementById(anchor(message.id)) - if (!el) { - updateHash(message.id) - requestAnimationFrame(() => { - const next = document.getElementById(anchor(message.id)) - if (!next) return - if (!scrollToElement(next, behavior)) return - }) - return + const id = anchor(message.id) + const attempt = (tries: number) => { + const el = document.getElementById(id) + if (el && scrollToElement(el, behavior)) return + if (tries >= 8) return + requestAnimationFrame(() => attempt(tries + 1)) } - if (scrollToElement(el, behavior)) { - updateHash(message.id) - return - } - - requestAnimationFrame(() => { - const next = document.getElementById(anchor(message.id)) - if (!next) return - if (!scrollToElement(next, behavior)) return - }) - updateHash(message.id) + attempt(0) } const applyHash = (behavior: ScrollBehavior) => { @@ -1283,13 +1272,29 @@ export default function Page() { } >
+ +
+ +
+
{ autoScroll.handleScroll() - if (isDesktop()) scheduleScrollSpy(e.currentTarget) + if (isDesktop() && autoScroll.userScrolled()) scheduleScrollSpy(e.currentTarget) }} - onClick={autoScroll.handleInteraction} class="relative min-w-0 w-full h-full overflow-y-auto no-scrollbar" style={{ "--session-title-height": info()?.title ? "40px" : "0px" }} > -- cgit v1.2.3