From 5cb7bf458a27600d8bf0268785a3132022840809 Mon Sep 17 00:00:00 2001 From: richard-epsilla Date: Thu, 13 Aug 2026 02:20:42 -0700 Subject: [PATCH] fix(slides): the Export menu could never open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate from the dead endpoint: even with a working handler behind it, the menu closed itself the instant it opened. DeckBanner listened for `click` on window with no containment check, and React 18 flushes state and effects synchronously during a discrete event — so the listener was attached while the very click that opened the menu was still propagating, and it closed it again before paint. Fixed to the pattern AvatarMenu in this same file already uses: mousedown, plus a contains() check so a click inside the menu does not dismiss it. Found by driving it: the button reported enabled, on top, and its handler ran, yet .sl-export-menu never appeared in the DOM. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_013DCjqvx3L4VKAPnFaPe7YJ --- kits/slides/app/src/pages/DeckPage.jsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/kits/slides/app/src/pages/DeckPage.jsx b/kits/slides/app/src/pages/DeckPage.jsx index 0fbcc0a..96a0758 100644 --- a/kits/slides/app/src/pages/DeckPage.jsx +++ b/kits/slides/app/src/pages/DeckPage.jsx @@ -354,11 +354,18 @@ export function DeckPage({ id: routeId, seed, template }) { function DeckBanner({ deck, onPresent, onExport, exporting, peers = [], live, saveState, copilotBusy }) { const [menu, setMenu] = useState(false); + const menuRef = useRef(null); + // Close on a click OUTSIDE the menu, on mousedown — the same pattern AvatarMenu already uses. + // + // The old version listened for `click` on window with no containment check, and the menu could + // never open: React 18 flushes state and effects synchronously during a discrete event, so this + // listener was attached while the very click that opened the menu was still propagating, and it + // immediately closed it again. Export looked like a dead button for that reason alone. useEffect(() => { if (!menu) return undefined; - const close = () => setMenu(false); - window.addEventListener('click', close); - return () => window.removeEventListener('click', close); + const onDown = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) setMenu(false); }; + window.addEventListener('mousedown', onDown); + return () => window.removeEventListener('mousedown', onDown); }, [menu]); return (
@@ -371,7 +378,7 @@ function DeckBanner({ deck, onPresent, onExport, exporting, peers = [], live, sa )} {onExport && ( -
+