From dd018cf98bad236753e2870aff0e0eccf040bc86 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Sat, 25 Apr 2026 10:22:58 +0200 Subject: [PATCH] fix: queue diff opening until tab activation --- src/App.tsx | 37 +++++++++++--------- src/hooks/useDiffMode.test.ts | 63 ++++++++++++++++++++++++++--------- src/hooks/useDiffMode.ts | 38 ++++++++++++++++----- 3 files changed, 98 insertions(+), 40 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 41933a81..61c7bb9d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -534,8 +534,8 @@ function App() { }, onToast: (msg) => setToastMessage(msg), }) - const pulseCommitDiffRequestIdRef = useRef(0) - const [pulseCommitDiffRequest, setPulseCommitDiffRequest] = useState(null) + const pendingDiffRequestIdRef = useRef(0) + const [pendingDiffRequest, setPendingDiffRequest] = useState(null) // Note window: auto-open the note from URL params without scanning the whole vault. const noteWindowOpenedRef = useRef(false) @@ -600,17 +600,17 @@ function App() { onSelectNote: notes.handleSelectNote, }) - const queuePulseCommitDiff = useCallback((path: string, commitHash: string) => { - pulseCommitDiffRequestIdRef.current += 1 - setPulseCommitDiffRequest({ - requestId: pulseCommitDiffRequestIdRef.current, + const queuePendingDiff = useCallback((path: string, commitHash?: string) => { + pendingDiffRequestIdRef.current += 1 + setPendingDiffRequest({ + requestId: pendingDiffRequestIdRef.current, path, commitHash, }) }, []) - const handlePulseCommitDiffHandled = useCallback((requestId: number) => { - setPulseCommitDiffRequest((current) => + const handlePendingDiffHandled = useCallback((requestId: number) => { + setPendingDiffRequest((current) => current?.requestId === requestId ? null : current, ) }, []) @@ -621,7 +621,7 @@ function App() { if (commitHash) { const targetPath = entry?.path ?? fullPath - queuePulseCommitDiff(targetPath, commitHash) + queuePendingDiff(targetPath, commitHash) if (entry) { void handleSelectNote(entry) } else { @@ -633,7 +633,7 @@ function App() { if (entry) { void handleSelectNote(entry) } - }, [entriesByPath, resolvedPath, queuePulseCommitDiff, handleSelectNote, openTabWithContent]) + }, [entriesByPath, resolvedPath, queuePendingDiff, handleSelectNote, openTabWithContent]) const handleOpenFavorite = useCallback(async (entry: VaultEntry) => { await handleReplaceActiveTab(entry) @@ -812,11 +812,18 @@ function App() { } notes.openTabWithContent(entry, previewContent) if (hasDiff) { - setTimeout(() => diffToggleRef.current(), 50) + queuePendingDiff(entry.path) } else { setToastMessage('Content not available (untracked)') } - }, [vault, notes, setToastMessage]) + }, [vault, notes, queuePendingDiff, setToastMessage]) + + const handleReplaceActiveTabWithQueuedDiff = useCallback((entry: VaultEntry) => { + notes.handleReplaceActiveTab(entry) + if (effectiveSelection.kind === 'filter' && effectiveSelection.filter === 'changes') { + queuePendingDiff(entry.path) + } + }, [effectiveSelection, notes, queuePendingDiff]) const commitFlow = useCommitFlow({ savePending: appSave.savePending, @@ -1444,7 +1451,7 @@ function App() { {effectiveSelection.kind === 'filter' && effectiveSelection.filter === 'pulse' ? ( handleSetViewMode('all')} /> ) : ( - diffToggleRef.current()} onOpenDeletedNote={handleOpenDeletedNote} allNotesNoteListProperties={vaultConfig.allNotes?.noteListProperties ?? null} onUpdateAllNotesNoteListProperties={handleUpdateAllNotesNoteListProperties} inboxNoteListProperties={vaultConfig.inbox?.noteListProperties ?? null} onUpdateInboxNoteListProperties={handleUpdateInboxNoteListProperties} views={vault.views} visibleNotesRef={visibleNotesRef} multiSelectionCommandRef={multiSelectionCommandRef} /> + )} @@ -1458,8 +1465,8 @@ function App() { onNavigateWikilink={notes.handleNavigateWikilink} onLoadDiff={vault.loadDiff} onLoadDiffAtCommit={vault.loadDiffAtCommit} - pendingCommitDiffRequest={pulseCommitDiffRequest} - onPendingCommitDiffHandled={handlePulseCommitDiffHandled} + pendingCommitDiffRequest={pendingDiffRequest} + onPendingCommitDiffHandled={handlePendingDiffHandled} getNoteStatus={vault.getNoteStatus} onCreateNote={notes.handleCreateNoteImmediate} inspectorCollapsed={layout.inspectorCollapsed} diff --git a/src/hooks/useDiffMode.test.ts b/src/hooks/useDiffMode.test.ts index de35e336..e037cf86 100644 --- a/src/hooks/useDiffMode.test.ts +++ b/src/hooks/useDiffMode.test.ts @@ -35,6 +35,43 @@ describe('useDiffMode', () => { warn.mockRestore() } + async function expectPendingDiffRequestLoaded(options: { + diffContent: string + requestId: number + commitHash?: string + }) { + const { diffContent, requestId, commitHash = '' } = options + const onPendingCommitDiffHandled = vi.fn() + + if (commitHash) { + onLoadDiffAtCommit.mockResolvedValue(diffContent) + } else { + onLoadDiff.mockResolvedValue(diffContent) + } + + const { result } = renderHook(() => useDiffMode({ + activeTabPath: '/note.md', + onLoadDiff, + onLoadDiffAtCommit, + pendingCommitDiffRequest: { requestId, path: '/note.md', commitHash }, + onPendingCommitDiffHandled, + })) + + await waitFor(() => { + if (commitHash) { + expect(onLoadDiffAtCommit).toHaveBeenCalledWith('/note.md', commitHash) + } else { + expect(onLoadDiff).toHaveBeenCalledWith('/note.md') + expect(onLoadDiffAtCommit).not.toHaveBeenCalled() + } + }) + await waitFor(() => { + expect(result.current.diffMode).toBe(true) + expect(result.current.diffContent).toBe(diffContent) + expect(onPendingCommitDiffHandled).toHaveBeenCalledWith(requestId) + }) + } + it('starts with diff mode off', () => { const { result } = renderDiffHook() expect(result.current.diffMode).toBe(false) @@ -121,23 +158,17 @@ describe('useDiffMode', () => { }) it('loads a pending commit diff request when the matching tab is active', async () => { - onLoadDiffAtCommit.mockResolvedValue('pulse diff') - const onPendingCommitDiffHandled = vi.fn() - - const { result } = renderHook(() => useDiffMode({ - activeTabPath: '/note.md', - onLoadDiffAtCommit, - pendingCommitDiffRequest: { requestId: 7, path: '/note.md', commitHash: 'abc123' }, - onPendingCommitDiffHandled, - })) - - await waitFor(() => { - expect(onLoadDiffAtCommit).toHaveBeenCalledWith('/note.md', 'abc123') + await expectPendingDiffRequestLoaded({ + diffContent: 'pulse diff', + requestId: 7, + commitHash: 'abc123', }) - await waitFor(() => { - expect(result.current.diffMode).toBe(true) - expect(result.current.diffContent).toBe('pulse diff') - expect(onPendingCommitDiffHandled).toHaveBeenCalledWith(7) + }) + + it('loads a pending working-tree diff request when the matching tab is active', async () => { + await expectPendingDiffRequestLoaded({ + diffContent: 'working tree diff', + requestId: 9, }) }) diff --git a/src/hooks/useDiffMode.ts b/src/hooks/useDiffMode.ts index 4f06f10d..354d4881 100644 --- a/src/hooks/useDiffMode.ts +++ b/src/hooks/useDiffMode.ts @@ -9,7 +9,7 @@ import { export interface CommitDiffRequest { requestId: number path: string - commitHash: string + commitHash?: string | null } interface UseDiffModeParams { @@ -73,6 +73,10 @@ function shouldHandlePendingCommitDiffRequest( return !!pendingCommitDiffRequest && pendingCommitDiffRequest.path === activeTabPath } +function hasCommitHash(pendingCommitDiffRequest: CommitDiffRequest): pendingCommitDiffRequest is CommitDiffRequest & { commitHash: string } { + return typeof pendingCommitDiffRequest.commitHash === 'string' && pendingCommitDiffRequest.commitHash.length > 0 +} + function buildGuardedDiffStateSetters( cancelledRef: { current: boolean }, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath }: DiffStateSetters, @@ -87,18 +91,27 @@ function buildGuardedDiffStateSetters( function runPendingCommitDiffRequest( pendingCommitDiffRequest: CommitDiffRequest, + onLoadDiff: ((path: string) => Promise) | undefined, onLoadDiffAtCommit: (path: string, commitHash: string) => Promise, onPendingCommitDiffHandled: ((requestId: number) => void) | undefined, diffState: DiffStateSetters, ) { const cancelledRef = { current: false } - void loadCommitDiffForPath( - pendingCommitDiffRequest.path, - pendingCommitDiffRequest.commitHash, - onLoadDiffAtCommit, - buildGuardedDiffStateSetters(cancelledRef, diffState), - ).finally(() => { + const loadDiffPromise = hasCommitHash(pendingCommitDiffRequest) + ? loadCommitDiffForPath( + pendingCommitDiffRequest.path, + pendingCommitDiffRequest.commitHash, + onLoadDiffAtCommit, + buildGuardedDiffStateSetters(cancelledRef, diffState), + ) + : loadDiffForPath( + pendingCommitDiffRequest.path, + onLoadDiff, + buildGuardedDiffStateSetters(cancelledRef, diffState), + ) + + void loadDiffPromise.finally(() => { if (cancelledRef.current) return onPendingCommitDiffHandled?.(pendingCommitDiffRequest.requestId) }) @@ -110,6 +123,7 @@ function runPendingCommitDiffRequest( function usePendingCommitDiffRequest({ activeTabPath, + onLoadDiff, onLoadDiffAtCommit, pendingCommitDiffRequest, onPendingCommitDiffHandled, @@ -120,18 +134,23 @@ function usePendingCommitDiffRequest({ }: UseDiffModeParams & DiffStateSetters) { useEffect(() => { if (!shouldHandlePendingCommitDiffRequest(activeTabPath, pendingCommitDiffRequest)) return - if (!onLoadDiffAtCommit) { + if (hasCommitHash(pendingCommitDiffRequest) && !onLoadDiffAtCommit) { + onPendingCommitDiffHandled?.(pendingCommitDiffRequest.requestId) + return + } + if (!hasCommitHash(pendingCommitDiffRequest) && !onLoadDiff) { onPendingCommitDiffHandled?.(pendingCommitDiffRequest.requestId) return } return runPendingCommitDiffRequest( pendingCommitDiffRequest, + onLoadDiff, onLoadDiffAtCommit, onPendingCommitDiffHandled, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath }, ) - }, [activeTabPath, onLoadDiffAtCommit, onPendingCommitDiffHandled, pendingCommitDiffRequest, setDiffContent, setDiffLoading, setDiffMode, setDiffPath]) + }, [activeTabPath, onLoadDiff, onLoadDiffAtCommit, onPendingCommitDiffHandled, pendingCommitDiffRequest, setDiffContent, setDiffLoading, setDiffMode, setDiffPath]) } export function useDiffMode({ @@ -148,6 +167,7 @@ export function useDiffMode({ usePendingCommitDiffRequest({ activeTabPath, + onLoadDiff, onLoadDiffAtCommit, pendingCommitDiffRequest, onPendingCommitDiffHandled,