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 a8be7e1ea..8abdd83c7 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 3d1667a6e..1d198049b 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 af6efe3ed..8ab6b3fa6 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 47205eccf..0c5dfe455 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 40c3e4ec6..3ca5d4f97 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 344a27dc7..968cc4f21 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 99add9101..abf68ae83 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 22ae64220..8ad063f70 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 ab5a6af81..dfe4ca240 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 4a1eca7a6..7eef00492 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 73cfab5c9..a98b0d9b1 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"