diff --git a/apps/desktop/package.json b/apps/desktop/package.json index af6efe3e..8ab6b3fa 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@zennotes/desktop", "productName": "ZenNotes", - "version": "2.54.0", + "version": "2.54.1", "description": "ZenNotes desktop shell", "private": true, "main": "./out/main/index.js", diff --git a/apps/share-viewer/package.json b/apps/share-viewer/package.json index 47205ecc..0c5dfe45 100644 --- a/apps/share-viewer/package.json +++ b/apps/share-viewer/package.json @@ -1,7 +1,7 @@ { "name": "@zennotes/share-viewer", "private": true, - "version": "2.54.0", + "version": "2.54.1", "type": "module", "description": "Read-only renderer for publicly shared ZenNotes, embedded by the zennotes.org website", "homepage": "https://zennotes.org", diff --git a/apps/web/package.json b/apps/web/package.json index 40c3e4ec..3ca5d4f9 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@zennotes/web", "private": true, - "version": "2.54.0", + "version": "2.54.1", "type": "module", "description": "ZenNotes web client for self-hosted and hosted deployments", "homepage": "https://zennotes.org", diff --git a/package-lock.json b/package-lock.json index 344a27dc..968cc4f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zennotes-monorepo", - "version": "2.54.0", + "version": "2.54.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zennotes-monorepo", - "version": "2.54.0", + "version": "2.54.1", "hasInstallScript": true, "workspaces": [ "apps/*", @@ -23,7 +23,7 @@ }, "apps/desktop": { "name": "@zennotes/desktop", - "version": "2.54.0", + "version": "2.54.1", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.18.3", @@ -874,7 +874,7 @@ }, "apps/share-viewer": { "name": "@zennotes/share-viewer", - "version": "2.54.0", + "version": "2.54.1", "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", @@ -945,7 +945,7 @@ }, "apps/web": { "name": "@zennotes/web", - "version": "2.54.0", + "version": "2.54.1", "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", @@ -16382,7 +16382,7 @@ }, "packages/app-core": { "name": "@zennotes/app-core", - "version": "2.54.0", + "version": "2.54.1", "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", @@ -16469,14 +16469,14 @@ }, "packages/bridge-contract": { "name": "@zennotes/bridge-contract", - "version": "2.54.0", + "version": "2.54.1", "devDependencies": { "typescript": "^5.7.2" } }, "packages/shared-domain": { "name": "@zennotes/shared-domain", - "version": "2.54.0", + "version": "2.54.1", "dependencies": { "@zennotes/bridge-contract": "*", "lz-string": "^1.5.0" @@ -16488,7 +16488,7 @@ }, "packages/shared-ui": { "name": "@zennotes/shared-ui", - "version": "2.54.0" + "version": "2.54.1" } } } diff --git a/package.json b/package.json index 99add910..abf68ae8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zennotes-monorepo", "private": true, - "version": "2.54.0", + "version": "2.54.1", "description": "ZenNotes monorepo for desktop, web, and self-hosted server builds", "packageManager": "npm@10.9.2", "engines": { diff --git a/packages/app-core/package.json b/packages/app-core/package.json index 22ae6422..8ad063f7 100644 --- a/packages/app-core/package.json +++ b/packages/app-core/package.json @@ -1,7 +1,7 @@ { "name": "@zennotes/app-core", "private": true, - "version": "2.54.0", + "version": "2.54.1", "type": "module", "exports": { "./main": "./src/main.tsx", diff --git a/packages/app-core/src/components/SearchCreateForm.tsx b/packages/app-core/src/components/SearchCreateForm.tsx index a8be7e1e..8abdd83c 100644 --- a/packages/app-core/src/components/SearchCreateForm.tsx +++ b/packages/app-core/src/components/SearchCreateForm.tsx @@ -397,6 +397,9 @@ export function SearchCreateForm({ variant="ghost" size="sm" className="text-xs" + // Same reason as the footer: a blur here commits the typed tag + // as a chip, and a chips row that wraps moves this button. + onMouseDown={(e) => e.preventDefault()} onClick={() => onOpenExisting(collision.note)} > Open it Shift+↵ @@ -484,7 +487,15 @@ export function SearchCreateForm({ esc back -
+
e.preventDefault()} + > diff --git a/packages/app-core/src/components/SearchPalette.test.ts b/packages/app-core/src/components/SearchPalette.test.ts index 3d1667a6..1d198049 100644 --- a/packages/app-core/src/components/SearchPalette.test.ts +++ b/packages/app-core/src/components/SearchPalette.test.ts @@ -456,6 +456,62 @@ describe('SearchPalette: the create row opens a New note form', () => { expect(createAndOpen).toHaveBeenCalledWith('inbox', '', { title: 'Runbook', tags: ['oncall'] }) }) + // A browser moves focus to a pressed button as mousedown's default action, + // unless the event is cancelled; jsdom leaves that step to the test. The + // blur used to unmount the list under the fields and move the footer + // before mouseup, so the click never fired and the first Create was lost. + const press = async (button: HTMLElement, focusedField: HTMLInputElement): Promise => { + await act(async () => { + const uncancelled = button.dispatchEvent( + new MouseEvent('mousedown', { bubbles: true, cancelable: true }) + ) + if (uncancelled) focusedField.blur() + await Promise.resolve() + }) + } + + it('pressing the mouse on Create keeps the Tags field focused, so the click lands and the typed tag counts', async () => { + type(search(), 'Runbook') + await key(search(), 'Enter', { shiftKey: true }) + focus(field('tags')) + type(field('tags'), 'oncall') + expect(formRows('tag')).toEqual(['oncall']) + + await press(createButton(), field('tags')) + expect(document.activeElement).toBe(field('tags')) + expect(formRows('tag')).toEqual(['oncall']) + expect(chipTags()).toEqual([]) + + await act(async () => { + createButton().click() + await Promise.resolve() + }) + expect(createAndOpen).toHaveBeenCalledWith('inbox', '', { title: 'Runbook', tags: ['oncall'] }) + }) + + it('pressing the mouse on Back keeps the folder list until the click', async () => { + type(search(), 'Runbook') + await key(search(), 'Enter', { shiftKey: true }) + focus(field('folder')) + expect(formRows('folder')).toEqual(['', 'projects', 'projects/ideas', 'quick', 'archive', 'archive/old']) + + const back = [...document.querySelectorAll('button')].find( + (b) => b.textContent === 'Back' + ) + if (!back) throw new Error('Back button not rendered') + await press(back, field('folder')) + expect(document.activeElement).toBe(field('folder')) + expect(formRows('folder')).toHaveLength(6) + + await act(async () => { + back.click() + await Promise.resolve() + }) + expect(form()).toBeNull() + expect(search().value).toBe('Runbook') + expect(createAndOpen).not.toHaveBeenCalled() + }) + it('Ctrl+Enter or Cmd+Enter creates from any field', async () => { type(search(), 'Runbook') await key(search(), 'Enter', { shiftKey: true }) diff --git a/packages/bridge-contract/package.json b/packages/bridge-contract/package.json index ab5a6af8..dfe4ca24 100644 --- a/packages/bridge-contract/package.json +++ b/packages/bridge-contract/package.json @@ -1,7 +1,7 @@ { "name": "@zennotes/bridge-contract", "private": true, - "version": "2.54.0", + "version": "2.54.1", "type": "module", "exports": { "./bridge": "./src/bridge.ts", diff --git a/packages/shared-domain/package.json b/packages/shared-domain/package.json index 4a1eca7a..7eef0049 100644 --- a/packages/shared-domain/package.json +++ b/packages/shared-domain/package.json @@ -1,7 +1,7 @@ { "name": "@zennotes/shared-domain", "private": true, - "version": "2.54.0", + "version": "2.54.1", "type": "module", "exports": { "./*": "./src/*.ts" diff --git a/packages/shared-ui/package.json b/packages/shared-ui/package.json index 73cfab5c..a98b0d9b 100644 --- a/packages/shared-ui/package.json +++ b/packages/shared-ui/package.json @@ -1,7 +1,7 @@ { "name": "@zennotes/shared-ui", "private": true, - "version": "2.54.0", + "version": "2.54.1", "type": "module", "exports": { ".": "./src/index.ts"