Colour themes, defaulting to gruvbox dark #1

Merged
niklas merged 1 commits from color-themes into main 2026-08-20 09:11:14 +00:00
Owner

Every colour was a 256-colour ANSI index hardcoded in styles.go, so changing the palette meant editing the styles themselves. This puts a semantic token set between the two: styles name roles, a theme supplies the colours.

What's here

internal/theme — the only place a colour literal exists now.

  • Twelve semantic tokens: primary, on_primary, text, muted, accent, firing, resolved, error, and four for severity.
  • Two built-ins: gruvbox-dark (the new default) and gruvbox-light.
  • A loader for user themes in ~/.config/terdut-tui/themes/<name>.yaml. A file may extends: a built-in and override only what it cares about, and may shadow a built-in name to tweak it in place.

Config — a theme: key, empty meaning the default.

internal/tui/styles.go — the 21 package-level style vars become a Styles struct on the Model, which is what architecture rule 3 asked for all along. severityStyle/incidentStatusStyle become methods; the four free functions in view.go take a Styles as their first argument.

Bubbles components are restyled from the same tokens. Without this a theme would leave a pink selected row and grey help text behind.

Colours are truecolor hex now — lipgloss downsamples for 256- and 16-colour terminals and honours NO_COLOR, so themes carry no fallbacks of their own. An ANSI index is still accepted for anyone who would rather follow their terminal's own palette.

# ~/.config/terdut-tui/themes/mine.yaml
extends: gruvbox-dark
primary: "#d3869b"

Two things worth a reviewer's eye

The table's Cell style deliberately carries no foreground. Bubbles renders each cell before wrapping the whole row in Selected, so a colour on Cell emits a reset mid-row and cuts the selection highlight short after the first character. I hit this, caught it by rendering, and pinned it with a test — please don't "fix" the apparent omission.

Themes colour foregrounds only, plus the two inverted spots that need a pair. The app never paints the full alt-screen background, so the terminal supplies the canvas and gruvbox-dark assumes a dark one. That is the normal contract for a TUI colourscheme, but it is a deliberate choice rather than an oversight.

Verification

gofmt, go vet, go test ./... and go build are all clean.

The rewrite of view.go was mechanical: un-renaming the diff reproduces the original line for line, and the style-reference count is unchanged at 104.

New tests cover the loader (extends merging, shadowing, missing tokens, unknown keys, malformed colours, path-like names) and the token→style mapping. I checked they aren't vacuous by mutation — reverting the table selection, dropping firing's bold and breaking the extends merge each produce failures. That caught one genuinely vacuous assertion, since GetForeground() returns lipgloss.NoColor{} rather than Color("").

Beyond that I rendered real output under a forced truecolor profile to confirm gruvbox reaches the screen, and ran the built binary against a temporary XDG_CONFIG_HOME to check gruvbox-light, a user extends: file, and all four error paths — each exits 1 with a message naming what went wrong.

Not tested: the app against a live terdut-server, and gruvbox-light on an actual light terminal. The latter is worth a look before merging, since a light scheme is where a poorly chosen muted shows up first.

