Skip to content

Commit 24678c7

Browse files
committed
fix(keybindings): stop task-reorder shadowing word-select
Ctrl/Cmd+Shift+Arrow (move task) is the universal extend-selection-by-word shortcut and, being a global binding, shadowed it in terminals/inputs. Split per platform so each uses the Shift combo that ISN'T text selection: - Linux: Alt+Shift+Arrow (frees Ctrl+Shift+Arrow word-select) - macOS: Ctrl+Shift+Arrow (frees Cmd+Shift select-to-line and Opt+Shift select-by-word) Mirrors the existing app.nav.task-*-linux mac/linux split. Adds behavior tests (resolve + findConflict on the Linux path) and structural guards locking both variants' modifiers. Note: pre-existing Linux user overrides for app.task.reorder-* become orphaned (graceful fallback to the new default) - accepted, not migrated.
1 parent d08a744 commit 24678c7

4 files changed

Lines changed: 123 additions & 28 deletions

File tree

README.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -138,30 +138,30 @@ Requires [Node.js](https://nodejs.org/) v18+.
138138

139139
`Ctrl` = `Cmd` on macOS.
140140

141-
| Shortcut | Action |
142-
| --------------------- | ---------------------------------- |
143-
| **Tasks** | |
144-
| `Ctrl+N` | New task |
145-
| `Ctrl+Shift+A` | New task (alternative) |
146-
| `Ctrl+Enter` | Send prompt |
147-
| `Ctrl+Shift+M` | Merge task to main |
148-
| `Ctrl+Shift+P` | Push to remote |
149-
| `Ctrl+W` | Close focused terminal session |
150-
| `Ctrl+Shift+W` | Close active task |
151-
| **Navigation** | |
152-
| `Alt+Arrows` | Focus pane/task in arrow direction |
153-
| `Ctrl+Alt+Left/Right` | Move task left/right |
154-
| `Ctrl+B` | Toggle sidebar |
155-
| `Ctrl+Shift+F` | Toggle focus mode |
156-
| **Terminals** | |
157-
| `Ctrl+Shift+T` | New shell terminal |
158-
| `Ctrl+Shift+D` | New standalone terminal |
159-
| **App** | |
160-
| `Ctrl+,` | Open settings |
161-
| `Ctrl+/` or `F1` | Show all shortcuts |
162-
| `Ctrl+0` | Reset zoom |
163-
| `Ctrl+Scroll` | Adjust zoom |
164-
| `Escape` | Close dialog |
141+
| Shortcut | Action |
142+
| ---------------------- | ---------------------------------- |
143+
| **Tasks** | |
144+
| `Ctrl+N` | New task |
145+
| `Ctrl+Shift+A` | New task (alternative) |
146+
| `Ctrl+Enter` | Send prompt |
147+
| `Ctrl+Shift+M` | Merge task to main |
148+
| `Ctrl+Shift+P` | Push to remote |
149+
| `Ctrl+W` | Close focused terminal session |
150+
| `Ctrl+Shift+W` | Close active task |
151+
| **Navigation** | |
152+
| `Alt+Arrows` | Focus pane/task in arrow direction |
153+
| `Alt+Shift+Left/Right` | Move task (macOS: `Ctrl+Shift`) |
154+
| `Ctrl+B` | Toggle sidebar |
155+
| `Ctrl+Shift+F` | Toggle focus mode |
156+
| **Terminals** | |
157+
| `Ctrl+Shift+T` | New shell terminal |
158+
| `Ctrl+Shift+D` | New standalone terminal |
159+
| **App** | |
160+
| `Ctrl+,` | Open settings |
161+
| `Ctrl+/` or `F1` | Show all shortcuts |
162+
| `Ctrl+0` | Reset zoom |
163+
| `Ctrl+Scroll` | Adjust zoom |
164+
| `Escape` | Close dialog |
165165

166166
</details>
167167

src/lib/keybindings/__tests__/defaults.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const APP_LAYER_IDS = [
88
'app.nav.column-right',
99
'app.task.reorder-left',
1010
'app.task.reorder-right',
11+
'app.task.reorder-left-linux',
12+
'app.task.reorder-right-linux',
1113
'app.task.close-shell',
1214
'app.task.close',
1315
'app.task.merge',
@@ -95,3 +97,33 @@ describe('DEFAULT_BINDINGS', () => {
9597
}
9698
});
9799
});
100+
101+
// Locks the per-platform task-reorder modifiers so a future edit can't
102+
// silently regress to a combo that shadows native text selection
103+
// (Cmd+Shift = select-to-line, Opt+Shift / Ctrl+Shift = select-by-word).
104+
describe('task-reorder split avoids native text-selection shadowing', () => {
105+
const byId = (id: string) => {
106+
const binding = DEFAULT_BINDINGS.find((b) => b.id === id);
107+
if (!binding) throw new Error(`expected binding not found: ${id}`);
108+
return binding;
109+
};
110+
111+
it('macOS variant uses Ctrl+Shift (not Cmd+Shift / Opt+Shift)', () => {
112+
for (const id of ['app.task.reorder-left', 'app.task.reorder-right']) {
113+
const b = byId(id);
114+
expect(b.platform).toBe('mac');
115+
// Exactly Ctrl+Shift — NOT cmdOrCtrl (Cmd+Shift = select-to-line) nor
116+
// alt (Opt+Shift = select-by-word); toEqual is exhaustive so this also
117+
// proves those modifiers are absent.
118+
expect(b.modifiers).toEqual({ ctrl: true, shift: true });
119+
}
120+
});
121+
122+
it('Linux variant uses Alt+Shift (not Ctrl+Shift)', () => {
123+
for (const id of ['app.task.reorder-left-linux', 'app.task.reorder-right-linux']) {
124+
const b = byId(id);
125+
expect(b.platform).toBe('linux');
126+
expect(b.modifiers).toEqual({ alt: true, shift: true });
127+
}
128+
});
129+
});

