fix: session turn scroll

This commit is contained in:
Adam 2025-12-13 06:15:11 -06:00
parent f254cf76d9
commit 307af10c8b
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
2 changed files with 62 additions and 54 deletions

View file

@ -1,5 +1,6 @@
[data-component="session-turn"] {
/* flex: 1; */
--scroll-y: 0px;
height: 100%;
min-height: 0;
min-width: 0;
@ -26,18 +27,26 @@
align-items: flex-start;
align-self: stretch;
min-width: 0;
gap: 32px;
gap: clamp(8px, calc(42px - var(--scroll-y) * 0.48), 42px);
}
[data-slot="session-turn-sticky-header"] {
[data-slot="session-turn-sticky-title"] {
width: 100%;
position: sticky;
top: 0;
background-color: var(--background-stronger);
z-index: 21;
/* padding-bottom: clamp(0px, calc(8px - var(--scroll-y) * 0.16), 8px); */
}
[data-slot="session-turn-response-trigger"] {
position: sticky;
top: 32px;
background-color: var(--background-stronger);
z-index: 20;
display: flex;
flex-direction: column;
gap: 8px;
width: calc(100% + 9px);
margin-left: -9px;
padding-left: 9px;
padding-bottom: 8px;
}
@ -49,13 +58,8 @@
height: 32px;
}
/* [data-slot="session-turn-message-content"] { */
/* } */
[data-slot="session-turn-response-trigger"] {
width: calc(100% + 9px);
margin-left: -9px;
padding-left: 9px;
[data-slot="session-turn-message-content"] {
margin-top: -24px;
}
[data-slot="session-turn-message-title"] {
@ -292,6 +296,7 @@
[data-slot="session-turn-collapsible"] {
gap: 32px;
overflow: visible;
/* margin-top: clamp(8px, calc(24px - var(--scroll-y) * 0.32), 24px); */
}
[data-slot="session-turn-collapsible-trigger-content"] {

View file

@ -3,18 +3,7 @@ import { useData } from "../context"
import { useDiffComponent } from "../context/diff"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { checksum } from "@opencode-ai/util/encode"
import {
createEffect,
createMemo,
createSignal,
For,
Match,
onCleanup,
onMount,
ParentProps,
Show,
Switch,
} from "solid-js"
import { createEffect, createMemo, createSignal, For, Match, onCleanup, ParentProps, Show, Switch } from "solid-js"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { DiffChanges } from "./diff-changes"
import { Typewriter } from "./typewriter"
@ -61,12 +50,15 @@ export function SessionTurn(
let scrollRef: HTMLDivElement | undefined
const [contentRef, setContentRef] = createSignal<HTMLDivElement>()
const [stickyHeaderRef, setStickyHeaderRef] = createSignal<HTMLDivElement>()
const [stickyTitleRef, setStickyTitleRef] = createSignal<HTMLDivElement>()
const [stickyTriggerRef, setStickyTriggerRef] = createSignal<HTMLDivElement>()
const [userScrolled, setUserScrolled] = createSignal(false)
const [stickyHeaderHeight, setStickyHeaderHeight] = createSignal(0)
const [scrollY, setScrollY] = createSignal(0)
function handleScroll() {
if (!scrollRef) return
setScrollY(scrollRef.scrollTop)
const { scrollTop, scrollHeight, clientHeight } = scrollRef
const atBottom = scrollHeight - scrollTop - clientHeight < 50
if (!atBottom && working()) {
@ -88,15 +80,24 @@ export function SessionTurn(
createResizeObserver(contentRef, () => {
if (!scrollRef || userScrolled() || !working()) return
scrollRef.scrollTop = scrollRef.scrollHeight
requestAnimationFrame(() => {
if (!scrollRef) return
scrollRef.scrollTop = scrollRef.scrollHeight
})
})
createResizeObserver(stickyHeaderRef, ({ height }) => {
setStickyHeaderHeight(height + 8)
createResizeObserver(stickyTitleRef, ({ height }) => {
const triggerHeight = stickyTriggerRef()?.offsetHeight ?? 0
setStickyHeaderHeight(height + triggerHeight + 8)
})
createResizeObserver(stickyTriggerRef, ({ height }) => {
const titleHeight = stickyTitleRef()?.offsetHeight ?? 0
setStickyHeaderHeight(titleHeight + height + 8)
})
return (
<div data-component="session-turn" class={props.classes?.root}>
<div data-component="session-turn" class={props.classes?.root} style={{ "--scroll-y": `${scrollY()}px` }}>
<div ref={scrollRef} onScroll={handleScroll} data-slot="session-turn-content" class={props.classes?.content}>
<div ref={setContentRef} onClick={handleInteraction}>
<Show when={message()}>
@ -250,8 +251,8 @@ export function SessionTurn(
class={props.classes?.container}
style={{ "--sticky-header-height": `${stickyHeaderHeight()}px` }}
>
{/* Sticky Header */}
<div ref={setStickyHeaderRef} data-slot="session-turn-sticky-header">
{/* Title (sticky) */}
<div ref={setStickyTitleRef} data-slot="session-turn-sticky-title">
<div data-slot="session-turn-message-header">
<div data-slot="session-turn-message-title">
<Switch>
@ -264,29 +265,31 @@ export function SessionTurn(
</Switch>
</div>
</div>
<div data-slot="session-turn-message-content">
<Message message={message()} parts={parts()} />
</div>
<div data-slot="session-turn-response-trigger">
<Button
data-slot="session-turn-collapsible-trigger-content"
variant="ghost"
size="small"
onClick={() => setStore("stepsExpanded", !store.stepsExpanded)}
>
<Show when={working()}>
<Spinner />
</Show>
<Switch>
<Match when={working()}>{store.status ?? "Considering next steps..."}</Match>
<Match when={store.stepsExpanded}>Hide steps</Match>
<Match when={!store.stepsExpanded}>Show steps</Match>
</Switch>
<span>·</span>
<span>{store.duration}</span>
<Icon name="chevron-grabber-vertical" size="small" />
</Button>
</div>
</div>
{/* User Message (non-sticky, scrolls under sticky header) */}
<div data-slot="session-turn-message-content">
<Message message={message()} parts={parts()} />
</div>
{/* Trigger (sticky) */}
<div ref={setStickyTriggerRef} data-slot="session-turn-response-trigger">
<Button
data-slot="session-turn-collapsible-trigger-content"
variant="ghost"
size="small"
onClick={() => setStore("stepsExpanded", !store.stepsExpanded)}
>
<Show when={working()}>
<Spinner />
</Show>
<Switch>
<Match when={working()}>{store.status ?? "Considering next steps..."}</Match>
<Match when={store.stepsExpanded}>Hide steps</Match>
<Match when={!store.stepsExpanded}>Show steps</Match>
</Switch>
<span>·</span>
<span>{store.duration}</span>
<Icon name="chevron-grabber-vertical" size="small" />
</Button>
</div>
{/* Response */}
<Show when={store.stepsExpanded}>