Problem
Three mechanisms own the scroll position of the same element (chatLayout.scrollContainerRef):
- Astryx
ChatLayout's built-in useChatStreamScroll.
useChatScroll -> createArrivalBottomPin, which exists only to override (1). Its own doc comment states why: Astryx positions the first fill of a scroller instantly and springs every later growth, and that one-shot is spent on whichever session was open at boot, so every later switch is "later growth".
useTurnVirtualizer, which observes every turn with a ResizeObserver, captures and restores its own scroll anchor, and bumps geometryRevision.
Five call sites write scrollTop: chat-scroll-anchor.ts:75, arrival-bottom-pin.ts:120, use-chat-scroll.ts:74, use-chat-scroll.ts:115, plus the scrollIntoView at use-turn-virtualizer.ts:195.
Why this is hard to change today
A session switch starts all three. The pin sets scrollTop = scrollHeight; the virtualizer restores an anchor; use-chat-scroll gates turnWindow='ready' behind document.fonts.ready, then up to 50 x 100 ms of polling for .maka-markdown-pending, then a double requestAnimationFrame.
Timing is what keeps the three from colliding, so each new arrival condition tends to add another gate rather than remove one. That is the maintenance cost this issue is about.
Measurements, and what they do not yet show
On a dev build, five consecutive session switches produced 9 long tasks (67, 51, 78, 65, 65, 109, 102, 51, 129 ms), 717 ms total, and a CLS of 0.55.
These numbers were observed on the same path but are not yet attributed. Three authorities fighting over one scroller is a plausible source of layout shift, but no discriminating measurement has been run, so this issue does not claim them as the cause. Root-causing that jank is separate work; this issue stands on the duplicated authority alone.
Decision needed
Who owns transcript scroll position.
- Astryx owns it - delete
arrival-bottom-pin.ts (237 lines) and the positioning half of use-chat-scroll. Cost: ChatLayout must reset its first-fill one-shot per conversationKey, which needs either an upstream change or a remount by key; otherwise switches animate instead of arriving.
- Maka owns it - disable Astryx's path explicitly and merge
useChatScroll with useTurnVirtualizer. The virtualizer already holds the full geometry, so bottom-pinning becomes a window state rather than a separate mechanism.
- Status quo - keep three authorities separated by timing.
No current architecture document assigns this ownership.
Non-goals
No visual or copy change. Prompt-anchor-rail navigation and earlier-history loading keep their current behavior.
Problem
Three mechanisms own the scroll position of the same element (
chatLayout.scrollContainerRef):ChatLayout's built-inuseChatStreamScroll.useChatScroll->createArrivalBottomPin, which exists only to override (1). Its own doc comment states why: Astryx positions the first fill of a scroller instantly and springs every later growth, and that one-shot is spent on whichever session was open at boot, so every later switch is "later growth".useTurnVirtualizer, which observes every turn with aResizeObserver, captures and restores its own scroll anchor, and bumpsgeometryRevision.Five call sites write
scrollTop:chat-scroll-anchor.ts:75,arrival-bottom-pin.ts:120,use-chat-scroll.ts:74,use-chat-scroll.ts:115, plus thescrollIntoViewatuse-turn-virtualizer.ts:195.Why this is hard to change today
A session switch starts all three. The pin sets
scrollTop = scrollHeight; the virtualizer restores an anchor;use-chat-scrollgatesturnWindow='ready'behinddocument.fonts.ready, then up to 50 x 100 ms of polling for.maka-markdown-pending, then a doublerequestAnimationFrame.Timing is what keeps the three from colliding, so each new arrival condition tends to add another gate rather than remove one. That is the maintenance cost this issue is about.
Measurements, and what they do not yet show
On a dev build, five consecutive session switches produced 9 long tasks (67, 51, 78, 65, 65, 109, 102, 51, 129 ms), 717 ms total, and a CLS of 0.55.
These numbers were observed on the same path but are not yet attributed. Three authorities fighting over one scroller is a plausible source of layout shift, but no discriminating measurement has been run, so this issue does not claim them as the cause. Root-causing that jank is separate work; this issue stands on the duplicated authority alone.
Decision needed
Who owns transcript scroll position.
arrival-bottom-pin.ts(237 lines) and the positioning half ofuse-chat-scroll. Cost:ChatLayoutmust reset its first-fill one-shot perconversationKey, which needs either an upstream change or a remount by key; otherwise switches animate instead of arriving.useChatScrollwithuseTurnVirtualizer. The virtualizer already holds the full geometry, so bottom-pinning becomes a window state rather than a separate mechanism.No current architecture document assigns this ownership.
Non-goals
No visual or copy change. Prompt-anchor-rail navigation and earlier-history loading keep their current behavior.