Revert "[eric] canvas: a render ring keeps everything near the camera pre-drawn so pans land on warm pixels (ENG-301)"

This reverts commit c854d353c0.
This commit is contained in:
ciregenz
2026-08-17 12:10:48 -07:00
parent f563068ccf
commit c404ef15f9
2 changed files with 1 additions and 97 deletions
@@ -55,7 +55,6 @@ import { useClaudeTokens, DarkTokensScope } from '@/shared/styles/ThemeContext';
import { GLASS_SURFACE, GLASS_SURFACE_BLUR, GLASS_SURFACE_TEXT } from '@/shared/styles/glassSurface';
import { useDashboardActive } from '@/shared/hooks/useDashboardActive';
import { useOverlayScrollPassthrough } from '../hooks/interaction/useOverlayScrollPassthrough';
import { useRenderRing } from '../hooks/interaction/useRenderRing';
import { useStreamingMessage } from '@/shared/state/streamingSlice';
import { isCanvasInteractionActive, onCanvasInteractionEnd } from '@/shared/canvasInteractionState';
import { setCardSidecar } from '@/shared/state/workflowsSlice';
@@ -724,8 +723,6 @@ const AgentCard: React.FC<Props> = ({
);
// Drafts collapse to the pill like everything else; only a pending approval keeps the full card, since you have to see what you are approving.
const pillMode = !expanded && !hasPending && !tileZone;
// Pre-render a ring of canvas around the camera so a pan lands on already-drawn pills (ENG-301).
const ringNear = useRenderRing(cardX, cardY, cardWidth, cardHeight, getCanvasState, pillMode && !isFullscreen);
// Two-phase expand: mounting a long transcript synchronously inside the expand click blocked its paint for ~630ms (the measured INP worst case), so the click paints the expanded shell first and the chat mounts on the next frame.
// Keep-alive: once mounted, the chat STAYS mounted across collapse (hidden, not unmounted). The transcript windowing bounds its kept DOM to ~a screen of bubbles, and re-expand becomes a display toggle instead of a full subtree rebuild + WS reconnect.
@@ -884,9 +881,7 @@ const AgentCard: React.FC<Props> = ({
// expanded card is the one you are reading. contain-intrinsic-size keeps a skipped card's
// box the size it would have been, so tethers and fit-to-view still measure it correctly.
...(pillMode && !expanded && !isFullscreen && !tiledSize ? {
// Inside the camera ring: force-render so arriving pans hit pre-drawn pixels (ENG-301,
// Eric's render-ahead call); outside it: keep the ENG-261 skip that holds big boards.
contentVisibility: ringNear ? 'visible' : 'auto',
contentVisibility: 'auto',
// `auto` = remember the size this card last really rendered at, falling back to the guess
// only before its first paint. A pill is fit-content/auto sized, so a fixed guess is wrong
// for every card and costs a relayout each time one scrolls in: measured 27.8ms p95 with a
@@ -1,91 +0,0 @@
import { useEffect, useRef, useState } from 'react';
// Chromium only pre-renders content-visibility:auto elements a sliver past the viewport, so a pan
// always lands on never-drawn cards and pays first-raster right under the gesture (ENG-301's
// 100-700ms singles). Eric's call: keep a RING of canvas around the camera always rendered, so by
// the time you arrive it is already drawn. Cards inside the ring force 'visible'; far cards keep
// 'auto' (that skip is what holds 150-card boards at frame budget, ENG-261). Enter at ENTER_VP
// viewports, leave at LEAVE_VP, so boundary jitter never flips a card back and forth.
const ENTER_VP = 1.5;
const LEAVE_VP = 2.25;
// Newly-near cards render at most this many per frame: a fast pan crossing 30 cards must warm them
// over a few frames, not stall the gesture frame it happens in.
const PROMOTIONS_PER_FRAME = 3;
let p_pending: Array<() => void> = [];
let p_drainScheduled = false;
function p_drain(): void {
p_drainScheduled = false;
const batch = p_pending.splice(0, PROMOTIONS_PER_FRAME);
for (const fn of batch) fn();
if (p_pending.length > 0) {
p_drainScheduled = true;
requestAnimationFrame(p_drain);
}
}
function p_enqueuePromotion(fn: () => void): void {
p_pending.push(fn);
if (!p_drainScheduled) {
p_drainScheduled = true;
requestAnimationFrame(p_drain);
}
}
export function useRenderRing(
cardX: number,
cardY: number,
cardWidth: number,
cardHeight: number,
getCanvasState: () => { panX: number; panY: number; zoom: number },
active: boolean,
): boolean {
const [near, setNear] = useState(false);
const nearRef = useRef(false);
const rectRef = useRef({ x: cardX, y: cardY, w: cardWidth, h: cardHeight });
rectRef.current = { x: cardX, y: cardY, w: cardWidth, h: cardHeight };
useEffect(() => {
if (!active) return undefined;
let raf = 0;
const evaluate = (): void => {
raf = 0;
const { panX, panY, zoom } = getCanvasState();
const vw = window.innerWidth;
const vh = window.innerHeight;
// Viewport in canvas coordinates.
const vx = -panX / zoom;
const vy = -panY / zoom;
const vwC = vw / zoom;
const vhC = vh / zoom;
const marginVp = nearRef.current ? LEAVE_VP : ENTER_VP;
const mx = vwC * marginVp;
const my = vhC * marginVp;
const r = rectRef.current;
const isNear =
r.x + r.w > vx - mx && r.x < vx + vwC + mx &&
r.y + r.h > vy - my && r.y < vy + vhC + my;
if (isNear !== nearRef.current) {
nearRef.current = isNear;
// Leaving the ring is free (back to skipped); entering pays a render, so it rides the
// per-frame promotion budget instead of landing all at once mid-gesture.
if (isNear) p_enqueuePromotion(() => setNear(true));
else setNear(false);
}
};
const schedule = (): void => {
if (!raf) raf = requestAnimationFrame(evaluate);
};
schedule();
window.addEventListener('openswarm:canvas-pan-changed', schedule);
window.addEventListener('resize', schedule);
return () => {
window.removeEventListener('openswarm:canvas-pan-changed', schedule);
window.removeEventListener('resize', schedule);
if (raf) cancelAnimationFrame(raf);
};
}, [getCanvasState, active]);
return near;
}