From 8ea2cb48e9f18667923faa2ad016ade279829332 Mon Sep 17 00:00:00 2001 From: byGalax Date: Tue, 2 Jun 2026 21:34:04 +0200 Subject: [PATCH] debug(desktop): on-screen scroll-metrics overlay (temporary) --- apps/desktop/src/components/MessageList.tsx | 97 ++++++++++++++++++--- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/components/MessageList.tsx b/apps/desktop/src/components/MessageList.tsx index 1ac7ba1..6e6051b 100644 --- a/apps/desktop/src/components/MessageList.tsx +++ b/apps/desktop/src/components/MessageList.tsx @@ -2,11 +2,14 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import { forwardRef, useCallback, + useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, + type MutableRefObject, type ReactNode, + type RefObject, } from 'react'; import { isNearBottom, isNearTop } from '../lib/scrollController'; @@ -73,7 +76,13 @@ export const MessageList = forwardRef(funct const pinToBottom = useCallback(() => { const el = scrollElRef.current; - if (el) el.scrollTop = el.scrollHeight; + if (!el) return; + const before = el.scrollTop; + el.scrollTop = el.scrollHeight; + // eslint-disable-next-line no-console + console.log( + `[scroll] pin sh=${el.scrollHeight} ch=${el.clientHeight} top:${Math.round(before)}->${Math.round(el.scrollTop)}`, + ); }, []); // Deferred reveal: when ready, position at the anchor (before paint), let one @@ -90,6 +99,10 @@ export const MessageList = forwardRef(funct if (!ready || revealed || rows.length === 0) return; const el = scrollElRef.current; if (!el) return; + // eslint-disable-next-line no-console + console.log( + `[scroll] reveal anchor=${initialAnchor.type} rows=${rows.length} total=${Math.round(virtualizer.getTotalSize())} sh=${el.scrollHeight} ch=${el.clientHeight}`, + ); if (initialAnchor.type === 'bottom') { pinToBottom(); atBottomRef.current = true; @@ -120,6 +133,10 @@ export const MessageList = forwardRef(funct // taller) and we were at the bottom, re-pin to the true bottom. Runs while // hidden too, so the list stays pinned through the initial measure settle. useLayoutEffect(() => { + // eslint-disable-next-line no-console + console.log( + `[scroll] stick? atBottom=${atBottomRef.current} total=${Math.round(virtualizer.getTotalSize())} revealed=${revealed}`, + ); if (!atBottomRef.current) return; pinToBottom(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -172,12 +189,20 @@ export const MessageList = forwardRef(funct const items = virtualizer.getVirtualItems(); return ( -
+ <> + virtualizer.getTotalSize()} + atBottomRef={atBottomRef} + revealed={revealed} + ready={ready} + /> +
{items.map((vi) => (
(funct
))}
- {/* 12px bottom breathing space (matches the old Virtuoso Footer). */} -
-
+ {/* 12px bottom breathing space (matches the old Virtuoso Footer). */} +
+
+ ); }); + +interface ScrollDebugOverlayProps { + scrollElRef: RefObject; + getTotal: () => number; + atBottomRef: MutableRefObject; + revealed: boolean; + ready: boolean; +} + +// TEMP on-screen debug readout — remove after the chat-switch scroll bug is fixed. +function ScrollDebugOverlay({ + scrollElRef, + getTotal, + atBottomRef, + revealed, + ready, +}: ScrollDebugOverlayProps) { + const [, force] = useState(0); + useEffect(() => { + let raf = 0; + const loop = () => { + force((n) => (n + 1) % 1_000_000); + raf = requestAnimationFrame(loop); + }; + raf = requestAnimationFrame(loop); + return () => cancelAnimationFrame(raf); + }, []); + const el = scrollElRef.current; + const sh = el?.scrollHeight ?? 0; + const ch = el?.clientHeight ?? 0; + const st = el ? Math.round(el.scrollTop) : 0; + return ( +
+ {`scrollHeight=${sh} clientHeight=${ch}\nscrollTop=${st}\ntotalSize=${Math.round(getTotal())}\nSCROLLABLE=${sh > ch + 4 ? 'YES' : 'NO'}\natBottom=${atBottomRef.current} revealed=${revealed} ready=${ready}`} +
+ ); +}