feat(tiles): Move Tile Left/Right/Up/Down (Ctrl+Shift+Arrows)

Four rebindable registry chords in the Tiles group move the focused
tile: it trades places with the neighbour the Alt+Shift+Arrow focus
chords pick (tileInDirection), through the same _reorderTiles path as
the header drag, and keeps the focus. Nothing at an edge.

They go through tileShortcutFor()/runTileShortcut() like the other
tile chords, so every xterm key handler swallows them while they
apply: only while the grid is open, a zoomed grid included (a no-op
there, so the keys never reach the CLI), and never in a text field
other than xterm's own textarea, where Ctrl+Shift+Arrows select by
word.

Ctrl+Shift+Arrows because every other two-modifier arrow chord is
taken: Ctrl+Alt switches workspaces (GNOME, Xfce, some Windows
graphics drivers), Ctrl+Alt+Shift moves a window to another workspace
(GNOME, Cinnamon, Xfce), Super belongs to the desktop, Alt is the
browser's back and forward, Alt+Shift focuses tiles. No browser,
GNOME, KDE, macOS or Claude Code default uses Ctrl+Shift+Arrows; it
costs a terminal editor's word selection inside a tile while the grid
is open.

The Help modal lists the chords and the header drag; the shortcut
overlay lists the registry. zh-CN entries for every new string.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-07 23:49:11 +02:00
parent 2b20288ca0
commit 20c2561a45
7 changed files with 328 additions and 6 deletions
+5 -1
View File
@@ -369,7 +369,7 @@ describe('every tile grid string the code puts on screen translates to zh-CN', (
it('the shortcut registry entries: the Tiles group, its labels, "not bound", the group menu item', () => {
const app = read('app.js');
const labels = [...app.matchAll(/group: 'Tiles',\s*label: '([^']+)'/g)].map((m) => m[1]);
expect(labels).toHaveLength(7);
expect(labels).toHaveLength(11);
expect(app).toContain("label: 'Open group as tiles'");
expect(app).toContain('not bound');
const bad = ['Tiles', 'not bound', 'Open group as tiles', ...labels].filter((s) => {
@@ -417,6 +417,10 @@ describe('the static markup through the real translator (JSDOM, zh-CN)', () => {
expect(leftover(tiles!.textContent!)).toEqual([]);
expect(tiles!.textContent).toContain('切换平铺网格');
expect(tiles!.textContent).toContain('右键单击');
// Moving tiles: the chords and the header drag.
expect(tiles!.textContent).toContain('向左 / 右 / 上 / 下移动窗格');
expect(tiles!.textContent).toContain('拖动');
expect(tiles!.textContent).toContain('窗格的标题栏');
});
it('user text stays as typed: a session name and a group name that are also UI words', () => {