[aidan] chore/dashboard: drop dead configure/missed-run cards, refetch on reconnect

This commit is contained in:
abccodes
2026-06-23 01:22:36 -07:00
parent 4f1d1b1e34
commit d5dfa4da30
11 changed files with 24 additions and 259 deletions
-1
View File
@@ -58,7 +58,6 @@ class DashboardLayout(BaseModel):
view_cards: dict[str, ViewCardPosition] = Field(default_factory=dict)
browser_cards: dict[str, BrowserCardPosition] = Field(default_factory=dict)
workflow_cards: dict = Field(default_factory=dict)
configure_panels: dict = Field(default_factory=dict)
workflows_hub: Optional[dict] = None
notes: dict[str, NotePosition] = Field(default_factory=dict)
expanded_session_ids: list[str] = Field(default_factory=list)
@@ -14,7 +14,6 @@ import type {
NotePosition,
WorkflowCardPosition,
WorkflowsHubPosition,
ConfigurePanelPosition,
} from '@/shared/state/dashboardLayoutSlice';
import type { Output } from '@/shared/state/outputsSlice';
import type { CardType, useDashboardSelection } from '../hooks/state/useDashboardSelection';
@@ -43,7 +42,6 @@ interface DashboardCanvasProps {
notes: Record<string, NotePosition>;
workflowCards: Record<string, WorkflowCardPosition>;
workflowsHub: WorkflowsHubPosition | null;
configurePanels: Record<string, ConfigurePanelPosition>;
outputs: Record<string, Output>;
glowingAgentCards: Record<string, GlowingAgentCard>;
expandedSessionIds: string[];
@@ -106,7 +104,6 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
notes,
workflowCards,
workflowsHub,
configurePanels,
outputs,
glowingAgentCards,
expandedSessionIds,
@@ -251,7 +248,6 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
notes={notes}
workflowCards={workflowCards}
workflowsHub={workflowsHub}
configurePanels={configurePanels}
outputs={outputs}
glowingAgentCards={glowingAgentCards}
expandedSessionIds={expandedSessionIds}
@@ -16,7 +16,6 @@ import {
type NotePosition,
type WorkflowCardPosition,
type WorkflowsHubPosition,
type ConfigurePanelPosition,
} from '@/shared/state/dashboardLayoutSlice';
import { useAppSelector, useAppDispatch } from '@/shared/hooks';
import { closeWorkflowMonitor } from '@/shared/state/dashboardLayoutSlice';
@@ -36,7 +35,6 @@ interface DashboardCardLayerProps {
notes: Record<string, NotePosition>;
workflowCards: Record<string, WorkflowCardPosition>;
workflowsHub: WorkflowsHubPosition | null;
configurePanels: Record<string, ConfigurePanelPosition>;
outputs: Record<string, Output>;
glowingAgentCards: Record<string, GlowingAgentCard>;
expandedSessionIds: string[];
@@ -73,7 +71,6 @@ const DashboardCardLayer: React.FC<DashboardCardLayerProps> = ({
notes,
workflowCards,
workflowsHub,
configurePanels,
outputs,
glowingAgentCards,
expandedSessionIds,
@@ -101,9 +98,6 @@ const DashboardCardLayer: React.FC<DashboardCardLayerProps> = ({
onBranch,
onMeasuredHeight,
}) => {
// Ephemeral singleton, not part of the saved layout, so read it straight
// from the store rather than threading it through the selector chain.
const missedRunsCard = useAppSelector((s) => s.dashboardLayout.missedRunsCard);
const dispatch = useAppDispatch();
const monitorCard = useAppSelector((s) => s.dashboardLayout.workflowsMonitorCard);
const monitorWorkflowId = useAppSelector((s) => s.dashboardLayout.workflowsMonitorId);
@@ -1,6 +1,5 @@
import React, { type RefObject } from 'react';
import Box from '@mui/material/Box';
import { useAppSelector } from '@/shared/hooks';
import DashboardToolbar from '../DashboardToolbar';
import CanvasControls from '../controls/CanvasControls';
import CardSearchPalette from '../controls/CardSearchPalette';
@@ -82,7 +81,6 @@ const DashboardOverlays: React.FC<DashboardOverlaysProps> = ({
toolbarPrefill,
toolbarPrefillMode,
}) => {
const missedRunsCard = useAppSelector((s) => s.dashboardLayout.missedRunsCard);
return (
<>
{/* Floating bottom toolbar */}
@@ -133,7 +131,6 @@ const DashboardOverlays: React.FC<DashboardOverlaysProps> = ({
browserCards,
workflowCards,
workflowsHub,
missedRunsCard,
}}
onMinimapPan={(px, py) => canvas.actions.setState({ panX: px, panY: py, zoom: canvas.zoom })}
/>
@@ -1,6 +1,6 @@
import React, { useRef, useCallback, useMemo } from 'react';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import type { CardPosition, ViewCardPosition, BrowserCardPosition, WorkflowCardPosition, WorkflowsHubPosition, MissedRunsCardPosition } from '@/shared/state/dashboardLayoutSlice';
import type { CardPosition, ViewCardPosition, BrowserCardPosition, WorkflowCardPosition, WorkflowsHubPosition } from '@/shared/state/dashboardLayoutSlice';
const MINIMAP_W = 200;
const MINIMAP_H = 140;
@@ -16,7 +16,6 @@ export interface MinimapProps {
browserCards: Record<string, BrowserCardPosition>;
workflowCards: Record<string, WorkflowCardPosition>;
workflowsHub: WorkflowsHubPosition | null;
missedRunsCard: MissedRunsCardPosition | null;
onPan: (panX: number, panY: number) => void;
}
@@ -25,12 +24,12 @@ interface CardRect {
y: number;
width: number;
height: number;
type: 'agent' | 'view' | 'browser' | 'workflow' | 'workflows-hub' | 'missed-runs';
type: 'agent' | 'view' | 'browser' | 'workflow' | 'workflows-hub';
}
const Minimap: React.FC<MinimapProps> = ({
panX, panY, zoom, viewportRef,
cards, viewCards, browserCards, workflowCards, workflowsHub, missedRunsCard,
cards, viewCards, browserCards, workflowCards, workflowsHub,
onPan,
}) => {
const c = useClaudeTokens();
@@ -60,17 +59,8 @@ const Minimap: React.FC<MinimapProps> = ({
type: 'workflows-hub',
});
}
if (missedRunsCard) {
result.push({
x: missedRunsCard.x,
y: missedRunsCard.y,
width: missedRunsCard.width,
height: missedRunsCard.height,
type: 'missed-runs',
});
}
return result;
}, [cards, viewCards, browserCards, workflowCards, workflowsHub, missedRunsCard]);
}, [cards, viewCards, browserCards, workflowCards, workflowsHub]);
const layout = useMemo(() => {
const vp = viewportRef.current;
@@ -159,7 +149,6 @@ const Minimap: React.FC<MinimapProps> = ({
case 'browser': return c.status.success;
case 'workflow': return c.status.warning;
case 'workflows-hub': return c.status.warning;
case 'missed-runs': return c.status.warning;
}
};
@@ -1,5 +1,5 @@
import { useMemo, type RefObject } from 'react';
import type { CardPosition, BrowserCardPosition, WorkflowCardPosition, ConfigurePanelPosition, WorkflowsHubPosition } from '@/shared/state/dashboardLayoutSlice';
import type { CardPosition, BrowserCardPosition, WorkflowCardPosition, WorkflowsHubPosition } from '@/shared/state/dashboardLayoutSlice';
import type { Workflow, OpenCard } from '@/shared/state/workflowsSlice';
import { EXPANDED_CARD_MIN_H, GRID_GAP } from '@/shared/state/dashboardLayoutSlice';
import type { AgentSession } from '@/shared/state/agentsSlice';
@@ -82,7 +82,6 @@ interface UseTethersArgs {
workflowCards: Record<string, WorkflowCardPosition>;
workflowItems: Record<string, Workflow>;
workflowOpenCards: Record<string, OpenCard>;
configurePanels: Record<string, ConfigurePanelPosition>;
expandedSessionIds: string[];
liveDragInfo: LiveDragInfo | null;
measuredHeightsRef: RefObject<Record<string, number>>;
@@ -101,7 +100,6 @@ export function useTethers({
workflowCards,
workflowItems,
workflowOpenCards,
configurePanels,
expandedSessionIds,
liveDragInfo,
measuredHeightsRef,
@@ -390,54 +388,6 @@ export function useTethers({
});
}
// Configure-panel tethers: anchor each open configure panel to its workflow card.
const configureTethers: Tether[] = [];
for (const p of Object.values(configurePanels)) {
const wc = workflowCards[p.workflow_id];
if (!wc) continue;
let srcX = wc.x, srcY = wc.y;
let dstX = p.x, dstY = p.y;
if (liveDragInfo) {
if (liveDragInfo.cardId === p.workflow_id) { srcX += liveDragInfo.dx; srcY += liveDragInfo.dy; }
}
const wcH = wfHeight(wc);
const srcCx = srcX + wc.width / 2;
const dstCx = dstX + p.width / 2;
const srcAnchors: Anchor[] = [
{ x: srcX + wc.width, y: srcY + wcH * 0.5, side: 'right' },
{ x: srcX, y: srcY + wcH * 0.5, side: 'left' },
{ x: srcCx, y: srcY, side: 'top' },
{ x: srcCx, y: srcY + wcH, side: 'bottom' },
];
const dstAnchors: Anchor[] = [
{ x: dstX, y: dstY + p.height * 0.5, side: 'left' },
{ x: dstX + p.width, y: dstY + p.height * 0.5, side: 'right' },
{ x: dstCx, y: dstY, side: 'top' },
{ x: dstCx, y: dstY + p.height, side: 'bottom' },
];
let bestSrc = srcAnchors[0], bestDst = dstAnchors[0];
let bestDist = Infinity;
for (const sa of srcAnchors) {
for (const da of dstAnchors) {
const d = Math.hypot(sa.x - da.x, sa.y - da.y);
if (d < bestDist) { bestDist = d; bestSrc = sa; bestDst = da; }
}
}
const x1 = bestSrc.x, y1 = bestSrc.y;
const x2 = bestDst.x, y2 = bestDst.y;
const pathD = elbowPath(x1, y1, x2, y2);
const midX = x1 + (x2 - x1) / 2;
const midY = y1 + (y2 - y1) / 2;
configureTethers.push({
key: `configure-${p.workflow_id}`,
path: pathD,
labelX: midX,
labelY: midY,
label: 'Configure',
fading: false,
});
}
// Run Monitor tether: the Workflows window to its spawned live-run card.
// Same border-anchor + elbow math as the sidecar "Watching" arrow.
const monitorTethers: Tether[] = [];
@@ -470,9 +420,9 @@ export function useTethers({
});
}
return [...agentTethers, ...browserTethers, ...workflowTethers, ...configureTethers, ...monitorTethers];
return [...agentTethers, ...browserTethers, ...workflowTethers, ...monitorTethers];
// measuredHeightsTick re-runs the memo once ResizeObserver reports a new
// height after a collapse (the ref read is invisible to the dep checker).
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, workflowCards, workflowItems, workflowOpenCards, configurePanels, expandedSessionIds, liveDragInfo, measuredHeightsTick, sessionList, workflowsHub, workflowsMonitorCard, workflowsMonitorLabel]);
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, workflowCards, workflowItems, workflowOpenCards, expandedSessionIds, liveDragInfo, measuredHeightsTick, sessionList, workflowsHub, workflowsMonitorCard, workflowsMonitorLabel]);
}
@@ -17,12 +17,11 @@ import {
clearPendingFocusBrowserId,
clearPendingFocusWorkflowId,
clearPendingFocusWorkflowsHub,
clearPendingFocusMissedRuns,
type ViewCardPosition,
} from '@/shared/state/dashboardLayoutSlice';
import { fetchOutputs, type Output } from '@/shared/state/outputsSlice';
import { generateDashboardName } from '@/shared/state/dashboardsSlice';
import { fetchWorkflows } from '@/shared/state/workflowsSlice';
import { fetchWorkflows, fetchAllRuns, fetchActiveRuns } from '@/shared/state/workflowsSlice';
import { fetchMissedRuns } from '@/shared/state/missedRunsSlice';
import { dashboardWs } from '@/shared/ws/WebSocketManager';
import { initBrowserCommandHandler } from '@/shared/browserCommandHandler';
@@ -70,7 +69,6 @@ export function useDashboardLifecycle({
const pendingFocusAgentId = useAppSelector((state) => state.tempState.pendingFocusAgentId);
const pendingFocusBrowserId = useAppSelector((state) => state.dashboardLayout.pendingFocusBrowserId);
const pendingFocusWorkflowId = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowId);
const pendingFocusMissedRuns = useAppSelector((state) => state.dashboardLayout.pendingFocusMissedRuns);
const pendingFocusWorkflowsHub = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowsHub);
// Once per app launch: if scheduled fires elapsed while we were closed, fetch
@@ -110,6 +108,12 @@ export function useDashboardLifecycle({
const unsubReconnect = dashboardWs.on('dashboard:reconnected', () => {
dispatch(fetchSessions({ dashboardId }));
dispatch(fetchLayout({ dashboardId, isReconnect: true }));
// workflow:run/updated/deleted are global broadcasts that skip the replay
// log, so a socket gap drops them: refetch to heal stale "running" cards,
// ghost workflows, and missed run history on reconnect.
dispatch(fetchWorkflows(dashboardId));
dispatch(fetchAllRuns(200));
dispatch(fetchActiveRuns());
});
// DEFERRABLE: history list (for the search palette) and outputs
// (for the apps panel) aren't on the first-paint path. Same for the
@@ -264,24 +268,6 @@ export function useDashboardLifecycle({
}, 200);
}, [isActive, pendingFocusWorkflowId, layoutInitialized, dispatch, canvasActions, handleHighlightCard]);
// Same pan/highlight choreography when the missed-runs card opens from its toast.
useEffect(() => {
if (!isActive) return;
if (!pendingFocusMissedRuns || !layoutInitialized) return;
dispatch(clearPendingFocusMissedRuns());
setTimeout(() => {
const card = store.getState().dashboardLayout.missedRunsCard;
if (card) {
canvasActions.fitToCards(
[{ x: card.x, y: card.y, width: card.width, height: card.height }],
1.15,
true,
);
handleHighlightCard('missed-runs');
}
}, 200);
}, [isActive, pendingFocusMissedRuns, layoutInitialized, dispatch, canvasActions, handleHighlightCard]);
// Pan/zoom to Workflows Hub on Expand; chained rAFs ensure fit runs after the hub div lands at its new coords.
useEffect(() => {
if (!isActive) return;
@@ -32,7 +32,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
const isElementSelectMode = elementSelectionCtx?.selectMode ?? false;
const {
dashboardName, sessions, expandedSessionIds, cards, viewCards, browserCards,
workflowCards, workflowItems, workflowOpenCards, configurePanels, workflowsHub,
workflowCards, workflowItems, workflowOpenCards, workflowsHub,
pendingFocusWorkflowId, pendingFocusWorkflowsHub,
notes, pendingFocusNoteId, layoutInitialized, persistedExpandedSessionIds,
zoomSensitivity, newAgentShortcut, browserHomepage, expandNewChats,
@@ -177,7 +177,6 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
viewCards,
browserCards,
workflowCards,
configurePanels,
workflowsHub,
notes,
expandedSessionIds,
@@ -296,7 +295,6 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
workflowCards,
workflowItems,
workflowOpenCards,
configurePanels,
expandedSessionIds,
liveDragInfo,
measuredHeightsRef,
@@ -310,7 +308,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
return {
c, dashboardId, dashboardName, canvas, selection, sessions, sessionList,
cards, viewCards, browserCards, notes, outputs, glowingAgentCards,
workflowCards, workflowsHub, configurePanels,
workflowCards, workflowsHub,
expandedSessionIds, tethers, highlightedCardId, autoFocusSessionId,
focusedCardId, pendingFocusNoteId, multiDragDelta, shakeDirection,
neighborDirections, toolbarOpen, searchPaletteOpen, newAgentBounce,
@@ -25,7 +25,6 @@ export function useDashboardSelectors(dashboardId: string) {
return out;
}, [allBrowserCards, dashboardId]);
const workflowCards = useAppSelector((state) => state.dashboardLayout.workflowCards);
const configurePanels = useAppSelector((state) => state.dashboardLayout.configurePanels);
const workflowsHub = useAppSelector((state) => state.dashboardLayout.workflowsHub);
const pendingFocusWorkflowId = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowId);
const pendingFocusWorkflowsHub = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowsHub);
@@ -55,7 +54,6 @@ export function useDashboardSelectors(dashboardId: string) {
workflowCards,
workflowItems,
workflowOpenCards,
configurePanels,
workflowsHub,
pendingFocusWorkflowId,
pendingFocusWorkflowsHub,
@@ -7,7 +7,6 @@ import {
type BrowserCardPosition,
type NotePosition,
type WorkflowCardPosition,
type ConfigurePanelPosition,
type WorkflowsHubPosition,
} from '@/shared/state/dashboardLayoutSlice';
@@ -19,7 +18,6 @@ interface UseLayoutSaveArgs {
viewCards: Record<string, ViewCardPosition>;
browserCards: Record<string, BrowserCardPosition>;
workflowCards: Record<string, WorkflowCardPosition>;
configurePanels: Record<string, ConfigurePanelPosition>;
workflowsHub: WorkflowsHubPosition | null;
notes: Record<string, NotePosition>;
expandedSessionIds: string[];
@@ -38,7 +36,6 @@ export function useLayoutSave({
viewCards,
browserCards,
workflowCards,
configurePanels,
workflowsHub,
notes,
expandedSessionIds,
@@ -56,7 +53,7 @@ export function useLayoutSave({
skipInitialSave.current = false;
return;
}
const payload = { dashboardId, cards, viewCards, browserCards, workflowCards, configurePanels, workflowsHub, notes, expandedSessionIds };
const payload = { dashboardId, cards, viewCards, browserCards, workflowCards, workflowsHub, notes, expandedSessionIds };
pendingSaveRef.current = payload;
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
saveTimerRef.current = setTimeout(() => {
@@ -65,7 +62,7 @@ export function useLayoutSave({
saveTimerRef.current = null;
captureNow();
}, 500);
}, [isActive, cards, viewCards, browserCards, workflowCards, configurePanels, workflowsHub, notes, expandedSessionIds, layoutInitialized, dashboardId, dispatch, captureNow]);
}, [isActive, cards, viewCards, browserCards, workflowCards, workflowsHub, notes, expandedSessionIds, layoutInitialized, dashboardId, dispatch, captureNow]);
useEffect(() => {
return () => {
@@ -24,14 +24,12 @@ export const DEFAULT_WORKFLOW_CARD_H = 520;
// comfortable size automatically.
export const DEFAULT_WORKFLOWS_HUB_W = DEFAULT_BROWSER_CARD_W;
export const DEFAULT_WORKFLOWS_HUB_H = DEFAULT_BROWSER_CARD_H;
export const DEFAULT_MISSED_RUNS_W = 460;
export const DEFAULT_MISSED_RUNS_H = 420;
export const EXPANDED_CARD_MIN_H = 620;
export const GRID_GAP = 24;
const GRID_ORIGIN = { x: 40, y: 100 };
const GRID_COLS_FALLBACK = 4;
export type CardType = 'agent' | 'view' | 'browser' | 'note' | 'workflow' | 'workflows-hub' | 'workflows-monitor' | 'missed_runs';
export type CardType = 'agent' | 'view' | 'browser' | 'note' | 'workflow' | 'workflows-hub' | 'workflows-monitor';
export interface CardPosition {
session_id: string;
@@ -93,16 +91,6 @@ export interface WorkflowsHubPosition {
zOrder: number;
}
// Ephemeral launch-time card listing scheduled fires missed while the app was
// closed. Singleton like workflowsHub, but deliberately NOT persisted to the
// saved layout: the launch hook decides each session whether to show it.
export interface MissedRunsCardPosition {
x: number;
y: number;
width: number;
height: number;
zOrder: number;
}
export type NoteColor = 'yellow' | 'pink' | 'blue' | 'green' | 'purple' | 'gray';
@@ -120,22 +108,12 @@ export interface NotePosition {
export const DEFAULT_NOTE_W = 240;
export const DEFAULT_NOTE_H = 200;
export interface ConfigurePanelPosition {
workflow_id: string;
x: number;
y: number;
width: number;
height: number;
}
export interface DashboardLayoutState {
cards: Record<string, CardPosition>;
viewCards: Record<string, ViewCardPosition>;
browserCards: Record<string, BrowserCardPosition>;
workflowCards: Record<string, WorkflowCardPosition>;
configurePanels: Record<string, ConfigurePanelPosition>;
workflowsHub: WorkflowsHubPosition | null;
missedRunsCard: MissedRunsCardPosition | null;
notes: Record<string, NotePosition>;
closedCardPositions: Record<string, CardPosition>;
glowingBrowserCards: Record<string, { sourceId: string; fading: boolean; label?: string }>;
@@ -154,8 +132,6 @@ export interface DashboardLayoutState {
/** Transient: id of the view card the user has clicked into; preload stops forwarding canvas gestures while set. */
activeViewCardId: string | null;
pendingFocusWorkflowId: string | null;
/** Transient: signals Dashboard to pan/zoom to the missed-runs card on open. */
pendingFocusMissedRuns: boolean;
/** Transient: signals Dashboard to pan/zoom to the singleton Workflows Hub on open. */
pendingFocusWorkflowsHub: boolean;
/** Transient deep-link target: the Workflows card jumps to this workflow's detail on open, then clears it. */
@@ -183,9 +159,7 @@ const initialState: DashboardLayoutState = {
viewCards: {},
browserCards: {},
workflowCards: {},
configurePanels: {},
workflowsHub: null,
missedRunsCard: null,
notes: {},
closedCardPositions: {},
glowingBrowserCards: {},
@@ -200,7 +174,6 @@ const initialState: DashboardLayoutState = {
endingBrowserCards: {},
activeViewCardId: null,
pendingFocusWorkflowId: null,
pendingFocusMissedRuns: false,
pendingFocusWorkflowsHub: false,
workflowsAppTarget: null,
workflowsMonitorId: null,
@@ -214,7 +187,6 @@ interface LayoutPayload {
viewCards: Record<string, ViewCardPosition>;
browserCards: Record<string, BrowserCardPosition>;
workflowCards: Record<string, WorkflowCardPosition>;
configurePanels: Record<string, ConfigurePanelPosition>;
workflowsHub: WorkflowsHubPosition | null;
notes: Record<string, NotePosition>;
expandedSessionIds: string[];
@@ -253,7 +225,6 @@ export const fetchLayout = createAsyncThunk(
viewCards: (layout.view_cards ?? {}) as Record<string, ViewCardPosition>,
browserCards: browserCards as Record<string, BrowserCardPosition>,
workflowCards: (layout.workflow_cards ?? {}) as Record<string, WorkflowCardPosition>,
configurePanels: (layout.configure_panels ?? {}) as Record<string, ConfigurePanelPosition>,
workflowsHub: (layout.workflows_hub ?? null) as WorkflowsHubPosition | null,
notes: (layout.notes ?? {}) as Record<string, NotePosition>,
expandedSessionIds: (layout.expanded_session_ids ?? []) as string[],
@@ -277,7 +248,6 @@ export const saveLayout = createAsyncThunk(
view_cards: payload.viewCards,
browser_cards: payload.browserCards,
workflow_cards: payload.workflowCards,
configure_panels: payload.configurePanels,
workflows_hub: payload.workflowsHub,
notes: payload.notes,
expanded_session_ids: payload.expandedSessionIds,
@@ -321,9 +291,6 @@ function collectOccupiedRects(
if (state.workflowsHub) {
rects.push({ x: state.workflowsHub.x, y: state.workflowsHub.y, w: state.workflowsHub.width, h: state.workflowsHub.height });
}
if (state.missedRunsCard) {
rects.push({ x: state.missedRunsCard.x, y: state.missedRunsCard.y, w: state.missedRunsCard.width, h: state.missedRunsCard.height });
}
for (const n of Object.values(state.notes)) {
rects.push({ x: n.x, y: n.y, w: n.width, h: n.height });
}
@@ -520,14 +487,12 @@ const dashboardLayoutSlice = createSlice({
for (const n of Object.values(state.notes)) tally(n.zOrder);
if (state.workflowsHub) tally(state.workflowsHub.zOrder);
if (state.workflowsMonitorCard) tally(state.workflowsMonitorCard.zOrder);
if (state.missedRunsCard) tally(state.missedRunsCard.zOrder);
if (type === 'agent') currentZ = state.cards[id]?.zOrder ?? 0;
else if (type === 'view') currentZ = state.viewCards[id]?.zOrder ?? 0;
else if (type === 'note') currentZ = state.notes[id]?.zOrder ?? 0;
else if (type === 'workflow') currentZ = state.workflowCards[id]?.zOrder ?? 0;
else if (type === 'workflows-hub') currentZ = state.workflowsHub?.zOrder ?? 0;
else if (type === 'workflows-monitor') currentZ = state.workflowsMonitorCard?.zOrder ?? 0;
else if (type === 'missed_runs') currentZ = state.missedRunsCard?.zOrder ?? 0;
else currentZ = state.browserCards[id]?.zOrder ?? 0;
if (currentZ >= maxZ) return; // Already on top: no-op.
@@ -548,8 +513,6 @@ const dashboardLayoutSlice = createSlice({
if (state.workflowsHub) state.workflowsHub.zOrder = z;
} else if (type === 'workflows-monitor') {
if (state.workflowsMonitorCard) state.workflowsMonitorCard.zOrder = z;
} else if (type === 'missed_runs') {
if (state.missedRunsCard) state.missedRunsCard.zOrder = z;
} else {
const card = state.browserCards[id];
if (card) card.zOrder = z;
@@ -607,7 +570,8 @@ const dashboardLayoutSlice = createSlice({
const bCards = Object.values(state.browserCards);
const wCards = Object.values(state.workflowCards);
const hub = state.workflowsHub;
const total = agentCards.length + viewCards.length + bCards.length + wCards.length + (hub ? 1 : 0);
const mon = state.workflowsMonitorCard;
const total = agentCards.length + viewCards.length + bCards.length + wCards.length + (hub ? 1 : 0) + (mon ? 1 : 0);
if (total === 0) return;
const allItems = [
@@ -616,6 +580,7 @@ const dashboardLayoutSlice = createSlice({
...bCards.map((c) => ({ kind: 'browser' as const, id: c.browser_id, x: c.x, y: c.y, storedW: c.width, storedH: c.height })),
...wCards.map((c) => ({ kind: 'workflow' as const, id: c.workflow_id, x: c.x, y: c.y, storedW: c.width, storedH: c.height })),
...(hub ? [{ kind: 'workflows-hub' as const, id: 'workflows-hub', x: hub.x, y: hub.y, storedW: hub.width, storedH: hub.height }] : []),
...(mon ? [{ kind: 'workflows-monitor' as const, id: 'workflows-monitor', x: mon.x, y: mon.y, storedW: mon.width, storedH: mon.height }] : []),
];
allItems.sort((a, b) => a.y - b.y || a.x - b.x);
@@ -645,6 +610,8 @@ const dashboardLayoutSlice = createSlice({
if (card) { card.x = pos.x; card.y = pos.y; }
} else if (item.kind === 'workflows-hub') {
if (state.workflowsHub) { state.workflowsHub.x = pos.x; state.workflowsHub.y = pos.y; }
} else if (item.kind === 'workflows-monitor') {
if (state.workflowsMonitorCard) { state.workflowsMonitorCard.x = pos.x; state.workflowsMonitorCard.y = pos.y; }
} else {
const card = state.browserCards[item.id];
if (card) { card.x = pos.x; card.y = pos.y; }
@@ -888,65 +855,9 @@ const dashboardLayoutSlice = createSlice({
if (!card) return;
delete state.workflowCards[oldId];
state.workflowCards[newId] = { ...card, workflow_id: newId };
// Carry any open Action-Library panel along with the rekey so the
// popout doesn't disappear when a draft is saved.
const panel = state.configurePanels[oldId];
if (panel) {
delete state.configurePanels[oldId];
state.configurePanels[newId] = { ...panel, workflow_id: newId };
}
if (state.pendingFocusWorkflowId === oldId) state.pendingFocusWorkflowId = newId;
},
openConfigurePanel(
state,
action: PayloadAction<{ workflowId: string }>,
) {
const { workflowId } = action.payload;
// Anchor the panel just to the right of the workflow card.
const wfCard = state.workflowCards[workflowId];
const baseX = wfCard ? wfCard.x + wfCard.width + GRID_GAP * 6 : 600;
const baseY = wfCard ? wfCard.y : 200;
const existing = state.configurePanels[workflowId];
if (existing) {
existing.x = baseX;
existing.y = baseY;
return;
}
state.configurePanels[workflowId] = {
workflow_id: workflowId,
x: baseX,
y: baseY,
width: 580,
height: 600,
};
},
setConfigurePanelPosition(
state,
action: PayloadAction<{ workflowId: string; x: number; y: number }>,
) {
const { workflowId, x, y } = action.payload;
const p = state.configurePanels[workflowId];
if (p) { p.x = x; p.y = y; }
},
setConfigurePanelSize(
state,
action: PayloadAction<{ workflowId: string; width: number; height: number }>,
) {
const { workflowId, width, height } = action.payload;
const p = state.configurePanels[workflowId];
if (p) {
p.width = Math.max(360, width);
p.height = Math.max(280, height);
}
},
closeConfigurePanel(state, action: PayloadAction<string>) {
delete state.configurePanels[action.payload];
},
clearPendingFocusWorkflowId(state) {
state.pendingFocusWorkflowId = null;
},
@@ -973,37 +884,6 @@ const dashboardLayoutSlice = createSlice({
state.pendingFocusWorkflowsHub = false;
},
openMissedRunsCard(state, action: PayloadAction<{ expandedSessionIds?: string[] } | undefined>) {
state.pendingFocusMissedRuns = true;
if (state.missedRunsCard) {
state.missedRunsCard.zOrder = state.nextZOrder++;
return;
}
const rects = collectOccupiedRects(state, action.payload?.expandedSessionIds);
const pos = findOpenGridCell(rects, DEFAULT_MISSED_RUNS_W, DEFAULT_MISSED_RUNS_H);
state.missedRunsCard = {
x: pos.x,
y: pos.y,
width: DEFAULT_MISSED_RUNS_W,
height: DEFAULT_MISSED_RUNS_H,
zOrder: state.nextZOrder++,
};
},
clearPendingFocusMissedRuns(state) {
state.pendingFocusMissedRuns = false;
},
closeMissedRunsCard(state) {
state.missedRunsCard = null;
},
setMissedRunsCardPosition(state, action: PayloadAction<{ x: number; y: number }>) {
if (!state.missedRunsCard) return;
state.missedRunsCard.x = action.payload.x;
state.missedRunsCard.y = action.payload.y;
},
closeWorkflowsHub(state) {
state.workflowsHub = null;
},
@@ -1281,11 +1161,6 @@ const dashboardLayoutSlice = createSlice({
state.workflowsHub.x += dx;
state.workflowsHub.y += dy;
}
} else if (item.type === 'missed_runs') {
if (state.missedRunsCard) {
state.missedRunsCard.x += dx;
state.missedRunsCard.y += dy;
}
} else {
const card = state.browserCards[item.id];
if (card) {
@@ -1414,9 +1289,7 @@ const dashboardLayoutSlice = createSlice({
state.viewCards = {};
state.browserCards = {};
state.workflowCards = {};
state.configurePanels = {};
state.workflowsHub = null;
state.missedRunsCard = null;
state.notes = {};
state.closedCardPositions = {};
state.glowingBrowserCards = {};
@@ -1428,7 +1301,6 @@ const dashboardLayoutSlice = createSlice({
state.suspendedBrowserCards = {};
state.endingBrowserCards = {};
state.pendingFocusWorkflowId = null;
state.pendingFocusMissedRuns = false;
},
},
@@ -1456,7 +1328,6 @@ const dashboardLayoutSlice = createSlice({
card.dashboard_id = ownerDashboardId;
}
state.workflowCards = action.payload.workflowCards || {};
state.configurePanels = action.payload.configurePanels || {};
state.workflowsHub = action.payload.workflowsHub || null;
state.notes = action.payload.notes || {};
// Cards boot parked (no guest process, title placeholder); the suspend
@@ -1475,7 +1346,6 @@ const dashboardLayoutSlice = createSlice({
if (!card.dashboard_id) card.dashboard_id = ownerDashboardId;
}
addMissingCards(state.workflowCards, action.payload.workflowCards || {}, occupied);
addMissingCards(state.configurePanels, action.payload.configurePanels || {}, occupied);
if (!state.workflowsHub && action.payload.workflowsHub) state.workflowsHub = action.payload.workflowsHub;
addMissingCards(state.notes, action.payload.notes || {}, occupied);
}
@@ -1520,7 +1390,6 @@ const dashboardLayoutSlice = createSlice({
.addCase(deleteWorkflowFulfilledAction, (state, action) => {
const id = action.payload;
if (id && state.workflowCards[id]) delete state.workflowCards[id];
if (id && state.configurePanels[id]) delete state.configurePanels[id];
})
.addCase(launchAndSendFirstMessage.fulfilled, (state, action) => {
const { draftId, session } = action.payload;
@@ -1579,10 +1448,6 @@ export const {
setWorkflowCardSize,
removeWorkflowCard,
rekeyWorkflowCard,
openConfigurePanel,
closeConfigurePanel,
setConfigurePanelPosition,
setConfigurePanelSize,
clearPendingFocusWorkflowId,
openWorkflowsHub,
closeWorkflowsHub,
@@ -1597,10 +1462,6 @@ export const {
setWorkflowsHubPosition,
setWorkflowsHubSize,
clearPendingFocusWorkflowsHub,
openMissedRunsCard,
clearPendingFocusMissedRuns,
closeMissedRunsCard,
setMissedRunsCardPosition,
addNote,
setNotePosition,
setNoteSize,