feat(tiles): tile chords in the shortcut registry, and the grid never beside a split

Shortcut registry (DEFAULT_SHORTCUTS, group Tiles, all rebindable):

- Toggle Tile Grid, Ctrl+Shift+G: opens the grid this tab last left (one step
  back after a selection outside it), else an open split as two tiles, else
  the active session as one tile; pressed again, back to the single view of
  the focused session. xterm emits nothing for a shifted Ctrl letter; the
  browser's find-previous is overridden only where the grid can open.
- Focus Tile Left/Right/Up/Down, Alt+Shift+Arrows: a human selection of the
  tile in that direction.
- Remove Focused Tile, unbound: the session keeps running.

tileShortcutFor() decides whether a chord applies (the toggle wherever a grid
could open, the rest only while one is open, so outside the grid
Alt+Shift+Arrows reach the terminal untouched) and is registry-aware. The
capture handler dispatches it, and the main terminal's and every tile's xterm
key handler return false for it, for every event type and before the
Shift+Enter gate, so a chord that applies never reaches a PTY.

Coexistence with the split pane: opening the grid over an open split closes
it (no wasted resize for the pane about to park) and seeds the grid with both
of its sessions, Pane A focused. While the grid is open openSplitPicker and
openSplitPane refuse and the Split button reads as unavailable
(aria-disabled); closing the grid never reopens a split.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-06 16:31:29 +02:00
parent 1a04a75c3d
commit 6adf750c50
9 changed files with 672 additions and 8 deletions
+59
View File
@@ -525,6 +525,56 @@ const DEFAULT_SHORTCUTS = [
bindings: [{ modifiers: ['alt'], key: 'b', code: 'KeyB' }],
action: 'toggleSessionSidebar',
},
// Tile grid (tile-grid.js). Dispatched by tileShortcutFor()/runTileShortcut()
// and deliberately absent from SHORTCUT_ACTIONS: each applies only in some
// states (the focus chords only while the grid is open), and outside them the
// chord must reach the terminal untouched. Every xterm key handler swallows a
// chord that applies, so it never reaches a PTY. Defaults: Ctrl+Shift+G makes
// xterm emit nothing (a shifted Ctrl letter) and overrides only the browser's
// find-previous; Alt+Shift+Arrows are bound by no CLI Codeman runs.
{
id: 'toggle-tile-grid',
group: 'Tiles',
label: 'Toggle Tile Grid',
bindings: [{ modifiers: ['ctrl', 'shift'], key: 'G', code: 'KeyG' }],
action: 'toggleTileGrid',
},
{
id: 'focus-tile-left',
group: 'Tiles',
label: 'Focus Tile Left',
bindings: [{ modifiers: ['alt', 'shift'], key: 'ArrowLeft' }],
action: 'focusTileLeft',
},
{
id: 'focus-tile-right',
group: 'Tiles',
label: 'Focus Tile Right',
bindings: [{ modifiers: ['alt', 'shift'], key: 'ArrowRight' }],
action: 'focusTileRight',
},
{
id: 'focus-tile-up',
group: 'Tiles',
label: 'Focus Tile Up',
bindings: [{ modifiers: ['alt', 'shift'], key: 'ArrowUp' }],
action: 'focusTileUp',
},
{
id: 'focus-tile-down',
group: 'Tiles',
label: 'Focus Tile Down',
bindings: [{ modifiers: ['alt', 'shift'], key: 'ArrowDown' }],
action: 'focusTileDown',
},
{
id: 'remove-tile',
group: 'Tiles',
label: 'Remove Focused Tile',
// Unbound by default; the session keeps running either way.
bindings: [],
action: 'removeTile',
},
{
id: 'previous-next-session',
group: 'Session',
@@ -1362,6 +1412,15 @@ class CodemanApp {
}
}
// Tile grid chords: only where they apply (tile-grid.js tileShortcutFor),
// so outside the grid Alt+Shift+Arrows still reach the terminal.
const tileShortcut = this.tileShortcutFor?.(e);
if (tileShortcut) {
e.preventDefault();
this.runTileShortcut(tileShortcut);
return;
}
// Match against the shortcut registry so user rebinds and per-shortcut
// disables (App Settings → Shortcuts) take effect. Every dispatchable
// binding requires Ctrl/Cmd/Alt (capture enforces the same), so plain