From 5b0103ffdcfacc7c896ca8b61b6eb7a5bc471fff Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 12 Sep 2026 07:06:41 -0700 Subject: [PATCH] refactor(desktop): extract terminalLcCtype so the Linux locale rule is testable The picked fix inlined the platform ternary inside terminalShellEnv(), which reads process.platform and can only be exercised by booting Electron. Lift it into a pure terminalLcCtype(env, platform) that takes the platform as data (root AGENTS.md: never fake the host OS) and pin the contract with one vitest: Linux reuses LANG, falls back to C.UTF-8, respects an explicit LC_CTYPE; macOS keeps the bare "UTF-8" it accepts. Red on origin/main (helper absent), green here. --- apps/desktop/electron/terminal-ipc.ts | 20 +++++++++++++++---- .../electron/terminal-lc-ctype.test.ts | 17 ++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 apps/desktop/electron/terminal-lc-ctype.test.ts diff --git a/apps/desktop/electron/terminal-ipc.ts b/apps/desktop/electron/terminal-ipc.ts index 8a4eb61118..92f2560c32 100644 --- a/apps/desktop/electron/terminal-ipc.ts +++ b/apps/desktop/electron/terminal-ipc.ts @@ -31,6 +31,21 @@ export interface TerminalIpcApi { disposeAllTerminalSessions: () => void } +// macOS accepts the bare charset name "UTF-8" as a locale; glibc does not, so +// every Linux pane would print `bash: warning: setlocale: LC_CTYPE: cannot +// change locale (UTF-8)`. Reuse the user's LANG there, else the glibc-guaranteed +// C.UTF-8. Pure: the platform arrives as data so tests need not fake the host. +export function terminalLcCtype( + env: { LANG?: string; LC_CTYPE?: string }, + platform: NodeJS.Platform = process.platform +): string { + if (env.LC_CTYPE) { + return env.LC_CTYPE + } + + return platform === 'darwin' ? 'UTF-8' : env.LANG || 'C.UTF-8' +} + export function registerTerminalIpc({ isWindows, findOnPath, @@ -156,10 +171,7 @@ export function registerTerminalIpc({ delete env.COLORFGBG env.COLORTERM = 'truecolor' - // macOS accepts the bare charset name "UTF-8"; glibc does not, and bash - // warns: setlocale: LC_CTYPE: cannot change locale (UTF-8). Prefer LANG or C.UTF-8 on Linux. - env.LC_CTYPE = - env.LC_CTYPE || (process.platform === 'darwin' ? 'UTF-8' : env.LANG || 'C.UTF-8') + env.LC_CTYPE = terminalLcCtype(env) env.TERM = 'xterm-256color' env.TERM_PROGRAM = 'Hermes' env.TERM_PROGRAM_VERSION = app.getVersion() diff --git a/apps/desktop/electron/terminal-lc-ctype.test.ts b/apps/desktop/electron/terminal-lc-ctype.test.ts new file mode 100644 index 0000000000..f05d1227f4 --- /dev/null +++ b/apps/desktop/electron/terminal-lc-ctype.test.ts @@ -0,0 +1,17 @@ +import assert from 'node:assert/strict' + +import { test, vi } from 'vitest' + +// terminal-ipc.ts imports the Electron and native PTY modules at module scope; +// neither loads under plain node, and this test only needs the pure locale helper. +vi.mock('electron', () => ({ app: {}, ipcMain: {} })) +vi.mock('node-pty', () => ({ default: {} })) + +import { terminalLcCtype } from './terminal-ipc' + +test('terminalLcCtype never hands glibc the bare "UTF-8" locale on Linux', () => { + assert.equal(terminalLcCtype({ LANG: 'en_US.UTF-8' }, 'linux'), 'en_US.UTF-8') + assert.equal(terminalLcCtype({}, 'linux'), 'C.UTF-8') + assert.equal(terminalLcCtype({ LANG: 'en_US.UTF-8', LC_CTYPE: 'de_DE.UTF-8' }, 'linux'), 'de_DE.UTF-8') + assert.equal(terminalLcCtype({ LANG: 'en_US.UTF-8' }, 'darwin'), 'UTF-8') +})