diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx index b5303ee3..228d1bb9 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx @@ -203,60 +203,8 @@ const DashboardCanvas: React.FC = ({ return () => window.removeEventListener('mousemove', onMove); }, [fullscreenCardId]); - // Arc/Zen fullscreen: the dock hides with the rest of the chrome but slides back on a left-edge - // graze, and clicking a tile SWAPS which card owns the full screen instead of moving the camera. - // Hover semantics are a 1:1 port of Zen's compact mode (ZenCompactMode.mjs + compact-mode.yaml): - // sidebar-keep-hover 150ms grace after leaving, 1000ms retention when the cursor exits the window - // across the dock's screen edge, and an 800ms flash when the active (fullscreen) card switches. - const FS_KEEP_HOVER_MS = 150; - const FS_WINDOW_LEAVE_MS = 1000; - const FS_FLASH_MS = 800; - const [fsDockRevealed, setFsDockRevealed] = React.useState(false); - const fsDockRef = React.useRef(null); - const fsRevealedRef = React.useRef(false); - fsRevealedRef.current = fsDockRevealed; - useEffect(() => { - if (!fullscreenCardId) { setFsDockRevealed(false); return undefined; } - let hideTimer: number | null = null; - const cancelHide = (): void => { if (hideTimer != null) { window.clearTimeout(hideTimer); hideTimer = null; } }; - const scheduleHide = (ms: number): void => { cancelHide(); hideTimer = window.setTimeout(() => setFsDockRevealed(false), ms); }; - const overDock = (x: number, y: number): boolean => { - const el = fsDockRef.current; - if (!el) return false; - const r = el.getBoundingClientRect(); - return x >= r.left - 8 && x <= r.right + 8 && y >= r.top - 8 && y <= r.bottom + 8; - }; - const onMove = (e: MouseEvent): void => { - if (e.clientX <= 16 || overDock(e.clientX, e.clientY)) { - cancelHide(); - if (!fsRevealedRef.current) setFsDockRevealed(true); - } else if (fsRevealedRef.current && hideTimer == null) { - scheduleHide(FS_KEEP_HOVER_MS); - } - }; - // Cursor flung out of the window across the dock's edge: keep the dock up (Zen's screen-edge - // retention), then let the next in-window move decide. - const onOut = (e: MouseEvent): void => { - if (!e.relatedTarget && e.clientX <= 24 && fsRevealedRef.current) scheduleHide(FS_WINDOW_LEAVE_MS); - }; - window.addEventListener('mousemove', onMove); - window.addEventListener('mouseout', onOut); - return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseout', onOut); cancelHide(); }; - }, [fullscreenCardId]); - // Zen flashes the sidebar on tab switch in compact mode; swapping the fullscreen card is our tab switch. - const prevFsCardRef = React.useRef(null); - useEffect(() => { - const prev = prevFsCardRef.current; - prevFsCardRef.current = fullscreenCardId; - if (!fullscreenCardId || !prev || prev === fullscreenCardId) return undefined; - setFsDockRevealed(true); - const t = window.setTimeout(() => { - const el = fsDockRef.current; - if (el && el.matches(':hover')) return; - setFsDockRevealed(false); - }, FS_FLASH_MS); - return () => window.clearTimeout(t); - }, [fullscreenCardId]); + // Arc fullscreen layout: the dock rail STAYS PUT (Arc's persistent sidebar) while the card + // expands into the whole dashboard beside it; clicking a tile SWAPS which card owns the screen. const swapFullscreen = React.useCallback((cardId: string) => { if (!fullscreenCardId || cardId === fullscreenCardId) return; dispatch(clearTiledCard(fullscreenCardId)); @@ -325,16 +273,11 @@ const DashboardCanvas: React.FC = ({ /> )} - {(!fullscreenCardId || fsDockRevealed) && ( + {( = ({ const justDraggedRef = useRef(false); const lastPointerRef = useRef<{ clientX: number; clientY: number }>({ clientX: 0, clientY: 0 }); + const tileZone = useAppSelector((s) => s.dashboardLayout.tiledCards[session.id]); const handleDragPointerDown = useCallback((e: React.PointerEvent) => { if (e.button !== 0) return; + if (tileZone) return; e.preventDefault(); e.stopPropagation(); const cs = getCanvasState(); @@ -483,7 +485,7 @@ const AgentCard: React.FC = ({ setIsDragging(true); (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); onDragStart?.(session.id, 'agent'); - }, [cardX, cardY, onDragStart, session.id, getCanvasState]); + }, [cardX, cardY, onDragStart, session.id, getCanvasState, tileZone]); const recomputeDragPos = useCallback(() => { const ds = dragState.current; @@ -656,7 +658,6 @@ const AgentCard: React.FC = ({ } }; - const tileZone = useAppSelector((s) => s.dashboardLayout.tiledCards[session.id]); const isFullscreen = tileZone === 'fullscreen'; // Fullscreen pins the card to the viewport, so while tiled the geometry must track canvas pan/zoom. // Chat cards read the camera via a getter (not props) to avoid re-rendering on every pan tick, so diff --git a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx index 1e7554eb..74312337 100644 --- a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx @@ -712,6 +712,7 @@ const BrowserCard: React.FC = ({ const handleDragPointerDown = useCallback((e: React.PointerEvent) => { if (e.button !== 0) return; + if (tileZone) return; e.preventDefault(); e.stopPropagation(); const cs = getCanvasState(); @@ -721,7 +722,7 @@ const BrowserCard: React.FC = ({ setIsDragging(true); (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); onDragStart?.(browserId, 'browser'); - }, [cardX, cardY, onDragStart, browserId, getCanvasState]); + }, [cardX, cardY, onDragStart, browserId, getCanvasState, tileZone]); const recomputeDragPos = useCallback(() => { const ds = dragState.current; diff --git a/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx b/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx index b13941dd..627e961d 100644 --- a/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx @@ -242,6 +242,7 @@ const DashboardViewCard: React.FC = ({ const handleDragPointerDown = useCallback((e: React.PointerEvent) => { if (e.button !== 0) return; + if (tileZone) return; e.preventDefault(); e.stopPropagation(); const cs = getCanvasState(); @@ -251,7 +252,7 @@ const DashboardViewCard: React.FC = ({ setIsDragging(true); (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); onDragStart?.(cardKey, 'view'); - }, [cardX, cardY, onDragStart, cardKey, getCanvasState]); + }, [cardX, cardY, onDragStart, cardKey, getCanvasState, tileZone]); const recomputeDragPos = useCallback(() => { const ds = dragState.current; diff --git a/frontend/src/app/pages/Dashboard/cards/NoteCard.tsx b/frontend/src/app/pages/Dashboard/cards/NoteCard.tsx index 8a8f599c..809baa99 100644 --- a/frontend/src/app/pages/Dashboard/cards/NoteCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/NoteCard.tsx @@ -108,6 +108,7 @@ const NoteCard: React.FC = ({ const handleDragPointerDown = useCallback((e: React.PointerEvent) => { if (e.button !== 0) return; + if (tileZone) return; e.preventDefault(); e.stopPropagation(); const cs = getCanvasState(); @@ -121,7 +122,7 @@ const NoteCard: React.FC = ({ setIsDragging(true); (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); onDragStart?.(noteId, 'note'); - }, [cardX, cardY, noteId, onDragStart, getCanvasState]); + }, [cardX, cardY, noteId, onDragStart, getCanvasState, tileZone]); const recomputeDragPos = useCallback(() => { const ds = dragState.current; diff --git a/frontend/src/app/pages/Dashboard/cards/tileZones.ts b/frontend/src/app/pages/Dashboard/cards/tileZones.ts index 7434e32b..3d34b755 100644 --- a/frontend/src/app/pages/Dashboard/cards/tileZones.ts +++ b/frontend/src/app/pages/Dashboard/cards/tileZones.ts @@ -23,6 +23,8 @@ export const TILE_ZONES: Record { const rect = getCardRect(id, type); if (rect) canvas.actions.fitToCards([rect], 1.15, true);