diff --git a/packages/apps/app/e2e/terminal/30-terminal-core-behavior.spec.ts b/packages/apps/app/e2e/terminal/30-terminal-core-behavior.spec.ts index 1886db5c..6778b16d 100644 --- a/packages/apps/app/e2e/terminal/30-terminal-core-behavior.spec.ts +++ b/packages/apps/app/e2e/terminal/30-terminal-core-behavior.spec.ts @@ -123,6 +123,54 @@ test.describe('Terminal state transitions', () => { }) }) +// ── Keyboard input ────────────────────────────────────────────────────────── + +test.describe('Terminal keyboard input', () => { + let projectAbbrev: string + let taskId: string + + test.beforeAll(async ({ mainWindow }) => { + const s = seed(mainWindow) + const p = await s.createProject({ + name: 'Keyboard Input', + color: '#06b6d4', + path: TEST_PROJECT_PATH + }) + projectAbbrev = p.name.slice(0, 2).toUpperCase() + + const t = await s.createTask({ + projectId: p.id, + title: 'Keyboard input task', + status: 'in_progress' + }) + taskId = t.id + + await mainWindow.evaluate( + (id) => window.api.db.updateTask({ id, terminalMode: 'terminal' }), + taskId + ) + await s.refreshData() + }) + + test('lets macOS Option produce printable keyboard-layout characters', async ({ mainWindow }) => { + await openTaskTerminal(mainWindow, { projectAbbrev, taskTitle: 'Keyboard input task' }) + + const sessionId = getMainSessionId(taskId) + await waitForPtySession(mainWindow, sessionId) + + await expect + .poll(async () => + mainWindow.evaluate((sid) => { + const links = (window as any).__slayzone_terminalLinks as + | Record + | undefined + return links?.[sid]?._terminal?.options?.macOptionIsMeta ?? null + }, sessionId) + ) + .toBe(false) + }) +}) + // ── Mode switch teardown ──────────────────────────────────────────────────── test.describe('Terminal mode switch teardown', () => { diff --git a/packages/domains/terminal/src/client/Terminal.tsx b/packages/domains/terminal/src/client/Terminal.tsx index 6cd296a8..67270e54 100644 --- a/packages/domains/terminal/src/client/Terminal.tsx +++ b/packages/domains/terminal/src/client/Terminal.tsx @@ -28,6 +28,10 @@ const trimSelectionTrailingSpaces = (s: string): string => .map((l) => l.replace(/[ \t]+$/, '')) .join('\n') +// Keep Option available for keyboard-layout chars ($, €, etc.). Option+Arrow +// word nav is handled explicitly in handleTerminalKeyEvent. +const MAC_OPTION_IS_META = false + // Override xterm underline styles - Claude Code outputs these and they persist incorrectly // This is a definitive fix that works regardless of ANSI code filtering const underlineOverride = document.createElement('style') @@ -420,6 +424,7 @@ export const Terminal = forwardRef(function Termi searchAddonRef.current = cached.searchAddon webglAddonRef.current = cached.webglAddon ?? null registerActiveAddon(sessionId, cached.serializeAddon) + cached.terminal.options.macOptionIsMeta = MAC_OPTION_IS_META if (cached.lastRenderedSeq !== undefined) { lastRenderedSeqRef.current = cached.lastRenderedSeq } @@ -544,7 +549,7 @@ export const Terminal = forwardRef(function Termi // Create new terminal const terminal = new XTerm({ allowProposedApi: true, - macOptionIsMeta: true, + macOptionIsMeta: MAC_OPTION_IS_META, cursorBlink: false, fontSize: terminalFontSize, fontFamily: terminalFontFamily,