Skip to content

fix(settings): widen theme schema to accept custom theme IDs#714

Open
zeshuochen wants to merge 3 commits into
Piebald-AI:mainfrom
zeshuochen:fix/settings-theme-schema
Open

fix(settings): widen theme schema to accept custom theme IDs#714
zeshuochen wants to merge 3 commits into
Piebald-AI:mainfrom
zeshuochen:fix/settings-theme-schema

Conversation

@zeshuochen
Copy link
Copy Markdown

@zeshuochen zeshuochen commented May 3, 2026

Problem

When a user configures custom tweakcc themes and selects one (e.g. winter), CC writes the bare theme ID to settings.json. However, CC's Zod schema for the theme field only accepts:

  1. Built-in theme IDs (auto, dark, light, light-daltonized, dark-daltonized, light-ansi, dark-ansi)
  2. Strings starting with "custom:"

A bare ID like "winter" fails both checks. The .catch(void 0) on the field silently resets it to undefined, so the theme is discarded on every startup. Only a legacy fallback via ~/.claude.json keeps it working — a fragile dependency that may not always be present.

Fixes #702.

Solution

Add a new patch settingsTheme that replaces:

// Before
theme:VAR.union([VAR.enum(THEMES),VAR.string().startsWith("custom:").transform(fn)])
         .optional().catch(void 0)

// After  
theme:VAR.string().optional().catch(void 0)

This mirrors the approach used in allowCustomAgentModels (replacing a narrow enum with z.string()). The patch applies to both the user settings and managed settings schema instances. It is gated behind the same condition as the themes patch — only active when custom themes are configured.

CC version tested

Verified against claude 2.1.126: the regex matches both schema instances (user settings + managed settings) and correctly transforms them.

Test plan

  • TypeScript compiles without errors (tsc --noEmit)
  • All 221 existing tests pass
  • Regex verified against claude 2.1.126 binary — matches 2 occurrences
  • Install tweakcc with a custom theme → select it → restart CC → verify theme persists in settings.json

Summary by CodeRabbit

  • Bug Fixes
    • Added a settings theme patch that improves validation and handling of user theme configurations; it now conditionally applies when custom themes are present and differ from defaults, reducing incorrect theme application and ensuring safer migration of theme settings.

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 Piebald-AI#702
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b51af039-5566-49b8-b981-e72d67f6dbc3

📥 Commits

Reviewing files that changed from the base of the PR and between aba7c5b and 656957e.

📒 Files selected for processing (1)
  • src/patches/settingsTheme.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/patches/settingsTheme.ts

📝 Walkthrough

Walkthrough

Adds a new patch "settings-theme-schema" that rewrites the settings theme validator to accept custom theme IDs. Implements writeSettingsTheme, registers the patch in PATCH_DEFINITIONS, and wires it into patchImplementations with an enablement condition based on config.settings.themes vs DEFAULT_SETTINGS.themes.

Changes

Settings Theme Schema Patch

