diff options
Diffstat (limited to 'docs/superpowers/specs/2026-09-11-unified-desktop-theme-design.md')
| -rw-r--r-- | docs/superpowers/specs/2026-09-11-unified-desktop-theme-design.md | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-09-11-unified-desktop-theme-design.md b/docs/superpowers/specs/2026-09-11-unified-desktop-theme-design.md new file mode 100644 index 0000000..8db59e5 --- /dev/null +++ b/docs/superpowers/specs/2026-09-11-unified-desktop-theme-design.md @@ -0,0 +1,200 @@ +# Unified Desktop Theme, Design + +Date: 2026-09-11 +Status: approved, phase 1 not yet implemented + +## Goal + +One consistent visual identity across the desktop: Catppuccin Macchiato as the +fixed base, Noto Sans as the UI font, Inconsolata Nerd Font Mono as the +monospace font. The accent color follows the wallpaper through pywal, but is +always snapped to a real Macchiato accent so the result is never unreadable. + +Phase 1 covers rofi only. Later phases extend the same mechanism to waybar, +dunst, kitty, conky, hyprland and quickshell. + +## Problem + +The rofi setup has grown five independent theme systems with three different +color sources and three different fonts: + +| Theme system | Color source | Used by | +| --- | --- | --- | +| `darknix/` | pywal (`~/.cache/wal/darknix-colors.rasi`) | appsmenu, emoji, notes, qemu, scrotmenu | +| `launchers/type-2/style-1` (adi1090x) | `colors/catppuccin.rasi` | sshmenu, github-repos, hypr-windows, cliphist | +| `launchers/type-1` (adi1090x) | `colors/catppuccin.rasi` | ALT+F2 launcher | +| `powermenu/type-4` (adi1090x) | `colors/catppuccin.rasi` | Mod+x powermenu | +| `elegantVagrant/` | self-contained | rofipass | +| (none) | rofi defaults | ddgr_search.py | + +Fonts in play: `JetBrains Mono Nerd Font 10` (adi1090x shared/fonts.rasi), +`Mono 12` (rofi config.rasi), plus whatever each standalone theme sets. Neither +matches the Qt/GTK setting, which is already Noto Sans + Inconsolata Nerd Font +Mono. + +The `colors/catppuccin.rasi` shipped by adi1090x is not Macchiato. Its +background is `#1E1D2F` and its `selected` is `#7AA2F7`, a Tokyo Night blue. + +## Decisions + +Four decisions were made during brainstorming, each with alternatives +considered: + +1. **Color model: Macchiato base with a pywal-driven accent.** Structural + colors (backgrounds, text) are fixed Macchiato. Exactly one color, the + accent, follows the wallpaper. Rejected: fully fixed palette (loses the + wallpaper tie-in, and the pywal pipeline already exists); fully + pywal-driven (readability not guaranteed). + +2. **Accent selection: snap to the nearest Macchiato accent.** pywal's dominant + color is matched by hue against the 14 named Macchiato accents and the + closest one wins. Rejected: raw pywal color (can be muddy or dark against + the base); raw color with a contrast floor (keeps more wallpaper fidelity + but can emit colors outside the palette). + +3. **Scope: rofi only in phase 1.** Rofi holds the actual inconsistency and + exercises every part of the pipeline. Once proven, each further app is a + single template file. + +4. **Layouts: three shapes, one palette.** The scripts genuinely have three + different jobs and forcing one shape on all of them would push the variation + back into scattered `-theme-str` strings. Revisit if three proves wrong. + +## Architecture + +``` +~/.config/wal/templates/udt-accent.rasi # pywal template, emits raw dominant color +~/.config/rofi/udt/ + palette.rasi # Macchiato, fixed, hand-written + accent.rasi # symlink -> ~/.cache/wal/udt-accent.rasi (generated) + common.rasi # fonts, radii, spacing, element states; imports palette + accent + launcher.rasi # grid + search + menu.rasi # small, fixed options + list.rasi # tall searchable list +~/bin/udt-accent # snaps pywal dominant color to nearest Macchiato accent +``` + +Data flow on wallpaper change: + +1. pywal renders `udt-accent.rasi` into `~/.cache/wal/` with the raw dominant + color. +2. `udt-accent` reads it, snaps to the nearest Macchiato accent, rewrites the + file in place. +3. Next rofi invocation picks it up. Rofi reads its theme per launch, so no + daemon and no reload are needed. + +Step 3 is why this needs no running process. `udt-accent` is called from +`wal.sh`, which already performs the symlink-and-restart sequence for dunst and +kitty. + +## Color contract + +`palette.rasi` defines the full Macchiato palette by semantic name. Values are +from the official `catppuccin/palette` repository: + +``` +base #24273a mantle #1e2030 crust #181926 +text #cad3f5 subtext1 #b8c0e0 subtext0 #a5adcb +overlay2 #939ab7 overlay1 #8087a2 overlay0 #6e738d +surface2 #5b6078 surface1 #494d64 surface0 #363a4f + +rosewater #f4dbd6 flamingo #f0c6c6 pink #f5bde6 mauve #c6a0f6 +red #ed8796 maroon #ee99a0 peach #f5a97f yellow #eed49f +green #a6da95 teal #8bd5ca sky #91d7e3 sapphire #7dc4e4 +blue #8aadf4 lavender #b7bdf8 +``` + +`accent.rasi` defines exactly one variable, `@accent`, always equal to one of +the 14 accent colors above. + +Themes reference semantic names only, never literal hex. Everything structural +is fixed; only `@accent` moves. + +## The accent snapper + +`~/bin/udt-accent`, Python 3, standard library only (`colorsys`, `re`). + +- Reads the raw color from `~/.cache/wal/udt-accent.rasi`. +- Converts it and the 14 candidate accents to HLS. +- Picks the candidate with the smallest circular hue distance. +- Writes `* { accent: #rrggbb; }` back to the same path. +- Falls back to `mauve` when the cache file is missing or unparseable, so a + fresh machine or a failed pywal run still yields a working theme. + +Hue alone is the comparison metric: the Macchiato accents are already +normalized for lightness and saturation against the base, so matching hue is +what selects the perceptually right one. A near-grey wallpaper color has an +unstable hue, so colors below a small saturation threshold fall back to `mauve` +rather than snapping arbitrarily. + +Leaves one runnable self-check (`udt-accent --selftest`) asserting that a known +orange input snaps to `peach`, a known green to `green`, and a grey to the +`mauve` fallback. + +## Layouts + +All three import `common.rasi`, which holds the shared identity: Noto Sans for +UI text, Inconsolata Nerd Font Mono for monospace fields, one border radius, +one border width, and one `element.selected` treatment (accent background, +`@base` text). + +- **`launcher.rasi`**: centered, search bar, icon grid. App launching. +- **`menu.rasi`**: small centered box, no search, few fixed options. +- **`list.rasi`**: tall, search bar, single wide column. Long searchable lists. + +## Migration + +Eleven targets. Each gets its `-theme` argument pointed at one of the three new +files; `ddgr_search.py` has no `-theme` today and gains one. + +| Layout | Targets | +| --- | --- | +| `launcher.rasi` | `blackpearl-appsmenu.sh`, `launchers/type-1/launcher.sh` (ALT+F2) | +| `menu.rasi` | `qar-scrotmenu.sh`, `blackpearl-notes.sh`, `powermenu/type-4/powermenu.sh` (Mod+x) | +| `list.rasi` | `blackpearl-sshmenu.sh`, `blackpearl-emoji.sh`, `rofi-qemu.sh`, `github-repos.sh`, `hypr-windows.sh`, `rofipass`, `ddgr_search.py`, cliphist bind | + +The cliphist binding carries its `-theme` inline in +`~/.config/hypr/sections/keybindings.lua` rather than in a script, so that line +is edited directly. + +`launchers/type-1` and `powermenu/type-4` are third-party adi1090x scripts. +They are treated like any other target, a `-theme` path change only, with no +attempt to restructure them. + +The old theme directories (`darknix/`, `elegantVagrant/`, `launchers/`, +`applets/`, `powermenu/`) stay on disk untouched. Deleting them is a separate +decision to be made after the new themes are confirmed good. + +Out of scope: `ronema` (rofi NetworkManager applet, no longer used, pending +archival by the user). + +## Verification + +Visual, performed by the user. Each of the eleven targets is launched and +eyeballed against three checks: colors are Macchiato, fonts are Noto Sans and +Inconsolata, and the shape suits the job. + +The accent pipeline is verified separately by changing the wallpaper to images +with clearly different dominant hues and confirming the accent tracks and stays +readable. + +`udt-accent --selftest` covers the snapping logic non-visually. + +## Later phases + +Each subsequent app reuses `palette.rasi` and `accent.rasi` through a +format-appropriate template, and needs no new mechanism: + +- **waybar**: CSS, consumes a generated `colors.css`. +- **dunst**: already pywal-templated, template gets repointed. +- **kitty**: already pywal-templated, same. +- **conky**: Lua config, reads generated values. +- **hyprland**: window borders use the accent. +- **quickshell**: new work, to be designed when the user starts on it. + +## License + +GPLv2 (to be confirmed). Needs `LICENSE`, per-file header notices with +`Copyright (C) 2026 Danilo M. <danix@danix.xyz>`, and a License section in the +README, added early rather than retrofitted. The README also needs the standard +Development Approach section disclosing AI-assisted development. |
