From a5ea9e0b0e4a9866c2bd3c28645cea66a718d5b8 Mon Sep 17 00:00:00 2001 From: xarmian Date: Tue, 22 Sep 2026 18:28:10 +0000 Subject: [PATCH] fix(web): quick-add is read-only while its create is in flight, and a new opening does not inherit the busy state (BUG-3115, codex r2) --- .../layout/Sidebar.quickAdd.svelte.test.ts | 20 +++++++++++++++++++ web/src/lib/components/layout/Sidebar.svelte | 16 +++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/web/src/lib/components/layout/Sidebar.quickAdd.svelte.test.ts b/web/src/lib/components/layout/Sidebar.quickAdd.svelte.test.ts index 69dc8132f..61b4dc739 100644 --- a/web/src/lib/components/layout/Sidebar.quickAdd.svelte.test.ts +++ b/web/src/lib/components/layout/Sidebar.quickAdd.svelte.test.ts @@ -90,4 +90,24 @@ describe('Sidebar quick-add — an in-flight create belongs to ONE opening of th expect(document.querySelector('#quick-add-error'), 'the old refusal was shown on the new dialog').toBeNull(); }); } + + it('the reopened dialog is usable while the old create is still in flight (codex round 2)', async () => { + render(Sidebar); + await open(); + await type('first title'); + await fireEvent.keyDown(input(), { key: 'Enter' }); + expect(pending.resolve, 'no create was sent').not.toBeNull(); + // While ITS create is in flight the opening is read-only: what was sent + // is what the result lands on. + expect(input().readOnly, 'typing is possible while the create is in flight').toBe(true); + + await fireEvent.keyDown(input(), { key: 'Escape' }); + await tick(); + await open(); + await type('second title'); + await tick(); + expect(input().readOnly, 'the new opening inherited the old one\'s busy state').toBe(false); + const create = document.querySelector('.quick-add-btn'); + expect(create?.disabled, 'Create is disabled until an unrelated request settles').toBe(false); + }); }); diff --git a/web/src/lib/components/layout/Sidebar.svelte b/web/src/lib/components/layout/Sidebar.svelte index fe4a62251..26f9bca98 100644 --- a/web/src/lib/components/layout/Sidebar.svelte +++ b/web/src/lib/components/layout/Sidebar.svelte @@ -138,6 +138,7 @@ function startQuickAdd(coll: Collection) { quickAddSession++; + quickAddSubmitting = false; quickAddCollection = coll; quickAddTitle = ''; quickAddError = null; @@ -163,6 +164,9 @@ function cancelQuickAdd() { quickAddSession++; + // The in-flight create belongs to the opening just closed; a new one + // must not wait on it (codex round 2). + quickAddSubmitting = false; quickAddCollection = null; quickAddTitle = ''; quickAddError = null; @@ -212,7 +216,7 @@ toastStore.show(err?.message || 'Failed to create item', 'error'); } } finally { - quickAddSubmitting = false; + if (quickAddSession === session) quickAddSubmitting = false; } } @@ -230,7 +234,7 @@ } function togglePicker() { - if (!canSwitchCollection) return; + if (!canSwitchCollection || quickAddSubmitting) return; pickerOpen = !pickerOpen; if (pickerOpen) { const i = pickerCollections.findIndex(c => c.id === quickAddCollection?.id); @@ -240,6 +244,9 @@ } function selectCollection(coll: Collection) { + // While a create is in flight the dialog is read-only: what was sent is + // what the result lands on (codex round 2). + if (quickAddSubmitting) return; quickAddCollection = coll; pickerOpen = false; requestAnimationFrame(() => quickAddInputEl?.focus()); @@ -751,14 +758,14 @@