diff --git a/packages/obsidian/src/library/CandidatePickerModal.ts b/packages/obsidian/src/library/CandidatePickerModal.ts index 0daab33..063d195 100644 --- a/packages/obsidian/src/library/CandidatePickerModal.ts +++ b/packages/obsidian/src/library/CandidatePickerModal.ts @@ -55,7 +55,10 @@ export class CandidatePickerModal extends FuzzySuggestModal { } onClose(): void { - this.settle(null); // dismissed without choosing -> treat as skip + // Obsidian's selectSuggestion() calls close() BEFORE onChooseItem(), so settling here + // synchronously would discard a real choice. Defer one tick: a choice made in the same + // tick settles first and this becomes a no-op; a genuine dismissal settles as skip. + window.setTimeout(() => this.settle(null), 0); this.contentEl.empty(); } diff --git a/tests/library-candidate-picker-modal.test.ts b/tests/library-candidate-picker-modal.test.ts index 7f5f5a1..b59988a 100644 --- a/tests/library-candidate-picker-modal.test.ts +++ b/tests/library-candidate-picker-modal.test.ts @@ -117,4 +117,13 @@ describe('CandidatePickerModal', () => { modal.onClose(); // Obsidian calls onClose() after a choice too -- must not clobber the resolved value expect(await result).toBe(0); }); + + test('REAL Obsidian order — close() fires BEFORE onChooseItem: choice still wins over dismiss-skip', async () => { + const modal = new CandidatePickerModal(fakeApp(), 'Some Note.md', CANDIDATES); + const result = modal.pick(); + // selectSuggestion() in Obsidian calls close() (→ onClose) first, THEN onChooseItem + modal.onClose(); + modal.onChooseItem({ label: 'Foo (2020)', index: 0 }, {} as MouseEvent); + expect(await result).toBe(0); + }); });