diff options
| author | Adam <[email protected]> | 2026-01-20 16:01:00 -0600 |
|---|---|---|
| committer | opencode <[email protected]> | 2026-01-20 22:04:13 +0000 |
| commit | a0636fcd50ebf9f9bddb54718fbb319b7df6dee2 (patch) | |
| tree | 539fc6c0c2ef9fa5f77903abe025a19c7c355d59 /packages/app/src | |
| parent | d2fcdef571464c64668062718f3dceec0e79fbeb (diff) | |
| download | opencode-a0636fcd50ebf9f9bddb54718fbb319b7df6dee2.tar.gz opencode-a0636fcd50ebf9f9bddb54718fbb319b7df6dee2.zip | |
fix(app): auto-scroll ux
Diffstat (limited to 'packages/app/src')
| -rw-r--r-- | packages/app/src/pages/session.tsx | 85 |
1 files changed, 45 insertions, 40 deletions
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() { } > <div class="relative w-full h-full min-w-0"> + <Show when={autoScroll.userScrolled()}> + <div class="absolute right-4 md:right-6 bottom-[calc(var(--prompt-height,8rem)+16px)] z-[60] pointer-events-none"> + <Button + variant="secondary" + size="small" + icon="chevron-down" + class="pointer-events-auto shadow-sm" + onClick={() => { + setStore("messageId", undefined) + autoScroll.forceScrollToBottom() + window.history.replaceState(null, "", window.location.href.replace(/#.*$/, "")) + }} + > + Jump to latest + </Button> + </div> + </Show> <div ref={setScrollRef} onScroll={(e) => { 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" }} > |
