From cdb5711f447f4c2b068af3d26c917678adcbd1a4 Mon Sep 17 00:00:00 2001 From: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:57:24 +0530 Subject: [PATCH] fix(memory): keep Trees toolbar stable when View Vault panel opens (#4342) --- .../intelligence/ObsidianVaultSection.tsx | 72 +++++++++++++++++-- .../__tests__/ObsidianVaultSection.test.tsx | 65 +++++++++++++++++ 2 files changed, 133 insertions(+), 4 deletions(-) diff --git a/app/src/components/intelligence/ObsidianVaultSection.tsx b/app/src/components/intelligence/ObsidianVaultSection.tsx index 97347fa9f..ce5398ffd 100644 --- a/app/src/components/intelligence/ObsidianVaultSection.tsx +++ b/app/src/components/intelligence/ObsidianVaultSection.tsx @@ -19,7 +19,7 @@ * (Flatpak/Snap/portable). "Open anyway" and the config-dir override are the * escape hatches for that case; a false "not registered" never blocks the user. */ -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useT } from '../../lib/i18n/I18nContext'; import type { ToastNotification } from '../../types/intelligence'; @@ -58,6 +58,35 @@ export function ObsidianVaultSection({ contentRootAbs, onToast }: ObsidianVaultS const [configFound, setConfigFound] = useState(null); const [showAdvanced, setShowAdvanced] = useState(false); const [configDir, setConfigDir] = useState(readConfigDirOverride); + const containerRef = useRef(null); + + const closePanel = useCallback(() => setExpanded(false), []); + + // The guidance panel is a floating popover, so it needs explicit dismissal: + // click outside the section or press Escape to close it. (Clicking the View + // Vault button itself stays inside `containerRef`, so it re-runs the check + // rather than dismissing — matching the panel's "click View Vault again" copy.) + useEffect(() => { + if (!expanded) return; + const onPointerDown = (e: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(e.target as Node)) { + console.debug('[ui-flow][obsidian-vault] dismiss: outside click'); + setExpanded(false); + } + }; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + console.debug('[ui-flow][obsidian-vault] dismiss: escape'); + setExpanded(false); + } + }; + document.addEventListener('mousedown', onPointerDown); + document.addEventListener('keydown', onKeyDown); + return () => { + document.removeEventListener('mousedown', onPointerDown); + document.removeEventListener('keydown', onKeyDown); + }; + }, [expanded]); /** * Build + fire the `obsidian://` deep link. @@ -192,8 +221,14 @@ export function ObsidianVaultSection({ contentRootAbs, onToast }: ObsidianVaultS ? t('workspace.obsidianNotFoundHelp') : t('workspace.vaultNotRegisteredHelp'); + // The guidance panel is rendered as an absolutely-positioned popover anchored + // to the button (out of normal flow) rather than as an inline sibling. This + // component lives inside the horizontal MemoryControls toolbar; an in-flow + // `w-full`/wide panel would grow this flex item and force the whole toolbar to + // wrap/misalign (issue #4266). Taking the panel out of flow keeps the toolbar + // row stable regardless of the panel's visibility. return ( -
+

{helpText}