Skip to content

fix(theme): validate and merge user theme files instead of blind override - #260

Closed
HANCORE-linux wants to merge 3 commits into
bjarneo:mainfrom
HANCORE-linux:main
Closed

fix(theme): validate and merge user theme files instead of blind override#260
HANCORE-linux wants to merge 3 commits into
bjarneo:mainfrom
HANCORE-linux:main

Conversation

@HANCORE-linux

@HANCORE-linux HANCORE-linux commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Problem

loadUserDir() in theme/theme.go overwrites built-in themes with user files from ~/.config/cliamp/themes/*.toml without any validation. A partial or corrupt file (e.g. only accent = "#ff0000") replaces the entire built-in theme, leaving the other 5 colour fields empty. lipgloss.Color("") in ui/styles.go then produces broken colours.

Workaround: rm -rf ~/.config/cliamp/themes/ removes the bad files and restores correct colours -- until the next corrupt file appears.

Fix

Three new helpers in theme/theme.go:

Function Purpose
validHex(s string) bool Validates #rgb, #rrggbb or #rrggbbaa format
Theme.Validate() error Checks all 6 colour fields are valid hex
merge(dst *Theme, src Theme) Applies only valid-hex fields from src onto dst

loadUserDir now has two modes:

Mode Condition Behaviour
Merge Filename matches built-in (e.g. dracula.toml -> dracula) User fields are merged onto the built-in; missing/invalid fields keep the built-in value
Validate No built-in match (e.g. my-theme.toml) All 6 hex fields required; file is silently skipped otherwise

Invalid hex values are silently ignored in either mode -- the corresponding built-in (or zero) value survives. This prevents partial or corrupt files from breaking colours while still allowing single-field customisation.

Tests

22 tests, all PASS. New tests cover:

  • Partial override merging onto a built-in theme
  • Full override with all 6 valid fields (merge and validate modes)
  • Partial theme without built-in match -> rejected
  • Invalid hex field ignored during merge
  • validHex -- valid: #fff #aabbcc #aabbccdd -- invalid: empty string #xyz 2-char hex
  • Validate with valid, partial, empty, and invalid-hex themes

Files changed

  • theme/theme.go -- +validHex, +Validate, +merge, loadUserDir updated
  • theme/theme_test.go -- +5 tests
  • theme/load_test.go -- existing tests adapted to merge, +3 new tests

Summary by CodeRabbit

  • New Features

    • User themes can partially override built-in themes — specify only colors you want to change while keeping defaults.
    • Stronger theme validation: user themes must provide valid hex color values; invalid entries are ignored.
  • Chores

    • Improved test coverage for theme discovery, validation, merging, and sorting.
    • User theme lookup now respects a configurable config directory for per-user themes.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 41a4701d-ae69-4f19-a8a6-2b9485c340ab

📥 Commits

Reviewing files that changed from the base of the PR and between 31bf2dd and 05ee021.

📒 Files selected for processing (1)
  • theme/load_test.go

📝 Walkthrough

Walkthrough

This PR adds hex color validation, a Theme.Validate() method, and a merge(dst, src) that overlays only valid non-empty fields. loadUserDir now merges matching user themes onto built-ins or requires full validation for standalone themes. Unit and LoadAll tests are added/updated and use CLIAMP_CONFIG_DIR temp dirs.

Changes

Theme validation and conditional merging

Layer / File(s) Summary
Hex validation, theme validation, and merge functions
theme/theme.go
Adds validHex helper, Theme.Validate() method requiring all six color fields as valid hex, and merge function that overlays only valid hex fields from source onto destination, plus fmt import for validation errors.
Unit tests for validation and merge
theme/theme_test.go
Tests validHex hex-string validation, Theme.Validate() with full/partial/invalid themes and error messaging by field name, and merge behavior when overlaying valid fields while ignoring invalid hex values.
LoadUserDir conditional merge and validation
theme/theme.go
Updates documentation and implementation so user themes matching built-in names merge onto the built-in, while standalone user themes require full validation and are skipped if incomplete or invalid.
LoadAll integration tests for merge and validation
theme/load_test.go
Tests partial user overrides merging onto built-ins, partial standalone themes being skipped, invalid hex in merges being ignored, and test envs updated to use CLIAMP_CONFIG_DIR temp directories.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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: adding validation and merge behavior for user theme files instead of blindly overwriting built-in themes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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

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

@HANCORE-linux HANCORE-linux reopened this Jun 8, 2026
@HANCORE-linux
HANCORE-linux marked this pull request as draft June 8, 2026 08:22
@HANCORE-linux
HANCORE-linux marked this pull request as ready for review June 8, 2026 08:23
@HANCORE-linux
HANCORE-linux marked this pull request as draft June 8, 2026 08:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
theme/load_test.go (1)

48-183: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Convert the new/updated LoadAll cases to a table-driven test.

These added scenarios are currently separate single-case tests with duplicated setup/assert flow; repo policy for *_test.go requires table-driven patterns.

Refactor sketch
+func TestLoadAllUserThemeScenarios(t *testing.T) {
+	cases := []struct {
+		name      string
+		fileName  string
+		fileBody  string
+		assertFn  func(t *testing.T, themes []Theme)
+	}{
+		// partial merge onto builtin
+		// standalone partial rejected
+		// invalid hex ignored during merge
+		// full standalone theme accepted
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			home := t.TempDir()
+			t.Setenv("HOME", home)
+			userDir := filepath.Join(home, ".config", "cliamp", "themes")
+			if err := os.MkdirAll(userDir, 0o755); err != nil {
+				t.Fatalf("MkdirAll: %v", err)
+			}
+			if tc.fileName != "" {
+				if err := os.WriteFile(filepath.Join(userDir, tc.fileName), []byte(tc.fileBody), 0o644); err != nil {
+					t.Fatalf("WriteFile: %v", err)
+				}
+			}
+			tc.assertFn(t, LoadAll())
+		})
+	}
+}

As per coding guidelines, **/*_test.go: “Tests must use table-driven test patterns.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@theme/load_test.go` around lines 48 - 183, Multiple near-duplicate tests
exercise LoadAll with different user theme files; convert them into a single
table-driven test that iterates test cases (e.g., name "partial merge",
"user-only", "partial-no-builtin", "invalid-hex-merge") and performs the shared
setup/teardown, file writes, call to LoadAll, and assertions per-case. Replace
TestLoadAllPartialUserOverrideMergesOntoBuiltin, TestLoadAllAddsUserOnlyTheme,
TestLoadAllSkipsPartialThemeWithoutBuiltinMatch, and
TestLoadAllSkipsInvalidHexInMerge with one TestLoadAll_TableDriven that defines
a slice of structs containing fields: case name, filename, file contents,
expected presence (bool), and per-field expectations (Accent, FG, BrightFG, Red,
etc.), then loop over cases, set HOME/temp dir, create userDir, write the file,
call LoadAll(), find Theme by name and assert expectations accordingly; reuse
Theme, LoadAll, and strings.EqualFold to locate themes and keep individual
subtests using t.Run(case.name, func(t *testing.T) { ... }).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@theme/theme_test.go`:
- Around line 152-232: Rewrite the four tests into table-driven subtests: create
a TestTheme table that contains cases for Validate (cases: valid, partial,
empty, badHex with expected error bool), ValidateErrorContainsFieldNames (a case
that runs Validate on Theme{"broken",...} and asserts the error string contains
the field names), and Merge scenarios (partial override and ignores-invalid-hex)
— each case should use t.Run(name, func(t *testing.T){...}) and perform the same
assertions currently in TestValidate, TestValidateErrorContainsFieldNames,
TestMerge and TestMergeIgnoresInvalidHex; reference and call the same symbols
(Theme, Validate, merge, BrightFG/FG/Accent/Green/Yellow/Red) and use table
fields for input Theme values and expected outcomes (expectedErr bool, expected
fields or expected substrings) to drive assertions.