Layer / File(s) Summary
Data Shape / Types
src/patches/index.ts
PatchId union extended to include 'settings-theme-schema'.
Core Implementation
src/patches/settingsTheme.ts
Added `writeSettingsTheme(file: string): string
Patch Registration / Wiring
src/patches/index.ts
Imported writeSettingsTheme, added PATCH_DEFINITIONS entry for settings-theme-schema (group PatchGroup.MISC_CONFIGURABLE), and registered implementation in patchImplementations, enabled only when config.settings.themes exists, is non-empty, and differs from DEFAULT_SETTINGS.themes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • georpar

Poem

🐰
I nudged a pattern in the code,
so custom colors find a road.
A regex hop, a gentle tweak,
now every theme may speak. 🎨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: widening the theme schema validator to accept custom theme IDs, which is the core fix implemented by the patch.
Linked Issues check ✅ Passed The PR successfully addresses issue #702 by implementing a patch that replaces the restrictive theme validator with a permissive one accepting all string theme IDs, allowing custom themes to persist without invalidating the entire settings file.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the theme schema patch: new patch registration, validator replacement implementation, and type updates for the PatchId union. No out-of-scope modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/patches/index.ts (1)

705-713: ⚡ Quick win

Extract shared theme-gating condition to one constant.

This condition is duplicated between themes and settings-theme-schema.
Centralizing it avoids drift where one patch gets enabled and the other does
not.

Suggested refactor
+  const customThemesConfigured = !!(
+    config.settings.themes &&
+    config.settings.themes.length > 0 &&
+    JSON.stringify(config.settings.themes) !==
+      JSON.stringify(DEFAULT_SETTINGS.themes)
+  );
+
   const patchImplementations: Record<PatchId, PatchImplementation> = {
@@
     themes: {
       fn: c => writeThemes(c, config.settings.themes!),
-      condition: !!(
-        config.settings.themes &&
-        config.settings.themes.length > 0 &&
-        JSON.stringify(config.settings.themes) !==
-          JSON.stringify(DEFAULT_SETTINGS.themes)
-      ),
+      condition: customThemesConfigured,
     },
     '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)
-      ),
+      condition: customThemesConfigured,
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/index.ts` around lines 705 - 713, The duplicated gating logic for
theme-related patches should be extracted into a single boolean constant (e.g.,
hasCustomThemes) and reused in both the 'settings-theme-schema' and 'themes'
patch entries; locate where the current condition references
config.settings.themes, DEFAULT_SETTINGS.themes and writeSettingsTheme, create a
descriptive constant (computed once from config.settings.themes.length and
JSON.stringify comparison to DEFAULT_SETTINGS.themes) and replace the inline
conditions in the 'settings-theme-schema' and 'themes' patch objects with that
constant to ensure both patches enable/disable in sync.
src/patches/settingsTheme.ts (2)

3-12: ⚡ Quick win

Remove the explanatory comment block from this logic file.

Please drop these comments unless explicitly requested by maintainers.

