Skip to content

Add theme to lets settings#353

Merged
kindermax merged 1 commit into
masterfrom
theme-settings
Jun 13, 2026
Merged

Add theme to lets settings#353
kindermax merged 1 commit into
masterfrom
theme-settings

Conversation

@kindermax

@kindermax kindermax commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Summary by Sourcery

Introduce a configurable UI theme setting for lets and apply it to help and error output.

New Features:

  • Add a user-configurable theme setting with default, ansi, and synthwave options for lets output styling.

Enhancements:

  • Validate theme names when loading settings and fall back to the default theme for unknown values.
  • Wire the selected theme into the CLI so help and error output uses the configured color scheme.

Documentation:

  • Document the new theme setting, supported values, defaults, and update the changelog and settings examples accordingly.

Tests:

  • Extend settings tests to cover theme defaults, file loading, and validation, and add unit tests for theme name validation and color scheme resolution.

@sourcery-ai

sourcery-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduce a configurable theme setting that controls lets CLI help/error color schemes, with validation, wiring into the CLI, tests, and user-facing docs/changelog updates.

Sequence diagram for applying the new theme setting to CLI color schemes

sequenceDiagram
    actor User
    participant CLI as Main
    participant Settings as LoadFile
    participant Theme as theme
    participant Fang as fang.Run

    User->>CLI: execute lets
    CLI->>Settings: LoadFile(path)
    Settings->>Settings: applyEnvOverrides(&cfg)
    Settings->>Theme: ValidName(cfg.Theme)
    Theme-->>Settings: bool
    Settings-->>CLI: Settings{Theme, NoColor, UpgradeNotify}

    CLI->>Theme: ColorSchemeByName(appSettings.Theme)
    Theme-->>CLI: fang.ColorSchemeFunc

    CLI->>Fang: Run(rootCmd, WithColorSchemeFunc(...), ...)
    Fang-->>User: themed help and error output
Loading

File-Level Changes

Change Details Files
Add theme selection and validation to settings loading and configuration model.
  • Extend FileSettings and Settings structs with a Theme field and set default theme in Default()
  • Populate Theme from YAML settings file if provided
  • Validate the resolved theme name after applying file and env overrides, returning an error for unsupported values
  • Adjust settings tests to cover default theme, file-based theme values, env override behavior, and rejection of invalid themes
internal/settings/settings.go
internal/settings/settings_test.go
Expose theme name constants, validation, and name-to-colorscheme resolution in the theme package with tests.
  • Define constants for supported theme names (default, ansi, synthwave)
  • Add ValidName helper to check if a theme name is supported
  • Add ColorSchemeByName helper that returns the appropriate fang.ColorSchemeFunc, falling back to the default theme for unknown names
  • Add unit tests to verify ValidName and ColorSchemeByName behavior and key color expectations
internal/theme/theme.go
internal/theme/theme_test.go
Wire the configured theme into the CLI rendering pipeline.
  • Update CLI initialization to use ColorSchemeByName with the configured Settings.Theme instead of the hardcoded DefaultColorScheme
internal/cli/cli.go
Document the new theme setting and update examples and changelog.
  • Update settings documentation to describe the theme setting, supported values, defaults, and notes on scope
  • Update the example settings YAML to include theme alongside no_color
  • Add a changelog entry describing the new theme user setting and supported themes
docs/docs/settings.md
docs/docs/changelog.md

Possibly linked issues

  • #N/A: PR adds the theme config setting, validation, tests, CLI wiring, and documentation as requested in issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 3 issues, and left some high level feedback:

  • The LoadFile invalid-theme error message hardcodes the allowed themes; consider deriving the list from a single source (e.g., a slice in the theme package) to avoid it drifting when new themes are added.
  • ColorSchemeByName silently falls back to the default for unknown names while LoadFile rejects invalid themes; consider either documenting this discrepancy or having ColorSchemeByName surface an error for unexpected names to better catch programmer mistakes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `LoadFile` invalid-theme error message hardcodes the allowed themes; consider deriving the list from a single source (e.g., a slice in the `theme` package) to avoid it drifting when new themes are added.
