chore: remove scroll jank investigation logging

This commit is contained in:
Mohamed Boudra
2026-04-27 17:13:52 +07:00
parent 40ce317ac2
commit e250024b03
7 changed files with 21 additions and 630 deletions
@@ -25,17 +25,6 @@ const AUTO_SCROLL_BOTTOM_THRESHOLD_PX = 64;
const AUTO_SCROLL_RESUME_THRESHOLD_PX = 1;
import { useWebElementScrollbar } from "./use-web-scrollbar";
function logWebStickyBottom(_event: string, _details: Record<string, unknown>): void {
// Intentionally disabled: this path is too noisy during voice debugging.
}
function getDebugNow(): number | null {
if (typeof performance !== "undefined" && typeof performance.now === "function") {
return Number(performance.now().toFixed(3));
}
return null;
}
function isScrollContainerNearBottom(
scrollContainer: Pick<HTMLElement, "scrollTop" | "clientHeight" | "scrollHeight">,
thresholdPx = AUTO_SCROLL_BOTTOM_THRESHOLD_PX,
@@ -92,36 +81,6 @@ function isScrollContainerOverscrolledPastBottom(
return getScrollContainerDistanceFromBottom(scrollContainer) < 0;
}
const VIEWPORT_LOG_PREFIX = "[WebStreamViewport]";
function vlog(agentId: string, event: string, details: Record<string, unknown> = {}): void {
console.log(VIEWPORT_LOG_PREFIX, agentId, event, details);
}
function useRenderTracker(
label: string,
agentId: string,
watched: Record<string, unknown>,
): number {
const renderCountRef = useRef(0);
const prevRef = useRef<Record<string, unknown> | null>(null);
renderCountRef.current += 1;
const changed: string[] = [];
if (prevRef.current !== null) {
for (const key of Object.keys(watched)) {
if (!Object.is(prevRef.current[key], watched[key])) {
changed.push(key);
}
}
}
prevRef.current = { ...watched };
console.log(VIEWPORT_LOG_PREFIX, agentId, `${label} render #${renderCountRef.current}`, {
isFirst: renderCountRef.current === 1,
changed,
});
return renderCountRef.current;
}
function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: boolean }) {
const {
segments,
@@ -135,26 +94,6 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
scrollEnabled,
isMobileBreakpoint,
} = props;
useRenderTracker("WebStreamViewport", props.agentId, {
agentId: props.agentId,
segments,
"segments.historyMounted": segments.historyMounted,
"segments.historyVirtualized": segments.historyVirtualized,
"segments.liveHead": segments.liveHead,
boundary,
renderers,
"renderers.renderHistoryMountedRow": renderers.renderHistoryMountedRow,
"renderers.renderHistoryVirtualizedRow": renderers.renderHistoryVirtualizedRow,
"renderers.renderLiveHeadRow": renderers.renderLiveHeadRow,
"renderers.renderLiveAuxiliary": renderers.renderLiveAuxiliary,
listEmptyComponent,
viewportRef,
routeBottomAnchorRequest,
isAuthoritativeHistoryReady,
onNearBottomChange,
scrollEnabled,
isMobileBreakpoint,
});
const scrollContainerRef = useRef<HTMLElement | null>(null);
const contentRef = useRef<HTMLElement | null>(null);
const handleScrollContainerRef = useCallback((node: HTMLElement | null) => {
@@ -170,13 +109,6 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
};
const followOutputRef = useRef(followOutput);
const lastKnownScrollTopRef = useRef(0);
const lastLoggedMetricsRef = useRef<{
scrollTop: number;
clientWidth: number;
clientHeight: number;
scrollWidth: number;
scrollHeight: number;
} | null>(null);
const pendingUserScrollUpIntentRef = useRef(false);
const isPointerScrollActiveRef = useRef(false);
const lastTouchClientYRef = useRef<number | null>(null);
@@ -214,28 +146,16 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
overscan: 8,
});
useEffect(() => {
vlog(props.agentId, "effect:virtualizer-adjust setup fired", {});
rowVirtualizer.shouldAdjustScrollPositionOnItemSizeChange = (_item, _delta, instance) => {
const viewportHeight = instance.scrollRect?.height ?? 0;
const scrollOffset = instance.scrollOffset ?? 0;
const remainingDistance = instance.getTotalSize() - (scrollOffset + viewportHeight);
logWebStickyBottom("virtualizer_item_size_change", {
agentId: props.agentId,
delta: _delta,
itemIndex: _item.index,
itemStart: _item.start,
itemSize: _item.size,
viewportHeight,
scrollOffset,
totalSize: instance.getTotalSize(),
remainingDistance,
});
return remainingDistance > AUTO_SCROLL_BOTTOM_THRESHOLD_PX;
};
return () => {
rowVirtualizer.shouldAdjustScrollPositionOnItemSizeChange = undefined;
};
}, [rowVirtualizer, props.agentId]);
}, [rowVirtualizer]);
const virtualRows = rowVirtualizer.getVirtualItems();
const virtualTotalSize = rowVirtualizer.getTotalSize();
@@ -293,21 +213,11 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
if (isScrollContainerOverscrolledPastBottom(scrollContainer)) {
return;
}
logWebStickyBottom("viewport_scroll_to_bottom", {
agentId: props.agentId,
behavior,
followOutput: followOutputRef.current,
scrollTop: scrollContainer.scrollTop,
clientWidth: scrollContainer.clientWidth,
clientHeight: scrollContainer.clientHeight,
scrollWidth: scrollContainer.scrollWidth,
scrollHeight: scrollContainer.scrollHeight,
});
scrollElementToBottom(scrollContainer, behavior);
lastKnownScrollTopRef.current = scrollContainer.scrollTop;
syncNearBottom(scrollContainer, onNearBottomChange);
},
[onNearBottomChange, props.agentId],
[onNearBottomChange],
);
const scheduleStickToBottom = useCallback(() => {
@@ -318,15 +228,6 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
if (pendingAutoScrollFrameRef.current !== null) {
return;
}
logWebStickyBottom("viewport_schedule_stick_to_bottom", {
agentId: props.agentId,
followOutput: followOutputRef.current,
scrollTop: scrollContainer?.scrollTop ?? null,
clientWidth: scrollContainer?.clientWidth ?? null,
clientHeight: scrollContainer?.clientHeight ?? null,
scrollWidth: scrollContainer?.scrollWidth ?? null,
scrollHeight: scrollContainer?.scrollHeight ?? null,
});
pendingAutoScrollFrameRef.current = window.requestAnimationFrame(() => {
pendingAutoScrollFrameRef.current = null;
if (!followOutputRef.current) {
@@ -334,7 +235,7 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
}
scrollMessagesToBottom("auto");
});
}, [props.agentId, scrollMessagesToBottom]);
}, [scrollMessagesToBottom]);
const forceStickToBottom = useCallback(() => {
cancelPendingStickToBottom();
@@ -349,31 +250,7 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
return;
}
syncNearBottom(scrollContainer, onNearBottomChange);
const currentMetrics = {
scrollTop: scrollContainer.scrollTop,
clientWidth: scrollContainer.clientWidth,
clientHeight: scrollContainer.clientHeight,
scrollWidth: scrollContainer.scrollWidth,
scrollHeight: scrollContainer.scrollHeight,
};
const previousMetrics = lastLoggedMetricsRef.current;
const shouldLog =
!previousMetrics ||
previousMetrics.scrollTop !== currentMetrics.scrollTop ||
previousMetrics.clientWidth !== currentMetrics.clientWidth ||
previousMetrics.clientHeight !== currentMetrics.clientHeight ||
previousMetrics.scrollWidth !== currentMetrics.scrollWidth ||
previousMetrics.scrollHeight !== currentMetrics.scrollHeight;
if (shouldLog) {
lastLoggedMetricsRef.current = currentMetrics;
logWebStickyBottom("viewport_metrics_updated", {
agentId: props.agentId,
followOutput: followOutputRef.current,
distanceFromBottom: getScrollContainerDistanceFromBottom(scrollContainer),
...currentMetrics,
});
}
}, [onNearBottomChange, props.agentId]);
}, [onNearBottomChange]);
const handleDomScroll = useCallback(() => {
const scrollContainer = scrollContainerRef.current;
@@ -402,26 +279,10 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
}
lastKnownScrollTopRef.current = currentScrollTop;
logWebStickyBottom("viewport_dom_scroll", {
agentId: props.agentId,
now: getDebugNow(),
scrollTop: currentScrollTop,
clientHeight: scrollContainer.clientHeight,
scrollHeight: scrollContainer.scrollHeight,
activeElementTag:
typeof document !== "undefined"
? (document.activeElement?.tagName?.toLowerCase() ?? null)
: null,
activeElementRole:
typeof document !== "undefined"
? (document.activeElement?.getAttribute?.("aria-label") ?? null)
: null,
});
updateScrollMetrics();
}, [cancelPendingStickToBottom, updateScrollMetrics, props.agentId]);
}, [cancelPendingStickToBottom, updateScrollMetrics]);
useLayoutEffect(() => {
vlog(props.agentId, "effect:activation-reset fired", { activationKey, isActivationReady });
if (!isActivationReady) {
return;
}
@@ -443,12 +304,9 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
return () => {
window.clearTimeout(timeout);
};
}, [activationKey, forceStickToBottom, isActivationReady, scheduleStickToBottom, props.agentId]);
}, [activationKey, forceStickToBottom, isActivationReady, scheduleStickToBottom]);
useEffect(() => {
vlog(props.agentId, "effect:stick-on-segments-change fired", {
followOutput: followOutputRef.current,
});
if (!followOutputRef.current) {
return;
}
@@ -458,28 +316,16 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
segments.historyMounted,
segments.historyVirtualized,
segments.liveHead,
props.agentId,
]);
useEffect(() => {
vlog(props.agentId, "effect:stick-on-virtual-total fired", {
followOutput: followOutputRef.current,
shouldUseVirtualizer,
virtualTotalSize,
});
if (!followOutputRef.current || !shouldUseVirtualizer) {
return;
}
scheduleStickToBottom();
}, [scheduleStickToBottom, shouldUseVirtualizer, virtualTotalSize, props.agentId]);
}, [scheduleStickToBottom, shouldUseVirtualizer, virtualTotalSize]);
useEffect(() => {
vlog(props.agentId, "effect:metrics-on-segments-change fired", {
mounted: segments.historyMounted.length,
virtualized: segments.historyVirtualized.length,
liveHead: segments.liveHead.length,
virtualTotalSize,
});
updateScrollMetrics();
}, [
segments.historyMounted.length,
@@ -487,11 +333,9 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
segments.liveHead.length,
updateScrollMetrics,
virtualTotalSize,
props.agentId,
]);
useEffect(() => {
vlog(props.agentId, "effect:resize-observer setup fired", {});
const scrollContainer = scrollContainerRef.current;
const contentNode = contentRef.current;
if (!scrollContainer || typeof ResizeObserver === "undefined") {
@@ -500,15 +344,6 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
updateScrollMetrics();
const observer = new ResizeObserver(() => {
logWebStickyBottom("viewport_resize_observed", {
agentId: props.agentId,
followOutput: followOutputRef.current,
scrollTop: scrollContainer.scrollTop,
clientWidth: scrollContainer.clientWidth,
clientHeight: scrollContainer.clientHeight,
scrollWidth: scrollContainer.scrollWidth,
scrollHeight: scrollContainer.scrollHeight,
});
updateScrollMetrics();
if (!followOutputRef.current) {
return;
@@ -522,68 +357,14 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
return () => {
observer.disconnect();
};
}, [props.agentId, scheduleStickToBottom, updateScrollMetrics]);
}, [scheduleStickToBottom, updateScrollMetrics]);
useEffect(() => {
vlog(props.agentId, "effect:scroll-listeners setup fired", {});
const scrollContainer = scrollContainerRef.current;
if (!scrollContainer) {
return;
}
const originalScrollTo = scrollContainer.scrollTo.bind(scrollContainer);
const scrollTopDescriptor =
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(scrollContainer), "scrollTop") ??
Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop");
scrollContainer.scrollTo = ((...args: Parameters<HTMLElement["scrollTo"]>) => {
const firstArg = args[0] as ScrollToOptions | number | undefined;
const target =
typeof firstArg === "object" && firstArg !== null
? {
top: firstArg.top ?? null,
left: firstArg.left ?? null,
behavior: firstArg.behavior ?? null,
}
: {
top: typeof args[1] === "number" ? args[1] : null,
left: typeof firstArg === "number" ? firstArg : null,
behavior: null,
};
logWebStickyBottom("viewport_scroll_to_called", {
agentId: props.agentId,
now: getDebugNow(),
currentScrollTop: scrollContainer.scrollTop,
target,
stack:
typeof Error !== "undefined"
? (new Error().stack?.split("\n").slice(1, 6).join("\n") ?? null)
: null,
});
return originalScrollTo(...args);
}) as typeof scrollContainer.scrollTo;
if (scrollTopDescriptor?.get && scrollTopDescriptor?.set) {
Object.defineProperty(scrollContainer, "scrollTop", {
configurable: true,
enumerable: scrollTopDescriptor.enumerable ?? false,
get() {
return scrollTopDescriptor.get?.call(scrollContainer);
},
set(value: number) {
logWebStickyBottom("viewport_scroll_top_set", {
agentId: props.agentId,
now: getDebugNow(),
currentScrollTop: scrollTopDescriptor.get?.call(scrollContainer) ?? null,
nextScrollTop: value,
stack:
typeof Error !== "undefined"
? (new Error().stack?.split("\n").slice(1, 6).join("\n") ?? null)
: null,
});
return scrollTopDescriptor.set?.call(scrollContainer, value);
},
});
}
const handleWheel = (event: WheelEvent) => {
if (event.deltaY < 0) {
pendingUserScrollUpIntentRef.current = true;
@@ -618,27 +399,6 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
const handleTouchEnd = () => {
lastTouchClientYRef.current = null;
};
const handleSelectionChange = () => {
const activeElement =
typeof document !== "undefined"
? (document.activeElement as HTMLTextAreaElement | null)
: null;
logWebStickyBottom("document_selection_changed", {
agentId: props.agentId,
now: getDebugNow(),
activeElementTag: activeElement?.tagName?.toLowerCase() ?? null,
activeElementRole: activeElement?.getAttribute?.("aria-label") ?? null,
selectionStart:
activeElement && typeof activeElement.selectionStart === "number"
? activeElement.selectionStart
: null,
selectionEnd:
activeElement && typeof activeElement.selectionEnd === "number"
? activeElement.selectionEnd
: null,
scrollTop: scrollContainer.scrollTop,
});
};
scrollContainer.addEventListener("scroll", handleDomScroll, { passive: true });
scrollContainer.addEventListener("wheel", handleWheel, { passive: true });
@@ -649,9 +409,6 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
scrollContainer.addEventListener("touchmove", handleTouchMove, { passive: true });
scrollContainer.addEventListener("touchend", handleTouchEnd, { passive: true });
scrollContainer.addEventListener("touchcancel", handleTouchEnd, { passive: true });
if (typeof document !== "undefined") {
document.addEventListener("selectionchange", handleSelectionChange, { passive: true });
}
return () => {
scrollContainer.removeEventListener("scroll", handleDomScroll);
@@ -663,18 +420,10 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
scrollContainer.removeEventListener("touchmove", handleTouchMove);
scrollContainer.removeEventListener("touchend", handleTouchEnd);
scrollContainer.removeEventListener("touchcancel", handleTouchEnd);
scrollContainer.scrollTo = originalScrollTo;
if (scrollTopDescriptor) {
Reflect.deleteProperty(scrollContainer, "scrollTop");
}
if (typeof document !== "undefined") {
document.removeEventListener("selectionchange", handleSelectionChange);
}
};
}, [cancelPendingStickToBottom, handleDomScroll, props.agentId]);
}, [cancelPendingStickToBottom, handleDomScroll]);
useEffect(() => {
vlog(props.agentId, "effect:viewportRef-handle setup fired", {});
const handle: StreamViewportHandle = {
scrollToBottom: () => {
setFollowOutput(true);
@@ -685,16 +434,6 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
if (!followOutputRef.current) {
return;
}
const scrollContainer = scrollContainerRef.current;
logWebStickyBottom("viewport_prepare_for_change", {
agentId: props.agentId,
followOutput: followOutputRef.current,
scrollTop: scrollContainer?.scrollTop ?? null,
clientWidth: scrollContainer?.clientWidth ?? null,
clientHeight: scrollContainer?.clientHeight ?? null,
scrollWidth: scrollContainer?.scrollWidth ?? null,
scrollHeight: scrollContainer?.scrollHeight ?? null,
});
scheduleStickToBottom();
},
};
@@ -705,16 +444,9 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
}
cancelPendingStickToBottom();
};
}, [
cancelPendingStickToBottom,
forceStickToBottom,
props.agentId,
scheduleStickToBottom,
viewportRef,
]);
}, [cancelPendingStickToBottom, forceStickToBottom, scheduleStickToBottom, viewportRef]);
const contentContainerStyle = useMemo((): CSSProperties => {
vlog(props.agentId, "memo:contentContainerStyle rebuilt", { isMobileBreakpoint });
return {
display: "flex",
flexDirection: "column",
@@ -725,9 +457,8 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
paddingRight: isMobileBreakpoint ? 8 : 16,
boxSizing: "border-box",
};
}, [isMobileBreakpoint, props.agentId]);
}, [isMobileBreakpoint]);
const scrollContainerStyle = useMemo((): CSSProperties => {
vlog(props.agentId, "memo:scrollContainerStyle rebuilt", { scrollEnabled });
return {
flex: 1,
minHeight: 0,
@@ -735,15 +466,14 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
overflowY: scrollEnabled ? "auto" : "hidden",
overscrollBehaviorY: "contain",
};
}, [scrollEnabled, props.agentId]);
}, [scrollEnabled]);
const virtualRowsContainerStyle = useMemo((): CSSProperties => {
vlog(props.agentId, "memo:virtualRowsContainerStyle rebuilt", { virtualTotalSize });
return {
position: "relative",
width: "100%",
height: virtualTotalSize,
};
}, [virtualTotalSize, props.agentId]);
}, [virtualTotalSize]);
const renderVirtualRowStyle = useCallback(
(start: number): CSSProperties => ({
position: "absolute",
@@ -757,40 +487,26 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
[],
);
const mountedHistoryRows = useMemo(() => {
vlog(props.agentId, "memo:mountedHistoryRows rebuilt", {
count: segments.historyMounted.length,
});
return segments.historyMounted.map((item, index) => (
<Fragment key={item.id}>
{renderHistoryMountedRow(item, index, segments.historyMounted)}
</Fragment>
));
}, [renderHistoryMountedRow, segments.historyMounted, props.agentId]);
}, [renderHistoryMountedRow, segments.historyMounted]);
const liveHeadRows = useMemo(() => {
vlog(props.agentId, "memo:liveHeadRows rebuilt", { count: segments.liveHead.length });
return segments.liveHead.map((item, index) => (
<Fragment key={item.id}>{renderLiveHeadRow(item, index, segments.liveHead)}</Fragment>
));
}, [renderLiveHeadRow, segments.liveHead, props.agentId]);
}, [renderLiveHeadRow, segments.liveHead]);
const liveAuxiliary = useMemo(() => {
vlog(props.agentId, "memo:liveAuxiliary rebuilt", {});
return renderLiveAuxiliary();
}, [renderLiveAuxiliary, props.agentId]);
}, [renderLiveAuxiliary]);
const shouldRenderEmpty =
!boundary.hasMountedHistory &&
!boundary.hasVirtualizedHistory &&
!boundary.hasLiveHead &&
!liveAuxiliary;
vlog(props.agentId, "render summary", {
virtualRowsRendered: virtualRows.length,
mountedHistoryRowsRendered: segments.historyMounted.length,
liveHeadRowsRendered: segments.liveHead.length,
shouldUseVirtualizer,
virtualTotalSize,
shouldRenderEmpty,
});
return (
<>
<div