feat: TypeScript calls sync_note_title on note open
- handleSelectNote syncs title frontmatter before loading content - handleSelectNoteWithSync reloads entry after open to update display title - Added sync_note_title to mock handlers - Fixed rapid-switching test to flush sync microtask Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { useCallback } from 'react'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { isTauri } from '../mock-tauri'
|
||||
import type { VaultEntry } from '../types'
|
||||
import type { FrontmatterValue } from '../components/Inspector'
|
||||
import { useTabManagement } from './useTabManagement'
|
||||
@@ -78,15 +80,27 @@ export function useNoteActions(config: NoteActionsConfig) {
|
||||
setTabs((prev) => prev.map((t) => t.entry.path === path ? { ...t, content: newContent } : t))
|
||||
}, [setTabs])
|
||||
|
||||
const creation = useNoteCreation(config, { openTabWithContent, handleSelectNote, handleCloseTab, handleCloseTabRef })
|
||||
// After opening a note, reload its VaultEntry so title reflects any sync.
|
||||
const handleSelectNoteWithSync = useCallback(async (entry: VaultEntry) => {
|
||||
await handleSelectNote(entry)
|
||||
// Reload entry from disk to pick up title changes from sync_note_title
|
||||
if (isTauri()) {
|
||||
try {
|
||||
const fresh = await invoke<VaultEntry>('reload_vault_entry', { path: entry.path })
|
||||
if (fresh.title !== entry.title) updateEntry(entry.path, { title: fresh.title })
|
||||
} catch { /* non-fatal: entry display may be stale */ }
|
||||
}
|
||||
}, [handleSelectNote, updateEntry])
|
||||
|
||||
const creation = useNoteCreation(config, { openTabWithContent, handleSelectNote: handleSelectNoteWithSync, handleCloseTab, handleCloseTabRef })
|
||||
const rename = useNoteRename(
|
||||
{ entries, setToastMessage },
|
||||
{ tabs: tabMgmt.tabs, setTabs, activeTabPathRef, handleSwitchTab, updateTabContent },
|
||||
)
|
||||
|
||||
const handleNavigateWikilink = useCallback(
|
||||
(target: string) => navigateWikilink(entries, target, handleSelectNote),
|
||||
[entries, handleSelectNote],
|
||||
(target: string) => navigateWikilink(entries, target, handleSelectNoteWithSync),
|
||||
[entries, handleSelectNoteWithSync],
|
||||
)
|
||||
|
||||
const runFrontmatterOp = useCallback(
|
||||
@@ -97,6 +111,7 @@ export function useNoteActions(config: NoteActionsConfig) {
|
||||
|
||||
return {
|
||||
...tabMgmt,
|
||||
handleSelectNote: handleSelectNoteWithSync,
|
||||
handleCloseTab: creation.handleCloseTabWithCleanup,
|
||||
handleNavigateWikilink,
|
||||
handleCreateNote: creation.handleCreateNote,
|
||||
|
||||
@@ -559,14 +559,17 @@ describe('useTabManagement', () => {
|
||||
|
||||
// Start loading A (don't await — simulates rapid click)
|
||||
let selectADone = false
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
result.current.handleSelectNote(makeEntry({ path: '/vault/a.md', title: 'A' })).then(() => { selectADone = true })
|
||||
// Flush microtask from sync_note_title (no-op in mock mode) so loadAndSetTab starts
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
// Start loading B while A is still loading
|
||||
let selectBDone = false
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
result.current.handleSelectNote(makeEntry({ path: '/vault/b.md', title: 'B' })).then(() => { selectBDone = true })
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
// B resolves first
|
||||
|
||||
@@ -69,6 +69,16 @@ async function loadNoteContent(path: string): Promise<string> {
|
||||
: mockInvoke<string>('get_note_content', { path })
|
||||
}
|
||||
|
||||
/** Sync title frontmatter with filename on note open.
|
||||
* Returns true if the file was modified. */
|
||||
async function syncNoteTitle(path: string): Promise<boolean> {
|
||||
try {
|
||||
return isTauri()
|
||||
? await invoke<boolean>('sync_note_title', { path })
|
||||
: false // mock: no-op
|
||||
} catch { return false }
|
||||
}
|
||||
|
||||
function addTabIfAbsent(prev: Tab[], entry: VaultEntry, content: string): Tab[] {
|
||||
if (prev.some((t) => t.entry.path === entry.path)) return prev
|
||||
return [...prev, { entry, content }]
|
||||
@@ -147,6 +157,8 @@ export function useTabManagement() {
|
||||
const handleSelectNote = useCallback(async (entry: VaultEntry) => {
|
||||
if (isTabOpen(tabsRef.current, entry.path)) { setActiveTabPath(entry.path); return }
|
||||
const seq = ++navSeqRef.current
|
||||
// Sync title frontmatter with filename before loading content
|
||||
await syncNoteTitle(entry.path)
|
||||
await loadAndSetTab(entry, (prev, content) => addTabIfAbsent(prev, entry, content), setTabs)
|
||||
if (navSeqRef.current === seq) setActiveTabPath(entry.path)
|
||||
}, [])
|
||||
|
||||
@@ -156,6 +156,7 @@ export const mockHandlers: Record<string, (args: any) => any> = {
|
||||
list_vault: () => MOCK_ENTRIES,
|
||||
reload_vault: () => MOCK_ENTRIES,
|
||||
reload_vault_entry: (args: { path: string }) => MOCK_ENTRIES.find(e => e.path === args.path) ?? { path: args.path, title: 'Unknown', filename: 'unknown.md', aliases: [], belongsTo: [], relatedTo: [], archived: false, trashed: false, snippet: '', wordCount: 0, fileSize: 0, relationships: {}, outgoingLinks: [], properties: {} },
|
||||
sync_note_title: () => false,
|
||||
get_note_content: (args: { path: string }) => MOCK_CONTENT[args.path] ?? '',
|
||||
get_all_content: () => MOCK_CONTENT,
|
||||
get_file_history: (args: { path: string }) => mockFileHistory(args.path),
|
||||
|
||||
Reference in New Issue
Block a user