---

Outside diff comments:
In `@theme/load_test.go`:
- Around line 48-183: Multiple near-duplicate tests exercise LoadAll with
different user theme files; convert them into a single table-driven test that
iterates test cases (e.g., name "partial merge", "user-only",
"partial-no-builtin", "invalid-hex-merge") and performs the shared
setup/teardown, file writes, call to LoadAll, and assertions per-case. Replace
TestLoadAllPartialUserOverrideMergesOntoBuiltin, TestLoadAllAddsUserOnlyTheme,
TestLoadAllSkipsPartialThemeWithoutBuiltinMatch, and
TestLoadAllSkipsInvalidHexInMerge with one TestLoadAll_TableDriven that defines
a slice of structs containing fields: case name, filename, file contents,
expected presence (bool), and per-field expectations (Accent, FG, BrightFG, Red,
etc.), then loop over cases, set HOME/temp dir, create userDir, write the file,
call LoadAll(), find Theme by name and assert expectations accordingly; reuse
Theme, LoadAll, and strings.EqualFold to locate themes and keep individual
subtests using t.Run(case.name, func(t *testing.T) { ... }).
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b1623432-60fc-452f-8aac-8d84def0eafe

📥 Commits

Reviewing files that changed from the base of the PR and between 213ada6 and 182645f.

