Files
dotfiles/docs/superpowers/specs/2026-06-07-theme-centralization-and-bluetooth-design.md
funman300 1e3adbc842 feat: centralize theme palette + add waybar bluetooth module
Theme centralization:
- theme/colors.json is now the single source of truth for the palette.
- New theme/generate-theme.py renders, at install time, the three derived
  artifacts into ~/.config: colors.css (waybar/wofi @import), mako config,
  and hyprlock-colors.conf (hyprlang $tn_* variables).
- theme/colors.css removed from the repo (now generated); install.sh calls
  the generator instead of the old inline mako snippet.
- hyprlock.conf sources the generated color file and uses $tn_* variables
  instead of hardcoded rgba() values.
- Generator covered by theme/test_generate_theme.py.

Bluetooth:
- Added waybar built-in bluetooth module (config.jsonc + style.css), with
  bluez/bluez-utils in packages.txt.

Includes design spec and implementation plan under docs/superpowers/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:45:01 -07:00

6.5 KiB

Theme Centralization & Bluetooth Module — Design

Date: 2026-06-07 Status: Approved (pending spec review)

Summary

Two independent enhancements to the niri dotfiles:

  1. Theme centralization — collapse the duplicated color palette into a single source of truth (theme/colors.json) and generate all derived configs from it at install time. Bring hyprlock under the shared palette. Leave alacritty untouched (out of scope).
  2. Bluetooth module — add a waybar bluetooth indicator using the already-installed blueman/bluez stack.

Motivation

CLAUDE.md mandates a single source of truth and warns against duplicating configs / hardcoding colors in multiple places. Current state violates this:

  • theme/colors.css (@define-color palette) and theme/colors.json (same palette as JSON) both hand-maintain the same 12 colors. Changing a color requires editing both files.
  • hyprlock/hyprlock.conf hardcodes 7 rgba(...) colors, every one of which already exists in the palette.

Already-correct (not changed by this work):

  • waybar (waybar/style.css) and wofi (wofi/style.css) @import colors.css.
  • mako config is generated from colors.json by install.sh.

Scope

In scope: unify the two palette files into one source; generate colors.css, the mako config, and a hyprlock color file from it; convert hyprlock.conf to reference the shared palette; add a waybar bluetooth module.

Out of scope: alacritty terminal colors (needs a full 16-color normal+bright set the palette does not currently model — deferred); any change to the Tomorrow Night palette values themselves; GTK theming (handled by Materia).

Approach

Chosen approach A: theme/colors.json is the single source of truth; everything else is generated at install time. This extends the existing Python generation block already in install.sh (which generates the mako config). Rejected: parsing colors.css as the source (fragile regex), and introducing a new neutral source format (YAGNI for this scope, discards working JSON+Python).

Detailed Design

1. Single source of truth

theme/colors.json remains the only hand-edited palette file. It holds the 12 base colors (already present). The hand-maintained theme/colors.css is deleted from the repo; it becomes a generated artifact.

2. Generator: theme/generate-theme.py

A new standalone script, invoked by install.sh, replacing the current inline Python mako snippet. Reads theme/colors.json and writes three artifacts directly into ~/.config (written, not symlinked — matching how mako is handled today):

  • ~/.config/theme/colors.css@define-color block for all 12 base colors, plus the three alpha variants (tn-bg-a90, tn-bg-a95, tn-bg-a96) computed from the background color at opacities 0.90 / 0.95 / 0.96. This is what waybar and wofi already @import; their files do not change.
  • ~/.config/mako/config — same output as the current inline snippet.
  • ~/.config/theme/hyprlock-colors.conf — hyprlang variable declarations, one per needed color, e.g. $tn_fg = rgba(c5c8c6ff). Hex from colors.json is converted to hyprlock's rgba(RRGGBBAA) form with AA = ff.

The generator must be idempotent and safe to re-run (overwrites its outputs).

3. hyprlock.conf

  • Add source = ~/.config/theme/hyprlock-colors.conf near the top.
  • Replace the 7 hardcoded rgba(...) values with the matching $tn_* variables. Color → variable mapping (from current values):
    • rgba(c5c8c6ff)$tn_fg (foreground)
    • rgba(969896ff)$tn_fg_dim (foreground_dim)
    • rgba(282a2eff)$tn_bg_alt (background_alt)
    • rgba(81a2beff)$tn_blue (blue)
    • rgba(cc6666ff)$tn_red (red)
    • rgba(b5bd68ff)$tn_green (green)
  • hyprlock.conf stays a hand-edited, symlinked file; only its colors come from the generated source file.

Risk + fallback: hyprlock v0.9.5 uses the hyprlang parser, which documents support for $variables and source=. This is verified by documentation, not empirically (no dry-run flag; launching hyprlock grabs the screen). The implementation plan MUST include an explicit parse test (lock once and confirm colors render, or launch with --grace 0 and watch --verbose for parse errors). Fallback if source=/variables misbehave: generate the entire hyprlock.conf from a hyprlock/hyprlock.conf.tmpl template with color placeholders, instead of using source=. Same single-source outcome, more generation.

4. install.sh changes

  • Remove the inline python3 - <<'PYEOF' ... PYEOF mako block.
  • Add a call to python3 theme/generate-theme.py after configs are linked.
  • Remove the ln -sf .../theme/colors.css ~/.config/theme/colors.css line (colors.css is now generated into that path, not symlinked).
  • Ensure ~/.config/theme exists before generation (already created).

5. Bluetooth waybar module

  • waybar/config.jsonc: add "bluetooth" to modules-right (placed next to network), with a bluetooth module block:
    • format, format-connected, format-disabled using Nerd Font glyphs.
    • on-click: blueman-manager.
  • waybar/style.css: add a #bluetooth padding rule (consistent with the other right-side modules) plus a connected-state accent color using a palette variable (e.g. @tn-blue).
  • Dependencies: blueman + bluez/bluez-utils (blueman pulls bluez). Confirm packages.txt lists what's needed; the waybar bluetooth module requires the bluez daemon running.

Testing / Verification

  • Generator: run python3 theme/generate-theme.py; diff generated ~/.config/theme/colors.css against the previous hand-written file to confirm identical palette + alpha variants. Confirm mako config output unchanged.
  • waybar/wofi: restart waybar (waybar-restart), confirm no CSS errors and colors render. Open wofi, confirm styling intact.
  • hyprlock: parse test per the fallback note above; confirm lock screen colors match before/after.
  • Bluetooth: confirm module appears, reflects adapter state, and blueman-manager opens on click.
  • Idempotency: re-run install.sh; confirm no errors and stable output.

Rollout Notes

  • Generated files (~/.config/theme/colors.css, ~/.config/theme/hyprlock-colors.conf) are not committed and not symlinked — consistent with the existing mako generation. The committed theme/colors.css is removed in this change.
  • The two features (theme, bluetooth) are independent and can be implemented and verified separately.