refactor(web): replace slideout panel window event with jotai atom (fixes #1358)

Replace the `SLIDEOUT_PANEL_OPENED_EVENT` window event with a
`slideoutOpenedTickAtom` jotai atom. The dispatcher in
`SidebarSlideOutPanel` now bumps the tick via `useSetAtom`, and the
listener in `Thread` reads it via `useAtomValue` and reacts on change
behind a ref guard that skips the initial render — preserving the
one-shot-per-open semantics of the previous event.

This removes the implicit cross-module string contract, makes the
signal traceable through React DevTools / jotai inspector, and lets
TypeScript catch typos that the string-based event API silently
swallowed.
This commit is contained in:
suryo12 2026-05-24 16:41:47 +07:00
parent d53866d87d
commit ddae506631
3 changed files with 26 additions and 13 deletions

View file

@ -105,7 +105,7 @@ import { useMediaQuery } from "@/hooks/use-media-query";
import { useElectronAPI } from "@/hooks/use-platform"; import { useElectronAPI } from "@/hooks/use-platform";
import { captureDisplayToPngDataUrl } from "@/lib/chat/display-media-capture"; import { captureDisplayToPngDataUrl } from "@/lib/chat/display-media-capture";
import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; 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"; import { cn } from "@/lib/utils";
const COMPOSER_PLACEHOLDER = "Ask anything, type / for prompts, type @ to mention docs"; const COMPOSER_PLACEHOLDER = "Ask anything, type / for prompts, type @ to mention docs";
@ -478,15 +478,18 @@ const Composer: FC = () => {
editorRef.current?.focus(); editorRef.current?.focus();
}, [isDesktop, showDocumentPopover, showPromptPicker, threadId]); }, [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.
const slideoutOpenedTick = useAtomValue(slideoutOpenedTickAtom);
const isFirstSlideoutTickRef = useRef(true);
useEffect(() => { useEffect(() => {
const handler = () => { void slideoutOpenedTick;
if (isFirstSlideoutTickRef.current) {
isFirstSlideoutTickRef.current = false;
return;
}
setShowDocumentPopover(false); setShowDocumentPopover(false);
setMentionQuery(""); setMentionQuery("");
}; }, [slideoutOpenedTick]);
window.addEventListener(SLIDEOUT_PANEL_OPENED_EVENT, handler);
return () => window.removeEventListener(SLIDEOUT_PANEL_OPENED_EVENT, handler);
}, []);
// Sync editor text into assistant-ui's composer and mirror the chip // Sync editor text into assistant-ui's composer and mirror the chip
// atom from the editor's reported ``docs``. The editor is the // atom from the editor's reported ``docs``. The editor is the

View file

@ -1,9 +1,10 @@
"use client"; "use client";
import { useSetAtom } from "jotai";
import { AnimatePresence, motion } from "motion/react"; import { AnimatePresence, motion } from "motion/react";
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import { useIsMobile } from "@/hooks/use-mobile"; import { useIsMobile } from "@/hooks/use-mobile";
import { SLIDEOUT_PANEL_OPENED_EVENT } from "@/lib/layout-events"; import { slideoutOpenedTickAtom } from "@/lib/layout-events";
interface SidebarSlideOutPanelProps { interface SidebarSlideOutPanelProps {
open: boolean; open: boolean;
@ -29,12 +30,13 @@ export function SidebarSlideOutPanel({
children, children,
}: SidebarSlideOutPanelProps) { }: SidebarSlideOutPanelProps) {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const bumpSlideoutOpenedTick = useSetAtom(slideoutOpenedTickAtom);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
window.dispatchEvent(new Event(SLIDEOUT_PANEL_OPENED_EVENT)); bumpSlideoutOpenedTick((tick) => tick + 1);
} }
}, [open]); }, [open, bumpSlideoutOpenedTick]);
const handleEscape = useCallback( const handleEscape = useCallback(
(e: KeyboardEvent) => { (e: KeyboardEvent) => {

View file

@ -1 +1,9 @@
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 react to it changing guard
* the initial render with a ref so the effect only fires on subsequent
* opens, matching the one-shot semantics of the previous window event.
*/
export const slideoutOpenedTickAtom = atom(0);