From 2c4d865ef90c81593c02f844b0fda278fcd9c604 Mon Sep 17 00:00:00 2001 From: Said Turksever <28983926+asturksever@users.noreply.github.com> Date: Fri, 13 Nov 2026 01:12:58 +0000 Subject: [PATCH 1/3] fix(shortcuts): a shortcut key held with Cmd, Ctrl or Alt runs no shortcut The application shortcut listener read the key alone, so copying with Cmd+C or Ctrl+C toggled the CCTV layer and opened the nearest camera, Cmd+F opened the layers panel and Cmd+1 to 6 changed the visual style. Chords are now left to the browser. Shift is not a chord, a held key still repeats, and Escape dismisses search either way. --- docs/CURRENT-STATE.md | 7 +++++-- src/ui/applicationShortcuts.js | 10 +++++++++- src/ui/visualInput.test.mjs | 19 ++++++++++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index bdfefe043..0e4b03194 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -2325,8 +2325,11 @@ Browser acceptance: `QA_BASE_URL=http://127.0.0.1:4173 node scripts/qa-segmented `ui/input` supplies the bubbling application shortcut listener and generated shader-parameter rows. Number/style keys, H/O/V/F/D/C actions, native form-control -typing and Escape behavior retain their existing mappings. Capture-phase -surfaces continue to arbitrate their own keyboard events first. +typing and Escape behavior retain their existing mappings. A shortcut key held +with Cmd, Ctrl or Alt runs no shortcut, so the browser's own chord (Cmd+C to +copy, Cmd+F to find, Cmd+1 to switch tab) does not also toggle CCTV, the layers +panel or a style; Shift does not count, and Escape dismisses search either way. +Capture-phase surfaces continue to arbitrate their own keyboard events first. The UI facade retains shader values, share-restore authority, render requests, search dismissal, visibility and Cockpit portal policy. Parameter rows preserve diff --git a/src/ui/applicationShortcuts.js b/src/ui/applicationShortcuts.js index 57214a570..195a557f7 100644 --- a/src/ui/applicationShortcuts.js +++ b/src/ui/applicationShortcuts.js @@ -10,7 +10,8 @@ const STYLE_KEYS = Object.freeze({ /** * Bind the application's existing bubbling keyboard shortcuts. * Capture-phase surfaces keep first refusal. The caller owns each action and - * persistence; form controls keep native typing except for Escape. + * persistence; form controls keep native typing except for Escape, and a key + * held with Cmd, Ctrl or Alt is left to the browser. * @param {object} options * @param {Document} options.documentRef Keyboard event target. * @param {HTMLElement} options.searchInput Additional editing target. @@ -27,6 +28,13 @@ export function bindApplicationShortcuts({ event.target?.matches?.('select, input, textarea') || event.target === searchInput; if (isFormControl && event.key !== 'Escape') return; + // Cmd+C, Ctrl+F, Cmd+1 and the like belong to the browser and the system. + // Shift is not a chord here: Shift+C is still C. + if ( + (event.metaKey || event.ctrlKey || event.altKey) && + event.key !== 'Escape' + ) + return; if (STYLE_KEYS[event.key]) actions.setStyle(STYLE_KEYS[event.key]); if (event.key === 'Escape') actions.dismissSearch(); diff --git a/src/ui/visualInput.test.mjs b/src/ui/visualInput.test.mjs index d49a19bda..541e8775f 100644 --- a/src/ui/visualInput.test.mjs +++ b/src/ui/visualInput.test.mjs @@ -117,10 +117,23 @@ test('the supplied search target retains editing even without form markup', () = assert.deepEqual(f.calls, [['dismissSearch']]); }); -test('shortcut extraction does not change repeat or modifier policy', () => { +test('a held key still repeats its shortcut, and Shift is not a chord', () => { const f = shortcuts(); - f.press('h', new Element(), { repeat: true, ctrlKey: true }); - assert.deepEqual(f.calls, [['toggleHud']]); + f.press('h', new Element(), { repeat: true }); + f.press('C', new Element(), { shiftKey: true }); + assert.deepEqual(f.calls, [['toggleHud'], ['toggleCctv']]); +}); + +test('browser and system chords on a shortcut key run no shortcut', () => { + const f = shortcuts(); + for (const modifier of ['metaKey', 'ctrlKey', 'altKey']) + for (const key of ['1', '6', 'h', 'o', 'v', 'f', 'd', 'c', 'C']) + f.press(key, new Element(), { [modifier]: true }); + // Copying (Cmd+C, Ctrl+C) used to turn CCTV on and open the nearest camera. + assert.deepEqual(f.calls, []); + f.press('c'); + f.press('Escape', new Element(), { ctrlKey: true }); + assert.deepEqual(f.calls, [['toggleCctv'], ['dismissSearch']]); }); test('destroy synchronously removes shortcuts and a replacement binds once', () => { From 8ac0d95f1bc5b4a8ec418ac3c8454a1af06637b1 Mon Sep 17 00:00:00 2001 From: Said Turksever <28983926+asturksever@users.noreply.github.com> Date: Fri, 13 Nov 2026 01:13:42 +0000 Subject: [PATCH 2/3] docs(changelog): shortcut keys ignore Cmd, Ctrl and Alt chords (#1691) --- changelog.d/1691-shortcuts-ignore-modifier-chords.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog.d/1691-shortcuts-ignore-modifier-chords.md diff --git a/changelog.d/1691-shortcuts-ignore-modifier-chords.md b/changelog.d/1691-shortcuts-ignore-modifier-chords.md new file mode 100644 index 000000000..4a850507e --- /dev/null +++ b/changelog.d/1691-shortcuts-ignore-modifier-chords.md @@ -0,0 +1,4 @@ +- Leave browser and system chords alone: a shortcut key held with Cmd, Ctrl or + Alt no longer runs its single-key shortcut. Copying with Cmd+C or Ctrl+C used + to turn the CCTV layer on and open the nearest camera, Cmd+F opened the layers + panel and Cmd+1 to 6 changed the visual style (asturksever, #1691). From 1d476c87eeaf40c499b07ca03d6f48055aace242 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 12 Nov 2026 18:02:41 -0800 Subject: [PATCH 3/3] fix(location): a POI key held with Cmd, Ctrl or Alt runs no POI The points-of-interest keys (Q/W/E/R/T while a city is expanded) are a second bare-key document listener, and it did not look at modifiers either: Alt+R or a delivered Ctrl+R picked the fourth POI and flew the camera there. It now leaves Cmd, Ctrl and Alt chords to the browser like the application shortcuts; Shift still counts as the key. The docs and the changelog entry say so. --- changelog.d/1691-shortcuts-ignore-modifier-chords.md | 3 ++- docs/CURRENT-STATE.md | 2 ++ src/ui/locationControls.js | 3 +++ src/ui/locationControls.test.mjs | 10 ++++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/changelog.d/1691-shortcuts-ignore-modifier-chords.md b/changelog.d/1691-shortcuts-ignore-modifier-chords.md index 4a850507e..6bc84f8f8 100644 --- a/changelog.d/1691-shortcuts-ignore-modifier-chords.md +++ b/changelog.d/1691-shortcuts-ignore-modifier-chords.md @@ -1,4 +1,5 @@ - Leave browser and system chords alone: a shortcut key held with Cmd, Ctrl or Alt no longer runs its single-key shortcut. Copying with Cmd+C or Ctrl+C used to turn the CCTV layer on and open the nearest camera, Cmd+F opened the layers - panel and Cmd+1 to 6 changed the visual style (asturksever, #1691). + panel and Cmd+1 to 6 changed the visual style. The Q/W/E/R/T keys of an + expanded city's points of interest follow the same rule (asturksever, #1691). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 0e4b03194..6b9628030 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -2329,6 +2329,8 @@ typing and Escape behavior retain their existing mappings. A shortcut key held with Cmd, Ctrl or Alt runs no shortcut, so the browser's own chord (Cmd+C to copy, Cmd+F to find, Cmd+1 to switch tab) does not also toggle CCTV, the layers panel or a style; Shift does not count, and Escape dismisses search either way. +The Q/W/E/R/T keys of an expanded city's points of interest follow the same +rule. Capture-phase surfaces continue to arbitrate their own keyboard events first. The UI facade retains shader values, share-restore authority, render requests, diff --git a/src/ui/locationControls.js b/src/ui/locationControls.js index 6fd827788..388fb51e1 100644 --- a/src/ui/locationControls.js +++ b/src/ui/locationControls.js @@ -88,6 +88,9 @@ export class LocationControls { event.target === elements.search ) return; + // As for the application shortcuts: Cmd+R, Ctrl+W, Alt+E and the like + // belong to the browser and the system. Shift is not a chord. + if (event.metaKey || event.ctrlKey || event.altKey) return; const index = POI_KEYS.indexOf(event.key.toUpperCase()); if (index !== -1 && index < (cities[cityId]?.pois.length || 0)) onPoi(cityId, index); diff --git a/src/ui/locationControls.test.mjs b/src/ui/locationControls.test.mjs index ccae6454b..2863564c8 100644 --- a/src/ui/locationControls.test.mjs +++ b/src/ui/locationControls.test.mjs @@ -191,6 +191,16 @@ test('location and POI keys route once while form controls retain typing', () => f.controls.reset(); assert.deepEqual(f.calls, [['poi', 'a', 1], ['reset']]); }); +test('POI keys held with Cmd, Ctrl or Alt run no POI; Shift still does', () => { + const f = fixture(); + const target = { matches: () => false }; + for (const modifier of ['metaKey', 'ctrlKey', 'altKey']) + for (const key of ['q', 'w', 'r', 'R']) + f.doc.fire('keydown', { key, target, [modifier]: true }); + assert.deepEqual(f.calls, []); + f.doc.fire('keydown', { key: 'W', target, shiftKey: true }); + assert.deepEqual(f.calls, [['poi', 'a', 1]]); +}); test('typing clears a failed search error and closing the emptied field drops it too', () => { const f = fixture(); f.elements.search.value = 'Pariss';