From 09e31160416a5c3f217d9ce6a13a3f3b51d1e2cf Mon Sep 17 00:00:00 2001 From: ciregenz Date: Thu, 6 Aug 2026 10:23:36 -0700 Subject: [PATCH] [eric] desktop: app tiles are always generated icons, the screenshot rides a hover quick-look instead --- .../Dashboard/desktop/ApplicationsWindow.tsx | 67 +++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/frontend/src/app/pages/Dashboard/desktop/ApplicationsWindow.tsx b/frontend/src/app/pages/Dashboard/desktop/ApplicationsWindow.tsx index 4458522e..1d63b931 100644 --- a/frontend/src/app/pages/Dashboard/desktop/ApplicationsWindow.tsx +++ b/frontend/src/app/pages/Dashboard/desktop/ApplicationsWindow.tsx @@ -19,33 +19,22 @@ function hueFor(name: string): number { return h % 360; } +// Every tile is a generated ICON (gradient + monogram): a screenshot crushed to 68px reads as mud, +// so the screenshot lives in the hover preview instead and the row stays dock-uniform. function AppTile({ output }: { output: Output }): React.ReactElement { - const tile = { - width: 68, - height: 68, - borderRadius: '16px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexShrink: 0, - // Hairline inset so screenshots and light tiles read as crafted app icons on the glass, not raw pasted images. - boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.14), 0 6px 18px rgba(0,0,0,0.32)', - } as const; - if (output.thumbnail) { - return ( - - - {/* A soft top gloss makes a raw screenshot read as an app ICON instead of a pasted image. */} - - - ); - } const glyph = (output.icon || '').trim(); const h = hueFor(output.name || '?'); return ( + + + ); +} + /** Launchpad-style window over the canvas: the user's OpenSwarm apps, newest first. Deliberately NOT the machine's /Applications; this launcher is for things built in OpenSwarm. */ function ApplicationsWindow({ outputs, onOpenApp, onClose }: ApplicationsWindowProps): React.ReactElement { const [query, setQuery] = useState(''); + const [preview, setPreview] = useState<{ src: string; x: number; y: number; below: boolean } | null>(null); const apps = useMemo(() => { const all = Object.values(outputs); @@ -153,6 +170,13 @@ function ApplicationsWindow({ outputs, onOpenApp, onClose }: ApplicationsWindowP { onOpenApp(output.id); onClose(); }} + onMouseEnter={(e: React.MouseEvent) => { + if (!output.thumbnail) { setPreview(null); return; } + const r = e.currentTarget.getBoundingClientRect(); + const below = r.top < 170; + setPreview({ src: output.thumbnail, x: r.left + r.width / 2, y: below ? r.bottom + 10 : r.top - 148, below }); + }} + onMouseLeave={() => setPreview(null)} title={output.description || output.name} sx={{ display: 'flex', @@ -180,6 +204,7 @@ function ApplicationsWindow({ outputs, onOpenApp, onClose }: ApplicationsWindowP )} + {preview && } ); }