mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
Make Option/Alt session shortcuts work on macOS (physical key codes)
Tab-switch shortcuts matched e.key, so on macOS Option+1 emits a special
character ('¡', not '1') and the shortcut silently failed. Switch to physical
e.code (Digit1-9), which is layout-independent. Also adds Option+[ / Option+]
for previous / next session. Help modal + README updated.
Test: test/keyboard-shortcuts.test.ts.
This commit is contained in:
@@ -483,7 +483,8 @@ Single-digit selection (1-9), color-coded status, token counts, auto-refresh. De
|
||||
|----------|--------|
|
||||
| `Ctrl/Cmd+W` | Kill active session |
|
||||
| `Ctrl/Cmd+Tab` | Next session |
|
||||
| `Alt+1`–`Alt+9` | Switch to tab N |
|
||||
| `Option+[` / `Option+]` | Previous / next session |
|
||||
| `Option+1`-`Option+9` | Switch to tab N (physical keys, so macOS Option layouts work) |
|
||||
| `Ctrl+Shift+{` / `Ctrl+Shift+}` | Move active tab left / right |
|
||||
| `Ctrl/Cmd+L` | Clear terminal |
|
||||
| `Ctrl+Shift+R` | Restore terminal size |
|
||||
|
||||
+23
-6
@@ -770,14 +770,31 @@ class CodemanApp {
|
||||
if (this.attachmentHistoryDrawerOpen) this.closeAttachmentHistory();
|
||||
}
|
||||
|
||||
// Alt+1-9: switch to Codeman session by index
|
||||
if (e.altKey && !e.ctrlKey && !e.shiftKey && e.key >= '1' && e.key <= '9') {
|
||||
const idx = parseInt(e.key) - 1;
|
||||
if (idx < this.sessionOrder.length) {
|
||||
// Option/Alt session navigation uses physical key CODES, not e.key, so macOS
|
||||
// keyboard layouts that emit special characters under Option (Option+1 -> ¡,
|
||||
// Option+[ -> "“") still switch sessions. e.code is the physical key regardless
|
||||
// of layout. Option+1-9 = switch by index; Option+[ / Option+] = prev / next.
|
||||
if (e.altKey && !e.ctrlKey && !e.shiftKey) {
|
||||
const code = e.code || '';
|
||||
const digitMatch = code.match(/^Digit([1-9])$/);
|
||||
if (digitMatch) {
|
||||
const idx = parseInt(digitMatch[1], 10) - 1;
|
||||
if (idx < this.sessionOrder.length) {
|
||||
e.preventDefault();
|
||||
this.selectSession(this.sessionOrder[idx]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.code === 'BracketLeft') {
|
||||
e.preventDefault();
|
||||
this.selectSession(this.sessionOrder[idx]);
|
||||
this.prevSession();
|
||||
return;
|
||||
}
|
||||
if (e.code === 'BracketRight') {
|
||||
e.preventDefault();
|
||||
this.nextSession();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Match against shortcut table
|
||||
|
||||
@@ -505,7 +505,8 @@
|
||||
<div class="shortcuts-grid">
|
||||
<div><kbd>Ctrl</kbd>+<kbd>W</kbd></div><div>Close Session</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>Tab</kbd></div><div>Next Session</div>
|
||||
<div><kbd>Alt</kbd>+<kbd>1-9</kbd></div><div>Switch to Tab N</div>
|
||||
<div><kbd>Option</kbd>+<kbd>[</kbd> / <kbd>Option</kbd>+<kbd>]</kbd></div><div>Previous / Next Session</div>
|
||||
<div><kbd>Option</kbd>+<kbd>1-9</kbd></div><div>Switch to Tab N</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>L</kbd></div><div>Clear Terminal</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>+</kbd></div><div>Increase Font</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>-</kbd></div><div>Decrease Font</div>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const appSource = readFileSync('src/web/public/app.js', 'utf8');
|
||||
const helpHtml = readFileSync('src/web/public/index.html', 'utf8');
|
||||
const readme = readFileSync('README.md', 'utf8');
|
||||
|
||||
describe('keyboard shortcuts', () => {
|
||||
it('uses physical Option+number keys so macOS special characters do not break tab switching', () => {
|
||||
expect(appSource).toContain('e.code ||');
|
||||
expect(appSource).toContain('Digit([1-9])');
|
||||
expect(appSource).toContain('parseInt(digitMatch[1], 10) - 1');
|
||||
});
|
||||
|
||||
it('provides Option+bracket shortcuts for previous and next session', () => {
|
||||
expect(appSource).toContain("e.code === 'BracketLeft'");
|
||||
expect(appSource).toContain("e.code === 'BracketRight'");
|
||||
expect(appSource).toContain('this.prevSession()');
|
||||
expect(appSource).toContain('this.nextSession()');
|
||||
});
|
||||
|
||||
it('documents mac-friendly Option shortcuts in help and README', () => {
|
||||
expect(helpHtml).toContain('<kbd>Option</kbd>+<kbd>[</kbd>');
|
||||
expect(helpHtml).toContain('<kbd>Option</kbd>+<kbd>]</kbd>');
|
||||
expect(helpHtml).toContain('<kbd>Option</kbd>+<kbd>1-9</kbd>');
|
||||
expect(readme).toContain('`Option+[` / `Option+]`');
|
||||
expect(readme).toContain('`Option+1`-`Option+9`');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user