diff --git a/packages/app/e2e/regression/session-timeline-notices.spec.ts b/packages/app/e2e/regression/session-timeline-notices.spec.ts index 7dc138d0ba..d8a9e0440d 100644 --- a/packages/app/e2e/regression/session-timeline-notices.spec.ts +++ b/packages/app/e2e/regression/session-timeline-notices.spec.ts @@ -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 }) => { diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index f0dcbc293e..fa595e2165 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -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 diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index 74ef46c958..afb91409b1 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -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() 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)