As per coding guidelines, "Do not add comments unless explicitly requested".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/settingsTheme.ts` around lines 3 - 12, Remove the explanatory
comment block at the top of src/patches/settingsTheme.ts (the multi-line comment
that starts with "CC validates the settings.json `theme` field..." and includes
the CC 2.1.x diff examples), leaving the actual schema line(s) intact (e.g., the
new theme:h.string().optional().catch(void 0) replacement). Ensure you only
delete the comment text and do not modify the functional code that widens the
schema to accept any string.

15-15: ⚡ Quick win

Tighten the regex to the exact minified sequence you intend to patch.

The current wildcard parts ("[^"]*", broad transform shape) make the match
more permissive than needed. Narrowing to the expected minified structure
reduces accidental matches and keeps patch behavior predictable across builds.

Based on learnings, "Patch regexes must be strict and match the minified format exactly (ignore formatting differences like whitespace/newlines)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/settingsTheme.ts` at line 15, The regex in the patch (the literal
shown starting with
/,theme:([$\w]+)\.union\(\[\1\.enum\([$\w$]+\),\1\.string\(\)\.startsWith\("[^"]*"\)\.transform\(\([^)]+\)=>[^)]+\)\]\)\.optional\(\)\.catch\(void
0\)/) is too permissive; tighten it to match the exact minified output by
replacing the generic `"[^"]*"` and the broad transform matcher
`\([^)]+\)=>[^)]+` with the precise minified string and arrow-function body you
observe in production (escape any special chars), and ensure the rest of the
sequence (theme:..., .union([...]), .enum(...),
.string().startsWith("...").transform(...), .optional().catch(void 0)) is
matched verbatim with no loose wildcards so only the intended minified sequence
is patched.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/patches/settingsTheme.ts`:
- Around line 23-26: The check in settingsTheme.ts that treats only patched ===
0 as failure is insufficient because the function expects to replace both schema
occurrences; update the logic around the patched variable (the replacement
count) to treat fewer-than-expected replacements as failure or degraded state
(e.g., require patched === 2 or patched >= EXPECTED_PATCHES), and update the
console/error logging to include the actual patched count and a clear message;
modify the return path of the function (currently returning null on failure) to
also return null or an explicit degraded result when patched < expected so
callers can detect partial rewrites.

---

Nitpick comments:
In `@src/patches/index.ts`:
- Around line 705-713: The duplicated gating logic for theme-related patches
should be extracted into a single boolean constant (e.g., hasCustomThemes) and
reused in both the 'settings-theme-schema' and 'themes' patch entries; locate
where the current condition references config.settings.themes,
DEFAULT_SETTINGS.themes and writeSettingsTheme, create a descriptive constant
(computed once from config.settings.themes.length and JSON.stringify comparison
to DEFAULT_SETTINGS.themes) and replace the inline conditions in the
'settings-theme-schema' and 'themes' patch objects with that constant to ensure
both patches enable/disable in sync.

In `@src/patches/settingsTheme.ts`:
- Around line 3-12: Remove the explanatory comment block at the top of
src/patches/settingsTheme.ts (the multi-line comment that starts with "CC
validates the settings.json `theme` field..." and includes the CC 2.1.x diff
examples), leaving the actual schema line(s) intact (e.g., the new
theme:h.string().optional().catch(void 0) replacement). Ensure you only delete
the comment text and do not modify the functional code that widens the schema to
accept any string.
- Line 15: The regex in the patch (the literal shown starting with
/,theme:([$\w]+)\.union\(\[\1\.enum\([$\w$]+\),\1\.string\(\)\.startsWith\("[^"]*"\)\.transform\(\([^)]+\)=>[^)]+\)\]\)\.optional\(\)\.catch\(void
0\)/) is too permissive; tighten it to match the exact minified output by
replacing the generic `"[^"]*"` and the broad transform matcher
`\([^)]+\)=>[^)]+` with the precise minified string and arrow-function body you
observe in production (escape any special chars), and ensure the rest of the
sequence (theme:..., .union([...]), .enum(...),
.string().startsWith("...").transform(...), .optional().catch(void 0)) is
matched verbatim with no loose wildcards so only the intended minified sequence
is patched.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 07d6df88-552a-4399-a453-0a2283916cf8

📥 Commits

Reviewing files that changed from the base of the PR and between 3a6b5e6 and fbcb1ca.

📒 Files selected for processing (2)
  • src/patches/index.ts
  • src/patches/settingsTheme.ts

Comment thread src/patches/settingsTheme.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/patches/settingsTheme.ts (1)

32-35: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Partial-rewrite not detected — patched === 0 guard is still insufficient.

The PR documents two occurrences; patched === 1 currently returns success while leaving one schema instance unpatched. This issue was raised on the previous commit and remains unaddressed.

🐛 Proposed fix
-  if (patched === 0) {
+  if (patched < 2) {
     console.error('patch: settingsTheme: failed to find theme schema pattern');
     return null;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/settingsTheme.ts` around lines 32 - 35, The guard that only fails
when patched === 0 is insufficient because there are two schema occurrences to
patch; change the validation around the patched counter (variable patched) in
the settingsTheme patching routine so that success is only returned when patched
=== 2, and treat any other value as a failure (log an error mentioning the
unexpected patched count and return null); update the success and error messages
to reflect the expected two patches and reference the patched variable to locate
the check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/patches/settingsTheme.ts`:
- Around line 32-35: The guard that only fails when patched === 0 is
insufficient because there are two schema occurrences to patch; change the
validation around the patched counter (variable patched) in the settingsTheme
patching routine so that success is only returned when patched === 2, and treat
any other value as a failure (log an error mentioning the unexpected patched
count and return null); update the success and error messages to reflect the
expected two patches and reference the patched variable to locate the check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 93b38b6a-ab12-47a2-adaa-c0b64aa4d2c2

📥 Commits

Reviewing files that changed from the base of the PR and between fbcb1ca and aba7c5b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • src/patches/settingsTheme.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom theme selection writes invalid theme ID to settings.json, causing entire config to be skipped

1 participant