Skip to content

fix(desktop): paint thread replies on open without scroll nudge#1090

Merged
wesbillman merged 1 commit into
mainfrom
brain/thread-empty-scroll
Jun 17, 2026
Merged

fix(desktop): paint thread replies on open without scroll nudge#1090
wesbillman merged 1 commit into
mainfrom
brain/thread-empty-scroll

Conversation

@wesbillman

Copy link
Copy Markdown
Collaborator

Problem

Opening a thread panel rendered an empty reply list even when the thread had replies (e.g. 24). Scrolling a single pixel made the messages pop into view.

Root cause

The reply list is gated behind useDeferredValue (#1022): the first frame after a thread opens has an empty deferred snapshot while the live list already holds the replies (the head renders; replies stream in a frame later).

useTimelineScrollManager was called with a hardcoded isLoading: false, so its init effect ran scroll-to-bottom on that empty frame and immediately marked itself initialized. When the real replies committed a frame later, they only got the fragile "new latest message" autoscroll path — which loses the timing race. The rows mounted at scrollTop: 0 and stayed unpainted until a manual scroll forced them into view.

The main timeline doesn't have this bug because it passes a real isLoading, and the init effect waits (if (isLoading) return) until content is ready.

Fix

Give the thread panel the same gate the timeline gets for free: treat it as "still loading" while the deferred reply list hasn't committed yet (repliesRenderState === "pending"). Init scroll then fires on the frame the replies actually commit. Genuinely-empty threads ("empty") still init immediately.

   } = useTimelineScrollManager({
     channelId: threadHeadId,
-    isLoading: false,
+    // Wait for deferred replies to commit before scroll-init (else rows mount un-scrolled).
+    isLoading: repliesRenderState === "pending",
     messages: threadMessages,

One line, no new effects, no repaint hacks.

Testing

  • pnpm typecheck — clean
  • pnpm test — 893/893 pass
  • pnpm check (biome + file-size + px-text guards) — pass
  • Manual: thread replies render on open without a scroll nudge (verified by @wesbillman)

Opening a thread panel rendered an empty reply list until the user
scrolled a pixel, after which the replies popped into view.

The reply list is gated behind `useDeferredValue` (#1022): the first
frame after open has an empty deferred snapshot while the live list
already holds the replies. `useTimelineScrollManager` was called with a
hardcoded `isLoading: false`, so its init effect ran scroll-to-bottom on
that empty frame and marked itself initialized. When the real replies
committed a frame later they only got the fragile "new latest message"
autoscroll path, which loses the timing race — the rows mounted at
scrollTop 0 and stayed unpainted until a manual scroll.

The main timeline avoids this because it passes a real `isLoading` and
its init effect waits until content is ready. Give the thread panel the
same gate: treat it as loading while the deferred reply list hasn't
committed yet (`repliesRenderState === "pending"`). Init scroll then
fires on the frame the replies actually commit. Genuinely-empty threads
still init immediately.

Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman wesbillman merged commit 422a90f into main Jun 17, 2026
23 checks passed
@wesbillman wesbillman deleted the brain/thread-empty-scroll branch June 17, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant