From 3e455726ebfa8341f9a6fcc4ab2d9f472b53d8d0 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 7 Jun 2026 20:31:21 -0500 Subject: [PATCH] fix(desktop): render full sidebar content in hover-reveal overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hover-reveal overlay showed only the nav rail — session rows, search, pinned/recents were gated behind `sidebarOpen` (false while collapsed), so they never mounted in the floated panel. Add a $sidebarRevealed store the PaneShell overlay drives via a new onHoverRevealChange callback, and gate ChatSidebar's content on `sidebarOpen || sidebarRevealed` (contentVisible) instead of raw open state. The overlay now shows the complete sidebar. --- apps/desktop/src/app/chat/sidebar/index.tsx | 21 ++++++++++++------- apps/desktop/src/app/desktop-controller.tsx | 3 +++ .../src/components/pane-shell/pane-shell.tsx | 9 ++++++++ apps/desktop/src/store/layout.ts | 10 +++++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src/app/chat/sidebar/index.tsx b/apps/desktop/src/app/chat/sidebar/index.tsx index e8a2c3827e3..23f97e4c1ef 100644 --- a/apps/desktop/src/app/chat/sidebar/index.tsx +++ b/apps/desktop/src/app/chat/sidebar/index.tsx @@ -49,6 +49,7 @@ import { $sidebarOpen, $sidebarPinsOpen, $sidebarRecentsOpen, + $sidebarRevealed, pinSession, reorderPinnedSession, SESSION_SEARCH_FOCUS_EVENT, @@ -247,6 +248,10 @@ export function ChatSidebar({ const { t } = useI18n() const s = t.sidebar const sidebarOpen = useStore($sidebarOpen) + // When collapsed but hover-revealed (floated over content), render the full + // sidebar — search field, pinned + recents — not just the nav rail. + const sidebarRevealed = useStore($sidebarRevealed) + const contentVisible = sidebarOpen || sidebarRevealed const panesFlipped = useStore($panesFlipped) const agentsGrouped = useStore($sidebarAgentsGrouped) const pinnedSessionIds = useStore($pinnedSessionIds) @@ -629,7 +634,7 @@ export function ChatSidebar({ type="button" > - {sidebarOpen && ( + {contentVisible && ( <> {s.nav[item.id] ?? item.label} @@ -650,7 +655,7 @@ export function ChatSidebar({ - {sidebarOpen && showSessionSections && ( + {contentVisible && showSessionSections && (
)} - {sidebarOpen && showSessionSections && trimmedQuery && ( + {contentVisible && showSessionSections && trimmedQuery && ( )} - {sidebarOpen && showSessionSections && !trimmedQuery && ( + {contentVisible && showSessionSections && !trimmedQuery && ( )} - {sidebarOpen && showSessionSections && !trimmedQuery && ( + {contentVisible && showSessionSections && !trimmedQuery && ( )} - {sidebarOpen && !trimmedQuery && cronJobs.length > 0 && ( + {contentVisible && !trimmedQuery && cronJobs.length > 0 && ( )} - {sidebarOpen && !showSessionSections &&
} + {contentVisible && !showSessionSections &&
} - {sidebarOpen && ( + {contentVisible && (
diff --git a/apps/desktop/src/app/desktop-controller.tsx b/apps/desktop/src/app/desktop-controller.tsx index c0a8d4809c0..27ce9a013c4 100644 --- a/apps/desktop/src/app/desktop-controller.tsx +++ b/apps/desktop/src/app/desktop-controller.tsx @@ -23,6 +23,7 @@ import { FILE_BROWSER_MAX_WIDTH, FILE_BROWSER_MIN_WIDTH, pinSession, + setSidebarRevealed, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_SESSIONS_PAGE_SIZE, @@ -300,6 +301,7 @@ export function DesktopController() { // with few recent sessions isn't windowed out of the cross-profile // recency page — the empty-history-on-profile-switch bug. const sessionProfile = profileScope === ALL_PROFILES ? 'all' : profileScope + const result = await listAllProfileSessions(limit, 1, 'exclude', 'recent', sessionProfile, { excludeSources: ['cron'] }) @@ -877,6 +879,7 @@ export function DesktopController() { id="chat-sidebar" maxWidth={SIDEBAR_MAX_WIDTH} minWidth={SIDEBAR_DEFAULT_WIDTH} + onHoverRevealChange={setSidebarRevealed} resizable side={sidebarSide} width={`${SIDEBAR_DEFAULT_WIDTH}px`} diff --git a/apps/desktop/src/components/pane-shell/pane-shell.tsx b/apps/desktop/src/components/pane-shell/pane-shell.tsx index 2a34638e49f..284c64476d0 100644 --- a/apps/desktop/src/components/pane-shell/pane-shell.tsx +++ b/apps/desktop/src/components/pane-shell/pane-shell.tsx @@ -39,6 +39,8 @@ export interface PaneProps { * track stays at 0px. */ hoverReveal?: boolean + /** Called with the reveal state whenever a collapsed hoverReveal pane floats in/out. */ + onHoverRevealChange?: (revealed: boolean) => void id: string maxWidth?: WidthValue minWidth?: WidthValue @@ -205,6 +207,7 @@ export function Pane({ id, maxWidth, minWidth, + onHoverRevealChange, resizable = false, width }: PaneProps) { @@ -248,6 +251,12 @@ export function Pane({ } }, [overlayActive, hoverRevealed]) + // Surface the effective reveal state to consumers (e.g. so the sidebar can + // render its full content while floated, not just while the track is open). + useEffect(() => { + onHoverRevealChange?.(overlayActive && hoverRevealed) + }, [onHoverRevealChange, overlayActive, hoverRevealed]) + const startResize = useCallback( (event: ReactPointerEvent) => { const paneWidth = paneRef.current?.getBoundingClientRect().width ?? 0 diff --git a/apps/desktop/src/store/layout.ts b/apps/desktop/src/store/layout.ts index c01d8b58bd3..ae279ec1728 100644 --- a/apps/desktop/src/store/layout.ts +++ b/apps/desktop/src/store/layout.ts @@ -54,6 +54,10 @@ export const $sidebarWidth: ReadableAtom = computed($paneStates, states export const $pinnedSessionIds = atom(storedStringArray(SIDEBAR_PINNED_STORAGE_KEY)) export const $sidebarPinsOpen = atom(true) +// Set by the PaneShell hover-reveal overlay while the collapsed sidebar is +// floated over content. ChatSidebar treats `sidebarOpen || sidebarRevealed` as +// "show my full self" so session rows render in the overlay too. +export const $sidebarRevealed = atom(false) export const $sidebarRecentsOpen = atom(true) // Cron-job sessions live in their own section below recents, collapsed by // default (it only renders at all when cron sessions exist) so the @@ -116,6 +120,12 @@ export function setSidebarPinsOpen(open: boolean) { $sidebarPinsOpen.set(open) } +export function setSidebarRevealed(revealed: boolean) { + if ($sidebarRevealed.get() !== revealed) { + $sidebarRevealed.set(revealed) + } +} + export function setSidebarRecentsOpen(open: boolean) { $sidebarRecentsOpen.set(open) }