diff --git a/frontend/src/app/pages/Dashboard/cards/BrowserReplayOverlay.tsx b/frontend/src/app/pages/Dashboard/cards/BrowserReplayOverlay.tsx new file mode 100644 index 00000000..be1edc9c --- /dev/null +++ b/frontend/src/app/pages/Dashboard/cards/BrowserReplayOverlay.tsx @@ -0,0 +1,128 @@ +/** + * PROTOTYPE (not wired yet): the "fast data mode" overlay for shadow-API replay. + * + * Shown over a BrowserCard for the brief moment the agent reads bulk data via the + * page's own JSON feed instead of scroll-scraping. It's an HONEST visualization, + * the rows are the real extracted records, revealed at once (one API call returns + * them all), not a fake cursor and not a faked progress counter. + * + * Built deliberately cheap, OpenSwarm is memory/compute-sensitive and the webview + * compositor is fragile: + * - NO backdrop-blur. The overlay is OPAQUE, so it fully covers the + * and the compositor can stop painting it while covered. Blur over a webview + * is paint-heavy and risky here. + * - Animations are opacity/transform ONLY (compositor-only; no layout/paint). + * - Rows reveal via a SINGLE reused @keyframes + per-row animation-delay, pure + * CSS, zero per-row JS, state, timers, or WS messages. + * - No shimmer loops. The only infinite animation is one tiny opacity-pulse dot. + * - Renders at most MAX_VISIBLE rows (the full set goes to the agent, not here). + * - unmountOnExit => literally zero cost when not replaying. + */ +import React from 'react'; +import Box from '@mui/material/Box'; +import Fade from '@mui/material/Fade'; +import { useClaudeTokens } from '@/shared/styles/ThemeContext'; + +export interface ReplayRow { + title: string; + subtitle?: string; +} + +export interface BrowserReplayOverlayProps { + active: boolean; + site: string; // plain name, e.g. "LinkedIn" + rows: ReplayRow[]; // real extracted records (one payload) + total: number; + status: 'reading' | 'done' | 'empty'; + elapsedMs?: number; +} + +const MAX_VISIBLE = 6; // cap the DOM; the count conveys the rest + +function BrowserReplayOverlay({ + active, site, rows, total, status, elapsedMs, +}: BrowserReplayOverlayProps) { + const c = useClaudeTokens(); + const shown = rows.length > MAX_VISIBLE ? rows.slice(0, MAX_VISIBLE) : rows; + + const header = + status === 'done' + ? `✓ ${total} result${total === 1 ? '' : 's'}${elapsedMs != null ? ` · ${(elapsedMs / 1000).toFixed(1)}s` : ''}` + : status === 'empty' + ? `${site}'s feed came back empty, browsing instead` + : `Reading ${site}'s data feed`; + + return ( + + + + {status === 'reading' && ( + + )} + + {header} + + + + {status !== 'empty' && ( + + {shown.map((r, i) => ( + + {'▸'} + {r.title} + {r.subtitle && ( + + {r.subtitle} + + )} + + ))} + + )} + + + via the page's own data feed + + + + ); +} + +// memo: re-render only when the inputs actually change (cheap, but free insurance +// against parent BrowserCard re-renders during an agent run). +export default React.memo(BrowserReplayOverlay);