From 6de8a2570b9034ab8066611e1fe703ae16b0ce8b Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 20 Mar 2026 12:54:20 +0100 Subject: [PATCH] fix(tui): stop evicting unrelated default keybindings closes #2455 --- packages/tui/CHANGELOG.md | 4 +++ packages/tui/src/keybindings.ts | 13 ++------- packages/tui/test/keybindings.test.ts | 38 +++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 packages/tui/test/keybindings.test.ts diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 5b108b1d..a96d14d1 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed shared keybinding resolution to stop user overrides from evicting unrelated default shortcuts such as selector confirm and editor cursor keys ([#2455](https://github.com/badlogic/pi-mono/issues/2455)) + ## [0.61.0] - 2026-03-20 ### Breaking Changes diff --git a/packages/tui/src/keybindings.ts b/packages/tui/src/keybindings.ts index 014e1a59..356554cf 100644 --- a/packages/tui/src/keybindings.ts +++ b/packages/tui/src/keybindings.ts @@ -185,19 +185,10 @@ export class KeybindingsManager { } for (const [id, definition] of Object.entries(this.definitions)) { - const defaults = normalizeKeys(definition.defaultKeys); - const keys = defaults.filter((key) => { - const claimants = userClaims.get(key); - if (!claimants) return true; - return claimants.size === 1 && claimants.has(id as Keybinding); - }); + const userKeys = this.userBindings[id]; + const keys = userKeys === undefined ? normalizeKeys(definition.defaultKeys) : normalizeKeys(userKeys); this.keysById.set(id as Keybinding, keys); } - - for (const [keybinding, keys] of Object.entries(this.userBindings)) { - if (!(keybinding in this.definitions)) continue; - this.keysById.set(keybinding as Keybinding, normalizeKeys(keys)); - } } matches(data: string, keybinding: Keybinding): boolean { diff --git a/packages/tui/test/keybindings.test.ts b/packages/tui/test/keybindings.test.ts new file mode 100644 index 00000000..7e255b0a --- /dev/null +++ b/packages/tui/test/keybindings.test.ts @@ -0,0 +1,38 @@ +import assert from "node:assert"; +import { describe, it } from "node:test"; +import { KeybindingsManager, TUI_KEYBINDINGS } from "../src/keybindings.js"; + +describe("KeybindingsManager", () => { + it("does not evict selector confirm when input submit is rebound", () => { + const keybindings = new KeybindingsManager(TUI_KEYBINDINGS, { + "tui.input.submit": ["enter", "ctrl+enter"], + }); + + assert.deepStrictEqual(keybindings.getKeys("tui.input.submit"), ["enter", "ctrl+enter"]); + assert.deepStrictEqual(keybindings.getKeys("tui.select.confirm"), ["enter"]); + }); + + it("does not evict cursor bindings when another action reuses the same key", () => { + const keybindings = new KeybindingsManager(TUI_KEYBINDINGS, { + "tui.select.up": ["up", "ctrl+p"], + }); + + assert.deepStrictEqual(keybindings.getKeys("tui.select.up"), ["up", "ctrl+p"]); + assert.deepStrictEqual(keybindings.getKeys("tui.editor.cursorUp"), ["up"]); + }); + + it("still reports direct user binding conflicts without evicting defaults", () => { + const keybindings = new KeybindingsManager(TUI_KEYBINDINGS, { + "tui.input.submit": "ctrl+x", + "tui.select.confirm": "ctrl+x", + }); + + assert.deepStrictEqual(keybindings.getConflicts(), [ + { + key: "ctrl+x", + keybindings: ["tui.input.submit", "tui.select.confirm"], + }, + ]); + assert.deepStrictEqual(keybindings.getKeys("tui.editor.cursorLeft"), ["left", "ctrl+b"]); + }); +});