diff --git a/README.md b/README.md
index 63f1a542..ef36d07b 100644
--- a/README.md
+++ b/README.md
@@ -482,7 +482,7 @@ Single-digit selection (1-9), color-coded status, token counts, auto-refresh. De
| Shortcut | Action |
|----------|--------|
| `Ctrl/Cmd+W` | Kill active session |
-| `Ctrl/Cmd+K` | Find open session or start a new one |
+| `Ctrl/Cmd/Option+K` | Find open session or start a new one |
| `Ctrl/Cmd+Tab` | Next session |
| `Alt/Option+[` / `Alt/Option+]` | Previous / next session |
| `Alt/Option+1`-`Alt/Option+9` | Switch to tab N (physical keys, so macOS Option layouts work) |
diff --git a/src/web/public/index.html b/src/web/public/index.html
index de3daa80..cf45bcc9 100644
--- a/src/web/public/index.html
+++ b/src/web/public/index.html
@@ -573,7 +573,7 @@
Session
Ctrl+W
Close Session
-
Ctrl/Cmd+K
Find Open Session
+
Ctrl/Cmd/Option+K
Find Open Session
Ctrl+Tab
Next Session
Alt/Option+[ / Alt/Option+]
Previous / Next Session
Alt/Option+1-9
Switch to Tab N
diff --git a/src/web/public/panels-ui.js b/src/web/public/panels-ui.js
index 2ce47739..81cccfe0 100644
--- a/src/web/public/panels-ui.js
+++ b/src/web/public/panels-ui.js
@@ -257,12 +257,17 @@ Object.assign(CodemanApp.prototype, {
shouldOpenCommandPaletteFromShortcut(e) {
if (!e) return false;
- if ((e.key || '').toLowerCase() !== 'k') return false;
- if (!(e.metaKey || e.ctrlKey) || e.altKey) return false;
+ const key = (e.key || '').toLowerCase();
+ if (key !== 'k' && e.code !== 'KeyK') return false;
+ if (!(e.metaKey || e.ctrlKey || e.altKey)) return false;
const target = e.target;
if (!target) return true;
const tagName = (target.tagName || '').toUpperCase();
+ const className = typeof target.className === 'string' ? target.className : '';
+ const isXtermHelper =
+ target.classList?.contains?.('xterm-helper-textarea') || className.includes('xterm-helper-textarea');
+ if (isXtermHelper) return true;
if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') return false;
if (target.isContentEditable) return false;
if (typeof target.closest === 'function' && target.closest('[contenteditable="true"]')) return false;
diff --git a/src/web/public/terminal-ui.js b/src/web/public/terminal-ui.js
index d44e5dbf..7b287297 100644
--- a/src/web/public/terminal-ui.js
+++ b/src/web/public/terminal-ui.js
@@ -120,12 +120,17 @@ Object.assign(CodemanApp.prototype, {
this.terminal.attachCustomKeyEventHandler((ev) => {
if (ev.isComposing || ev.keyCode === 229) return false;
- // Let the app's Alt/Option session-nav shortcuts reach the document keydown handler
+ // Let the app's Alt/Option session-nav and Command Palette shortcuts reach the document keydown handler
// (app.js switches tabs by PHYSICAL e.code) instead of xterm injecting ESC
into
// the PTY. Mirror app.js's gate exactly — same physical codes + modifier guard — so
- // macOS Option layouts (Option+1 -> "¡", Option+[ -> "“") are suppressed here too and
+ // macOS Option layouts (Option+1 -> "¡", Option+[ -> "“", Option+K -> "˚") are suppressed here too and
// don't leak an escape sequence into the focused terminal on every tab switch.
- if (ev.altKey && !ev.ctrlKey && !ev.shiftKey && /^(Digit[1-9]|BracketLeft|BracketRight)$/.test(ev.code || '')) {
+ if (
+ ev.altKey &&
+ !ev.ctrlKey &&
+ !ev.shiftKey &&
+ /^(Digit[1-9]|BracketLeft|BracketRight|KeyK)$/.test(ev.code || '')
+ ) {
return false;
}
diff --git a/test/command-palette-ui.test.ts b/test/command-palette-ui.test.ts
index 1a3dc6ec..35bf10e8 100644
--- a/test/command-palette-ui.test.ts
+++ b/test/command-palette-ui.test.ts
@@ -122,6 +122,40 @@ describe('Command-K session palette', () => {
).toBe(false);
});
+ it('recognizes Ctrl-K from the focused xterm helper textarea', () => {
+ const { app } = loadPaletteHarness();
+
+ expect(
+ app.shouldOpenCommandPaletteFromShortcut({
+ key: 'k',
+ code: 'KeyK',
+ metaKey: false,
+ ctrlKey: true,
+ altKey: false,
+ target: {
+ tagName: 'TEXTAREA',
+ isContentEditable: false,
+ classList: { contains: (name: string) => name === 'xterm-helper-textarea' },
+ },
+ })
+ ).toBe(true);
+ });
+
+ it('recognizes macOS Option-K by physical key code', () => {
+ const { app } = loadPaletteHarness();
+
+ expect(
+ app.shouldOpenCommandPaletteFromShortcut({
+ key: '˚',
+ code: 'KeyK',
+ metaKey: false,
+ ctrlKey: false,
+ altKey: true,
+ target: null,
+ })
+ ).toBe(true);
+ });
+
it('opens and focuses the palette search box', () => {
const { app, elements } = loadPaletteHarness();
diff --git a/test/keyboard-shortcuts.test.ts b/test/keyboard-shortcuts.test.ts
index 1e3d8b8c..e8c6ed49 100644
--- a/test/keyboard-shortcuts.test.ts
+++ b/test/keyboard-shortcuts.test.ts
@@ -24,7 +24,7 @@ describe('keyboard shortcuts', () => {
// terminal-ui.js must gate its xterm pass-through on the SAME physical e.code set the
// app.js handler consumes; otherwise Alt+[ / Alt+] (and Option+digit on remapped macOS
// layouts) switch tabs AND inject ESC into the focused terminal. Keep in sync.
- expect(terminalUiSource).toContain('/^(Digit[1-9]|BracketLeft|BracketRight)$/.test(ev.code');
+ expect(terminalUiSource).toContain('/^(Digit[1-9]|BracketLeft|BracketRight|KeyK)$/.test(ev.code');
});
it('documents the Alt/Option shortcuts in help and README', () => {
@@ -37,7 +37,7 @@ describe('keyboard shortcuts', () => {
it('documents the Command-K open-session palette in help and README', () => {
expect(appSource).toContain('this.openCommandPalette()');
- expect(helpHtml).toContain('Ctrl/Cmd+K');
- expect(readme).toMatch(/\| `Ctrl\/Cmd\+K`\s+\| Find open session or start a new one\s+\|/);
+ expect(helpHtml).toContain('Ctrl/Cmd/Option+K');
+ expect(readme).toMatch(/\| `Ctrl\/Cmd\/Option\+K`\s+\| Find open session or start a new one\s+\|/);
});
});