📒 Files selected for processing (3)
  • theme/load_test.go
  • theme/theme.go
  • theme/theme_test.go

Comment thread theme/theme_test.go
…ride

User theme files in ~/.config/cliamp/themes/ could silently replace built-in themes even when partial or corrupt, leaving empty hex fields that produced broken colours via lipgloss.Color("") in styles.go.

- validHex() validates #rgb/#rrggbb/#rrggbbaa format
- Theme.Validate() checks all six colour fields
- merge() applies only valid-hex fields onto the destination theme
- loadUserDir now uses two modes:
  - MERGE: when the file name matches a built-in theme, partial fields are merged onto the built-in (rest survives)
  - VALIDATE: themes without built-in match require all six hex fields or are rejected
  - Invalid hex values in either mode are silently ignored
…solation

- TestValidate and TestMerge now use table-driven subtests
- TestLoadAllUserThemeScenarios combines 4 LoadAll scenarios with shared setup
- All env-based tests use CLIAMP_CONFIG_DIR instead of HOME to avoid
  XDG_CONFIG_HOME override in appdir.Dir()
@HANCORE-linux

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@theme/load_test.go`:
- Line 83: Replace the inline single-line TOML string assigned to fileBody with
a multi-line raw string literal (backtick) to improve readability; locate the
test case where fileBody is set (the struct field named fileBody in
theme/load_test.go) and change the value from a single quoted string ("accent =
\"`#abcdef`\"...") to a backtick-wrapped here-doc style preserving newlines and
content exactly (e.g. `accent = "`#abcdef`"\nbright_fg = "`#ffffff`"\n...`) so the
TOML fields are each on their own line.
- Line 114: Replace the long single-line TOML in the test case's fileBody with a
Go raw multi-line string literal (backtick string) to improve readability;
locate the failing test in load_test.go where fileBody is set (the example with
"accent = \"`#ff0000`\"...red = \"not-a-color\"") and convert that quoted
single-line value into a backtick-delimited multi-line TOML block that preserves
identical content and line breaks, matching the style used in the "full
standalone theme accepted" case.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: be8d1969-b508-4e66-bc66-27ae789ca15c

📥 Commits

Reviewing files that changed from the base of the PR and between 182645f and 31bf2dd.

📒 Files selected for processing (3)
  • theme/load_test.go
  • theme/theme.go
  • theme/theme_test.go

Comment thread theme/load_test.go Outdated
Comment thread theme/load_test.go Outdated
@HANCORE-linux

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@HANCORE-linux

Copy link
Copy Markdown
Contributor Author

Closing this — after testing on a real setup, the validate/merge approach doesn't fix the actual symptom ("wrong / too-bright colours, have to rm -rf ~/.config/cliamp/themes/").

The themes that break are complete, valid user themes generated by the Omarchy theme-set hook. They pass Theme.Validate() and load identically with or without this PR — cliamp theme list shows the theme in both the patched and the stock binary, and there's no built-in of the same name, so neither the validate nor the merge path ever engages.

The real bug is the hook's colour mapping, not cliamp:

  • accent = color4 is dark/low-contrast on monochrome themes, yet cliamp uses accent for the title, seek bar and key-hint background → unreadable.
  • bright_fg/fg (color15/color7) aren't ordered by brightness, so some palettes end up with dimmed text brighter than normal text.

rm only "helps" because the theme is then not found, SetTheme returns false (ignored at startup), and the ANSI defaults apply.

Real fix (in the hook's repo): OldJobobo/theme-hook-plugin-manager#3.

Optional cliamp-side hardening (separate, not required for this bug): when SetTheme(cfg.Theme) returns false at startup, apply Default() and log a warning instead of silently keeping ANSI; and consider not using accent as the key-hint background.

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.

1 participant