Every colour was a 256-colour ANSI index hardcoded in `styles.go`, so changing the palette meant editing the styles themselves. This puts a semantic token set between the two: styles name roles, a theme supplies the colours. ## What's here **`internal/theme`** — the only place a colour literal exists now. - Twelve semantic tokens: `primary`, `on_primary`, `text`, `muted`, `accent`, `firing`, `resolved`, `error`, and four for `severity`. - Two built-ins: `gruvbox-dark` (the new default) and `gruvbox-light`. - A loader for user themes in `~/.config/terdut-tui/themes/<name>.yaml`. A file may `extends:` a built-in and override only what it cares about, and may shadow a built-in name to tweak it in place. **Config** — a `theme:` key, empty meaning the default. **`internal/tui/styles.go`** — the 21 package-level style vars become a `Styles` struct on the `Model`, which is what architecture rule 3 asked for all along. `severityStyle`/`incidentStatusStyle` become methods; the four free functions in `view.go` take a `Styles` as their first argument. **Bubbles components** are restyled from the same tokens. Without this a theme would leave a pink selected row and grey help text behind. Colours are truecolor hex now — lipgloss downsamples for 256- and 16-colour terminals and honours `NO_COLOR`, so themes carry no fallbacks of their own. An ANSI index is still accepted for anyone who would rather follow their terminal's own palette. ```yaml # ~/.config/terdut-tui/themes/mine.yaml extends: gruvbox-dark primary: "#d3869b" ``` ## Two things worth a reviewer's eye **The table's `Cell` style deliberately carries no foreground.** Bubbles renders each cell *before* wrapping the whole row in `Selected`, so a colour on `Cell` emits a reset mid-row and cuts the selection highlight short after the first character. I hit this, caught it by rendering, and pinned it with a test — please don't "fix" the apparent omission. **Themes colour foregrounds only**, plus the two inverted spots that need a pair. The app never paints the full alt-screen background, so the terminal supplies the canvas and `gruvbox-dark` assumes a dark one. That is the normal contract for a TUI colourscheme, but it is a deliberate choice rather than an oversight. ## Verification `gofmt`, `go vet`, `go test ./...` and `go build` are all clean. The rewrite of `view.go` was mechanical: un-renaming the diff reproduces the original line for line, and the style-reference count is unchanged at 104. New tests cover the loader (`extends` merging, shadowing, missing tokens, unknown keys, malformed colours, path-like names) and the token→style mapping. I checked they aren't vacuous by mutation — reverting the table selection, dropping `firing`'s bold and breaking the `extends` merge each produce failures. That caught one genuinely vacuous assertion, since `GetForeground()` returns `lipgloss.NoColor{}` rather than `Color("")`. Beyond that I rendered real output under a forced truecolor profile to confirm gruvbox reaches the screen, and ran the built binary against a temporary `XDG_CONFIG_HOME` to check `gruvbox-light`, a user `extends:` file, and all four error paths — each exits 1 with a message naming what went wrong. **Not tested:** the app against a live terdut-server, and `gruvbox-light` on an actual light terminal. The latter is worth a look before merging, since a light scheme is where a poorly chosen `muted` shows up first.
niklas added 1 commit 2026-08-20 09:07:24 +00:00
Colour themes, defaulting to gruvbox dark
CI / test (pull_request) Successful in 4s
4a579bdbc6
Every colour was a 256-colour ANSI index hardcoded in styles.go, so changing
the palette meant editing the styles themselves. This puts a semantic token set
between the two: styles name roles, a theme supplies the colours.

internal/theme holds the twelve tokens, the two built-ins (gruvbox-dark, the
new default, and gruvbox-light) and the loader for user themes in
~/.config/terdut-tui/themes/. A user file may 'extends:' a built-in and
override only what it cares about, and may shadow a built-in name to tweak it
in place. Unknown keys, malformed colours and incomplete themes are refused
with a message naming what went wrong.

Colours are truecolor hex now: lipgloss downsamples for 256- and 16-colour
terminals and honours NO_COLOR, so themes carry no fallbacks of their own.
An ANSI index is still accepted for anyone who would rather follow their
terminal's own palette.

The 21 package-level style vars become a Styles struct on the Model, which is
what rule 3 asked for all along; the four free functions in view.go take one as
their first argument. The embedded bubbles components are restyled from the
same tokens — otherwise a theme would leave a pink selected row and grey help
text behind. Note that the table's Cell style deliberately keeps no foreground:
bubbles renders cells before wrapping the row in Selected, so a colour there
cuts the selection highlight short.
niklas merged commit 9a510ecc77 into main 2026-08-20 09:11:14 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: niklas/terdut-tui#1