8.1 KiB
Spec: theme-switch
ADDED Requirements
Requirement: Atomic theme swap via a single canonical pointer
The system SHALL provide a single shell command, theme-switch <theme-name>, that switches the active theme by atomically rewriting ~/.config/colorschemes/.active (a symlink) to point at ~/.config/colorschemes/<theme-name>/.
Scenario: Successful switch to a valid theme
- WHEN the user runs
theme-switch catppuccinand~/.config/colorschemes/catppuccin/exists - THEN
~/.config/colorschemes/.activeis a symlink whose target resolves to~/.config/colorschemes/catppuccin - AND
~/.config/colorschemes/.currentcontains the textcatppuccin
Scenario: Switch with an unknown theme name
- WHEN the user runs
theme-switch does-not-existand the directory does not exist - THEN the command exits non-zero
- AND prints a list of valid theme names to stderr
- AND does not modify
.activeor.current
Scenario: Re-applying the current theme is a no-op
- WHEN the user runs
theme-switchwith no arguments - THEN the command reads
.current - AND re-applies every per-app reload step (idempotent)
Requirement: Per-theme metadata drives picker and gsettings
The system SHALL read each theme's mode (dark or light) and display name from a meta file at ~/.config/colorschemes/<theme>/meta.
Scenario: meta file format
- WHEN the user runs
theme-switchagainst any theme - THEN the script reads
<theme>/meta - AND parses lines of the form
key=value - AND uses
mode=darkormode=lightto drive gsettings - AND uses
name=...for the rofi picker label
Scenario: Missing meta file defaults to dark
- WHEN a theme directory has no
metafile - THEN
theme-switchtreats the theme asmode=dark - AND uses the directory name as the display name
- AND emits a warning to stderr
Requirement: Per-app live reload covers every themed app on jpporta-nixos
theme-switch SHALL emit the appropriate reload signal for each themed app on the host, in an order that places file/data changes before signals.
Scenario: Hyprland picks up new colors via file watch
- WHEN
theme-switchrewrites~/.config/hypr/hyprland.confso that itssource =directive points at~/.config/colorschemes/.active/hypr/colors.conf - THEN the running Hyprland instance reloads automatically (it watches its config file)
Scenario: waybar picks up new colors via SIGUSR2
- WHEN
theme-switchswaps the waybar CSS symlink and the colors.css file under it - THEN the running waybar instance receives SIGUSR2
- AND restyles with the new palette
Scenario: swaync picks up new CSS via swaync-client
- WHEN
theme-switchswaps~/.config/swaync/colors.css - THEN the script invokes
swaync-client --reload-css - AND swaync restyles without restart
Scenario: kitty picks up new colors via IPC
- WHEN
theme-switchinvokeskitty @ set-colors --all ~/.config/colorschemes/.active/kitty/colors.conf - THEN every running kitty window restyles without restart
Scenario: ghostty picks up new colors via SIGHUP
- WHEN
theme-switchsends SIGHUP to the running ghostty process - THEN ghostty re-reads its config (including the colors block)
Scenario: alacritty uses the new palette on next launch
- WHEN
theme-switchswaps the alacritty colors file thatalacritty.tomlimports - THEN running alacritty windows keep their current colors until restarted
- AND any newly launched alacritty instance uses the new palette
Scenario: nvim recolors in-session when $NVIM is set
- WHEN
theme-switchruns and the user has$NVIMset (a running nvim server) - THEN the script invokes
nvim --remote-expr 'lua vim.cmd.colorscheme("<name>")' - AND the running nvim recolors immediately
- WHEN
$NVIMis not set - THEN the script takes no action for nvim
- AND newly launched nvim instances pick up the theme from the symlinked config
Scenario: GTK dark/light preference follows theme meta
- WHEN
theme-switchreadsmode=darkfrom the new theme's meta - THEN the script invokes
gsettings set org.gnome.desktop.interface color-scheme prefer-dark - WHEN
mode=light - THEN the script invokes
gsettings set org.gnome.desktop.interface color-scheme prefer-light
Requirement: Rofi-driven theme picker is a thin wrapper over theme-switch
The system SHALL provide a theme-picker script that lists available themes (display name + mode) via rofi -dmenu and invokes theme-switch on the user's selection.
Scenario: Picker shows all available themes
- WHEN the user runs
theme-picker - THEN rofi displays one entry per theme directory under
~/.config/colorschemes/ - AND each entry shows the display name from
metaand a(dark)or(light)suffix
Scenario: Picker invokes theme-switch on selection
- WHEN the user selects an entry in rofi
- THEN
theme-pickerinvokestheme-switch <selected-theme-name> - AND exits with the same status code
Requirement: Boot-time re-apply guarantees a consistent desktop on login
The system SHALL provide a systemd user oneshot service that runs theme-switch (no args) on graphical-session start, ensuring .current is applied before the user sees the desktop.
Scenario: Service runs on graphical-session start
- WHEN the user's graphical session (Hyprland on jpporta-nixos; cage on jpporta-deck) starts
- THEN
theme-apply.servicerunstheme-switch - AND the service has no effect if
.currentalready matches the running state
Scenario: Service does not run before graphical session
- WHEN the user's session is not yet at graphical-session.target
- THEN
theme-apply.serviceis not started - AND waits for the target
Requirement: Wallpaper hook is user-owned and exec'd at end of switch
theme-switch SHALL execute ~/.config/colorschemes/.hook <theme-name> at the end of every successful switch if that file exists and is executable.
Scenario: Hook is invoked after all app reloads
- WHEN
theme-switchfinishes the per-app reload block - AND
~/.config/colorschemes/.hookexists and is executable - THEN the script executes
~/.config/colorschemes/.hook <theme-name> - AND any non-zero exit status is logged but does not fail the switch
Scenario: Missing hook does not fail the switch
- WHEN
~/.config/colorschemes/.hookdoes not exist or is not executable - THEN the switch still completes successfully
Requirement: Writer-deck adopts the same mechanism with foot handled at switch time
The same theme-switch script SHALL work on jpporta-deck (cage + foot). Foot has no live color reload, so the deck profile SHALL include a foot-specific step that sends SIGUSR1 (footserver reload) or, as a fallback, restarts the foot server.
Scenario: Foot recolors via SIGUSR1 to footserver
- WHEN
theme-switchruns on jpporta-deck - THEN the script sends
SIGUSR1to the footserver process if running - AND foot reloads its config without losing the running instance
Scenario: footserver not running
- WHEN
theme-switchruns on jpporta-deck and no footserver is running - THEN the script takes no action for foot
- AND the next foot launch picks up the new theme
Requirement: First-install migration creates .active from custom.theme.current
On a fresh home-manager switch after this change is applied, the theme module SHALL ensure ~/.config/colorschemes/.active exists and points at the theme named in custom.theme.current.
Scenario: First install with default theme
- WHEN the user applies this change for the first time and
custom.theme.current = "gruvbox-dark" - THEN the activation creates
~/.config/colorschemes/.activeas a symlink to~/.config/colorschemes/gruvbox-dark/ - AND writes
gruvbox-darkto~/.config/colorschemes/.current
Scenario: Existing wallpapers symlink is left in place during migration
- WHEN
~/.config/colorschemes/wallpapersis already a symlink (legacy) - THEN the migration does not remove it
- AND documents in a comment that it is now redundant