From 357455d97996bc7873d831eab73be4c4c1b8a973 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Wed, 22 Apr 2026 19:43:43 -0400 Subject: [PATCH] fix(ext/popup): don't eat '/' and other keystrokes while typing in inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: item-list's global "/" shortcut (focus search) and "+" shortcut (new item) fired even when focus was inside any input/textarea other than the list's own search field. This ate forward-slashes typed into the setup wizard's host-url field and the add/edit form's notes area, and would have done the same for any printable shortcut in a future text field. Root cause: the handler was attached to `document`, stays attached when the user opens an item (and its click-handler navigated without removing the listener), and only excluded the search field by id. Fix: - Add isEditableTarget() helper — returns true for INPUT/TEXTAREA/SELECT and contenteditable elements. Global shortcut handlers bail early when this fires, passing the keystroke through to the field. - Apply the same guard in item-detail.ts (previously only guarded against INPUT, missing TEXTAREA + contenteditable). - Remove handleListKeydown on row-click so it doesn't linger on detail/edit views even before the route-transition keydown listeners install. - Escape in the list view still works from inside an editable field — only the printable-character interceptions are gated. Co-Authored-By: Claude Opus 4.7 (1M context) --- extension/src/popup/components/item-detail.ts | 8 +++++- extension/src/popup/components/item-list.ts | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/extension/src/popup/components/item-detail.ts b/extension/src/popup/components/item-detail.ts index 54957ba..e00f121 100644 --- a/extension/src/popup/components/item-detail.ts +++ b/extension/src/popup/components/item-detail.ts @@ -207,7 +207,13 @@ function renderLogin(app: HTMLElement, item: Item): void { // --- Keyboard shortcuts --- const handler = async (e: KeyboardEvent) => { - if ((e.target as HTMLElement).tagName === 'INPUT') return; + // Bail if the user is typing into any editable field — don't steal + // printable keystrokes meant for an input/textarea/contenteditable element. + const t = e.target; + if (t instanceof HTMLElement) { + const tag = t.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || t.isContentEditable) return; + } switch (e.key) { case 'Escape': diff --git a/extension/src/popup/components/item-list.ts b/extension/src/popup/components/item-list.ts index 8e3a569..db62bd7 100644 --- a/extension/src/popup/components/item-list.ts +++ b/extension/src/popup/components/item-list.ts @@ -97,6 +97,7 @@ export function renderItemList(app: HTMLElement): void { rows.forEach(row => { row.addEventListener('click', async () => { const id = (row as HTMLElement).dataset.id!; + document.removeEventListener('keydown', handleListKeydown); await openItem(id); }); }); @@ -141,11 +142,35 @@ function getFilteredEntries(): Array<[ItemId, ManifestEntry]> { return filtered; } +/// True if the event target is an editable field (input/textarea/contenteditable). +/// Global shortcut handlers should bail when the user is typing into a field — +/// otherwise printable characters like "/" and "+" get eaten by the shortcut +/// routing and never reach the input. +function isEditableTarget(target: EventTarget | null): boolean { + if (!(target instanceof HTMLElement)) return false; + const tag = target.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true; + if (target.isContentEditable) return true; + return false; +} + function handleListKeydown(e: KeyboardEvent): void { const state = getState(); const target = e.target as HTMLElement; const isSearch = target.id === 'search-input'; + // If the user is typing into any input/textarea (other than the list's own + // search field, which we want to focus on "/" even from outside it), let the + // keystroke through. The "/" shortcut below is specifically "jump to search + // from the list," not "steal printable characters while typing." + if (isEditableTarget(target) && !isSearch) { + if (e.key === 'Escape') { + document.removeEventListener('keydown', handleListKeydown); + window.close(); + } + return; + } + if (e.key === '/' && !isSearch) { e.preventDefault(); (document.getElementById('search-input') as HTMLInputElement | null)?.focus();