diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx index 7b08a4bf..2f5c396e 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx @@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from '@/shared/hooks'; import { addViewCard, clearTiledCard, toggleMinimizeCard, selectFullscreenCardId } from '@/shared/state/dashboardLayoutSlice'; import DashboardHeader from './DashboardHeader'; import TetherLayerHost from './TetherLayerHost'; +import { useLiveMultiDrag } from '../hooks/interaction/useLiveMultiDrag'; import DashboardCardLayer from './DashboardCardLayer'; import DashboardOverlays from './DashboardOverlays'; import { useCanvasContextMenu } from './useCanvasContextMenu'; @@ -62,7 +63,7 @@ interface DashboardCanvasProps { highlightedCardId: string | null; autoFocusSessionId: string | null; focusedCardId: string | null; - multiDragDelta: { dx: number; dy: number } | null; + multiDragActive: boolean; shakeDirection: Direction | null; neighborDirections: NeighborDirections; toolbarOpen: boolean; @@ -124,7 +125,7 @@ const DashboardCanvas: React.FC = ({ highlightedCardId, autoFocusSessionId, focusedCardId, - multiDragDelta, + multiDragActive, shakeDirection, neighborDirections, toolbarOpen, @@ -163,6 +164,7 @@ const DashboardCanvas: React.FC = ({ onTidy, onSearchPaletteClose, }) => { + useLiveMultiDrag(); const { accent, gradient } = useThemeAccent(); const { washOpacity, grain } = useThemeWash(); // A single picked color stores gradient=null, so fall back to the accent (mirrors BeatShell). @@ -469,7 +471,7 @@ const DashboardCanvas: React.FC = ({ highlightedCardId={highlightedCardId} autoFocusSessionId={autoFocusSessionId} focusedCardId={focusedCardId} - multiDragDelta={multiDragDelta} + multiDragActive={multiDragActive} shakeDirection={shakeDirection} spawnOriginsRef={spawnOriginsRef} revealSpawnedRef={revealSpawnedRef} diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCardLayer.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCardLayer.tsx index d225e799..80ced7f1 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCardLayer.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCardLayer.tsx @@ -39,7 +39,7 @@ interface DashboardCardLayerProps { highlightedCardId: string | null; autoFocusSessionId: string | null; focusedCardId: string | null; - multiDragDelta: { dx: number; dy: number } | null; + multiDragActive: boolean; shakeDirection: Direction | null; spawnOriginsRef: RefObject>; revealSpawnedRef: RefObject>; @@ -71,7 +71,7 @@ const DashboardCardLayer: React.FC = ({ highlightedCardId, autoFocusSessionId, focusedCardId, - multiDragDelta, + multiDragActive, shakeDirection, spawnOriginsRef, revealSpawnedRef, @@ -153,7 +153,7 @@ const DashboardCardLayer: React.FC = ({ isSelected={isSel} isHighlighted={highlightedCardId === sid} // Only selected cards need the live drag delta; passing it to everyone broke memo equality for unselected cards on every mouse-move during multi-drag. - multiDragDelta={isSel ? multiDragDelta : null} + multiDragActive={isSel && multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove} @@ -187,7 +187,7 @@ const DashboardCardLayer: React.FC = ({ cmdHeld={cmdHeld} isSelected={selection.isSelected(cardKey)} isHighlighted={highlightedCardId === cardKey} - multiDragDelta={multiDragDelta} + multiDragActive={multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove} @@ -214,7 +214,7 @@ const DashboardCardLayer: React.FC = ({ cmdHeld={cmdHeld} isSelected={selection.isSelected(bc.browser_id)} isHighlighted={highlightedCardId === bc.browser_id} - multiDragDelta={multiDragDelta} + multiDragActive={selection.isSelected(bc.browser_id) && multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove} @@ -227,7 +227,7 @@ const DashboardCardLayer: React.FC = ({ workflowsHub={workflowsHub} selection={selection} highlightedCardId={highlightedCardId} - multiDragDelta={multiDragDelta} + multiDragActive={multiDragActive} getCanvasState={getCanvasState} onCardSelect={onCardSelect} onDragStart={onDragStart} diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardWindowCards.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardWindowCards.tsx index 36036fba..3dff7cba 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardWindowCards.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardWindowCards.tsx @@ -16,7 +16,7 @@ interface DashboardWindowCardsProps { workflowsHub: WorkflowsHubPosition | null; selection: ReturnType; highlightedCardId: string | null; - multiDragDelta: { dx: number; dy: number } | null; + multiDragActive: boolean; getCanvasState: () => { panX: number; panY: number; zoom: number }; onCardSelect: (id: string, type: CardType, shiftKey: boolean, originTarget?: EventTarget | null) => void; onDragStart: (id: string, type: CardType) => void; @@ -30,7 +30,7 @@ const DashboardWindowCards: React.FC = ({ workflowsHub, selection, highlightedCardId, - multiDragDelta, + multiDragActive, getCanvasState, onCardSelect, onDragStart, @@ -61,7 +61,7 @@ const DashboardWindowCards: React.FC = ({ getCanvasState={getCanvasState} isSelected={selection.isSelected('workflows-hub')} isHighlighted={highlightedCardId === 'workflows-hub'} - multiDragDelta={selection.isSelected('workflows-hub') ? multiDragDelta : null} + multiDragActive={selection.isSelected('workflows-hub') && multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove} @@ -79,7 +79,7 @@ const DashboardWindowCards: React.FC = ({ getCanvasState={getCanvasState} isSelected={selection.isSelected(SETTINGS_CARD_ID)} isHighlighted={highlightedCardId === SETTINGS_CARD_ID} - multiDragDelta={selection.isSelected(SETTINGS_CARD_ID) ? multiDragDelta : null} + multiDragActive={selection.isSelected(SETTINGS_CARD_ID) && multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove} @@ -97,7 +97,7 @@ const DashboardWindowCards: React.FC = ({ getCanvasState={getCanvasState} isSelected={selection.isSelected(MARKETPLACE_CARD_ID)} isHighlighted={highlightedCardId === MARKETPLACE_CARD_ID} - multiDragDelta={selection.isSelected(MARKETPLACE_CARD_ID) ? multiDragDelta : null} + multiDragActive={selection.isSelected(MARKETPLACE_CARD_ID) && multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove} diff --git a/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx b/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx index 800e5ff7..24b01862 100644 --- a/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx @@ -268,7 +268,7 @@ interface OuterProps { exitTarget?: { x: number; y: number }; isSelected?: boolean; isHighlighted?: boolean; - multiDragDelta?: { dx: number; dy: number } | null; + multiDragActive?: boolean; onCardSelect?: (id: string, type: 'agent' | 'view', shiftKey: boolean) => void; onDragStart?: (id: string, type: 'agent' | 'view') => void; onDragMove?: (dx: number, dy: number, mouseX?: number, mouseY?: number) => void; @@ -305,7 +305,7 @@ const SNAP_THRESHOLD = 60; const AgentCard: React.FC = ({ session, expanded: expandedInStore, cardX, cardY, cardWidth, cardHeight, getCanvasState, spawnFrom, exitTarget, - isSelected = false, isHighlighted = false, multiDragDelta, onCardSelect, onDragStart, onDragMove, onDragEnd, + isSelected = false, isHighlighted = false, multiDragActive = false, onCardSelect, onDragStart, onDragMove, onDragEnd, onBranch, onMeasuredHeight, snapColumn, autoFocusInput, cardZOrder = 0, onDoubleClick, onBringToFront, shakeDirection, }) => { @@ -765,12 +765,10 @@ const AgentCard: React.FC = ({ const browserShot = useBrowserPillShot(session.id, pillMode && !pillArtifact); // justDraggedRef: the motion.div parks at the START position for the whole imperative drag, so the end-of-drag commit must snap (not spring) to the final spot or the card visibly re-glides from where the drag began. - const noTransition = isDragging || isResizing || (isSelected && !!multiDragDelta) || justDraggedRef.current; + const noTransition = isDragging || isResizing || (isSelected && multiDragActive) || justDraggedRef.current; - const mdDx = (!isDragging && isSelected && multiDragDelta) ? multiDragDelta.dx : 0; - const mdDy = (!isDragging && isSelected && multiDragDelta) ? multiDragDelta.dy : 0; - const activeX = localResize?.x ?? localDragPos?.x ?? (cardX + mdDx); - const activeY = localResize?.y ?? localDragPos?.y ?? (cardY + mdDy); + const activeX = localResize?.x ?? localDragPos?.x ?? cardX; + const activeY = localResize?.y ?? localDragPos?.y ?? cardY; const activeW = localResize?.w ?? cardWidth; const activeH = localResize?.h ?? cardHeight; const tiledSize = useTiledCard({ cardId: session.id, zone: tileZone, active: true, originX: activeX, originY: activeY, getCamera: getCanvasState }); diff --git a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx index 27952c34..247affe4 100644 --- a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx @@ -189,7 +189,7 @@ interface Props { cmdHeld?: boolean; isSelected?: boolean; isHighlighted?: boolean; - multiDragDelta?: { dx: number; dy: number } | null; + multiDragActive?: boolean; // Belongs to a non-active dashboard but kept mounted-hidden so its webContents + sessionStorage survive the switch. keepAliveHidden?: boolean; onCardSelect?: (id: string, type: 'agent' | 'view' | 'browser', shiftKey: boolean, originTarget?: EventTarget | null) => void; @@ -204,7 +204,7 @@ interface Props { const BrowserCard: React.FC = ({ browserId, tabs, activeTabId, cardX, cardY, cardWidth, cardHeight, getCanvasState, cmdHeld = false, - isSelected = false, isHighlighted = false, keepAliveHidden = false, multiDragDelta, onCardSelect, onDragStart, onDragMove, onDragEnd, + isSelected = false, isHighlighted = false, keepAliveHidden = false, multiDragActive = false, onCardSelect, onDragStart, onDragMove, onDragEnd, cardZOrder = 0, onDoubleClick, onBringToFront, }) => { const c = useClaudeTokens(); @@ -345,11 +345,19 @@ const BrowserCard: React.FC = ({ const hasDockRect = !!dockRect; useEffect(() => { if (!dockedTo || !hasDockRect) return undefined; + // Clear only after having followed: the old unconditional else cleared translate on EVERY + // drag frame of every other card, and would fight the multi-drag channel's writes. + let wasFollowing = false; const off = subscribeLiveDrag((info) => { const el = rootElRef.current; if (!el) return; - if (info && info.cardId === dockedTo) el.style.translate = `${info.dx}px ${info.dy}px`; - else el.style.translate = ''; + if (info && info.cardId === dockedTo) { + el.style.translate = `${info.dx}px ${info.dy}px`; + wasFollowing = true; + } else if (wasFollowing) { + el.style.translate = ''; + wasFollowing = false; + } }); return () => { off(); @@ -1045,13 +1053,11 @@ const BrowserCard: React.FC = ({ (e.target as HTMLElement).releasePointerCapture(e.pointerId); }, [computeResize, dispatch, browserId]); - const mdDx = (!isDragging && isSelected && multiDragDelta) ? multiDragDelta.dx : 0; - const mdDy = (!isDragging && isSelected && multiDragDelta) ? multiDragDelta.dy : 0; - const displayX = localResize?.x ?? localDragPos?.x ?? (cardX + mdDx); - const displayY = localResize?.y ?? localDragPos?.y ?? (cardY + mdDy); + const displayX = localResize?.x ?? localDragPos?.x ?? cardX; + const displayY = localResize?.y ?? localDragPos?.y ?? cardY; const displayW = localResize?.w ?? cardWidth; const displayH = localResize?.h ?? cardHeight; - const noTransition = isDragging || isResizing || (isSelected && !!multiDragDelta); + const noTransition = isDragging || isResizing || (isSelected && multiDragActive); // During a drag, move the card by a COMPOSITOR transform, not left/top layout: while edge-panning, the canvas transform and the card's left/top update land a frame apart, and the webview's guest surface follows the transform immediately while left/top relayouts late, so the browser visibly shimmers back and forth. A transform for the drag delta rides the same compositor path as the canvas pan, so they move together in one frame. const dragging = isDragging && !!localDragPos && !localResize; const dragTx = dragging ? displayX - cardX : 0; diff --git a/frontend/src/app/pages/Dashboard/cards/CanvasWindowCard.tsx b/frontend/src/app/pages/Dashboard/cards/CanvasWindowCard.tsx index b513f1fc..7cb7de00 100644 --- a/frontend/src/app/pages/Dashboard/cards/CanvasWindowCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/CanvasWindowCard.tsx @@ -42,7 +42,7 @@ interface CanvasWindowCardProps { background: string; highlightColor: string; getCanvasState: () => { panX: number; panY: number; zoom: number }; isSelected?: boolean; isHighlighted?: boolean; - multiDragDelta?: { dx: number; dy: number } | null; + multiDragActive?: boolean; onCardSelect?: (id: string, type: CardType, shiftKey: boolean, originTarget?: EventTarget | null) => void; onDragStart?: (id: string, type: CardType) => void; onDragMove?: (dx: number, dy: number, mouseX?: number, mouseY?: number) => void; @@ -64,7 +64,7 @@ const CanvasWindowCard: React.FC = ({ cardX, cardY, cardWidth, cardHeight, cardZOrder = 0, minimized = false, minWidth, minHeight, background, highlightColor, getCanvasState, - isSelected = false, isHighlighted = false, multiDragDelta = null, + isSelected = false, isHighlighted = false, multiDragActive = false, onCardSelect, onDragStart, onDragMove, onDragEnd, onBringToFront, onCommitPosition, onCommitSize, onMinimize, onClose, @@ -158,16 +158,14 @@ const CanvasWindowCard: React.FC = ({ getCanvasState, onCommitPosition, onCommitSize, untileForResize: tiling.untileForResize, }); - const mdDx = (!isDragging && !isResizing && isSelected && multiDragDelta) ? multiDragDelta.dx : 0; - const mdDy = (!isDragging && !isResizing && isSelected && multiDragDelta) ? multiDragDelta.dy : 0; - const dx = (localResize?.x ?? localDragPos?.x ?? cardX) + mdDx; - const dy = (localResize?.y ?? localDragPos?.y ?? cardY) + mdDy; + const dx = localResize?.x ?? localDragPos?.x ?? cardX; + const dy = localResize?.y ?? localDragPos?.y ?? cardY; const dw = localResize?.w ?? cardWidth; const dh = localResize?.h ?? cardHeight; const tiledSize = useTiledCard({ cardId, zone: tiling.zone, active: !minimized, originX: dx, originY: dy, getCamera: getCanvasState }); const border = isHighlighted ? `2px solid ${highlightColor}` : isSelected ? '2px solid #3b82f6' : `1px solid ${c.border.subtle}`; - const noTransition = isDragging || isResizing || (isSelected && !!multiDragDelta); + const noTransition = isDragging || isResizing || (isSelected && multiDragActive); return (
void; onDragStart?: (id: string, type: 'agent' | 'view') => void; onDragMove?: (dx: number, dy: number, mouseX?: number, mouseY?: number) => void; @@ -142,7 +142,7 @@ const BootingBody: React.FC = () => { const DashboardViewCard: React.FC = ({ output, cardKey: cardKeyProp, instance = 1, cardX, cardY, cardWidth, cardHeight, getCanvasState, cmdHeld = false, - isSelected = false, isHighlighted = false, multiDragDelta, onCardSelect, onDragStart, onDragMove, onDragEnd, + isSelected = false, isHighlighted = false, multiDragActive = false, onCardSelect, onDragStart, onDragMove, onDragEnd, cardZOrder = 0, onDoubleClick, onBringToFront, }) => { const cardKey = cardKeyProp ?? output.id; @@ -613,13 +613,11 @@ const DashboardViewCard: React.FC = ({ previewRef.current?.reload(); }; - const mdDx = (!isDragging && isSelected && multiDragDelta) ? multiDragDelta.dx : 0; - const mdDy = (!isDragging && isSelected && multiDragDelta) ? multiDragDelta.dy : 0; - const displayX = localResize?.x ?? localDragPos?.x ?? (cardX + mdDx); - const displayY = localResize?.y ?? localDragPos?.y ?? (cardY + mdDy); + const displayX = localResize?.x ?? localDragPos?.x ?? cardX; + const displayY = localResize?.y ?? localDragPos?.y ?? cardY; const displayW = localResize?.w ?? cardWidth; const displayH = localResize?.h ?? cardHeight; - const noTransition = isDragging || isResizing || (isSelected && !!multiDragDelta); + const noTransition = isDragging || isResizing || (isSelected && multiDragActive); // Drag via a compositor transform, not left/top: an app card's webview surface shimmers back and forth while edge-panning otherwise (the transform and the late left/top relayout desync a frame). Same fix as BrowserCard. const dragging = isDragging && !!localDragPos && !localResize; const dragTx = dragging ? displayX - cardX : 0; diff --git a/frontend/src/app/pages/Dashboard/hooks/interaction/multiDragLiveChannel.ts b/frontend/src/app/pages/Dashboard/hooks/interaction/multiDragLiveChannel.ts new file mode 100644 index 00000000..465308ee --- /dev/null +++ b/frontend/src/app/pages/Dashboard/hooks/interaction/multiDragLiveChannel.ts @@ -0,0 +1,21 @@ +// Co-selected cards follow a drag OUTSIDE React, same shape as marqueeLiveChannel: per-frame +// deltas at pointer rate re-rendered every selected card's whole subtree (the last ENG-88 hole). + +export interface MultiDragUpdate { + ids: ReadonlyArray; + dx: number; + dy: number; +} + +type Listener = (update: MultiDragUpdate | null) => void; + +const listeners = new Set(); + +export function publishMultiDrag(update: MultiDragUpdate | null): void { + for (const listener of listeners) listener(update); +} + +export function subscribeMultiDrag(listener: Listener): () => void { + listeners.add(listener); + return () => { listeners.delete(listener); }; +} diff --git a/frontend/src/app/pages/Dashboard/hooks/interaction/useCardDrag.ts b/frontend/src/app/pages/Dashboard/hooks/interaction/useCardDrag.ts index 0caa5e7f..36af7f12 100644 --- a/frontend/src/app/pages/Dashboard/hooks/interaction/useCardDrag.ts +++ b/frontend/src/app/pages/Dashboard/hooks/interaction/useCardDrag.ts @@ -5,6 +5,7 @@ import { moveCards } from '@/shared/state/dashboardLayoutSlice'; import type { CardType, useDashboardSelection } from '../state/useDashboardSelection'; import type { CanvasActions } from './useCanvasControls'; import { publishLiveDrag } from './liveDragChannel'; +import { publishMultiDrag } from './multiDragLiveChannel'; import { setCanvasInteractionActive } from '@/shared/canvasInteractionState'; type Selection = ReturnType; @@ -34,7 +35,11 @@ export function useCardDrag({ }: UseCardDragArgs) { const dispatch = useAppDispatch(); - const [multiDragDelta, setMultiDragDelta] = useState<{ dx: number; dy: number } | null>(null); + // Per-frame deltas ride multiDragLiveChannel (style writes, zero renders); this flag renders + // exactly twice per gesture and exists so follower cards suppress their spring on the commit. + const [multiDragActive, setMultiDragActive] = useState(false); + const multiDragActiveRef = useRef(false); + const followIdsRef = useRef([]); const activeDragCardRef = useRef(null); const isMultiDragRef = useRef(false); @@ -86,6 +91,7 @@ export function useCardDrag({ // Multi only when there is actually company: a lone selected card on this path made every drag after the first pay a setState per frame. if (selection.isSelected(id) && selection.selectedArray().length > 1) { isMultiDragRef.current = true; + followIdsRef.current = selection.selectedArray().filter((s) => s.id !== id).map((s) => s.id); } else { // Grabbing an unselected card SELECTS just it (was deselectAll, which left nothing selected, so the next spawn had no anchor and flew to viewport-center far from the card you just moved). Also survives the stale-read where the capture-phase click already selected it. selection.selectCard(id, type, false); @@ -106,7 +112,11 @@ export function useCardDrag({ edgePanFrameRef.current = requestAnimationFrame(tickEdgePan); } if (isMultiDragRef.current) { - setMultiDragDelta({ dx, dy }); + if (!multiDragActiveRef.current) { + multiDragActiveRef.current = true; + setMultiDragActive(true); + } + publishMultiDrag({ ids: followIdsRef.current, dx, dy }); } if (activeDragCardRef.current) { publishLiveDrag({ cardId: activeDragCardRef.current, dx, dy }); @@ -121,8 +131,16 @@ export function useCardDrag({ document.body.classList.remove('dashboard-marquee-active'); setCanvasInteractionActive(false); isMultiDragRef.current = false; - setMultiDragDelta(null); + followIdsRef.current = []; + publishMultiDrag(null); publishLiveDrag(null); + if (multiDragActiveRef.current) { + // Stay active through the moveCards commit paint (followers must snap, not spring), then release next frame. + requestAnimationFrame(() => { + multiDragActiveRef.current = false; + setMultiDragActive(false); + }); + } }, [stopEdgePan, canvasActions]); const handleCardDragEnd = useCallback((dx: number, dy: number, didDrag: boolean) => { @@ -152,7 +170,7 @@ export function useCardDrag({ }, [clearDrag, stopEdgePan]); return { - multiDragDelta, + multiDragActive, handleCardDragStart, handleCardDragMove, handleCardDragEnd, diff --git a/frontend/src/app/pages/Dashboard/hooks/interaction/useLiveMultiDrag.ts b/frontend/src/app/pages/Dashboard/hooks/interaction/useLiveMultiDrag.ts new file mode 100644 index 00000000..2f949e4d --- /dev/null +++ b/frontend/src/app/pages/Dashboard/hooks/interaction/useLiveMultiDrag.ts @@ -0,0 +1,27 @@ +import { useEffect } from 'react'; +import { subscribeMultiDrag } from './multiDragLiveChannel'; + +// The one consumer of multiDragLiveChannel: style-writes `translate` on co-selected card roots +// per frame. CSS `translate` composes with `transform`, so framer-motion's scale/spring inline +// transforms are never clobbered (the transform-trap that bit the minimized-cards work). +export function useLiveMultiDrag(): void { + useEffect(() => { + const els = new Map(); + return subscribeMultiDrag((update) => { + if (!update) { + for (const el of els.values()) { if (el) el.style.translate = ''; } + els.clear(); + return; + } + const value = `${update.dx}px ${update.dy}px`; + for (const id of update.ids) { + let el = els.get(id); + if (el === undefined) { + el = document.querySelector(`[data-osw-card="${CSS.escape(id)}"]`); + els.set(id, el); + } + if (el) el.style.translate = value; + } + }); + }, []); +} diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts index af08154f..e6384046 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts @@ -104,7 +104,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { }), [canvas.actions]); const { - multiDragDelta, + multiDragActive, handleCardDragStart, handleCardDragMove, handleCardDragEnd, @@ -342,7 +342,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { cards, viewCards, browserCards, keepAliveBrowserCards, outputs, glowingAgentCards, workflowCards, workflowsHub, expandedSessionIds, tetherInputs, highlightedCardId, autoFocusSessionId, - focusedCardId, multiDragDelta, shakeDirection, + focusedCardId, multiDragActive, shakeDirection, neighborDirections, toolbarOpen, searchPaletteOpen, newAgentBounce, canvasEmpty, toolbarRef, spawnOriginsRef, revealSpawnedRef, measuredHeightsRef, getCanvasState, toolbarPrefill, diff --git a/frontend/src/app/pages/Directory/MarketplaceAppCard.tsx b/frontend/src/app/pages/Directory/MarketplaceAppCard.tsx index 5d092eee..75dbcc51 100644 --- a/frontend/src/app/pages/Directory/MarketplaceAppCard.tsx +++ b/frontend/src/app/pages/Directory/MarketplaceAppCard.tsx @@ -26,7 +26,7 @@ interface Props { getCanvasState: () => { panX: number; panY: number; zoom: number }; isSelected?: boolean; isHighlighted?: boolean; - multiDragDelta?: { dx: number; dy: number } | null; + multiDragActive?: boolean; onCardSelect?: (id: string, type: CardType, shiftKey: boolean) => void; onDragStart?: (id: string, type: CardType) => void; onDragMove?: (dx: number, dy: number, mouseX?: number, mouseY?: number) => void; @@ -38,7 +38,7 @@ interface Props { const MarketplaceAppCard: React.FC = ({ cardX, cardY, cardWidth, cardHeight, cardZOrder = 0, getCanvasState, - isSelected = false, isHighlighted = false, multiDragDelta = null, + isSelected = false, isHighlighted = false, multiDragActive = false, onCardSelect, onDragStart, onDragMove, onDragEnd, onBringToFront, }) => { const c = useClaudeTokens(); @@ -73,7 +73,7 @@ const MarketplaceAppCard: React.FC = ({ getCanvasState={getCanvasState} isSelected={isSelected} isHighlighted={isHighlighted} - multiDragDelta={multiDragDelta} + multiDragActive={multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove} diff --git a/frontend/src/app/pages/Settings/SettingsAppCard.tsx b/frontend/src/app/pages/Settings/SettingsAppCard.tsx index 483c47ce..b668c8c5 100644 --- a/frontend/src/app/pages/Settings/SettingsAppCard.tsx +++ b/frontend/src/app/pages/Settings/SettingsAppCard.tsx @@ -27,7 +27,7 @@ interface Props { getCanvasState: () => { panX: number; panY: number; zoom: number }; isSelected?: boolean; isHighlighted?: boolean; - multiDragDelta?: { dx: number; dy: number } | null; + multiDragActive?: boolean; onCardSelect?: (id: string, type: CardType, shiftKey: boolean) => void; onDragStart?: (id: string, type: CardType) => void; onDragMove?: (dx: number, dy: number, mouseX?: number, mouseY?: number) => void; @@ -39,7 +39,7 @@ interface Props { const SettingsAppCard: React.FC = ({ cardX, cardY, cardWidth, cardHeight, cardZOrder = 0, getCanvasState, - isSelected = false, isHighlighted = false, multiDragDelta = null, + isSelected = false, isHighlighted = false, multiDragActive = false, onCardSelect, onDragStart, onDragMove, onDragEnd, onBringToFront, }) => { const c = useClaudeTokens(); @@ -75,7 +75,7 @@ const SettingsAppCard: React.FC = ({ getCanvasState={getCanvasState} isSelected={isSelected} isHighlighted={isHighlighted} - multiDragDelta={multiDragDelta} + multiDragActive={multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove} diff --git a/frontend/src/app/pages/Workflows/app/WorkflowsAppCard.tsx b/frontend/src/app/pages/Workflows/app/WorkflowsAppCard.tsx index e1829ad6..8d372d7f 100644 --- a/frontend/src/app/pages/Workflows/app/WorkflowsAppCard.tsx +++ b/frontend/src/app/pages/Workflows/app/WorkflowsAppCard.tsx @@ -18,7 +18,7 @@ interface Props { getCanvasState: () => { panX: number; panY: number; zoom: number }; isSelected?: boolean; isHighlighted?: boolean; - multiDragDelta?: { dx: number; dy: number } | null; + multiDragActive?: boolean; onCardSelect?: (id: string, type: CardType, shiftKey: boolean) => void; onDragStart?: (id: string, type: CardType) => void; onDragMove?: (dx: number, dy: number, mouseX?: number, mouseY?: number) => void; @@ -29,7 +29,7 @@ interface Props { const WorkflowsAppCard: React.FC = ({ cardX, cardY, cardWidth, cardHeight, cardZOrder = 0, getCanvasState, - isSelected = false, isHighlighted = false, multiDragDelta = null, + isSelected = false, isHighlighted = false, multiDragActive = false, onCardSelect, onDragStart, onDragMove, onDragEnd, onBringToFront, }) => { const WC = useWC(); @@ -67,7 +67,7 @@ const WorkflowsAppCard: React.FC = ({ getCanvasState={getCanvasState} isSelected={isSelected} isHighlighted={isHighlighted} - multiDragDelta={multiDragDelta} + multiDragActive={multiDragActive} onCardSelect={onCardSelect} onDragStart={onDragStart} onDragMove={onDragMove}