Pulse: fix slow note open — O(1) map lookup, no reloadVault on click

Root cause: clicking a note in Pulse used an inline arrow function that:
1. Was recreated on every render (new prop ref → PulseView memo bypassed)
2. Called vault.reloadVault() (full 9000-note rescan) when path didn't match

Fix:
- Add entriesByPath Map (useMemo) — O(1) lookups instead of O(n) .find()
- Add handlePulseOpenNote (useCallback) — stable ref, never triggers reloadVault
  (Pulse notes always exist in vault; no reload needed)
- Wire PulseView to handlePulseOpenNote instead of inline arrow
- Also use entriesByPath in openNoteByPath (MCP bridge)
This commit is contained in:
Test
2026-03-06 21:28:07 +01:00
parent b9d94abae4
commit 75d67623ce

View File

@@ -230,19 +230,26 @@ function App() {
useNavigationGestures({ onGoBack: handleGoBack, onGoForward: handleGoForward })
// O(1) path lookup map — rebuilt only when vault.entries changes
const entriesByPath = useMemo(() => {
const map = new Map<string, VaultEntry>()
for (const e of vault.entries) map.set(e.path, e)
return map
}, [vault.entries])
// MCP UI bridge: react to AI-driven open/highlight/vault-change events
const openNoteByPath = useCallback((path: string) => {
const entry = vault.entries.find(e => e.path === path || e.path === `${resolvedPath}/${path}`)
const entry = entriesByPath.get(path) ?? entriesByPath.get(`${resolvedPath}/${path}`)
if (entry) {
notes.handleSelectNote(entry)
} else {
// Entry not yet in vault (just created) — reload then open
vault.reloadVault().then(freshEntries => {
const fresh = freshEntries.find((e: VaultEntry) => e.path === path || e.path === `${resolvedPath}/${path}`)
const fresh = (freshEntries as VaultEntry[]).find(e => e.path === path || e.path === `${resolvedPath}/${path}`)
if (fresh) notes.handleSelectNote(fresh)
})
}
}, [vault, notes, resolvedPath])
}, [entriesByPath, vault, notes, resolvedPath])
const aiActivity = useAiActivity({
onOpenNote: openNoteByPath,
@@ -253,10 +260,18 @@ function App() {
onVaultChanged: () => { vault.reloadVault() },
})
// Stable callback for Pulse "open note" — never triggers reloadVault.
// Pulse files always exist in the vault; if somehow not found, silently skip.
const handlePulseOpenNote = useCallback((relativePath: string) => {
const fullPath = `${resolvedPath}/${relativePath}`
const entry = entriesByPath.get(fullPath) ?? entriesByPath.get(relativePath)
if (entry) notes.handleSelectNote(entry)
}, [entriesByPath, resolvedPath, notes])
// Agent file operation handlers: auto-open created notes, live-refresh modified notes
const handleAgentFileCreated = useCallback((relativePath: string) => {
vault.reloadVault().then(freshEntries => {
const entry = freshEntries.find((e: VaultEntry) => e.path === relativePath || e.path === `${resolvedPath}/${relativePath}`)
const entry = (freshEntries as VaultEntry[]).find(e => e.path === relativePath || e.path === `${resolvedPath}/${relativePath}`)
if (entry) notes.handleSelectNote(entry)
})
}, [vault, notes, resolvedPath])
@@ -509,11 +524,7 @@ function App() {
<>
<div className={`app__note-list${aiActivity.highlightElement === 'notelist' ? ' ai-highlight' : ''}`} style={{ width: layout.noteListWidth }}>
{selection.kind === 'filter' && selection.filter === 'pulse' ? (
<PulseView vaultPath={resolvedPath} onOpenNote={(relativePath) => {
const fullPath = `${resolvedPath}/${relativePath}`
const entry = vault.entries.find(e => e.path === fullPath || e.path === relativePath)
if (entry) notes.handleSelectNote(entry)
}} sidebarCollapsed={!sidebarVisible} onExpandSidebar={() => setViewMode('all')} />
<PulseView vaultPath={resolvedPath} onOpenNote={handlePulseOpenNote} sidebarCollapsed={!sidebarVisible} onExpandSidebar={() => setViewMode('all')} />
) : (
<NoteList entries={vault.entries} selection={selection} selectedNote={activeTab?.entry ?? null} allContent={vault.allContent} modifiedFiles={vault.modifiedFiles} modifiedFilesError={vault.modifiedFilesError} getNoteStatus={vault.getNoteStatus} sidebarCollapsed={!sidebarVisible} onSelectNote={notes.handleSelectNote} onReplaceActiveTab={notes.handleReplaceActiveTab} onCreateNote={notes.handleCreateNoteImmediate} onBulkArchive={bulkActions.handleBulkArchive} onBulkTrash={bulkActions.handleBulkTrash} onUpdateTypeSort={notes.handleUpdateFrontmatter} updateEntry={vault.updateEntry} />
)}