diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 60ce4b32..df8bd3dc 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -134,6 +134,16 @@ const getBaseName = (path: string) => { return file.replace(/\.md$/i, '') } +const getAncestorDirectoryPaths = (path: string): string[] => { + const parts = path.split('/').filter(Boolean) + if (parts.length <= 2) return [] + const ancestors: string[] = [] + for (let i = 1; i < parts.length - 1; i++) { + ancestors.push(parts.slice(0, i + 1).join('/')) + } + return ancestors +} + const isGraphTabPath = (path: string) => path === GRAPH_TAB_PATH const normalizeUsage = (usage?: Partial | null): LanguageModelUsage | null => { @@ -597,6 +607,25 @@ function App() { } }, [selectedPath]) + // Keep active file visible in the Knowledge tree by auto-expanding its ancestor folders. + useEffect(() => { + if (!selectedPath) return + const ancestorDirs = getAncestorDirectoryPaths(selectedPath) + if (ancestorDirs.length === 0) return + + setExpandedPaths((prev) => { + let changed = false + const next = new Set(prev) + for (const dirPath of ancestorDirs) { + if (!next.has(dirPath)) { + next.add(dirPath) + changed = true + } + } + return changed ? next : prev + }) + }, [selectedPath]) + // Keep runIdRef in sync with runId state (for use in event handlers to avoid stale closures) useEffect(() => { runIdRef.current = runId diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index 6a4bec64..fb890ecb 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -820,6 +820,36 @@ function KnowledgeSection({ onVoiceNoteCreated?: (path: string) => void }) { const isExpanded = expandedPaths.size > 0 + const treeContainerRef = React.useRef(null) + + useEffect(() => { + if (!selectedPath) return + + let cancelled = false + let rafId: number | null = null + let attempts = 0 + const maxAttempts = 20 + + const revealActiveFile = () => { + if (cancelled) return + const container = treeContainerRef.current + if (!container) return + const activeRow = container.querySelector('[data-knowledge-active="true"]') + if (activeRow) { + activeRow.scrollIntoView({ block: "nearest", inline: "nearest" }) + return + } + if (attempts >= maxAttempts) return + attempts += 1 + rafId = requestAnimationFrame(revealActiveFile) + } + + rafId = requestAnimationFrame(revealActiveFile) + return () => { + cancelled = true + if (rafId !== null) cancelAnimationFrame(rafId) + } + }, [selectedPath, expandedPaths, tree]) const quickActions = [ { icon: FilePlus, label: "New Note", action: () => actions.createNote() }, @@ -865,18 +895,20 @@ function KnowledgeSection({ - - {tree.map((item, index) => ( - - ))} - +
+ + {tree.map((item, index) => ( + + ))} + +
@@ -935,7 +967,7 @@ function Tree({ try { await actions.rename(item.path, trimmedName, isDir) toast('Renamed successfully', 'success') - } catch (err) { + } catch { toast('Failed to rename', 'error') } } @@ -950,7 +982,7 @@ function Tree({ try { await actions.remove(item.path) toast('Moved to trash', 'success') - } catch (err) { + } catch { toast('Failed to delete', 'error') } } @@ -1045,7 +1077,11 @@ function Tree({ return ( - + {