mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] browser: fix the REAL bleed (fit-to-view chased the off-screen keep-alive card); restore full keep-alive so Discord stays logged in across dashboard switches
This commit is contained in:
@@ -729,6 +729,8 @@ const BrowserCard: React.FC<Props> = ({
|
||||
data-select-type="browser-card"
|
||||
data-select-id={browserId}
|
||||
data-select-meta={JSON.stringify({ name: activeTitle || 'Browser', url: activeUrl })}
|
||||
// Marks a kept-alive card parked off-screen (it belongs to another dashboard); fit-to-view must skip it or it pans the canvas to chase it and the card bleeds onto the dashboard you're viewing.
|
||||
data-keepalive-hidden={keepAliveHidden ? '1' : undefined}
|
||||
onPointerDownCapture={() => onBringToFront?.(browserId, 'browser')}
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
if (justDraggedRef.current) return;
|
||||
|
||||
@@ -543,6 +543,9 @@ export function useCanvasControls(zoomSensitivity: number = 50, contentBounds?:
|
||||
const prev = stateRef.current;
|
||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i] as HTMLElement;
|
||||
// Skip a kept-alive browser card from another dashboard (parked off-screen): fitting to it pans the canvas right onto it, which is the cross-dashboard bleed. On an empty dashboard this leaves nothing to fit, so the !isFinite reset below restores an identity transform and the off-screen card stays off-screen.
|
||||
if (child.getAttribute?.('data-keepalive-hidden') === '1' || child.querySelector?.('[data-keepalive-hidden="1"]')) continue;
|
||||
const r = children[i].getBoundingClientRect();
|
||||
if (r.width === 0 && r.height === 0) continue;
|
||||
const sx = (r.left - vRect.left - prev.panX) / prev.zoom;
|
||||
|
||||
@@ -26,7 +26,7 @@ import { fetchWorkflows, fetchAllRuns, fetchActiveRuns } from '@/shared/state/wo
|
||||
import { fetchMissedRuns } from '@/shared/state/missedRunsSlice';
|
||||
import { dashboardWs } from '@/shared/ws/WebSocketManager';
|
||||
import { initBrowserCommandHandler } from '@/shared/browserCommandHandler';
|
||||
import { isAgentDrivingBrowser } from '@/shared/isAgentDrivingBrowser';
|
||||
import { getKeepAliveBrowserIds } from '@/shared/browserFocus';
|
||||
import { clearPendingBrowserUrl, clearPendingFocusAgentId } from '@/shared/state/tempStateSlice';
|
||||
import { API_BASE } from '@/shared/config';
|
||||
import type { CanvasActions } from '../interaction/useCanvasControls';
|
||||
@@ -99,12 +99,7 @@ export function useDashboardLifecycle({
|
||||
hasFittedRef.current = false;
|
||||
restoredExpandedRef.current = false;
|
||||
setOutputsRefetched(false);
|
||||
// Only keep AGENT-driven browsers alive across the switch; a manual browser is dropped here (reloads when you return) so its kept-alive surface can't bleed onto the dashboard you land on.
|
||||
const st = store.getState();
|
||||
const agentLiveIds = Object.keys(st.dashboardLayout.browserCards).filter(
|
||||
(id) => isAgentDrivingBrowser(st.agents.sessions, id, st.dashboardLayout.browserCards[id]?.spawned_by),
|
||||
);
|
||||
dispatch(resetLayout({ keepBrowserIds: agentLiveIds }));
|
||||
dispatch(resetLayout({ keepBrowserIds: getKeepAliveBrowserIds() }));
|
||||
// CRITICAL path: these populate the cards the user expects to see on first paint. Don't defer.
|
||||
dispatch(fetchSessions({ dashboardId }));
|
||||
dispatch(fetchLayout({ dashboardId }));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useAppSelector } from '@/shared/hooks';
|
||||
import { isAgentDrivingBrowser } from '@/shared/isAgentDrivingBrowser';
|
||||
|
||||
// All of the dashboard's Redux reads in one place. Keeps Dashboard.tsx a thin composition layer instead of a 25-line selector wall.
|
||||
export function useDashboardSelectors(dashboardId: string) {
|
||||
@@ -20,14 +19,14 @@ export function useDashboardSelectors(dashboardId: string) {
|
||||
}
|
||||
return out;
|
||||
}, [allBrowserCards, dashboardId]);
|
||||
// Only an AGENT-driven browser from another dashboard stays mounted-but-hidden here so its run keeps going in the background; a MANUAL browser is deliberately NOT rendered off its own dashboard (it reloads on return) because a kept-alive heavy page bleeds its webview surface onto whatever dashboard you're viewing. Kept OUT of `browserCards` so save/bounds/keyboard-nav only ever see THIS dashboard's cards.
|
||||
// Browser cards from OTHER dashboards stay mounted (so their webContents + session survive a switch, no Discord logout) but get rendered parked far off-screen by the card layer; that off-screen park reliably hides even a heavy live page (Discord), CDP-verified. Kept OUT of `browserCards` so save/bounds/keyboard-nav only ever see THIS dashboard's cards (no cross-dashboard leak), and tagging every card's home dashboard is what stops the real bleed (an untagged card renders as home everywhere).
|
||||
const keepAliveBrowserCards = useMemo(() => {
|
||||
const out: typeof allBrowserCards = {};
|
||||
for (const [id, bc] of Object.entries(allBrowserCards)) {
|
||||
if (bc.dashboard_id && bc.dashboard_id !== dashboardId && isAgentDrivingBrowser(sessions, id, bc.spawned_by)) out[id] = bc;
|
||||
if (bc.dashboard_id && bc.dashboard_id !== dashboardId) out[id] = bc;
|
||||
}
|
||||
return out;
|
||||
}, [allBrowserCards, dashboardId, sessions]);
|
||||
}, [allBrowserCards, dashboardId]);
|
||||
const workflowCards = useAppSelector((state) => state.dashboardLayout.workflowCards);
|
||||
const workflowsHub = useAppSelector((state) => state.dashboardLayout.workflowsHub);
|
||||
const pendingFocusWorkflowId = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowId);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { AgentSession } from '@/shared/state/agentsSlice';
|
||||
|
||||
const ACTIVE_STATUSES = new Set<AgentSession['status']>(['running', 'waiting_approval']);
|
||||
|
||||
// True while an agent is actively running against this browser, so its webContents must survive a dashboard switch (the run keeps going in the background and the agent reaches it over CDP). A MANUAL browser is false: it stops rendering the moment you leave its dashboard, so it can't bleed onto another, and it reloads when you come back.
|
||||
export function isAgentDrivingBrowser(
|
||||
sessions: Record<string, AgentSession>,
|
||||
browserId: string,
|
||||
spawnedBy?: string | null,
|
||||
): boolean {
|
||||
for (const s of Object.values(sessions)) {
|
||||
if (s.browser_id === browserId && ACTIVE_STATUSES.has(s.status)) return true;
|
||||
}
|
||||
if (spawnedBy) {
|
||||
const parent = sessions[spawnedBy];
|
||||
if (parent && ACTIVE_STATUSES.has(parent.status)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user