Skip to content

fix(app): recover blank transcript from empty virtual window - #12

Draft
jpc-owner wants to merge 1 commit into
devfrom
timeline-blank-recovery
Draft

fix(app): recover blank transcript from empty virtual window#12
jpc-owner wants to merge 1 commit into
devfrom
timeline-blank-recovery

Conversation

@jpc-owner

Copy link
Copy Markdown

Problem

The session message timeline (MessageTimeline, TanStack Virtual) can render completely blank, indefinitely, on:

  • cold direct navigation into a session (deep link)
  • tab re-show / re-layout after the pane's flex container briefly measures 0px

Reproduces most on long sessions, which is why it's easy to miss on short ones.

Root cause

The virtualizer's scroll element can bind while it still measures clientHeight === 0
(parent flex layout not yet resolved). At that point the virtualizer caches a 0px
viewport rect and seeds its scroll offset from initialOffset
(Number.MAX_SAFE_INTEGER when anchoring to bottom). Neither self-heals:

  • the cached rect only updates via the virtualizer's own ResizeObserver rect callback,
    which fires once, before rows exist, and never re-runs once the transcript populates.
  • the offset only updates on a real scroll event — but scrollToEnd() /
    scrollToOffset() write element.scrollTop, which clamps to 0 on unscrollable
    content and emits no scroll event.

So getVirtualItems() stays empty forever despite getTotalSize() being non-zero:
a blank transcript that only "fixes" once the user manually scrolls (which is the
only other path that updates scrollOffset/scrollRect), and often leaves the view
mid-scroll rather than pinned to bottom afterward.

Fix

Watch the scroll element's own size with a ResizeObserver (not the virtualizer's
internal one) and, whenever it has a real height but zero virtual items are rendered,
refresh the virtualizer's cached rect from the live element and reset the scroll offset
directly (bottom-anchored or not), then remeasure. Purely additive — does not change
initialOffset / existing anchoring behavior otherwise.

Testing

  • bun test in packages/app (timeline/message-timeline is not directly unit tested;
    covered by manual repro below).
  • Manual repro: cold direct-link into a long session with the pane's parent briefly at
    0 height (e.g. via Forge UI embedding) — transcript now paints immediately instead of
    requiring a scroll gesture.

The message timeline virtualizer can bind its scroll element while the
element still measures 0px (parent flex layout unresolved on first
paint). The virtualizer then caches a 0px viewport rect and seeds its
scroll offset from initialOffset (Number.MAX_SAFE_INTEGER when anchoring
bottom). Neither self-heals: the cached rect only updates via the
ResizeObserver rect callback, and the offset only updates on a real
scroll event. For a session whose content fits the viewport the element
never scrolls, so the range extractor keeps seeing a 0px viewport at a
bogus offset and renders no rows — a blank transcript that only recovers
once the user scrolls.

Watch the scroll element size and, when it has a height but nothing is
virtualized while rows exist, refresh the cached rect from the live
element and reset the offset directly, then remeasure. scrollToEnd /
scrollToOffset cannot correct this because they write element.scrollTop,
which clamps to 0 on unscrollable content and emits no scroll event.
@github-actions

Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions

Copy link
Copy Markdown

The following comment was made by an LLM, it may be inaccurate:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants