| author | |
| committer | |
| log | b1508672a63c4799fed6d9091001de00175d9316 |
| tree | e2cafb8d5a04b9849ec16af4da90e3f3fcf907fc |
| parent | 22ccf8a3dfc9094b4ccdb689e128c67bacbbcdf5 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>4 files changed, 62 insertions(+), 13 deletions(-)
apps/server/src/keybindings.test.ts+17-5| ... | ... | @@ -258,12 +258,16 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 258 | 258 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 259 | 259 | { key: "mod+shift+y", command: "terminal.toggle" }, |
| 260 | 260 | { key: "mod+shift+r", command: "script.run-tests.run" }, |
| 261 | { key: "mod+shift+b", command: "script.custom-panel.run" }, | |
| 262 | { | |
| 263 | key: "mod+shift+b", | |
| 264 | command: "script.custom-composer.run", | |
| 265 | when: "composerFocus", | |
| 266 | }, | |
| 261 | 267 | ]); |
| 262 | 268 | |
| 263 | yield* Effect.gen(function* () { | |
| 264 | const keybindings = yield* Keybindings.Keybindings; | |
| 265 | yield* keybindings.syncDefaultKeybindingsOnStartup; | |
| 266 | }); | |
| 269 | const keybindings = yield* Keybindings.Keybindings; | |
| 270 | yield* keybindings.syncDefaultKeybindingsOnStartup; | |
| 267 | 271 | |
| 268 | 272 | const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); |
| 269 | 273 | const byCommand = new Map(persisted.map((entry) => [entry.command, entry])); |
| ... | ... | @@ -279,6 +283,14 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 279 | 283 | assert.isTrue(byCommand.has(defaultRule.command), `expected ${defaultRule.command}`); |
| 280 | 284 | } |
| 281 | 285 | assert.isTrue(byCommand.has("script.run-tests.run")); |
| 286 | ||
| 287 | const configState = yield* keybindings.loadConfigState; | |
| 288 | assert.deepEqual( | |
| 289 | configState.keybindings | |
| 290 | .filter((entry) => entry.shortcut.key === "b" && entry.shortcut.shiftKey) | |
| 291 | .map((entry) => entry.command), | |
| 292 | ["threadPanel.toggle", "script.custom-panel.run", "script.custom-composer.run"], | |
| 293 | ); | |
| 282 | 294 | }).pipe(Effect.provide(layerKeybindings())), |
| 283 | 295 | ); |
| 284 | 296 | |
| ... | ... | @@ -295,8 +307,8 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 295 | 307 | |
| 296 | 308 | yield* keybindings.syncDefaultKeybindingsOnStartup; |
| 297 | 309 | assert.deepStrictEqual(yield* backgroundRules, [ |
| 298 | existing, | |
| 299 | 310 | { key: "mod+enter", command: "composer.sendBackground", when }, |
| 311 | existing, | |
| 300 | 312 | ]); |
| 301 | 313 | |
| 302 | 314 | // Removing the added rule later must survive the next startup. |
apps/server/src/keybindings.ts+3-3| ... | ... | @@ -592,8 +592,8 @@ const make = Effect.gen(function* () { |
| 592 | 592 | } |
| 593 | 593 | } |
| 594 | 594 | |
| 595 | // Startup backfill must never evict persisted user rules: append only | |
| 596 | // the defaults that fit and skip the rest. | |
| 595 | // Keep only defaults that fit, before persisted rules so existing | |
| 596 | // custom shortcuts retain priority when their conditions overlap. | |
| 597 | 597 | const availableSlots = Math.max(0, MAX_KEYBINDINGS_COUNT - customConfig.length); |
| 598 | 598 | const defaultsToAppend = missingDefaults.slice(0, availableSlots); |
| 599 | 599 | const skippedDefaults = missingDefaults.slice(availableSlots); |
| ... | ... | @@ -605,7 +605,7 @@ const make = Effect.gen(function* () { |
| 605 | 605 | }); |
| 606 | 606 | } |
| 607 | 607 | if (defaultsToAppend.length > 0) { |
| 608 | yield* writeConfigAtomically([...customConfig, ...defaultsToAppend]); | |
| 608 | yield* writeConfigAtomically([...defaultsToAppend, ...customConfig]); | |
| 609 | 609 | } |
| 610 | 610 | // A late default skipped at max entries stays pending for a later start. |
| 611 | 611 | const settledLateDefaults = pendingLateDefaults.filter( |
apps/web/src/keybindings.test.ts+41-5| ... | ... | @@ -748,16 +748,52 @@ describe("chat/editor shortcuts", () => { |
| 748 | 748 | ); |
| 749 | 749 | }); |
| 750 | 750 | |
| 751 | it("resolves a user-configured thread panel shortcut without assigning a default", () => { | |
| 752 | const bindings = compile([ | |
| 753 | { shortcut: modShortcut("b", { shiftKey: true }), command: "threadPanel.toggle" }, | |
| 754 | ]); | |
| 751 | it("toggles the thread panel by default without taking terminal keystrokes", () => { | |
| 752 | for (const platform of ["MacIntel", "Win32", "Linux"]) { | |
| 753 | const input = event({ | |
| 754 | key: "b", | |
| 755 | metaKey: platform === "MacIntel", | |
| 756 | ctrlKey: platform !== "MacIntel", | |
| 757 | shiftKey: true, | |
| 758 | }); | |
| 759 | assert.strictEqual( | |
| 760 | resolveShortcutCommand(input, DEFAULT_RESOLVED_KEYBINDINGS, { | |
| 761 | platform, | |
| 762 | context: { terminalFocus: false, editableFocus: true }, | |
| 763 | }), | |
| 764 | "threadPanel.toggle", | |
| 765 | ); | |
| 766 | assert.isNull( | |
| 767 | resolveShortcutCommand(input, DEFAULT_RESOLVED_KEYBINDINGS, { | |
| 768 | platform, | |
| 769 | context: { terminalFocus: true }, | |
| 770 | }), | |
| 771 | ); | |
| 772 | } | |
| 773 | }); | |
| 774 | ||
| 775 | it("preserves a user-configured thread panel shortcut instead of its default", () => { | |
| 776 | const bindings = mergeWithDefaultKeybindings( | |
| 777 | compile([ | |
| 778 | { | |
| 779 | shortcut: modShortcut("b", { altKey: true, shiftKey: true }), | |
| 780 | command: "threadPanel.toggle", | |
| 781 | }, | |
| 782 | ]), | |
| 783 | ); | |
| 755 | 784 | |
| 756 | 785 | assert.strictEqual( |
| 786 | resolveShortcutCommand( | |
| 787 | event({ key: "b", metaKey: true, altKey: true, shiftKey: true }), | |
| 788 | bindings, | |
| 789 | { platform: "MacIntel" }, | |
| 790 | ), | |
| 791 | "threadPanel.toggle", | |
| 792 | ); | |
| 793 | assert.isNull( | |
| 757 | 794 | resolveShortcutCommand(event({ key: "b", metaKey: true, shiftKey: true }), bindings, { |
| 758 | 795 | platform: "MacIntel", |
| 759 | 796 | }), |
| 760 | "threadPanel.toggle", | |
| 761 | 797 | ); |
| 762 | 798 | }); |
| 763 | 799 |
packages/shared/src/keybindings.ts+1| ... | ... | @@ -126,6 +126,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray<KeybindingRule> = [ |
| 126 | 126 | { key: "mod+]", command: "navigation.forward", when: "!terminalFocus" }, |
| 127 | 127 | { key: "mod+j", command: "terminal.toggle" }, |
| 128 | 128 | { key: "mod+alt+b", command: "rightPanel.toggle" }, |
| 129 | { key: "mod+shift+b", command: "threadPanel.toggle", when: "!terminalFocus" }, | |
| 129 | 130 | { key: "mod+t", command: "rightPanel.new", when: "rightPanelOpen && !terminalFocus" }, |
| 130 | 131 | { key: "mod+d", command: "terminal.split", when: "terminalFocus" }, |
| 131 | 132 | { key: "mod+shift+d", command: "terminal.splitVertical", when: "terminalFocus" }, |