Skip to content

refactor(ui): give the transcript scroller a single position authority #4099

Description

@Astro-Han

Problem

Three mechanisms own the scroll position of the same element (chatLayout.scrollContainerRef):

  1. Astryx ChatLayout's built-in useChatStreamScroll.
  2. 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".
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions