From fbcb1ca8a676eee84108a4eca5ec8369226936cc Mon Sep 17 00:00:00 2001 From: onempty0814 <3182459C@student.gla.ac.uk> Date: Sun, 3 May 2026 07:15:25 +0100 Subject: [PATCH 1/3] fix(settings): widen theme schema to accept custom theme IDs CC validates settings.json theme against built-in enum OR "custom:" prefix. Tweakcc custom themes use plain IDs (e.g. "winter") that fail both checks, causing the theme to be silently reset via .catch(void 0) on every startup. Add a new patch that replaces the union validator with z.string() so arbitrary theme IDs round-trip through settings.json without being discarded. Closes #702 --- src/patches/index.ts | 16 ++++++++++++++++ src/patches/settingsTheme.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/patches/settingsTheme.ts diff --git a/src/patches/index.ts b/src/patches/index.ts index a1c69861..9d71846c 100644 --- a/src/patches/index.ts +++ b/src/patches/index.ts @@ -31,6 +31,7 @@ import { DEFAULT_SETTINGS } from '../defaultSettings'; import { writeShowMoreItemsInSelectMenus } from './showMoreItemsInSelectMenus'; import { writeThemes } from './themes'; +import { writeSettingsTheme } from './settingsTheme'; import { writeContextLimit } from './contextLimit'; import { writeInputBoxBorder } from './inputBorderBox'; import { writeThinkerFormat } from './thinkerFormat'; @@ -212,6 +213,12 @@ const PATCH_DEFINITIONS = [ group: PatchGroup.MISC_CONFIGURABLE, description: 'Your custom themes will be available via /theme', }, + { + id: 'settings-theme-schema', + name: 'Settings theme schema', + group: PatchGroup.MISC_CONFIGURABLE, + description: 'Allows custom theme IDs to persist in settings.json', + }, { id: 'thinking-verbs', name: 'Thinking verbs', @@ -695,6 +702,15 @@ export const applyCustomization = async ( JSON.stringify(DEFAULT_SETTINGS.themes) ), }, + 'settings-theme-schema': { + fn: c => writeSettingsTheme(c), + condition: !!( + config.settings.themes && + config.settings.themes.length > 0 && + JSON.stringify(config.settings.themes) !== + JSON.stringify(DEFAULT_SETTINGS.themes) + ), + }, 'thinking-verbs': { fn: c => writeThinkingVerbs(c, config.settings.thinkingVerbs!.verbs), condition: !!config.settings.thinkingVerbs, diff --git a/src/patches/settingsTheme.ts b/src/patches/settingsTheme.ts new file mode 100644 index 00000000..c8fad4ba --- /dev/null +++ b/src/patches/settingsTheme.ts @@ -0,0 +1,29 @@ +import { globalReplace } from './index'; + +// CC validates the settings.json `theme` field against built-in enum + "custom:" prefix: +// theme:VAR.union([VAR.enum(THEMES),VAR.string().startsWith("custom:").transform(fn)]) +// .optional().catch(void 0) +// Tweakcc custom themes have plain IDs (e.g. "winter") that fail both constraints, +// causing the persisted theme value to be silently reset on every CC startup. +// Widen the schema to accept any string so custom IDs round-trip through settings.json. +// +// CC 2.1.x diff: +// -theme:h.union([h.enum(k7$),h.string().startsWith("custom:").transform((q)=>q)]).optional().catch(void 0) +// +theme:h.string().optional().catch(void 0) +export const writeSettingsTheme = (file: string): string | null => { + const pattern = + /,theme:([$\w]+)\.union\(\[\1\.enum\([$\w$]+\),\1\.string\(\)\.startsWith\("[^"]*"\)\.transform\(\([^)]+\)=>[^)]+\)\]\)\.optional\(\)\.catch\(void 0\)/; + + let patched = 0; + const newFile = globalReplace(file, pattern, (match, zodVar) => { + patched++; + return `,theme:${zodVar as string}.string().optional().catch(void 0)`; + }); + + if (patched === 0) { + console.error('patch: settingsTheme: failed to find theme schema pattern'); + return null; + } + + return newFile; +}; From aba7c5b32ad57613c73e3cca54e7d4b87aa9cfb9 Mon Sep 17 00:00:00 2001 From: onempty0814 <3182459C@student.gla.ac.uk> Date: Sun, 3 May 2026 13:52:15 +0100 Subject: [PATCH 2/3] docs(settingsTheme): add JSDoc to writeSettingsTheme --- package-lock.json | 167 +++++++++++++++++++++++++++++++++-- src/patches/settingsTheme.ts | 29 +++--- 2 files changed, 180 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index a7233938..20fc6a0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,24 @@ { "name": "tweakcc", - "version": "3.4.0", + "version": "4.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tweakcc", - "version": "3.4.0", + "version": "4.0.11", "license": "MIT", "dependencies": { "chalk": "^5.5.0", + "cli-spinners": "^3.4.0", "commander": "^14.0.0", "diff": "^8.0.3", "globby": "^14.1.0", "gray-matter": "^4.0.3", "ink": "^6.1.0", "ink-link": "^4.1.0", - "node-lief": "^1.0.0", + "node-lief": "^1.1.1", + "oxfmt": "^0.28.0", "react": "^19.1.1", "wasmagic": "^1.0.7", "which": "^6.0.0" @@ -911,6 +913,110 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@oxfmt/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@oxfmt/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-jmUfF7cNJPw57bEK7sMIqrYRgn4LH428tSgtgLTCtjuGuu1ShREyrkeB7y8HtkXRfhBs4lVY+HMLhqElJvZ6ww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxfmt/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@oxfmt/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-S6vlV8S7jbjzJOSjfVg2CimUC0r7/aHDLdUm/3+/B/SU/s1jV7ivqWkMv1/8EB43d1BBwT9JQ60ZMTkBqeXSFA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxfmt/linux-arm64-gnu": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@oxfmt/linux-arm64-gnu/-/linux-arm64-gnu-0.28.0.tgz", + "integrity": "sha512-TfJkMZjePbLiskmxFXVAbGI/OZtD+y+fwS0wyW8O6DWG0ARTf0AipY9zGwGoOdpFuXOJceXvN4SHGLbYNDMY4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxfmt/linux-arm64-musl": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@oxfmt/linux-arm64-musl/-/linux-arm64-musl-0.28.0.tgz", + "integrity": "sha512-7fyQUdW203v4WWGr1T3jwTz4L7KX9y5DeATryQ6fLT6QQp9GEuct8/k0lYhd+ys42iTV/IkJF20e3YkfSOOILg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxfmt/linux-x64-gnu": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@oxfmt/linux-x64-gnu/-/linux-x64-gnu-0.28.0.tgz", + "integrity": "sha512-sRKqAvEonuz0qr1X1ncUZceOBJerKzkO2gZIZmosvy/JmqyffpIFL3OE2tqacFkeDhrC+dNYQpusO8zsfHo3pw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxfmt/linux-x64-musl": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@oxfmt/linux-x64-musl/-/linux-x64-musl-0.28.0.tgz", + "integrity": "sha512-fW6czbXutX/tdQe8j4nSIgkUox9RXqjyxwyWXUDItpoDkoXllq17qbD7GVc0whrEhYQC6hFE1UEAcDypLJoSzw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxfmt/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@oxfmt/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-D/HDeQBAQRjTbD9OLV6kRDcStrIfO+JsUODDCdGmhRfNX8LPCx95GpfyybpZfn3wVF8Jq/yjPXV1xLkQ+s7RcA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxfmt/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@oxfmt/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-4+S2j4OxOIyo8dz5osm5dZuL0yVmxXvtmNdHB5xyGwAWVvyWNvf7tCaQD7w2fdSsAXQLOvK7KFQrHFe33nJUCA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@quansync/fs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", @@ -2451,6 +2557,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-truncate": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", @@ -4874,9 +4992,9 @@ } }, "node_modules/node-lief": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-lief/-/node-lief-1.0.0.tgz", - "integrity": "sha512-OyE3YqrUaHCs2U5xReoIaSe6XC6/VlBKD2Pt8xDJ43VZB+pVTytqg/DqaoddIGYptLdKitws1rNk9VLVb83ZlA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-lief/-/node-lief-1.1.1.tgz", + "integrity": "sha512-tD/MLRu8fnVEzcXmJpiy1Ge3u14yBmYHXGAQGbmrG8MvVk+eXhp42yDMAjP8aqn6MnD91GWD6x8R1WyHNd5TwA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -5053,6 +5171,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxfmt": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.28.0.tgz", + "integrity": "sha512-3+hhBqPE6Kp22KfJmnstrZbl+KdOVSEu1V0ABaFIg1rYLtrMgrupx9znnHgHLqKxAVHebjTdiCJDk30CXOt6cw==", + "license": "MIT", + "dependencies": { + "tinypool": "2.1.0" + }, + "bin": { + "oxfmt": "bin/oxfmt" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxfmt/darwin-arm64": "0.28.0", + "@oxfmt/darwin-x64": "0.28.0", + "@oxfmt/linux-arm64-gnu": "0.28.0", + "@oxfmt/linux-arm64-musl": "0.28.0", + "@oxfmt/linux-x64-gnu": "0.28.0", + "@oxfmt/linux-x64-musl": "0.28.0", + "@oxfmt/win32-arm64": "0.28.0", + "@oxfmt/win32-x64": "0.28.0" + } + }, + "node_modules/oxfmt/node_modules/tinypool": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", + "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", diff --git a/src/patches/settingsTheme.ts b/src/patches/settingsTheme.ts index c8fad4ba..daa70185 100644 --- a/src/patches/settingsTheme.ts +++ b/src/patches/settingsTheme.ts @@ -1,15 +1,24 @@ import { globalReplace } from './index'; -// CC validates the settings.json `theme` field against built-in enum + "custom:" prefix: -// theme:VAR.union([VAR.enum(THEMES),VAR.string().startsWith("custom:").transform(fn)]) -// .optional().catch(void 0) -// Tweakcc custom themes have plain IDs (e.g. "winter") that fail both constraints, -// causing the persisted theme value to be silently reset on every CC startup. -// Widen the schema to accept any string so custom IDs round-trip through settings.json. -// -// CC 2.1.x diff: -// -theme:h.union([h.enum(k7$),h.string().startsWith("custom:").transform((q)=>q)]).optional().catch(void 0) -// +theme:h.string().optional().catch(void 0) +/** + * Widens the settings.json `theme` field schema to accept arbitrary theme IDs. + * + * CC validates the theme field against built-in IDs (enum) or a `"custom:"` prefix. + * Tweakcc custom themes use plain IDs (e.g. `"winter"`) that fail both checks; + * the `.catch(void 0)` silently resets the field on every startup, so the theme + * never persists. This patch replaces the union validator with `z.string()`. + * + * Applies to both user-settings and managed-settings schema instances (two occurrences). + * + * CC 2.1.x diff (per occurrence): + * ``` + * -theme:h.union([h.enum(k7$),h.string().startsWith("custom:").transform((q)=>q)]).optional().catch(void 0) + * +theme:h.string().optional().catch(void 0) + * ``` + * + * @param file - The CC bundle source as a string + * @returns The modified bundle, or null if the schema pattern was not found + */ export const writeSettingsTheme = (file: string): string | null => { const pattern = /,theme:([$\w]+)\.union\(\[\1\.enum\([$\w$]+\),\1\.string\(\)\.startsWith\("[^"]*"\)\.transform\(\([^)]+\)=>[^)]+\)\]\)\.optional\(\)\.catch\(void 0\)/; From 656957e74181cd3e9d80b0374399e42e486506d6 Mon Sep 17 00:00:00 2001 From: onempty0814 <3182459C@student.gla.ac.uk> Date: Sun, 3 May 2026 23:44:25 +0100 Subject: [PATCH 3/3] fix(settingsTheme): require exactly 2 replacements, drop comment block --- src/patches/settingsTheme.ts | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/patches/settingsTheme.ts b/src/patches/settingsTheme.ts index daa70185..445cf814 100644 --- a/src/patches/settingsTheme.ts +++ b/src/patches/settingsTheme.ts @@ -1,24 +1,5 @@ import { globalReplace } from './index'; -/** - * Widens the settings.json `theme` field schema to accept arbitrary theme IDs. - * - * CC validates the theme field against built-in IDs (enum) or a `"custom:"` prefix. - * Tweakcc custom themes use plain IDs (e.g. `"winter"`) that fail both checks; - * the `.catch(void 0)` silently resets the field on every startup, so the theme - * never persists. This patch replaces the union validator with `z.string()`. - * - * Applies to both user-settings and managed-settings schema instances (two occurrences). - * - * CC 2.1.x diff (per occurrence): - * ``` - * -theme:h.union([h.enum(k7$),h.string().startsWith("custom:").transform((q)=>q)]).optional().catch(void 0) - * +theme:h.string().optional().catch(void 0) - * ``` - * - * @param file - The CC bundle source as a string - * @returns The modified bundle, or null if the schema pattern was not found - */ export const writeSettingsTheme = (file: string): string | null => { const pattern = /,theme:([$\w]+)\.union\(\[\1\.enum\([$\w$]+\),\1\.string\(\)\.startsWith\("[^"]*"\)\.transform\(\([^)]+\)=>[^)]+\)\]\)\.optional\(\)\.catch\(void 0\)/; @@ -29,8 +10,8 @@ export const writeSettingsTheme = (file: string): string | null => { return `,theme:${zodVar as string}.string().optional().catch(void 0)`; }); - if (patched === 0) { - console.error('patch: settingsTheme: failed to find theme schema pattern'); + if (patched < 2) { + console.error(`patch: settingsTheme: expected 2 replacements, got ${patched}`); return null; }