diff --git a/packages/app-core/src/store.test.ts b/packages/app-core/src/store.test.ts index 0126973e..5f54f4c6 100644 --- a/packages/app-core/src/store.test.ts +++ b/packages/app-core/src/store.test.ts @@ -1719,6 +1719,40 @@ describe('renameNote heading sync (#455)', () => { expect(writeNote).toHaveBeenCalledWith('inbox/Groceries.md', '# Groceries\n\nbody\n') }) + it('keeps the focused editor open when its rename unlink arrives first (#713)', async () => { + const pendingRename = deferred() + const renameNote = vi.fn().mockReturnValue(pendingRename.promise) + installRename({ renameNote }) + const { useStore } = await loadStore() + await useStore.getState().selectNote('inbox/Untitled.md') + + const renaming = useStore.getState().renameNote('inbox/Untitled.md', 'Groceries') + await vi.waitFor(() => expect(renameNote).toHaveBeenCalledWith('inbox/Untitled.md', 'Groceries')) + await useStore.getState().applyChange({ + kind: 'unlink', + path: 'inbox/Untitled.md', + folder: 'inbox' + }) + pendingRename.resolve(renamedMeta) + await renaming + + expect(useStore.getState().selectedPath).toBe('inbox/Groceries.md') + }) + + it('still closes the focused editor for an ordinary unlink', async () => { + installRename() + const { useStore } = await loadStore() + await useStore.getState().selectNote('inbox/Untitled.md') + + await useStore.getState().applyChange({ + kind: 'unlink', + path: 'inbox/Untitled.md', + folder: 'inbox' + }) + + expect(useStore.getState().selectedPath).toBeNull() + }) + it('leaves the body alone when the setting is off', async () => { const { writeNote, readNote } = installRename() const { useStore } = await loadStore() diff --git a/packages/app-core/src/store.ts b/packages/app-core/src/store.ts index 473f6606..5b549631 100644 --- a/packages/app-core/src/store.ts +++ b/packages/app-core/src/store.ts @@ -367,6 +367,7 @@ let coalescedNotesRefreshPending = false * IPC listener behind for the rest of the session. */ let vaultChangeUnsubscribe: (() => void) | null = null +const renamingNotePaths = new Set() function refreshNotesCoalesced(): Promise { if (coalescedNotesRefreshInFlight) { @@ -6175,6 +6176,8 @@ export const useStore = create((set, get) => { }, applyChange: async (ev) => { + if (ev.kind === 'unlink' && renamingNotePaths.has(ev.path)) return + // The live feed's unlink handling, shared with the resync path below: // a deleted note's tab closes wherever it is open. const closeUnlinkedNote = (notePath: string): void => { @@ -6620,15 +6623,20 @@ export const useStore = create((set, get) => { if (Object.values(get().noteDirty).some(Boolean)) { throw new Error('Could not rename while notes still have unsaved changes') } - const meta = await window.zen.renameNote(oldPath, nextTitle) - set((s) => renameNoteState(s, oldPath, meta)) - await get().applyFavorites( - rewriteFavoriteNotePath(get().vaultSettings.favorites, oldPath, meta.path) - ) - // Before the refresh so one listing picks up both the rename and the - // rewritten heading (excerpt, size). - await syncHeadingAfterRename(meta, get) - await get().refreshNotes() + renamingNotePaths.add(oldPath) + try { + const meta = await window.zen.renameNote(oldPath, nextTitle) + set((s) => renameNoteState(s, oldPath, meta)) + await get().applyFavorites( + rewriteFavoriteNotePath(get().vaultSettings.favorites, oldPath, meta.path) + ) + // Before the refresh so one listing picks up both the rename and the + // rewritten heading (excerpt, size). + await syncHeadingAfterRename(meta, get) + await get().refreshNotes() + } finally { + renamingNotePaths.delete(oldPath) + } } catch (err) { console.error('renameNote failed', err) }