Skip to content

Commit 2e3dc14

Browse files
alicodingclaude
andcommitted
feat: cmd+shift+bracket aliases for tab cycling
Add the browser-convention Cmd+Shift+] / Cmd+Shift+[ as extraBindings on tab.next/tab.prev, alongside the existing Ctrl+Tab defaults -- the same always-on alias mechanism palette.open already uses. keyFromEventCode grows BracketLeft/BracketRight support (shift- independent, like every key it maps). No collisions: checked against RESERVED_COMBOS, all command bindings, and the native menu. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FJ8wStsHyu7XPLTspNjMnQ
1 parent b74683d commit 2e3dc14

4 files changed

Lines changed: 35 additions & 0 deletions

File tree

frontend/src/shared/commands.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ describe('dispatchCommandForEvent with extraBindings', () => {
4747
expect(dispatchCommandForEvent(event({ code: 'KeyP', metaKey: true }), overrides)).toBe(true)
4848
})
4949

50+
it('tab.next/tab.prev carry the browser-convention bracket aliases', () => {
51+
expect(findCommand('tab.next')?.extraBindings).toEqual([{ mods: ['cmd', 'shift'], key: ']' }])
52+
expect(findCommand('tab.prev')?.extraBindings).toEqual([{ mods: ['cmd', 'shift'], key: '[' }])
53+
})
54+
55+
it('Cmd+Shift+] and Cmd+Shift+[ dispatch (tab cycling via the aliases)', () => {
56+
expect(dispatchCommandForEvent(event({ code: 'BracketRight', metaKey: true, shiftKey: true }), {})).toBe(true)
57+
expect(dispatchCommandForEvent(event({ code: 'BracketLeft', metaKey: true, shiftKey: true }), {})).toBe(true)
58+
})
59+
60+
it('bare Cmd+] stays unbound -- the aliases require Shift, exact-mods matching', () => {
61+
expect(dispatchCommandForEvent(event({ code: 'BracketRight', metaKey: true }), {})).toBe(false)
62+
expect(dispatchCommandForEvent(event({ code: 'BracketLeft', metaKey: true }), {})).toBe(false)
63+
})
64+
5065
it('a command with no extraBindings is unaffected (backward-compatible)', () => {
5166
// tab.close has no extras -- only its own Cmd+W default dispatches
5267
// it, same behavior as before this feature existed.

frontend/src/shared/commands.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,21 @@ export const COMMANDS: Command[] = [
100100
id: 'tab.next',
101101
label: 'Next tab',
102102
defaultBinding: { mods: ['ctrl'], key: 'Tab' },
103+
// ⌘⇧] -- the browser convention (Safari/Chrome "Show Next Tab")
104+
// for the identical action, alongside Ctrl+Tab the same way
105+
// palette.open carries its ⌘//⌘? aliases. Checked against
106+
// RESERVED_COMBOS (shared/keybinding.ts), every other command's
107+
// bindings here, and the native menu (no bracket accelerators):
108+
// no collision.
109+
extraBindings: [{ mods: ['cmd', 'shift'], key: ']' }],
103110
run: () => cycleWorkTab(1),
104111
},
105112
{
106113
id: 'tab.prev',
107114
label: 'Previous tab',
108115
defaultBinding: { mods: ['ctrl', 'shift'], key: 'Tab' },
116+
// ⌘⇧[ -- same browser convention as tab.next's ⌘⇧] above.
117+
extraBindings: [{ mods: ['cmd', 'shift'], key: '[' }],
109118
run: () => cycleWorkTab(-1),
110119
},
111120
{

frontend/src/shared/keybinding.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ describe('keyFromEventCode', () => {
4545
// physical key, shift-independent like every other key here -- Shift
4646
// held or not is captured separately as a mod (modsFromEvent), not a
4747
// second key value.
48+
it('maps the bracket codes to literal brackets (tab.next/prev ⌘⇧]/⌘⇧[ aliases)', () => {
49+
expect(keyFromEventCode('BracketLeft')).toBe('[')
50+
expect(keyFromEventCode('BracketRight')).toBe(']')
51+
})
52+
4853
it('maps the Slash code to a literal forward slash', () => {
4954
expect(keyFromEventCode('Slash')).toBe('/')
5055
})

frontend/src/shared/keybinding.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ export function keyFromEventCode(code: string): string | null {
2424
// Shift MOD is what distinguishes ⌘/ from ⌘? at the KeyCombo level
2525
// (modsFromEvent below), not a second key value here.
2626
if (code === 'Slash') return '/'
27+
// Bracket keys exist for tab.next/tab.prev's ⌘⇧]/⌘⇧[ extra bindings
28+
// (shared/commands.ts) -- the browser convention for cycling tabs.
29+
// Shift-independent like every other key here: the '{'/'}' glyphs are
30+
// the same physical keys, distinguished only by the Shift mod.
31+
if (code === 'BracketLeft') return '['
32+
if (code === 'BracketRight') return ']'
2733
return null
2834
}
2935

0 commit comments

Comments
 (0)