From 591107e14a63cdf37cf085dc56a268bb4ff8f6ea Mon Sep 17 00:00:00 2001 From: Adib Hanna Date: Tue, 22 Sep 2026 14:40:49 -0500 Subject: [PATCH 1/2] Fix(search): the first click on Create lands while a tag is still typed In the New note form (note search, #826), typing a tag in the Tags field and reaching for Create with the mouse lost the click. Pressing the mouse on the button moved focus to it, the Tags field blurred and committed the typed word as a chip, the suggestion row under the fields unmounted, and the footer moved up before the mouse was released. No click fired. In the built app the button moved 44 px, the release landed on the backdrop, and the palette closed with the typed name and tag gone; with a taller list the release lands inside the card and a second click works, which is how it was noticed. The footer buttons and the "Open it" button no longer take focus on mousedown, the way the folder and tag rows under the fields already did not, so nothing moves until the click completes. `create()` was already counting text still typed in Tags, the way Enter does. Found during the phone pass on core 2.54.0; the phones get it with the shell releases that adopt this core. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-01a0c45f-844c-77b8-846b-3e25b2447dab --- .../src/components/SearchCreateForm.tsx | 13 ++++- .../src/components/SearchPalette.test.ts | 56 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) 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 }) From 0285443b51889a6034dc24b5b603c0dbab099d4f Mon Sep 17 00:00:00 2001 From: Adib Hanna Date: Tue, 22 Sep 2026 14:41:09 -0500 Subject: [PATCH 2/2] Release: align desktop and shared packages at 2.54.1 Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-01a0c45f-844c-77b8-846b-3e25b2447dab --- apps/desktop/package.json | 2 +- apps/share-viewer/package.json | 2 +- apps/web/package.json | 2 +- package-lock.json | 18 +++++++++--------- package.json | 2 +- packages/app-core/package.json | 2 +- packages/bridge-contract/package.json | 2 +- packages/shared-domain/package.json | 2 +- packages/shared-ui/package.json | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) 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/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"