refactor: strip useUnistyles in favor of withUnistyles + StyleSheet
Replaces broad useUnistyles() subscriptions in composer, workspace, and related components with withUnistyles wrappers and StyleSheet.create so re-renders stay scoped to the tokens actually consumed. Lifts the design scales (spacing, iconSize, fontSize, etc.) to top-level exports in theme.ts so component literals share a single source of truth with the unistyles theme.
This commit is contained in:
@@ -92,6 +92,36 @@ 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,
|
||||
@@ -105,6 +135,26 @@ 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) => {
|
||||
@@ -164,6 +214,7 @@ 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;
|
||||
@@ -370,6 +421,7 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
|
||||
}, [cancelPendingStickToBottom, updateScrollMetrics, props.agentId]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
vlog(props.agentId, "effect:activation-reset fired", { activationKey, isActivationReady });
|
||||
if (!isActivationReady) {
|
||||
return;
|
||||
}
|
||||
@@ -391,9 +443,12 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
|
||||
return () => {
|
||||
window.clearTimeout(timeout);
|
||||
};
|
||||
}, [activationKey, forceStickToBottom, isActivationReady, scheduleStickToBottom]);
|
||||
}, [activationKey, forceStickToBottom, isActivationReady, scheduleStickToBottom, props.agentId]);
|
||||
|
||||
useEffect(() => {
|
||||
vlog(props.agentId, "effect:stick-on-segments-change fired", {
|
||||
followOutput: followOutputRef.current,
|
||||
});
|
||||
if (!followOutputRef.current) {
|
||||
return;
|
||||
}
|
||||
@@ -403,16 +458,28 @@ 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]);
|
||||
}, [scheduleStickToBottom, shouldUseVirtualizer, virtualTotalSize, props.agentId]);
|
||||
|
||||
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,
|
||||
@@ -420,9 +487,11 @@ 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") {
|
||||
@@ -456,6 +525,7 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
|
||||
}, [props.agentId, scheduleStickToBottom, updateScrollMetrics]);
|
||||
|
||||
useEffect(() => {
|
||||
vlog(props.agentId, "effect:scroll-listeners setup fired", {});
|
||||
const scrollContainer = scrollContainerRef.current;
|
||||
if (!scrollContainer) {
|
||||
return;
|
||||
@@ -604,6 +674,7 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
|
||||
}, [cancelPendingStickToBottom, handleDomScroll, props.agentId]);
|
||||
|
||||
useEffect(() => {
|
||||
vlog(props.agentId, "effect:viewportRef-handle setup fired", {});
|
||||
const handle: StreamViewportHandle = {
|
||||
scrollToBottom: () => {
|
||||
setFollowOutput(true);
|
||||
@@ -642,8 +713,9 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
|
||||
viewportRef,
|
||||
]);
|
||||
|
||||
const contentContainerStyle = useMemo(
|
||||
(): CSSProperties => ({
|
||||
const contentContainerStyle = useMemo((): CSSProperties => {
|
||||
vlog(props.agentId, "memo:contentContainerStyle rebuilt", { isMobileBreakpoint });
|
||||
return {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: "100%",
|
||||
@@ -652,27 +724,26 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
|
||||
paddingLeft: isMobileBreakpoint ? 8 : 16,
|
||||
paddingRight: isMobileBreakpoint ? 8 : 16,
|
||||
boxSizing: "border-box",
|
||||
}),
|
||||
[isMobileBreakpoint],
|
||||
);
|
||||
const scrollContainerStyle = useMemo(
|
||||
(): CSSProperties => ({
|
||||
};
|
||||
}, [isMobileBreakpoint, props.agentId]);
|
||||
const scrollContainerStyle = useMemo((): CSSProperties => {
|
||||
vlog(props.agentId, "memo:scrollContainerStyle rebuilt", { scrollEnabled });
|
||||
return {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
overflowX: "hidden",
|
||||
overflowY: scrollEnabled ? "auto" : "hidden",
|
||||
overscrollBehaviorY: "contain",
|
||||
}),
|
||||
[scrollEnabled],
|
||||
);
|
||||
const virtualRowsContainerStyle = useMemo(
|
||||
(): CSSProperties => ({
|
||||
};
|
||||
}, [scrollEnabled, props.agentId]);
|
||||
const virtualRowsContainerStyle = useMemo((): CSSProperties => {
|
||||
vlog(props.agentId, "memo:virtualRowsContainerStyle rebuilt", { virtualTotalSize });
|
||||
return {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: virtualTotalSize,
|
||||
}),
|
||||
[virtualTotalSize],
|
||||
);
|
||||
};
|
||||
}, [virtualTotalSize, props.agentId]);
|
||||
const renderVirtualRowStyle = useCallback(
|
||||
(start: number): CSSProperties => ({
|
||||
position: "absolute",
|
||||
@@ -685,29 +756,41 @@ function WebStreamViewport(props: StreamRenderInput & { isMobileBreakpoint: bool
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const mountedHistoryRows = useMemo(
|
||||
() =>
|
||||
segments.historyMounted.map((item, index) => (
|
||||
<Fragment key={item.id}>
|
||||
{renderHistoryMountedRow(item, index, segments.historyMounted)}
|
||||
</Fragment>
|
||||
)),
|
||||
[renderHistoryMountedRow, segments.historyMounted],
|
||||
);
|
||||
const liveHeadRows = useMemo(
|
||||
() =>
|
||||
segments.liveHead.map((item, index) => (
|
||||
<Fragment key={item.id}>{renderLiveHeadRow(item, index, segments.liveHead)}</Fragment>
|
||||
)),
|
||||
[renderLiveHeadRow, segments.liveHead],
|
||||
);
|
||||
const liveAuxiliary = useMemo(() => renderLiveAuxiliary(), [renderLiveAuxiliary]);
|
||||
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]);
|
||||
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]);
|
||||
const liveAuxiliary = useMemo(() => {
|
||||
vlog(props.agentId, "memo:liveAuxiliary rebuilt", {});
|
||||
return renderLiveAuxiliary();
|
||||
}, [renderLiveAuxiliary, props.agentId]);
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user