From cd6add56b115dfeebb424b4a0d2d52a3b967e6e8 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 21 Jul 2026 13:44:23 -0700 Subject: [PATCH] [eric] desktop: full size view reads like a chat page (centered reading column), drop the dock rail + fisheye --- .../src/app/pages/AgentChat/AgentChat.tsx | 10 +++- .../Dashboard/canvas/DashboardCanvas.tsx | 58 +++++++------------ .../app/pages/Dashboard/cards/AgentCard.tsx | 3 +- .../app/pages/Dashboard/cards/tileZones.ts | 13 ++--- .../pages/Dashboard/desktop/DesktopDock.tsx | 35 +---------- 5 files changed, 36 insertions(+), 83 deletions(-) diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx index 42a2e0ea..278817db 100644 --- a/frontend/src/app/pages/AgentChat/AgentChat.tsx +++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx @@ -84,6 +84,10 @@ const CONTEXT_WINDOWS: Record = { haiku: 200_000, }; +// Full size view reads like a chat page, not a card: the transcript + composer center in one +// column at a comfortable measure (assistant-ui parks at 44rem, LibreChat 48rem; 760 splits them). +const FULLSCREEN_READING_MAX_W = 760; + // Only a fallback for never-rendered items; real heights are measured once on screen. const RENDER_ITEM_ESTIMATED_HEIGHT = 140; // Conservative estimate for an unmeasured tool row: tool groups/pairs render collapsed (~40-50px) far more often than expanded. Leaning low keeps scrollHeight (and the scrollbar thumb) from jumping when a tool row measures shorter. @@ -228,6 +232,8 @@ interface AgentChatProps { workflowEditId?: string; // View-only transcript (e.g. the Run Monitor): renders messages + tool calls but no composer, so the session can't be typed into. readOnly?: boolean; + // Full size view: center the whole chat in a reading column so an expanded card reads like a chat page. + fullscreenChat?: boolean; // One-shot text to drop into the composer (e.g. a run attached as context). prefillPrompt?: string; // A workflow run attached as a removable context chip above the composer; while present, each send routes through onSendRunQuestion so the run's transcript rides along as hidden context for that turn. @@ -236,7 +242,7 @@ interface AgentChatProps { onSendRunQuestion?: (prompt: string, runId: string) => Promise; } -const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose, embedded, autoFocus, isGlowing, onDismissGlow, initialContextPaths, onBranch, workflowEditId, readOnly, prefillPrompt, runContext, onClearRunContext, onSendRunQuestion }) => { +const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose, embedded, autoFocus, isGlowing, onDismissGlow, initialContextPaths, onBranch, workflowEditId, readOnly, fullscreenChat, prefillPrompt, runContext, onClearRunContext, onSendRunQuestion }) => { const c = useClaudeTokens(); const STATUS_STYLES: Record = { running: { color: c.status.success, bg: c.status.successBg }, @@ -1443,7 +1449,7 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose return ( - + {!embedded && ( = ({ return () => window.removeEventListener('mousemove', onMove); }, [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)); - if (sessions[cardId]) dispatch(expandSession(cardId)); - dispatch(setTiledCard({ cardId, zone: 'fullscreen' })); - }, [fullscreenCardId, dispatch, sessions]); - // Gestures write the transform imperatively (no React commit per frame), so a foreign render mid-gesture would paint the stale committed transform for a frame. Re-applying live after EVERY render seals that; do not remove. React.useLayoutEffect(() => { canvas.actions.syncTransform(); @@ -273,33 +263,25 @@ const DashboardCanvas: React.FC = ({ /> )} - {( - - { - if (fullscreenCardId) { swapFullscreen(cardId); return; } - canvas.actions.fitToCards([rect], 1.15, true); - onHighlightCard?.(cardId); - }} - onApplications={() => setAppsWindowOpen((v) => !v)} - onNewAgent={onNewAgent} - onAddBrowser={onAddBrowser} - onAddNote={onAddNote} - /> - + {!fullscreenCardId && ( + { + canvas.actions.fitToCards([rect], 1.15, true); + onHighlightCard?.(cardId); + }} + onApplications={() => setAppsWindowOpen((v) => !v)} + onNewAgent={onNewAgent} + onAddBrowser={onAddBrowser} + onAddNote={onAddNote} + /> )} {appsWindowOpen && !fullscreenCardId && ( diff --git a/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx b/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx index 44f5da5e..5436279d 100644 --- a/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/AgentCard.tsx @@ -933,7 +933,7 @@ const AgentCard: React.FC = ({ backdropFilter: 'blur(24px) saturate(150%)', WebkitBackdropFilter: 'blur(24px) saturate(150%)', }), - border: isSelected ? '2px solid #3b82f6' : '1px solid rgba(255,255,255,0.08)', + border: isFullscreen ? 'none' : isSelected ? '2px solid #3b82f6' : '1px solid rgba(255,255,255,0.08)', borderRadius: tiledStyle ? '12px' : '20px', boxShadow: '0 18px 48px rgba(0,0,0,0.4)', }), @@ -1189,6 +1189,7 @@ const AgentCard: React.FC = ({ sessionId={session.id} onClose={() => dispatch(collapseSession(session.id))} embedded + fullscreenChat={isFullscreen} autoFocus={autoFocusInput} isGlowing={isGlowingRedux && !glowFading} onDismissGlow={dismissGlow} diff --git a/frontend/src/app/pages/Dashboard/cards/tileZones.ts b/frontend/src/app/pages/Dashboard/cards/tileZones.ts index eeb19867..63d389ae 100644 --- a/frontend/src/app/pages/Dashboard/cards/tileZones.ts +++ b/frontend/src/app/pages/Dashboard/cards/tileZones.ts @@ -23,8 +23,6 @@ export const TILE_ZONES: Record(null); - // macOS-dock fisheye: tiles swell as the cursor nears and neighbors fall off along the curve, - // with a slight outward bulge so the column reads as a wheel (OptionWheel-style), not a ruler. - const applyFisheye = useCallback((clientY: number | null) => { - const root = dockBodyRef.current; - if (!root) return; - const rootTop = root.getBoundingClientRect().top; - root.querySelectorAll('.osw-dock-tile').forEach((el) => { - if (clientY == null) { el.style.transform = ''; return; } - const cy = rootTop + el.offsetTop + el.offsetHeight / 2; - const t = Math.max(0, 1 - Math.abs(clientY - cy) / 90); - const emph = t * t; - el.style.transform = `translateX(${(9 * emph).toFixed(1)}px) scale(${(1 + 0.5 * emph).toFixed(3)})`; - }); - }, []); const [hovered, setHovered] = useState<{ id: string; top: number } | null>(null); const [liveShot, setLiveShot] = useState<{ id: string; dataUrl: string } | null>(null); const hoverTimer = useRef(null); @@ -217,9 +202,7 @@ function DesktopDock({ return ( applyFisheye(e.clientY)} - onMouseLeave={() => { endHover(); applyFisheye(null); }} + onMouseLeave={endHover} sx={{ position: 'absolute', left: 12, @@ -248,7 +231,6 @@ function DesktopDock({ endHover(); onFocusCard(entry.id, entry.rect); }} - className="osw-dock-tile" sx={{ position: 'relative', width: TILE, @@ -261,9 +243,6 @@ function DesktopDock({ cursor: 'pointer', overflow: 'hidden', flexShrink: 0, - transition: 'transform 0.12s ease-out', - willChange: 'transform', - transformOrigin: 'left center', ...(isActive && { outline: '2px solid #6aa2ff', outlineOffset: '2px' }), }} > @@ -296,7 +275,6 @@ function DesktopDock({ {a.icon} @@ -320,7 +295,6 @@ function DesktopDock({ dispatch(openSettingsModal(undefined))} onMouseEnter={endHover} - className="osw-dock-tile" sx={{ width: TILE, height: TILE, @@ -331,9 +305,6 @@ function DesktopDock({ justifyContent: 'center', cursor: 'pointer', flexShrink: 0, - transition: 'transform 0.12s ease-out', - willChange: 'transform', - transformOrigin: 'left center', }} > @@ -341,7 +312,6 @@ function DesktopDock({