- `ColorSchemeByName` silently falls back to the default for unknown names while `LoadFile` rejects invalid themes; consider either documenting this discrepancy or having `ColorSchemeByName` surface an error for unexpected names to better catch programmer mistakes.

## Individual Comments

### Comment 1
<location path="internal/settings/settings.go" line_range="80-81" />
<code_context>

 	applyEnvOverrides(&cfg)

+	if !theme.ValidName(cfg.Theme) {
+		return Settings{}, fmt.Errorf(
+			"invalid theme %q: must be one of %q, %q, %q",
+			cfg.Theme,
</code_context>
<issue_to_address>
**suggestion:** Avoid hard-coding the list of valid themes in the error message to prevent drift from `theme.ValidName`.

Right now the message hard-codes `DefaultName`, `ANSIName`, and `SynthwaveName`, which can fall out of sync as themes change. Consider exposing the valid theme names from `theme` (e.g., via a `Names()` helper or shared slice) and constructing the error message from that so the validation and error stay aligned.
</issue_to_address>

### Comment 2
<location path="internal/theme/theme_test.go" line_range="35" />
<code_context>
+		t.Fatalf("expected synthwave theme title color %v, got %v", charmtone.Grape, got)
+	}
+
+	if got := ColorSchemeByName("unknown")(lipgloss.LightDark(true)).Title; got != charmtone.Ash {
+		t.Fatalf("expected unknown theme to fall back to %v, got %v", charmtone.Ash, got)
+	}
</code_context>
<issue_to_address>
**suggestion (testing):** Reduce coupling of the fallback behavior test to specific color values

The last assertion hardcodes `charmtone.Ash` as the expected Title color for unknown themes, tying the test to the current `DefaultColorScheme` implementation. If that default color changes, this test will fail even though the fallback still works. Instead, compare the scheme for an unknown name to the scheme for `DefaultName`, e.g.:

```go
want := ColorSchemeByName(DefaultName)(lipgloss.LightDark(true))
got := ColorSchemeByName("unknown")(lipgloss.LightDark(true))
if got.Title != want.Title { /* ... */ }
```

Possibly compare multiple fields to assert the fallback behavior without hardcoding specific colors.

Suggested implementation:

```golang
func TestColorSchemeByName(t *testing.T) {
	lightDark := lipgloss.LightDark(true)

	defaultScheme := ColorSchemeByName(DefaultName)(lightDark)
	if defaultScheme.Title != charmtone.Ash {
		t.Fatalf("expected default theme title color %v, got %v", charmtone.Ash, defaultScheme.Title)
	}

	unknownScheme := ColorSchemeByName("unknown")(lightDark)
	if unknownScheme.Title != defaultScheme.Title {
		t.Fatalf("expected unknown theme to fall back to default title color %v, got %v", defaultScheme.Title, unknownScheme.Title)
	}
	if unknownScheme.ErrorDetails != defaultScheme.ErrorDetails {
		t.Fatalf("expected unknown theme to fall back to default error details color %v, got %v", defaultScheme.ErrorDetails, unknownScheme.ErrorDetails)
	}
}

```

If the `ColorScheme` struct has other important fields that must match for a correct fallback (e.g. `Body`, `Subtitle`, etc.), you may want to add analogous comparisons for those fields as well to further strengthen the test without hardcoding specific color values.
</issue_to_address>

### Comment 3
<location path="docs/docs/settings.md" line_range="8" />
<code_context>
 `lets` settings control the behavior of `lets` itself.

-Use settings for things like colored output or update notifications. Do not use this file for project commands or runtime env. Project behavior still belongs in `lets.yaml`.
+Use settings for things like colored output, theming, or update notifications. Do not use this file for project commands or runtime env. Project behavior still belongs in `lets.yaml`.

 ## Settings file location
</code_context>
<issue_to_address>
**suggestion (typo):** Consider expanding the abbreviation "runtime env" to "runtime environment" for clarity.

Spelling this out (e.g., "Do not use this file for project commands or runtime environment.") will make the docs clearer to all readers.

```suggestion
Use settings for things like colored output, theming, or update notifications. Do not use this file for project commands or runtime environment. Project behavior still belongs in `lets.yaml`.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +80 to +81
if !theme.ValidName(cfg.Theme) {
return Settings{}, fmt.Errorf(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Avoid hard-coding the list of valid themes in the error message to prevent drift from theme.ValidName.

Right now the message hard-codes DefaultName, ANSIName, and SynthwaveName, which can fall out of sync as themes change. Consider exposing the valid theme names from theme (e.g., via a Names() helper or shared slice) and constructing the error message from that so the validation and error stay aligned.

t.Fatalf("expected synthwave theme title color %v, got %v", charmtone.Grape, got)
}

if got := ColorSchemeByName("unknown")(lipgloss.LightDark(true)).Title; got != charmtone.Ash {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Reduce coupling of the fallback behavior test to specific color values

The last assertion hardcodes charmtone.Ash as the expected Title color for unknown themes, tying the test to the current DefaultColorScheme implementation. If that default color changes, this test will fail even though the fallback still works. Instead, compare the scheme for an unknown name to the scheme for DefaultName, e.g.:

want := ColorSchemeByName(DefaultName)(lipgloss.LightDark(true))
got := ColorSchemeByName("unknown")(lipgloss.LightDark(true))
if got.Title != want.Title { /* ... */ }

Possibly compare multiple fields to assert the fallback behavior without hardcoding specific colors.

Suggested implementation:

func TestColorSchemeByName(t *testing.T) {
	lightDark := lipgloss.LightDark(true)

	defaultScheme := ColorSchemeByName(DefaultName)(lightDark)
	if defaultScheme.Title != charmtone.Ash {
		t.Fatalf("expected default theme title color %v, got %v", charmtone.Ash, defaultScheme.Title)
	}

	unknownScheme := ColorSchemeByName("unknown")(lightDark)
	if unknownScheme.Title != defaultScheme.Title {
		t.Fatalf("expected unknown theme to fall back to default title color %v, got %v", defaultScheme.Title, unknownScheme.Title)
	}
	if unknownScheme.ErrorDetails != defaultScheme.ErrorDetails {
		t.Fatalf("expected unknown theme to fall back to default error details color %v, got %v", defaultScheme.ErrorDetails, unknownScheme.ErrorDetails)
	}
}

If the ColorScheme struct has other important fields that must match for a correct fallback (e.g. Body, Subtitle, etc.), you may want to add analogous comparisons for those fields as well to further strengthen the test without hardcoding specific color values.

Comment thread docs/docs/settings.md
`lets` settings control the behavior of `lets` itself.

Use settings for things like colored output or update notifications. Do not use this file for project commands or runtime env. Project behavior still belongs in `lets.yaml`.
Use settings for things like colored output, theming, or update notifications. Do not use this file for project commands or runtime env. Project behavior still belongs in `lets.yaml`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (typo): Consider expanding the abbreviation "runtime env" to "runtime environment" for clarity.

Spelling this out (e.g., "Do not use this file for project commands or runtime environment.") will make the docs clearer to all readers.

Suggested change
Use settings for things like colored output, theming, or update notifications. Do not use this file for project commands or runtime env. Project behavior still belongs in `lets.yaml`.
Use settings for things like colored output, theming, or update notifications. Do not use this file for project commands or runtime environment. Project behavior still belongs in `lets.yaml`.

@kindermax kindermax merged commit 31aa28e into master Jun 13, 2026
5 checks passed
@kindermax kindermax deleted the theme-settings branch June 13, 2026 19:00
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