authorgravatar for crazywolf132@gmail.com0x1f99d <crazywolf132@gmail.com> 2026-03-09 16:02:15+11:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-08 22:02:15-07:00
log4088b7ac3cddc1fc92ccf3f99e7a844c629ebad0
tree6459cfad8e76bea20197561e7498fd55f90d64f8
parent8e8904a159e3b29fa1eae0e6f023872ef03a3f63
signature Signed by PGP key B5690EEEBB952194

fix(desktop): use filesystem-friendly userData directory name (#607)

Electron derives the userData path from productName in package.json, which produces directories with spaces and parentheses on all platforms (e.g. ~/.config/T3 Code (Alpha) on Linux). This is hostile to shell usage and violates Linux XDG naming conventions. Override the userData path via app.setPath() before the ready event to use a clean lowercase name (t3code). If the legacy directory already exists it is used as-is so existing users keep their Chromium profile data (localStorage, cookies, sessions, cache). This follows the same pattern VS Code uses — keeping productName for display purposes while explicitly setting the data directory path. Closes #578

1 files changed, 35 insertions(+), 0 deletions(-)

apps/desktop/src/main.ts+35
...@@ -50,6 +50,8 @@ const ROOT_DIR = Path.resolve(__dirname, "../../..");...@@ -50,6 +50,8 @@ const ROOT_DIR = Path.resolve(__dirname, "../../..");
50const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);50const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);
51const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";51const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
52const APP_USER_MODEL_ID = "com.t3tools.t3code";52const APP_USER_MODEL_ID = "com.t3tools.t3code";
53const USER_DATA_DIR_NAME = isDevelopment ? "t3code-dev" : "t3code";
54const LEGACY_USER_DATA_DIR_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
53const COMMIT_HASH_PATTERN = /^[0-9a-f]{7,40}$/i;55const COMMIT_HASH_PATTERN = /^[0-9a-f]{7,40}$/i;
54const COMMIT_HASH_DISPLAY_LENGTH = 12;56const COMMIT_HASH_DISPLAY_LENGTH = 12;
55const LOG_DIR = Path.join(STATE_DIR, "logs");57const LOG_DIR = Path.join(STATE_DIR, "logs");
...@@ -593,6 +595,34 @@ function resolveIconPath(ext: "ico" | "icns" | "png"): string | null {...@@ -593,6 +595,34 @@ function resolveIconPath(ext: "ico" | "icns" | "png"): string | null {
593 return resolveResourcePath(`icon.${ext}`);595 return resolveResourcePath(`icon.${ext}`);
594}596}
595597
598/**
599 * Resolve the Electron userData directory path.
600 *
601 * Electron derives the default userData path from `productName` in
602 * package.json, which currently produces directories with spaces and
603 * parentheses (e.g. `~/.config/T3 Code (Alpha)` on Linux). This is
604 * unfriendly for shell usage and violates Linux naming conventions.
605 *
606 * We override it to a clean lowercase name (`t3code`). If the legacy
607 * directory already exists we keep using it so existing users don't
608 * lose their Chromium profile data (localStorage, cookies, sessions).
609 */
610function resolveUserDataPath(): string {
611 const appDataBase =
612 process.platform === "win32"
613 ? process.env.APPDATA || Path.join(OS.homedir(), "AppData", "Roaming")
614 : process.platform === "darwin"
615 ? Path.join(OS.homedir(), "Library", "Application Support")
616 : process.env.XDG_CONFIG_HOME || Path.join(OS.homedir(), ".config");
617
618 const legacyPath = Path.join(appDataBase, LEGACY_USER_DATA_DIR_NAME);
619 if (FS.existsSync(legacyPath)) {
620 return legacyPath;
621 }
622
623 return Path.join(appDataBase, USER_DATA_DIR_NAME);
624}
625
596function configureAppIdentity(): void {626function configureAppIdentity(): void {
597 app.setName(APP_DISPLAY_NAME);627 app.setName(APP_DISPLAY_NAME);
598 const commitHash = resolveAboutCommitHash();628 const commitHash = resolveAboutCommitHash();
...@@ -1187,6 +1217,11 @@ function createWindow(): BrowserWindow {...@@ -1187,6 +1217,11 @@ function createWindow(): BrowserWindow {
1187 return window;1217 return window;
1188}1218}
11891219
1220// Override Electron's userData path before the `ready` event so that
1221// Chromium session data uses a filesystem-friendly directory name.
1222// Must be called synchronously at the top level — before `app.whenReady()`.
1223app.setPath("userData", resolveUserDataPath());
1224
1190configureAppIdentity();1225configureAppIdentity();
11911226
1192async function bootstrap(): Promise<void> {1227async function bootstrap(): Promise<void> {