fix(agent-chat): preserve fractional scroll anchors across transcript and rail

Merged
TE
6d ago
+66-14

The transcript keeps a fractional row-height correction in a bounded CSS origin on its scroller, so its logical offset (where rows are laid out) differs from scrollTop by under a pixel. The transcript half of this branch landed with #26718 and #27365. Base's rails still read and write raw scrollTop, so their band, thumb and jump targets sit up to a pixel off the rows they point at. This PR fixes that.

Leo: "no jitter, there's no flicker, there's no flash" and "if I'm scrolling upwards, it's, like, there's some sort of weird recalculation."

What changes (after the carry onto current base)

This branch has been merged with feat-cmux-next. That merge takes base's App.tsx, scrollCoordinates.ts, anchor trace and upward-anchor test exactly as #27365 left them. What is left here is the part base lacks:

  • ChatMinimap.tsx: the minimap's view box and aria-valuenow read layoutScrollTop, and a drag writes through physicalScrollTop. Absolute Home/End navigation clears the fractional origin, and relative keys keep it.
  • ScrollMap.tsx: the thumb reads layoutScrollTop, and mark jumps and track clicks write through physicalScrollTop.
  • ThreadMinimap.tsx: the band, the restored offset and the reading position read layoutScrollTop, and a turn jump's target goes through physicalScrollTop.
  • Test: chat-minimap.test.tsx checks that absolute keys clear the origin and relative keys keep it.

The branch's earlier row-render cases (1/2/4px late scroll events, no-op upward gestures) are dropped. They read a row's place from style.top alone, but base now splits that into an integer top and a fractional marginTop. #27365 covers those transcript behaviors with its own tests.

No new timer, observer or animation is added. The ScrollMap track click stays instant, as base has it.

The fractional-anchor hosted scripts and the Chromium and video additions to the upward-anchor trace are dropped. Base's trace grew its own version in #27365.

No capture: the rails read and write the same offsets in the transcript's logical coordinates; the difference on screen is under a pixel of band placement, which the tests above pin.

Earlier measurements (pre-carry head 688a6927, for history)

Run 40658799574, on the same painted-frame harness with 9,000 rows: blank frames went from 168 to 0 on WebKit and from 187 to 0 on Chromium. Those numbers measure the transcript half, which now ships from #27365.

Changelog

Fixed the thread rail and minimap reading the transcript a fraction of a pixel off after a fractional scroll anchor.

Agent session: agent session