From 0d51e32c6d61aef2192dc37d230e6ab2e164ef5d Mon Sep 17 00:00:00 2001 From: xarmian Date: Sun, 11 Oct 2026 16:44:43 +0000 Subject: [PATCH] feat(web): quick capture keeps your place (TASK-3590) Quick capture used to open every item it created, so jotting down an interruption meant a context switch. Now "Add" saves the item, clears the title and keeps focus for the next capture, and a toast names the new ref with an Open action. "Add and open" (Ctrl/Cmd+Enter, or the mobile sheet's secondary button) keeps the old behaviour. Applies to both the desktop sidebar dialog and the mobile capture sheet. The mobile sheet clears only the text it sent, so anything typed while the create is in flight is kept. e2e/bug-3473 needs write-then-open, so it now uses Ctrl/Cmd+Enter. Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ --- web/e2e/bug-3473-write-then-open.spec.ts | 5 +- web/e2e/task-3590-quick-capture-stays.spec.ts | 85 +++++++++++++ .../layout/QuickCaptureSheet.svelte | 38 +++++- .../layout/QuickCaptureSheet.svelte.test.ts | 109 ++++++++++++++++ .../Sidebar.quickCapture.svelte.test.ts | 120 ++++++++++++++++++ web/src/lib/components/layout/Sidebar.svelte | 29 ++++- web/src/lib/items/quickCapture.ts | 26 ++++ 7 files changed, 398 insertions(+), 14 deletions(-) create mode 100644 web/e2e/task-3590-quick-capture-stays.spec.ts create mode 100644 web/src/lib/components/layout/QuickCaptureSheet.svelte.test.ts create mode 100644 web/src/lib/components/layout/Sidebar.quickCapture.svelte.test.ts create mode 100644 web/src/lib/items/quickCapture.ts diff --git a/web/e2e/bug-3473-write-then-open.spec.ts b/web/e2e/bug-3473-write-then-open.spec.ts index 5ce3e1ed1..5cc02a8b6 100644 --- a/web/e2e/bug-3473-write-then-open.spec.ts +++ b/web/e2e/bug-3473-write-then-open.spec.ts @@ -53,10 +53,11 @@ test.describe('write, then open at once (BUG-3473)', () => { const created = page.waitForResponse( (r) => r.request().method() === 'POST' && /\/collections\/docs\/items$/.test(r.url()) && r.ok(), ); - await dialog.locator('textarea.quick-add-input').press('Enter'); + // Add and open (TASK-3590: plain Enter adds and stays on the list). + await dialog.locator('textarea.quick-add-input').press('ControlOrMeta+Enter'); await created; await expect(dialog).toBeHidden(); - // The quick-add opens the new item's page; go straight back to the list. + // Add and open lands on the new item's page; go straight back to the list. await expect(page).toHaveURL(/\/docs\/[^/?]+$/); await page.goBack(); diff --git a/web/e2e/task-3590-quick-capture-stays.spec.ts b/web/e2e/task-3590-quick-capture-stays.spec.ts new file mode 100644 index 000000000..1ceaa2667 --- /dev/null +++ b/web/e2e/task-3590-quick-capture-stays.spec.ts @@ -0,0 +1,85 @@ +import { test, expect } from './fixtures'; +import { browserLogin } from './lib/collab-helpers'; + +/** + * TASK-3590: quick capture keeps your place. Adding an item used to open it, + * so jotting down an interruption was a context switch. "Add" now saves, + * stays on the current screen ready for the next capture, and confirms with a + * toast whose Open action goes to the item. "Add and open" (Ctrl/Cmd+Enter, + * or the mobile sheet's secondary button) keeps the old behaviour. + */ +test.describe('TASK-3590: quick capture keeps your place', () => { + test('desktop sidebar: Enter adds twice and stays; the toast opens the item; Ctrl/Cmd+Enter opens', async ({ page, fixture }, testInfo) => { + test.skip(testInfo.project.name !== 'desktop-chromium', 'the sidebar quick-add'); + const stamp = `qc${Date.now().toString(36)}`; + await browserLogin(page); + const listUrl = `/${fixture.adminUsername}/${fixture.workspaceSlug}/tasks`; + await page.goto(listUrl); + + const add = page.locator('button.nav-quick-add[title="New Task"]'); + await add.hover({ force: true }); + await add.click({ force: true }); + const dialog = page.locator('.quick-add-modal'); + const input = dialog.locator('textarea.quick-add-input'); + await expect(dialog).toBeVisible(); + + for (const n of [1, 2]) { + await input.fill(`${stamp} stay ${n}`); + const created = page.waitForResponse( + (r) => r.request().method() === 'POST' && /\/collections\/tasks\/items$/.test(new URL(r.url()).pathname) && r.ok(), + ); + await input.press('Enter'); + await created; + await expect(input).toHaveValue(''); + await expect(input).toBeFocused(); + } + await expect(dialog).toBeVisible(); + await expect(page).toHaveURL(new RegExp(`${listUrl}(\\?.*)?$`)); + + const toast = page.locator('.toast', { hasText: /Added TASK-\d+/ }).last(); + await expect(toast).toBeVisible(); + await input.press('Escape'); + await expect(dialog).toBeHidden(); + await toast.getByRole('button', { name: 'Open' }).click(); + await expect(page).toHaveURL(/\/tasks\/TASK-\d+/); + await expect(page.getByText(`${stamp} stay 2`).first()).toBeVisible(); + + // Add and open. + await page.goto(listUrl); + await add.hover({ force: true }); + await add.click({ force: true }); + await input.fill(`${stamp} open`); + await input.press('ControlOrMeta+Enter'); + await expect(dialog).toBeHidden(); + await expect(page).toHaveURL(/\/tasks\/TASK-\d+/); + await expect(page.getByText(`${stamp} open`).first()).toBeVisible(); + }); + + test('mobile sheet: Add stays open for the next capture; Add and open navigates', async ({ page, fixture }, testInfo) => { + test.skip(testInfo.project.name !== 'mobile-chromium', 'the mobile capture sheet'); + const stamp = `qm${Date.now().toString(36)}`; + await browserLogin(page); + const listUrl = `/${fixture.adminUsername}/${fixture.workspaceSlug}/tasks`; + await page.goto(listUrl); + + await page.getByRole('button', { name: 'Quick capture' }).click(); + const sheet = page.getByRole('dialog', { name: 'Quick capture' }); + const title = sheet.getByRole('textbox', { name: 'New item title' }); + await expect(title).toBeVisible(); + await title.fill(`${stamp} stay`); + const created = page.waitForResponse( + (r) => r.request().method() === 'POST' && /\/collections\/tasks\/items$/.test(new URL(r.url()).pathname) && r.ok(), + ); + await sheet.getByRole('button', { name: 'Add', exact: true }).click(); + await created; + await expect(title).toHaveValue(''); + await expect(sheet).toBeVisible(); + await expect(page).toHaveURL(new RegExp(`${listUrl}(\\?.*)?$`)); + await expect(page.locator('.toast', { hasText: /Added TASK-\d+/ }).last()).toBeVisible(); + + await title.fill(`${stamp} open`); + await sheet.getByRole('button', { name: 'Add and open' }).click(); + await expect(page).toHaveURL(/\/tasks\/TASK-\d+/); + await expect(page.getByText(`${stamp} open`).first()).toBeVisible(); + }); +}); diff --git a/web/src/lib/components/layout/QuickCaptureSheet.svelte b/web/src/lib/components/layout/QuickCaptureSheet.svelte index 7b7654a12..00c82e39c 100644 --- a/web/src/lib/components/layout/QuickCaptureSheet.svelte +++ b/web/src/lib/components/layout/QuickCaptureSheet.svelte @@ -1,11 +1,12 @@