mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] dashboard: minimizing a browser card actually frees its renderer (it used to keep a live one)
This commit is contained in:
@@ -10,6 +10,7 @@ import { getWebview } from '@/shared/browserRegistry';
|
||||
import { getActivity, isAnyBrowserBusy } from '@/shared/browserCommandHandler';
|
||||
import { isKeepAliveBrowser } from '@/shared/browserFocus';
|
||||
import { captureTabCapsule } from '@/shared/browserStateCapsule';
|
||||
import { getMinimizedShot } from '../../desktop/minimizedShots';
|
||||
|
||||
const isElectron = typeof navigator !== 'undefined' && navigator.userAgent.includes('Electron');
|
||||
|
||||
@@ -83,6 +84,12 @@ function cardIsAudible(browserId: string, card: BrowserCardPosition): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parked in the minimize rail: the card renders off-canvas behind a frozen still, so a live renderer
|
||||
// sitting behind it is pure waste (same call DashboardViewCard already makes for app previews).
|
||||
function isMinimized(browserId: string): boolean {
|
||||
return !!store.getState().dashboardLayout.minimizedCards[browserId];
|
||||
}
|
||||
|
||||
// A card we must never snapshot-swap: an agent is driving it, it's in the keep-alive set (recently used), or it's playing audio. Suspending destroys the webContents (sessionStorage, playback), the things we're preserving.
|
||||
function mustStayLive(browserId: string, card: BrowserCardPosition): boolean {
|
||||
// The shield class marks a live card/marquee drag: suspending a browser MID-DRAG unmounts the
|
||||
@@ -107,6 +114,7 @@ export function useWebviewSuspend(
|
||||
) {
|
||||
const dispatch = useAppDispatch();
|
||||
const suspended = useAppSelector((s) => s.dashboardLayout.suspendedBrowserCards);
|
||||
const minimized = useAppSelector((s) => s.dashboardLayout.minimizedCards);
|
||||
const vpRef = useRef<Viewport>({ panX, panY, zoom, vpW: 1200, vpH: 800 });
|
||||
|
||||
// Window resize changes the viewport without touching pan/zoom/cards; tick so the evaluation below reruns, or a shrunken window never suspends anything.
|
||||
@@ -146,7 +154,7 @@ export function useWebviewSuspend(
|
||||
budget--;
|
||||
continue;
|
||||
}
|
||||
if (budget <= 0) continue;
|
||||
if (budget <= 0 || minimized[id]) continue;
|
||||
const bigEnough = card.width * zoom >= RESUME_MIN_CARD_PX;
|
||||
if (bigEnough && cardIntersectsViewport(card, vpRef.current, RESUME_MARGIN_PX)) {
|
||||
dispatch(resumeBrowserCard(id));
|
||||
@@ -156,15 +164,18 @@ export function useWebviewSuspend(
|
||||
|
||||
const timer = setTimeout(async () => {
|
||||
const isSuspended = (id: string) => !!store.getState().dashboardLayout.suspendedBrowserCards[id];
|
||||
// Read live, not off the effect's closure: this re-runs after an await, and the user can restore a card mid-capture.
|
||||
const wantsPark = (id: string, card: BrowserCardPosition): boolean =>
|
||||
isMinimized(id) || !cardIntersectsViewport(card, vpRef.current, SUSPEND_MARGIN_PX);
|
||||
await refreshVisibleFrames(browserCards, isSuspended, vpRef.current);
|
||||
for (const [id, card] of Object.entries(browserCards)) {
|
||||
if (isSuspended(id)) continue;
|
||||
if (cardIntersectsViewport(card, vpRef.current, SUSPEND_MARGIN_PX)) continue;
|
||||
if (!wantsPark(id, card)) continue;
|
||||
if (mustStayLive(id, card)) continue;
|
||||
// An empty dataUrl still suspends (placeholder renders): a card whose capture hangs/fails must not keep its renderer alive forever.
|
||||
const dataUrl = await captureForSuspend(id, card);
|
||||
// The capture await yielded; conditions may have changed under us.
|
||||
if (cardIntersectsViewport(card, vpRef.current, SUSPEND_MARGIN_PX) || mustStayLive(id, card)) continue;
|
||||
if (!wantsPark(id, card) || mustStayLive(id, card)) continue;
|
||||
dispatch(suspendBrowserCard({ browserId: id, dataUrl }));
|
||||
}
|
||||
|
||||
@@ -183,7 +194,7 @@ export function useWebviewSuspend(
|
||||
}, SETTLE_MS);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [browserCards, suspended, panX, panY, zoom, viewportRef, dispatch, resizeTick]);
|
||||
}, [browserCards, suspended, minimized, panX, panY, zoom, viewportRef, dispatch, resizeTick]);
|
||||
}
|
||||
|
||||
function distFromCenter(card: BrowserCardPosition, vp: Viewport): number {
|
||||
@@ -219,6 +230,7 @@ async function refreshVisibleFrames(
|
||||
if (isAnyBrowserBusy()) return;
|
||||
for (const [id, card] of Object.entries(cards)) {
|
||||
if (isSuspended(id)) continue;
|
||||
if (isMinimized(id)) continue;
|
||||
if (!cardIntersectsViewport(card, vp, 0)) continue;
|
||||
const prev = lastFrames.get(id);
|
||||
if (prev && Date.now() - prev.at < FRAME_TTL_MS) continue;
|
||||
@@ -232,12 +244,14 @@ async function captureForSuspend(id: string, card: BrowserCardPosition): Promise
|
||||
for (const tab of card.tabs ?? []) {
|
||||
await captureTabCapsule(getWebview(id, tab.id), tab.id);
|
||||
}
|
||||
const live = await captureCard(id, card);
|
||||
// A minimized card is already parked off-canvas and will never paint again, so asking it for a frame
|
||||
// just burns the capture timeout; the shot frozen on the way in is the only real one it has.
|
||||
const live = isMinimized(id) ? '' : await captureCard(id, card);
|
||||
if (live) {
|
||||
rememberFrame(id, live);
|
||||
return live;
|
||||
}
|
||||
return lastFrames.get(id)?.dataUrl ?? '';
|
||||
return lastFrames.get(id)?.dataUrl ?? getMinimizedShot(id) ?? '';
|
||||
}
|
||||
|
||||
async function captureCard(id: string, card: BrowserCardPosition): Promise<string> {
|
||||
|
||||
Reference in New Issue
Block a user