src/lib/keybindings/__tests__/resolve.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,37 @@ describe('findConflict', () => {
7979
expect(conflict).toBeNull();
8080
});
8181
});
82+
83+
// Regression guard for the task-reorder shortcut change: the old global
84+
// Cmd/Ctrl+Shift+Arrow binding shadowed "extend selection by word" in
85+
// terminals/inputs. The fix is a per-platform split. The test env has no
86+
// `navigator` → isMac=false → this exercises the Linux (Alt+Shift) path,
87+
// which is exactly where the original word-select conflict lived.
88+
describe('task-reorder shortcut does not shadow text selection', () => {
89+
const resolved = resolveBindings(DEFAULT_BINDINGS, { preset: 'default', userOverrides: {} });
90+
91+
it('resolves the Linux Alt+Shift+Arrow variant, filtering out the mac-only one', () => {
92+
const linuxLeft = resolved.find((b) => b.id === 'app.task.reorder-left-linux');
93+
expect(linuxLeft?.key).toBe('ArrowLeft');
94+
expect(linuxLeft?.modifiers).toEqual({ alt: true, shift: true });
95+
expect(resolved.find((b) => b.id === 'app.task.reorder-left')).toBeUndefined();
96+
});
97+
98+
it('Alt+Shift+Arrow reorder is disjoint from Alt+Arrow pane-focus nav', () => {
99+
// The core correctness claim: adding Shift keeps reorder distinct from
100+
// column nav, so Ctrl+Shift+Arrow word-select is freed without a new clash.
101+
expect(
102+
findConflict(resolved, 'app.task.reorder-left-linux', {
103+
key: 'ArrowLeft',
104+
modifiers: { alt: true, shift: true },
105+
}),
106+
).toBeNull();
107+
// Inverse direction: plain Alt+Arrow still belongs to column nav alone.
108+
expect(
109+
findConflict(resolved, 'app.nav.column-left', {
110+
key: 'ArrowLeft',
111+
modifiers: { alt: true },
112+
}),
113+
).toBeNull();
114+
});
115+
});

src/lib/keybindings/defaults.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,21 @@ export const DEFAULT_BINDINGS: KeyBinding[] = [
9999
// -------------------------------------------------------------------------
100100
// App layer — Task reordering
101101
// -------------------------------------------------------------------------
102+
// This binding is global, so it must not shadow native text selection in
103+
// inputs/terminals. Each platform uses the Shift+modifier+Arrow combo that
104+
// ISN'T a text selection there: on Linux, Ctrl+Shift+Arrow is select-by-word
105+
// (so use Alt+Shift+Arrow); on macOS, Cmd+Shift = select-to-line and
106+
// Opt+Shift = select-by-word, while Ctrl+Shift+Arrow is unbound (so use it).
107+
// Alt+Shift+Arrow is not a default GNOME/KDE global (unlike Ctrl+Alt+Arrow),
108+
// though a tiling WM could rebind it — acceptable given the constraints.
102109
{
103110
id: 'app.task.reorder-left',
104111
layer: 'app',
105112
category: 'Tasks',
106113
description: 'Move task left',
107-
platform: 'both',
114+
platform: 'mac',
108115
key: 'ArrowLeft',
109-
modifiers: { cmdOrCtrl: true, shift: true },
116+
modifiers: { ctrl: true, shift: true },
110117
action: 'moveActiveTask:left',
111118
global: true,
112119
},
@@ -115,9 +122,31 @@ export const DEFAULT_BINDINGS: KeyBinding[] = [
115122
layer: 'app',
116123
category: 'Tasks',
117124
description: 'Move task right',
118-
platform: 'both',
125+
platform: 'mac',
119126
key: 'ArrowRight',
120-
modifiers: { cmdOrCtrl: true, shift: true },
127+
modifiers: { ctrl: true, shift: true },
128+
action: 'moveActiveTask:right',
129+
global: true,
130+
},
131+
{
132+
id: 'app.task.reorder-left-linux',
133+
layer: 'app',
134+
category: 'Tasks',
135+
description: 'Move task left',
136+
platform: 'linux',
137+
key: 'ArrowLeft',
138+
modifiers: { alt: true, shift: true },
139+
action: 'moveActiveTask:left',
140+
global: true,
141+
},
142+
{
143+
id: 'app.task.reorder-right-linux',
144+
layer: 'app',
145+
category: 'Tasks',
146+
description: 'Move task right',
147+
platform: 'linux',
148+
key: 'ArrowRight',
149+
modifiers: { alt: true, shift: true },
121150
action: 'moveActiveTask:right',
122151
global: true,
123152
},

0 commit comments

Comments
 (0)