fix(sidebar): scope the del shortcut without backspace (#10644)

* fix: scope the del shortcut without backspace

* test: add e2e
This commit is contained in:
George authored and GitHub committed 2026-10-22 07:49:51 +00:00
1 parent 59d832ba10
commit 7498c30b57
4 files changed
+56 -34

No files matched your search

@@ -26,7 +26,6 @@ export const keyboardShortcutDescriptions: Record<KeyboardShortcut, string> = {
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 }],
@@ -47,7 +47,6 @@ export type KeyboardShortcut =
| 'request_showGenerateCodeEditor'
| 'sidebar_focusFilter'
| 'sidebar_showCreateDropdown'
| 'sidebar_deleteItem'
| 'sidebar_toggle'
| 'sidebar_rename'
| 'response_focus'
@@ -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"])');
@@ -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) {