debug(desktop): on-screen scroll-metrics overlay (temporary)
This commit is contained in:
@@ -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<MessageListHandle, MessageListProps>(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<MessageListHandle, MessageListProps>(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<MessageListHandle, MessageListProps>(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<MessageListHandle, MessageListProps>(funct
|
||||
const items = virtualizer.getVirtualItems();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={scrollElRef}
|
||||
onScroll={handleScroll}
|
||||
className="min-h-0 flex-1 overflow-y-auto"
|
||||
style={{ opacity: revealed ? 1 : 0, position: 'relative' }}
|
||||
>
|
||||
<>
|
||||
<ScrollDebugOverlay
|
||||
scrollElRef={scrollElRef}
|
||||
getTotal={() => virtualizer.getTotalSize()}
|
||||
atBottomRef={atBottomRef}
|
||||
revealed={revealed}
|
||||
ready={ready}
|
||||
/>
|
||||
<div
|
||||
ref={scrollElRef}
|
||||
onScroll={handleScroll}
|
||||
className="min-h-0 flex-1 overflow-y-auto"
|
||||
style={{ opacity: revealed ? 1 : 0, position: 'relative' }}
|
||||
>
|
||||
<div style={{ height: virtualizer.getTotalSize(), position: 'relative', width: '100%' }}>
|
||||
{items.map((vi) => (
|
||||
<div
|
||||
@@ -196,8 +221,60 @@ export const MessageList = forwardRef<MessageListHandle, MessageListProps>(funct
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 12px bottom breathing space (matches the old Virtuoso Footer). */}
|
||||
<div style={{ height: 12 }} />
|
||||
</div>
|
||||
{/* 12px bottom breathing space (matches the old Virtuoso Footer). */}
|
||||
<div style={{ height: 12 }} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
interface ScrollDebugOverlayProps {
|
||||
scrollElRef: RefObject<HTMLDivElement>;
|
||||
getTotal: () => number;
|
||||
atBottomRef: MutableRefObject<boolean>;
|
||||
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 (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 8,
|
||||
right: 8,
|
||||
zIndex: 99999,
|
||||
background: 'rgba(0,0,0,0.82)',
|
||||
color: '#19ff8a',
|
||||
font: '11px/1.4 monospace',
|
||||
padding: '6px 9px',
|
||||
borderRadius: 5,
|
||||
pointerEvents: 'none',
|
||||
whiteSpace: 'pre',
|
||||
}}
|
||||
>
|
||||
{`scrollHeight=${sh} clientHeight=${ch}\nscrollTop=${st}\ntotalSize=${Math.round(getTotal())}\nSCROLLABLE=${sh > ch + 4 ? 'YES' : 'NO'}\natBottom=${atBottomRef.current} revealed=${revealed} ready=${ready}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user