feat: optimize note open and trash/archive latency

Latency root causes:
1. handleSelectNote/handleReplaceActiveTab awaited IPC before updating
   activeTabPath — zero visual feedback for 50-200ms file I/O
2. Trash/archive called updateEntry AFTER two sequential IPC calls —
   note stayed visible in list for 100-400ms

Optimizations:
- Content prefetch cache: hover on NoteItem and keyboard arrow
  navigation pre-load note content via IPC. When user clicks, content is
  already in memory — eliminates the IPC round-trip entirely.
- Optimistic trash/archive/restore/unarchive: updateEntry runs
  immediately, frontmatter writes happen async. On failure, UI rolls
  back and shows error toast.
- Rapid-switch safety: sequence counter (navSeqRef) ensures only the
  latest navigation sets activeTabPath — prevents stale content flash
  when user clicks multiple notes in quick succession.
- Prefetch cache cleared on vault reload to prevent stale content after
  external edits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-09 13:05:18 +01:00
parent db66e7c9da
commit d8b61dd62f
6 changed files with 97 additions and 19 deletions

View File

@@ -1,6 +1,7 @@
import { useState, useCallback, useEffect, useRef } from 'react'
import type { VirtuosoHandle } from 'react-virtuoso'
import type { VaultEntry } from '../types'
import { prefetchNoteContent } from './useTabManagement'
interface NoteListKeyboardOptions {
items: VaultEntry[]
@@ -29,6 +30,7 @@ export function useNoteListKeyboard({
setHighlightedIndex(prev => {
const next = Math.min((prev < 0 ? -1 : prev) + 1, items.length - 1)
virtuosoRef.current?.scrollToIndex({ index: next, behavior: 'auto' })
if (next >= 0 && next < items.length) prefetchNoteContent(items[next].path)
return next
})
} else if (e.key === 'ArrowUp') {
@@ -36,6 +38,7 @@ export function useNoteListKeyboard({
setHighlightedIndex(prev => {
const next = Math.max((prev < 0 ? items.length : prev) - 1, 0)
virtuosoRef.current?.scrollToIndex({ index: next, behavior: 'auto' })
if (next >= 0 && next < items.length) prefetchNoteContent(items[next].path)
return next
})
} else if (e.key === 'Enter' && highlightedIndex >= 0 && highlightedIndex < items.length) {