diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 6876ce23e..c4f6fed05 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -105,7 +105,7 @@ import { useMediaQuery } from "@/hooks/use-media-query"; import { useElectronAPI } from "@/hooks/use-platform"; import { captureDisplayToPngDataUrl } from "@/lib/chat/display-media-capture"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; -import { SLIDEOUT_PANEL_OPENED_EVENT } from "@/lib/layout-events"; +import { slideoutOpenedTickAtom } from "@/lib/layout-events"; import { cn } from "@/lib/utils"; const COMPOSER_PLACEHOLDER = "Ask anything, type / for prompts, type @ to mention docs"; @@ -478,15 +478,18 @@ const Composer: FC = () => { editorRef.current?.focus(); }, [isDesktop, showDocumentPopover, showPromptPicker, threadId]); - // Close document picker when a slide-out panel (inbox, etc.) opens. + // Close document picker when a sidebar slide-out panel (inbox, etc.) opens. + // React only on changes to the tick — comparing against the previously-seen + // value preserves the one-shot semantics of the prior window-event approach + // (no retroactive close on mount if a panel had already opened earlier). + const slideoutOpenedTick = useAtomValue(slideoutOpenedTickAtom); + const lastSeenSlideoutTickRef = useRef(slideoutOpenedTick); useEffect(() => { - const handler = () => { - setShowDocumentPopover(false); - setMentionQuery(""); - }; - window.addEventListener(SLIDEOUT_PANEL_OPENED_EVENT, handler); - return () => window.removeEventListener(SLIDEOUT_PANEL_OPENED_EVENT, handler); - }, []); + if (lastSeenSlideoutTickRef.current === slideoutOpenedTick) return; + lastSeenSlideoutTickRef.current = slideoutOpenedTick; + setShowDocumentPopover(false); + setMentionQuery(""); + }, [slideoutOpenedTick]); // Sync editor text into assistant-ui's composer and mirror the chip // atom from the editor's reported ``docs``. The editor is the diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx index 3fa4dd5d3..52b2cf998 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx @@ -1,9 +1,10 @@ "use client"; +import { useSetAtom } from "jotai"; import { AnimatePresence, motion } from "motion/react"; import { useCallback, useEffect } from "react"; import { useIsMobile } from "@/hooks/use-mobile"; -import { SLIDEOUT_PANEL_OPENED_EVENT } from "@/lib/layout-events"; +import { slideoutOpenedTickAtom } from "@/lib/layout-events"; interface SidebarSlideOutPanelProps { open: boolean; @@ -29,12 +30,13 @@ export function SidebarSlideOutPanel({ children, }: SidebarSlideOutPanelProps) { const isMobile = useIsMobile(); + const bumpSlideoutOpenedTick = useSetAtom(slideoutOpenedTickAtom); useEffect(() => { if (open) { - window.dispatchEvent(new Event(SLIDEOUT_PANEL_OPENED_EVENT)); + bumpSlideoutOpenedTick((tick) => tick + 1); } - }, [open]); + }, [open, bumpSlideoutOpenedTick]); const handleEscape = useCallback( (e: KeyboardEvent) => { diff --git a/surfsense_web/lib/layout-events.ts b/surfsense_web/lib/layout-events.ts index 45c52f7a4..755329c41 100644 --- a/surfsense_web/lib/layout-events.ts +++ b/surfsense_web/lib/layout-events.ts @@ -1 +1,11 @@ -export const SLIDEOUT_PANEL_OPENED_EVENT = "slideout-panel-opened"; +import { atom } from "jotai"; + +/** + * Tick counter that increments each time a sidebar slide-out panel opens. + * Consumers read this with `useAtomValue` and store the last-seen value in + * a ref so the effect fires only when the tick changes. This preserves the + * one-shot semantics of the previous window-event implementation: a + * subscriber that mounts after a panel has already opened does not + * retroactively re-trigger. + */ +export const slideoutOpenedTickAtom = atom(0);