diff --git a/surfsense_web/atoms/chat/current-thread.atom.ts b/surfsense_web/atoms/chat/current-thread.atom.ts index c19b2638c..dea926633 100644 --- a/surfsense_web/atoms/chat/current-thread.atom.ts +++ b/surfsense_web/atoms/chat/current-thread.atom.ts @@ -17,6 +17,8 @@ interface CurrentThreadState { visibility: ChatVisibility | null; hasComments: boolean; addingCommentToMessageId: number | null; + /** Whether the right-side comments panel is collapsed (desktop only) */ + commentsCollapsed: boolean; } const initialState: CurrentThreadState = { @@ -24,6 +26,7 @@ const initialState: CurrentThreadState = { visibility: null, hasComments: false, addingCommentToMessageId: null, + commentsCollapsed: false, }; export const currentThreadAtom = atom(initialState); @@ -34,6 +37,8 @@ export const commentsEnabledAtom = atom( export const showCommentsGutterAtom = atom((get) => { const thread = get(currentThreadAtom); + // Hide gutter if comments are collapsed + if (thread.commentsCollapsed) return false; return ( thread.visibility === "SEARCH_SPACE" && (thread.hasComments || thread.addingCommentToMessageId !== null) @@ -55,3 +60,19 @@ export const setThreadVisibilityAtom = atom(null, (get, set, newVisibility: Chat export const resetCurrentThreadAtom = atom(null, (_, set) => { set(currentThreadAtom, initialState); }); + +/** Atom to read whether comments panel is collapsed */ +export const commentsCollapsedAtom = atom( + (get) => get(currentThreadAtom).commentsCollapsed +); + +/** Atom to toggle the comments collapsed state */ +export const toggleCommentsCollapsedAtom = atom(null, (get, set) => { + const current = get(currentThreadAtom); + set(currentThreadAtom, { ...current, commentsCollapsed: !current.commentsCollapsed }); +}); + +/** Atom to explicitly set the comments collapsed state */ +export const setCommentsCollapsedAtom = atom(null, (get, set, collapsed: boolean) => { + set(currentThreadAtom, { ...get(currentThreadAtom), commentsCollapsed: collapsed }); +}); diff --git a/surfsense_web/components/assistant-ui/assistant-message.tsx b/surfsense_web/components/assistant-ui/assistant-message.tsx index b3cfc4476..4fb8d8393 100644 --- a/surfsense_web/components/assistant-ui/assistant-message.tsx +++ b/surfsense_web/components/assistant-ui/assistant-message.tsx @@ -11,6 +11,7 @@ import type { FC } from "react"; import { useContext, useEffect, useRef, useState } from "react"; import { addingCommentToMessageIdAtom, + commentsCollapsedAtom, commentsEnabledAtom, } from "@/atoms/chat/current-thread.atom"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; @@ -102,6 +103,7 @@ export const AssistantMessage: FC = () => { const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); const dbMessageId = parseMessageId(messageId); const commentsEnabled = useAtomValue(commentsEnabledAtom); + const commentsCollapsed = useAtomValue(commentsCollapsedAtom); const [addingCommentToMessageId, setAddingCommentToMessageId] = useAtom( addingCommentToMessageIdAtom ); @@ -157,8 +159,8 @@ export const AssistantMessage: FC = () => { > - {/* Desktop comment panel - only on lg screens and above */} - {searchSpaceId && commentsEnabled && !isMessageStreaming && ( + {/* Desktop comment panel - only on lg screens and above, hidden when collapsed */} + {searchSpaceId && commentsEnabled && !isMessageStreaming && !commentsCollapsed && (
-
- {children} -
+
+ {children} +
- {/* Mobile Inbox Sidebar */} - {inbox && ( - setMobileMenuOpen(false)} - /> - )} + {/* Mobile Inbox Sidebar - only render when open to avoid scroll blocking */} + {inbox?.isOpen && ( + setMobileMenuOpen(false)} + /> + )}
diff --git a/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx index db6d22cba..69ab714d8 100644 --- a/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx @@ -7,19 +7,21 @@ import { Check, CheckCheck, CheckCircle2, + ChevronLeft, + ChevronRight, History, Inbox, LayoutGrid, ListFilter, - PanelLeftClose, - PanelLeft, Search, X, } from "lucide-react"; +import { useAtom } from "jotai"; import { AnimatePresence, motion } from "motion/react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { setCommentsCollapsedAtom } from "@/atoms/chat/current-thread.atom"; import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; @@ -171,6 +173,9 @@ export function InboxSidebar({ const router = useRouter(); const isMobile = !useMediaQuery("(min-width: 640px)"); + // Comments collapsed state (desktop only, when docked) + const [, setCommentsCollapsed] = useAtom(setCommentsCollapsedAtom); + const [searchQuery, setSearchQuery] = useState(""); const [activeTab, setActiveTab] = useState("mentions"); const [activeFilter, setActiveFilter] = useState("all"); @@ -199,15 +204,16 @@ export function InboxSidebar({ return () => document.removeEventListener("keydown", handleEscape); }, [open, onOpenChange]); - // Only lock body scroll on mobile (Notion-style keeps desktop content scrollable) + // Only lock body scroll on mobile when inbox is open useEffect(() => { - if (open && isMobile) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = ""; - } + if (!open || !isMobile) return; + + // Store original overflow to restore on cleanup + const originalOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + return () => { - document.body.style.overflow = ""; + document.body.style.overflow = originalOverflow; }; }, [open, isMobile]); @@ -702,6 +708,25 @@ export function InboxSidebar({ {t("mark_all_read") || "Mark all as read"} + {/* Close button - mobile only */} + {isMobile && ( + + + + + + {t("close") || "Close"} + + + )} {/* Dock/Undock button - desktop only */} {!isMobile && onDockedChange && ( @@ -712,27 +737,29 @@ export function InboxSidebar({ className="h-8 w-8 rounded-full" onClick={() => { if (isDocked) { - // Undocking: close the inbox completely + // Collapse: show comments immediately, then close inbox + setCommentsCollapsed(false); onDockedChange(false); onOpenChange(false); } else { - // Docking: keep open and dock + // Expand: hide comments immediately + setCommentsCollapsed(true); onDockedChange(true); } }} > {isDocked ? ( - + ) : ( - + )} - {isDocked ? "Close inbox" : "Dock inbox"} + {isDocked ? "Collapse panel" : "Expand panel"} - {isDocked ? "Close inbox" : "Dock inbox"} + {isDocked ? "Collapse panel" : "Expand panel"} )}