fix(notes): serialize immediate note creation writes

This commit is contained in:
lucaronin
2026-05-02 03:56:48 +02:00
parent db16f2ff5e
commit c9698b853e
4 changed files with 154 additions and 20 deletions

View File

@@ -220,7 +220,11 @@ describe('useNoteActions hook', () => {
await flushAsyncWork()
})
await act(async () => {
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS * 2)
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
await flushAsyncWork()
})
await act(async () => {
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
await flushAsyncWork()
})
@@ -471,7 +475,11 @@ describe('useNoteActions hook', () => {
await flushAsyncWork()
})
await act(async () => {
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS * 2)
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
await flushAsyncWork()
})
await act(async () => {
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
await flushAsyncWork()
})

View File

@@ -261,7 +261,11 @@ describe('useNoteCreation hook', () => {
await flushImmediateCreate()
})
await act(async () => {
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS * 2)
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
await flushImmediateCreate()
})
await act(async () => {
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
await flushImmediateCreate()
})
const filenames = addEntry.mock.calls.map(([e]: [VaultEntry]) => e.filename)
@@ -326,6 +330,49 @@ describe('useNoteCreation hook', () => {
vi.restoreAllMocks()
})
it('waits for slow immediate note persistence before starting the queued create', async () => {
vi.useFakeTimers()
vi.mocked(isTauri).mockReturnValue(true)
vi.spyOn(Date, 'now').mockReturnValue(1700000000000)
let resolveFirstWrite: () => void
const firstWrite = new Promise<void>((resolve) => {
resolveFirstWrite = resolve
})
vi.mocked(invoke)
.mockImplementationOnce(() => firstWrite)
.mockResolvedValue(undefined)
const createCalls = () => vi.mocked(invoke).mock.calls.filter(([command]) => command === 'create_note_content')
const { result } = renderHook(() => useNoteCreation(makeConfig(), tabDeps))
await act(async () => {
result.current.handleCreateNoteImmediate()
result.current.handleCreateNoteImmediate()
await flushImmediateCreate()
})
expect(createCalls()).toHaveLength(1)
await act(async () => {
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS * 3)
await flushImmediateCreate()
})
expect(createCalls()).toHaveLength(1)
await act(async () => {
resolveFirstWrite()
await flushImmediateCreate()
})
await act(async () => {
vi.advanceTimersByTime(RAPID_CREATE_NOTE_SETTLE_MS)
await flushImmediateCreate()
})
expect(createCalls()).toHaveLength(2)
expect(addEntry).toHaveBeenCalledTimes(2)
vi.restoreAllMocks()
})
it('handleCreateNoteImmediate accepts custom type', async () => {
const { result } = renderHook(() => useNoteCreation(makeConfig(), tabDeps))
await act(async () => {

View File

@@ -536,18 +536,26 @@ function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: st
const queuedImmediateCreatesRef = useRef<ImmediateCreateRequest[]>([])
const immediateCreateLockedRef = useRef(false)
const immediateCreateTimerRef = useRef<number | null>(null)
const queueMountedRef = useRef(true)
const { latestDepsRef, syncDeps } = useLatestImmediateCreateDeps(config, pendingSlugsRef)
const executeRequest = useCallback((request: ImmediateCreateRequest) => {
const executeRequest = useCallback(async (request: ImmediateCreateRequest): Promise<void> => {
const deps = latestDepsRef.current
if (!deps) return
void createNoteImmediate(deps, request.type).then((didCreate) => trackImmediateCreate(request, didCreate))
try {
const didCreate = await createNoteImmediate(deps, request.type)
trackImmediateCreate(request, didCreate)
} catch (error) {
console.warn('Failed to create immediate note:', error)
}
}, [latestDepsRef])
const scheduleQueuedBurst = useCallback(function scheduleQueuedBurst() {
if (!queueMountedRef.current) return
if (immediateCreateTimerRef.current !== null) return
immediateCreateTimerRef.current = window.setTimeout(() => {
immediateCreateTimerRef.current = window.setTimeout(async () => {
immediateCreateTimerRef.current = null
const next = queuedImmediateCreatesRef.current.shift()
if (!next) {
@@ -555,14 +563,18 @@ function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: st
return
}
executeRequest(next)
await executeRequest(next)
scheduleQueuedBurst()
}, RAPID_CREATE_NOTE_SETTLE_MS)
}, [executeRequest])
useEffect(() => () => {
if (immediateCreateTimerRef.current !== null) {
window.clearTimeout(immediateCreateTimerRef.current)
useEffect(() => {
queueMountedRef.current = true
return () => {
queueMountedRef.current = false
if (immediateCreateTimerRef.current !== null) {
window.clearTimeout(immediateCreateTimerRef.current)
}
}
}, [])
@@ -575,8 +587,7 @@ function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: st
}
immediateCreateLockedRef.current = true
executeRequest(request)
scheduleQueuedBurst()
void executeRequest(request).then(scheduleQueuedBurst)
}, [syncDeps, executeRequest, scheduleQueuedBurst])
}