From 7498c30b57e4aa17c00a3bf11ac55a93b29c3d9b Mon Sep 17 00:00:00 2001 From: George Date: Thu, 22 Oct 2026 15:49:51 +0800 Subject: [PATCH] fix(sidebar): scope the del shortcut without backspace (#10644) * fix: scope the del shortcut without backspace * test: add e2e --- packages/insomnia-data/common-src/hotkeys.ts | 5 -- packages/insomnia-data/common-src/settings.ts | 1 - .../tests/smoke/focus-and-keyboard.test.ts | 32 ++++++++++++ .../project-navigation-sidebar.tsx | 52 +++++++++---------- 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/packages/insomnia-data/common-src/hotkeys.ts b/packages/insomnia-data/common-src/hotkeys.ts index 2f69718f37..3e764cd117 100644 --- a/packages/insomnia-data/common-src/hotkeys.ts +++ b/packages/insomnia-data/common-src/hotkeys.ts @@ -26,7 +26,6 @@ export const keyboardShortcutDescriptions: Record = { request_showGenerateCodeEditor: 'Generate Code', sidebar_focusFilter: 'Filter Sidebar', sidebar_showCreateDropdown: 'Open Create Dropdown', - sidebar_deleteItem: 'Delete Sidebar Item', sidebar_toggle: 'Toggle Sidebar', sidebar_rename: 'Rename Item', response_focus: 'Focus Response', @@ -130,10 +129,6 @@ const defaultRegistry: HotKeyRegistry = { macKeys: [{ meta: true, keyCode: keyboardKeys.n.keyCode }], winLinuxKeys: [{ ctrl: true, keyCode: keyboardKeys.n.keyCode }], }, - sidebar_deleteItem: { - macKeys: [{ keyCode: keyboardKeys.delete.keyCode }, { keyCode: keyboardKeys.backspace.keyCode }], - winLinuxKeys: [{ keyCode: keyboardKeys.delete.keyCode }, { keyCode: keyboardKeys.backspace.keyCode }], - }, sidebar_toggle: { macKeys: [{ meta: true, keyCode: keyboardKeys.backslash.keyCode }], winLinuxKeys: [{ ctrl: true, keyCode: keyboardKeys.backslash.keyCode }], diff --git a/packages/insomnia-data/common-src/settings.ts b/packages/insomnia-data/common-src/settings.ts index cccc548251..d6cb55c83d 100644 --- a/packages/insomnia-data/common-src/settings.ts +++ b/packages/insomnia-data/common-src/settings.ts @@ -47,7 +47,6 @@ export type KeyboardShortcut = | 'request_showGenerateCodeEditor' | 'sidebar_focusFilter' | 'sidebar_showCreateDropdown' - | 'sidebar_deleteItem' | 'sidebar_toggle' | 'sidebar_rename' | 'response_focus' diff --git a/packages/insomnia-smoke-test/tests/smoke/focus-and-keyboard.test.ts b/packages/insomnia-smoke-test/tests/smoke/focus-and-keyboard.test.ts index eeb6152922..8120641a41 100644 --- a/packages/insomnia-smoke-test/tests/smoke/focus-and-keyboard.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/focus-and-keyboard.test.ts @@ -10,6 +10,7 @@ import { test } from '../../playwright/test'; // - create/rename/settings dialogs focus the Name field // - the navigation sidebar expands/collapses folders with Left/Right arrows // - Cmd/Ctrl-N adds the new request inside the selected folder +// - Delete on a focused sidebar row asks to delete it; Backspace never does // // CodeMirror-backed editors (OneLineEditor) expose focus via `data-focused="on"` on their // `.editor__container`, which is what these tests assert against. @@ -150,6 +151,37 @@ test.describe('Focus and keyboard navigation', () => { await expect.soft(page.getByLabel('Collapse test folder')).toBeVisible(); }); + test('Delete on a focused sidebar row opens the delete confirmation, Backspace does not', async ({ page }) => { + const folderRow = page.locator('[role="row"]:has([data-testid="request-node-test folder"])'); + const deleteDialog = page.getByRole('dialog').filter({ hasText: 'Delete Folder' }); + + // Backspace must not delete (it's reserved for editing text), even with a row focused. + await folderRow.focus(); + await folderRow.press('Backspace'); + await expect.soft(deleteDialog).toBeHidden(); + + // Delete on the focused row asks for confirmation; cancelling keeps the folder. + await folderRow.press('Delete'); + await expect.soft(deleteDialog).toBeVisible(); + await deleteDialog.getByRole('button', { name: 'Cancel' }).click(); + await expect.soft(deleteDialog).toBeHidden(); + await expect.soft(page.getByTestId('request-node-test folder')).toBeVisible(); + + // Confirming removes it. + await folderRow.press('Delete'); + await deleteDialog.getByRole('button', { name: 'Delete' }).click(); + await expect.soft(page.getByTestId('request-node-test folder')).toBeHidden(); + }); + + test('Delete inside the sidebar filter edits text and does not open the delete confirmation', async ({ + page, + insomnia, + }) => { + await insomnia.navigationSidebar.fillFilter('test'); + await insomnia.navigationSidebar.filterInput.press('Delete'); + await expect.soft(page.getByRole('dialog')).toBeHidden(); + }); + test('Cmd/Ctrl-N adds the request inside the selected folder', async ({ page }) => { const folderRow = page.locator('[role="row"]:has([data-testid="request-node-test folder"])'); diff --git a/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/project-navigation-sidebar.tsx b/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/project-navigation-sidebar.tsx index 1e17e13885..559835ec7b 100644 --- a/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/project-navigation-sidebar.tsx +++ b/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/project-navigation-sidebar.tsx @@ -837,33 +837,6 @@ const ProjectNavigationSidebarInner = ( }); }); }, - sidebar_deleteItem: event => { - // Only act when a row itself is focused, not an inner control (button/input), so Backspace keeps editing text. - const active = document.activeElement; - if (!(active instanceof HTMLElement) || active.closest('[data-key]') !== active) { - return; - } - const { focusedItem } = getFocusedItem(); - if ( - !focusedItem || - (focusedItem.kind !== 'project' && - focusedItem.kind !== 'workspace' && - focusedItem.kind !== 'collectionChild' && - focusedItem.kind !== 'pinnedRequest') - ) { - return; - } - - event.preventDefault(); - window.main.trackAnalyticsEvent({ - event: AnalyticsEvent.keyboardShortcutUsed, - properties: { - source: 'navigation-sidebar', - action: 'deleteItem', - }, - }); - showDeleteModal(focusedItem); - }, }); const { hasKonnectPat } = settings; @@ -991,7 +964,7 @@ const ProjectNavigationSidebarInner = ( className="group/tree flex-1 overflow-y-auto pb-(--padding-sm)" data-testid="project-navigation-tree-container" onKeyDownCapture={(e: React.KeyboardEvent) => { - if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') { + if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft' && e.key !== 'Delete') { return; } const active = document.activeElement; @@ -1003,6 +976,29 @@ const ProjectNavigationSidebarInner = ( if (!rowEl || rowEl !== active) { return; } + if (e.key === 'Delete') { + const { focusedItem } = getFocusedItem(); + if ( + !focusedItem || + (focusedItem.kind !== 'project' && + focusedItem.kind !== 'workspace' && + focusedItem.kind !== 'collectionChild' && + focusedItem.kind !== 'pinnedRequest') + ) { + return; + } + e.preventDefault(); + e.stopPropagation(); + window.main.trackAnalyticsEvent({ + event: AnalyticsEvent.keyboardShortcutUsed, + properties: { + source: 'navigation-sidebar', + action: 'deleteItem', + }, + }); + showDeleteModal(focusedItem); + return; + } const docId = (active.dataset.key || '').replace(/^pinned-request-/, ''); const item = visibleFlatItems.find(i => i.doc._id === docId && i.kind !== 'pinnedRequest'); if (!item) {