mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-30 19:59:38 +02:00
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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user