diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx index b932035c..3063e4fd 100644 --- a/frontend/src/app/components/Layout/AppShell.tsx +++ b/frontend/src/app/components/Layout/AppShell.tsx @@ -467,6 +467,13 @@ const AppShell: React.FC = () => { else root.style.removeProperty('--osw-header-inset'); return () => { root.style.removeProperty('--osw-header-inset'); }; }, [sidebarAway]); + // When the sidebar is docked, the macOS traffic lights sit over its top strip, which is a window + // drag region that swallows mousemove, so the canvas hover-reveal can never fire there. Broadcast + // "chrome docked" so the canvas keeps the native lights visible while the sidebar is open (they only + // hide-until-hover in the immersive collapsed/fullscreen state). detail.docked = sidebar is present. + useEffect(() => { + window.dispatchEvent(new CustomEvent('openswarm:chrome-docked', { detail: { docked: !sidebarAway } })); + }, [sidebarAway]); // Close-on-leave with a grace delay (cancelled on re-enter): a bare mouseLeave closed the peek the // instant the cursor dipped past the panel edge while reaching for an item, so clicks never landed. const peekCloseTimerRef = useRef | null>(null); diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx index 7eb4b036..6341f302 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx @@ -187,11 +187,21 @@ const DashboardCanvas: React.FC = ({ return () => window.removeEventListener('keydown', onKey, true); }, [fullscreenCardId, dispatch]); + // While the sidebar is docked, its top strip (a window drag region) hides the traffic lights AND + // eats the hover that would reveal them, so keep them visible the whole time the sidebar is open, + // like every Mac app with a sidebar. Only the immersive collapsed/fullscreen state hover-reveals. + const [chromeDocked, setChromeDocked] = React.useState(false); + useEffect(() => { + const onDocked = (e: Event): void => setChromeDocked(!!(e as CustomEvent).detail?.docked); + window.addEventListener('openswarm:chrome-docked', onDocked); + return () => window.removeEventListener('openswarm:chrome-docked', onDocked); + }, []); + // Arc-style chrome: the mac traffic lights ride the top-edge hover, in fullscreen too (Arc/Zen both // keep the native buttons reachable in compact/fullscreen; Zen even exempts them from hover-leave). useEffect(() => { - window.openswarm?.setWindowButtonsVisible?.(headerRevealed); - }, [headerRevealed]); + window.openswarm?.setWindowButtonsVisible?.(headerRevealed || chromeDocked); + }, [headerRevealed, chromeDocked]); // Reveal on any pointer graze of the top edge. The old 22px strip Box was dead in practice: the // hidden header overlay's pointer-events:auto children sat above it and ate the mouseenter.