fix(desktop): paint thread replies on open without scroll nudge#1090
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).useTimelineScrollManagerwas called with a hardcodedisLoading: 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 atscrollTop: 0and 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— cleanpnpm test— 893/893 passpnpm check(biome + file-size + px-text guards) — pass