fix(app): own timeline scroll computations
This commit is contained in:
@@ -25,6 +25,11 @@ const assistant = (completed: boolean, tool = false, childID?: string) =>
|
||||
}) satisfies SessionMessageInfo
|
||||
|
||||
test("renders current protocol notices in CLI order", async ({ page }) => {
|
||||
const ownerWarnings: string[] = []
|
||||
page.on("console", (message) => {
|
||||
if (message.text().includes("computations created outside a `createRoot` or `render`"))
|
||||
ownerWarnings.push(message.text())
|
||||
})
|
||||
await setupTimeline(page, {
|
||||
currentMessages: [
|
||||
user,
|
||||
@@ -55,6 +60,7 @@ test("renders current protocol notices in CLI order", async ({ page }) => {
|
||||
await expect(notices.nth(1)).toContainText("explore finished · Search code")
|
||||
await expect(notices.nth(2)).toContainText("Continuing after restart")
|
||||
await expect(notices.nth(3)).toContainText("Skill · Review")
|
||||
expect(ownerWarnings).toEqual([])
|
||||
})
|
||||
|
||||
test("moves blocking work to the background with Ctrl+B", async ({ page }) => {
|
||||
|
||||
@@ -1483,6 +1483,8 @@ export default function Page() {
|
||||
working: () => true,
|
||||
overflowAnchor: "none",
|
||||
})
|
||||
const shouldAnchorBottom = () =>
|
||||
!location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
|
||||
createEffect(
|
||||
on(
|
||||
() => controller.identity.params.id,
|
||||
@@ -2091,9 +2093,7 @@ export default function Page() {
|
||||
onUserScroll={markUserScroll}
|
||||
onHistoryScroll={onHistoryScroll}
|
||||
onAutoScrollInteraction={autoScroll.handleInteraction}
|
||||
shouldAnchorBottom={
|
||||
!location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
|
||||
}
|
||||
shouldAnchorBottom={shouldAnchorBottom()}
|
||||
centered={centered()}
|
||||
setContentRef={(el) => {
|
||||
content = el
|
||||
|
||||
@@ -241,10 +241,12 @@ function MessageTimelineView(
|
||||
) {
|
||||
let touchGesture: number | undefined
|
||||
const language = useLanguage()
|
||||
const shouldAnchorBottom = createMemo(() => props.shouldAnchorBottom)
|
||||
const hasScrollGesture = createMemo(() => props.hasScrollGesture)
|
||||
const ownerSessionKey = props.data.sessionKey()
|
||||
const cached = timelineCache.get(ownerSessionKey)
|
||||
const initialMeasurements = cached?.measurements
|
||||
const coldBottomMount = !initialMeasurements?.length && props.shouldAnchorBottom
|
||||
const coldBottomMount = !initialMeasurements?.length && shouldAnchorBottom()
|
||||
|
||||
const [listRoot, setListRoot] = createSignal<HTMLDivElement>()
|
||||
const sessionID = props.data.sessionID
|
||||
@@ -377,7 +379,7 @@ function MessageTimelineView(
|
||||
},
|
||||
getScrollElement: () => listRoot() ?? null,
|
||||
observeElementOffset: observeElementOffsetReconnectAware,
|
||||
initialOffset: () => (props.shouldAnchorBottom ? Number.MAX_SAFE_INTEGER : 0),
|
||||
initialOffset: () => (shouldAnchorBottom() ? Number.MAX_SAFE_INTEGER : 0),
|
||||
initialMeasurementsCache: initialMeasurements,
|
||||
estimateSize: () => timelineFallbackItemSize,
|
||||
scrollToFn: (offset, options, instance) => {
|
||||
@@ -415,11 +417,11 @@ function MessageTimelineView(
|
||||
const resizeItem = virtualizer.resizeItem
|
||||
let resizeAnchorScheduled = false
|
||||
const anchorResizedBottom = () => {
|
||||
if (resizeAnchorScheduled || props.hasScrollGesture) return
|
||||
if (resizeAnchorScheduled || hasScrollGesture()) return
|
||||
resizeAnchorScheduled = true
|
||||
queueMicrotask(() => {
|
||||
resizeAnchorScheduled = false
|
||||
if (!props.shouldAnchorBottom || props.hasScrollGesture) return
|
||||
if (!shouldAnchorBottom() || hasScrollGesture()) return
|
||||
virtualizer.scrollToEnd()
|
||||
})
|
||||
}
|
||||
@@ -444,10 +446,10 @@ function MessageTimelineView(
|
||||
})
|
||||
}
|
||||
resizeItem(index, size)
|
||||
if (root && props.shouldAnchorBottom) anchorResizedBottom()
|
||||
if (root && shouldAnchorBottom()) anchorResizedBottom()
|
||||
}
|
||||
virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (item) => {
|
||||
if (props.shouldAnchorBottom) return false
|
||||
if (shouldAnchorBottom()) return false
|
||||
const first = virtualizer.range?.startIndex
|
||||
return first !== undefined && item.index < first
|
||||
}
|
||||
@@ -468,18 +470,18 @@ function MessageTimelineView(
|
||||
let overscanFrame: number | undefined
|
||||
onMount(() => {
|
||||
overscanFrame = requestAnimationFrame(() => {
|
||||
if (props.shouldAnchorBottom) virtualizer.scrollToEnd()
|
||||
if (shouldAnchorBottom()) virtualizer.scrollToEnd()
|
||||
overscanFrame = requestAnimationFrame(() => {
|
||||
overscanFrame = undefined
|
||||
if (renderOverscan() < 20) setRenderOverscan(20)
|
||||
if (props.shouldAnchorBottom) virtualizer.scrollToEnd()
|
||||
if (shouldAnchorBottom()) virtualizer.scrollToEnd()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const maybeAnchorBottom = () => {
|
||||
if (timelineRows().length === 0) return
|
||||
if (!props.shouldAnchorBottom || props.hasScrollGesture) return
|
||||
if (!shouldAnchorBottom() || hasScrollGesture()) return
|
||||
if (resizePinFrame !== undefined) cancelAnimationFrame(resizePinFrame)
|
||||
clearPrependAnchor()
|
||||
if (prependAnchorFrame !== undefined) cancelAnimationFrame(prependAnchorFrame)
|
||||
|
||||
Reference in New Issue
Block a user