diff --git a/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx b/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx index 467b92fe..e858af1a 100644 --- a/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx @@ -37,7 +37,7 @@ import { clearTiledCard, } from '@/shared/state/dashboardLayoutSlice'; import WindowControls, { ARC_CHIP_SX } from './WindowControls'; -import { useTiledStyle } from './tileZones'; +import { useTiledStyle, computeTiledStyle } from './tileZones'; import AgentNarratorPill from '../desktop/AgentNarratorPill'; import { openCardContextMenu } from '../desktop/CardContextMenu'; import { extractLatestTodos } from '../desktop/agentTodos'; @@ -559,21 +559,35 @@ const AgentCard: React.FC = ({ if (e.button !== 0) return; e.preventDefault(); e.stopPropagation(); - const effectiveW = Math.max(cardWidth, MIN_W); - const effectiveH = expanded ? Math.max(EXPANDED_OVERLAY_H, cardHeight) : cardHeight; + let effectiveX = cardX; + let effectiveY = cardY; + let effectiveW = Math.max(cardWidth, MIN_W); + let effectiveH = expanded ? Math.max(EXPANDED_OVERLAY_H, cardHeight) : cardHeight; + // Grabbing an edge of a TILED chat exits the tile and resizes from exactly where it sat, + // macOS-style; without this the handles resized the stale free-position geometry. + if (tileZone) { + const cam = getCanvasState(); + const ts = computeTiledStyle(tileZone, cam.panX, cam.panY, cam.zoom); + if (ts) { + effectiveX = ts.left; effectiveY = ts.top; + effectiveW = ts.width / cam.zoom; effectiveH = ts.height / cam.zoom; + setLocalResize({ x: effectiveX, y: effectiveY, w: effectiveW, h: effectiveH }); + dispatch(clearTiledCard(session.id)); + } + } resizeRef.current = { dir, startX: e.clientX, startY: e.clientY, - origX: cardX, - origY: cardY, + origX: effectiveX, + origY: effectiveY, origW: effectiveW, origH: effectiveH, }; setIsResizing(true); (e.target as HTMLElement).setPointerCapture(e.pointerId); }, - [cardX, cardY, cardWidth, cardHeight, expanded], + [cardX, cardY, cardWidth, cardHeight, expanded, tileZone, dispatch, session.id], ); const computeResize = useCallback( diff --git a/frontend/src/app/pages/Dashboard/cards/WindowControls.tsx b/frontend/src/app/pages/Dashboard/cards/WindowControls.tsx index 9b3d556d..03fc8606 100644 --- a/frontend/src/app/pages/Dashboard/cards/WindowControls.tsx +++ b/frontend/src/app/pages/Dashboard/cards/WindowControls.tsx @@ -101,7 +101,7 @@ function WindowControls({ onClose, onMinimize, onTile, tiled, noTileMenu }: Wind position: 'absolute', top: 19, width: 216, background: '#FFFFFF', // Invisible runway over the button-to-menu gap: without it the pointer crosses dead // space, mouseleave fires, and the menu vanishes mid-approach ("baited"). - '&::before': { content: '""', position: 'absolute', top: -20, left: 0, right: 0, height: 20 }, + '&::before': { content: '""', position: 'absolute', top: -8, left: 0, right: 0, height: 8 }, ...(alignRight ? { right: -8 } : { left: -8 }), border: '1px solid rgba(0,0,0,0.06)', borderRadius: '12px', boxShadow: '0 .5rem 2rem rgba(0,0,0,.14)', p: 1.25, zIndex: 1200, transformOrigin: alignRight ? 'top right' : 'top left',