From 89003f71a4e2284c2342c46f691e8458fc6c8ef4 Mon Sep 17 00:00:00 2001 From: byGalax Date: Tue, 2 Jun 2026 23:02:07 +0200 Subject: [PATCH] fix(desktop): remove chat-switch reveal flicker (decouple from reactions) The residual flicker on chat switch was a loading/reveal artifact, not scroll. listReady gated the MessageList reveal on reactionsReady OR a 300ms timeout, so on a cache-hit switch (messages already present from the first render) the list sat at opacity:0 for up to 300ms and then popped in. Drop the reactions/timeout gate: reveal as soon as messages exist. Reaction chips stream in a beat later; because the list is pinned to the bottom their height growth re-pins with no visible jump, and MessageList still defers its own reveal a few frames until the row-height measurement settles so it appears already at the final bottom. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/desktop/src/pages/ConversationPage.tsx | 23 +++++++++------------ 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/pages/ConversationPage.tsx b/apps/desktop/src/pages/ConversationPage.tsx index d4c143c..f16be11 100644 --- a/apps/desktop/src/pages/ConversationPage.tsx +++ b/apps/desktop/src/pages/ConversationPage.tsx @@ -144,21 +144,18 @@ export function ConversationPage() { byMessage: reactionsByMessage, toggle: toggleReaction, voteExclusive: votePoll, - ready: reactionsReady, } = useMessageReactions(messageIds, session?.user.id); - // Deferred-reveal gate for MessageList: keep the list hidden until messages - // AND their reactions (the main post-paint height changer) are loaded, so the - // chat opens already-stable instead of flickering through the load cascade. - // A 300 ms max-timeout ensures a slow/empty reactions fetch never hangs it. - const [revealTimedOut, setRevealTimedOut] = useState(false); - useEffect(() => { - setRevealTimedOut(false); - if (!id || loading || messages.length === 0) return; - const tmo = window.setTimeout(() => setRevealTimedOut(true), 300); - return () => window.clearTimeout(tmo); - }, [id, loading, messages.length]); - const listReady = !loading && messages.length > 0 && (reactionsReady || revealTimedOut); + // Reveal gate for MessageList: as soon as messages exist (cache hit = first + // render, so no spinner and no wait), let the list reveal. We deliberately do + // NOT gate on reactions readiness: on a cache-hit chat switch the messages are + // already present, and gating on the async reactions fetch held the list at + // opacity:0 for up to 300ms and then "popped" it in — that was the residual + // chat-switch flicker. Reaction chips stream in a beat later; because the list + // is pinned to the bottom, their height growth re-pins with no visible jump. + // MessageList still defers its own reveal a few frames until the row-height + // measurement settles, so the list still appears already at the final bottom. + const listReady = !loading && messages.length > 0; const myId = session?.user.id;