diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-11 09:17:17 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-11 09:17:17 +0200 |
| commit | ec0dacff5f9a273c2614c50e307db9fc245baf22 (patch) | |
| tree | 798054b5bb48cd02868070af133f16686634b47b /docs/superpowers/plans/2026-09-11-rofi-unified-theme.md | |
| parent | 3bfe81ea6ec7779558c08e08755893ca700a83e5 (diff) | |
| download | unified-desktop-theme-ec0dacff5f9a273c2614c50e307db9fc245baf22.tar.gz unified-desktop-theme-ec0dacff5f9a273c2614c50e307db9fc245baf22.zip | |
docs: correct accent pipeline premise and add implementation plan
Investigation while planning showed the spec's accent model could not work as
written. wallp invokes pywal with --theme, not -i, so colors come from a fixed
preset and never track the wallpaper: snapping would have mapped a constant to
a constant.
The obvious fix, switching to wallpaper-derived pywal, would have regressed a
problem the user had already solved. Wallpaper colors land in kitty's sixteen
ANSI slots, where nothing guarantees color4 stays legible against color0, which
is what made neovim comments disappear. Rofi is not exposed to that, since it
uses one accent against a fixed base.
So extraction now calls pywal.backends.colorz.get() directly. It returns a list
and writes nothing, leaving the preset cache that kitty, dunst and neovim read
untouched. Verified across six wallpapers with colors.json byte-identical after.
Also replaces HLS hue matching with CIELAB, after prototyping showed HLS
snapping orange to yellow and red to flamingo, and narrows the candidate set to
nine accents, dropping near-neutral tints and duplicate hues that captured
saturated inputs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6aeE37K4GHBsaM51yLTTq
Diffstat (limited to 'docs/superpowers/plans/2026-09-11-rofi-unified-theme.md')
| -rw-r--r-- | docs/superpowers/plans/2026-09-11-rofi-unified-theme.md | 1125 |
1 files changed, 1125 insertions, 0 deletions
diff --git a/docs/superpowers/plans/2026-09-11-rofi-unified-theme.md b/docs/superpowers/plans/2026-09-11-rofi-unified-theme.md new file mode 100644 index 0000000..ede0c05 --- /dev/null +++ b/docs/superpowers/plans/2026-09-11-rofi-unified-theme.md @@ -0,0 +1,1125 @@ +# Rofi Unified Theme Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace five inconsistent rofi theme systems with one Catppuccin Macchiato palette, three shared layouts, and a wallpaper-driven accent that cannot produce an unreadable result. + +**Architecture:** A fixed Macchiato palette file plus a single generated `@accent` variable. `udt-accent` extracts the wallpaper's signature color by calling pywal's colorz backend directly (which writes nothing, leaving the terminal's preset colors untouched) and snaps it to one of nine Macchiato accents by perceptual hue in CIELAB. Three layout files import a shared `common.rasi`; eleven call sites are repointed at them. + +**Tech Stack:** rofi 1.7.3 rasi themes, Python 3 (stdlib `math`/`sys`/`pathlib` plus `pywal.backends.colorz`), bash. + +**Repo note:** This project holds the canonical copies under `rofi/` and `bin/`. Installation is by symlink into `~/.config/rofi/udt/` and `~/bin/`, so the working config and the repo never diverge. Tasks 1-8 build and verify; Task 9 installs; Tasks 10-12 migrate call sites. + +**Design spec:** `docs/superpowers/specs/2026-09-11-unified-desktop-theme-design.md` + +**On verification:** every rofi step in this plan opens a real window and is +judged by eye. Rofi needs a display and cannot be usefully checked from a +headless shell, where it exits non-zero for reasons unrelated to the theme. Run +these steps in a terminal on the actual desktop session. A theme that fails to +parse prints `Failed to parse theme:` with a line number; that message, not the +exit code, is the failure signal. + +--- + +### Task 1: Project scaffolding and license + +**Files:** +- Create: `LICENSE`, `README.md`, `.gitignore` + +- [ ] **Step 1: Fetch the GPLv2 text** + +```bash +cd ~/Programming/unified-desktop-theme +curl -sL https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt -o LICENSE +head -3 LICENSE +``` + +Expected: prints ` GNU GENERAL PUBLIC LICENSE` and the version line. + +- [ ] **Step 2: Create .gitignore** + +```bash +cat > .gitignore <<'EOF' +HANDOFF.md +__pycache__/ +*.pyc +EOF +``` + +- [ ] **Step 3: Write README.md** + +```markdown +# unified-desktop-theme + +A single visual identity for a Hyprland desktop: Catppuccin Macchiato as the +fixed base, Noto Sans for UI text, Inconsolata Nerd Font Mono for monospace. + +The accent color follows the current wallpaper, but is always snapped to a real +Macchiato accent, so it can never render as unreadable. + +Phase 1 covers rofi. Later phases extend the same palette to waybar, dunst, +conky, hyprland and quickshell. + +## Layout + + rofi/udt/ the theme files + bin/udt-accent wallpaper accent extractor + +Both are installed by symlink, so the repo holds the canonical copies: + + ./install.sh + +## Design + +See `docs/superpowers/specs/` for the design spec and the reasoning behind +each decision, including why accent extraction is deliberately isolated from +the pywal cache. + +## License + +GPLv2 only. See `LICENSE`. + +## Development Approach + +This project is developed using AI-assisted tools. Code is generated with the help of AI based on human-provided specifications, design decisions, and iterative feedback. + +All contributions are reviewed, tested, and curated by the maintainer before being included in the codebase. AI is used as a productivity and exploration tool, while human oversight remains central to all decisions. + +The goal is to combine the flexibility of AI-assisted development with standard open-source practices such as transparency, review, and accountability. +``` + +- [ ] **Step 4: Commit** + +```bash +git add LICENSE README.md .gitignore +git commit -m "chore: add GPLv2 license, README and gitignore" +``` + +--- + +### Task 2: The Macchiato palette file + +**Files:** +- Create: `rofi/udt/palette.rasi` + +- [ ] **Step 1: Write the palette** + +Values are from the official `catppuccin/palette` repository. Create `rofi/udt/palette.rasi`: + +```css +/* + * Catppuccin Macchiato palette for rofi. + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * Licensed under the GNU General Public License v2 only. + * + * Source: https://github.com/catppuccin/palette + * Structural colors only. The accent lives in accent.rasi and is generated. + */ + +* { + base: #24273aff; + mantle: #1e2030ff; + crust: #181926ff; + + text: #cad3f5ff; + subtext1: #b8c0e0ff; + subtext0: #a5adcbff; + + overlay2: #939ab7ff; + overlay1: #8087a2ff; + overlay0: #6e738dff; + + surface2: #5b6078ff; + surface1: #494d64ff; + surface0: #363a4fff; + + rosewater: #f4dbd6ff; + flamingo: #f0c6c6ff; + pink: #f5bde6ff; + mauve: #c6a0f6ff; + red: #ed8796ff; + maroon: #ee99a0ff; + peach: #f5a97fff; + yellow: #eed49fff; + green: #a6da95ff; + teal: #8bd5caff; + sky: #91d7e3ff; + sapphire: #7dc4e4ff; + blue: #8aadf4ff; + lavender: #b7bdf8ff; +} +``` + +- [ ] **Step 2: Verify rofi parses it** + +```bash +mkdir -p /tmp/udt-check +printf '@import "%s/rofi/udt/palette.rasi"\n* { background-color: @base; text-color: @text; }\n' "$PWD" > /tmp/udt-check/t.rasi +echo probe | rofi -dmenu -theme /tmp/udt-check/t.rasi -e "palette parses" +``` + +Expected: a rofi window appears with a dark Macchiato background and no parse error printed to the terminal. Press Escape to dismiss. A parse failure prints `Failed to parse theme:` and is the failure signal. + +- [ ] **Step 3: Commit** + +```bash +git add rofi/udt/palette.rasi +git commit -m "feat: add Catppuccin Macchiato palette for rofi" +``` + +--- + +### Task 3: The accent snapper, hue matching + +This task is TDD. The self-check is written first and must fail before the implementation exists. + +**Files:** +- Create: `bin/udt-accent` + +- [ ] **Step 1: Write the failing self-check** + +Create `bin/udt-accent` containing only the self-check and the constants it needs: + +```python +#!/usr/bin/env python3 +# udt-accent: pick a Catppuccin Macchiato accent matching a wallpaper. +# Copyright (C) 2026 Danilo M. <danix@danix.xyz> +# Licensed under the GNU General Public License v2 only. +"""Extract a wallpaper's signature color and snap it to a Macchiato accent. + +Usage: udt-accent <wallpaper-path> + udt-accent --selftest + +The extraction deliberately calls pywal's colorz backend directly rather than +running `wal -i`, because `wal -i` rewrites the whole ~/.cache/wal directory +including the terminal's ANSI colors. See the design spec for why that matters. +""" + +import math +import sys + +# The nine candidate accents. rosewater and flamingo are excluded as +# near-neutral tints that capture saturated inputs; maroon, sapphire and +# lavender are excluded as near-duplicate hues of red, sky and mauve. +ACCENTS = { + "pink": "#f5bde6", + "mauve": "#c6a0f6", + "red": "#ed8796", + "peach": "#f5a97f", + "yellow": "#eed49f", + "green": "#a6da95", + "teal": "#8bd5ca", + "sky": "#91d7e3", + "blue": "#8aadf4", +} + +FALLBACK = "mauve" +MIN_CHROMA = 10.0 + + +def selftest(): + # Every accent must snap to itself, or the metric is not self-consistent. + for name, hexval in ACCENTS.items(): + got = snap(hexval) + assert got == name, f"{name} ({hexval}) snapped to {got}" + + # Representative real-world inputs. + assert snap("#ff8800") == "peach", snap("#ff8800") + assert snap("#00cc44") == "green", snap("#00cc44") + + # A near-grey has an unstable hue angle and must take the fallback. + assert snap("#888888") == FALLBACK, snap("#888888") + + print("selftest OK") + + +if __name__ == "__main__": + if len(sys.argv) == 2 and sys.argv[1] == "--selftest": + selftest() +``` + +- [ ] **Step 2: Run it to verify it fails** + +```bash +chmod +x bin/udt-accent +python3 bin/udt-accent --selftest +``` + +Expected: FAIL with `NameError: name 'snap' is not defined`. + +- [ ] **Step 3: Implement the color math** + +Insert these functions into `bin/udt-accent` after the `MIN_CHROMA` constant and before `def selftest():`: + +```python +def _to_lab(hexval): + """Convert #rrggbb to CIELAB. sRGB D65, the standard conversion.""" + r, g, b = (int(hexval[i:i + 2], 16) / 255 for i in (1, 3, 5)) + + def linear(c): + return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4 + + r, g, b = linear(r), linear(g), linear(b) + + x = (0.4124 * r + 0.3576 * g + 0.1805 * b) / 0.95047 + y = (0.2126 * r + 0.7152 * g + 0.0722 * b) + z = (0.0193 * r + 0.1192 * g + 0.9505 * b) / 1.08883 + + def f(t): + return t ** (1 / 3) if t > 0.008856 else 7.787 * t + 16 / 116 + + fx, fy, fz = f(x), f(y), f(z) + return (116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)) + + +def _hue(hexval): + """Perceptual hue angle in radians.""" + _, a, b = _to_lab(hexval) + return math.atan2(b, a) + + +def _chroma(hexval): + """Distance from the neutral axis. Near-greys sit close to zero.""" + _, a, b = _to_lab(hexval) + return math.hypot(a, b) + + +def snap(hexval): + """Return the name of the nearest candidate accent by perceptual hue.""" + if _chroma(hexval) < MIN_CHROMA: + return FALLBACK + + target = _hue(hexval) + + def distance(name): + delta = abs(_hue(ACCENTS[name]) - target) + return min(delta, 2 * math.pi - delta) # hue is circular + + return min(ACCENTS, key=distance) +``` + +- [ ] **Step 4: Run the self-check to verify it passes** + +```bash +python3 bin/udt-accent --selftest +``` + +Expected: `selftest OK` + +- [ ] **Step 5: Commit** + +```bash +git add bin/udt-accent +git commit -m "feat: add perceptual hue matching for Macchiato accents" +``` + +--- + +### Task 4: The accent snapper, extraction and output + +**Files:** +- Modify: `bin/udt-accent` + +- [ ] **Step 1: Add extraction, writing, and the CLI** + +Add to `bin/udt-accent`. Put the imports with the existing ones at the top, and the functions before `def selftest():`: + +```python +import os +import tempfile +from pathlib import Path +``` + +```python +OUTPUT = Path.home() / ".cache" / "wal" / "udt-accent.rasi" + + +def signature_color(image): + """Extract the image's most chromatic mid-tone color. + + Calls the colorz backend directly. It returns a list and writes nothing, + which is what keeps the pywal cache (and so the terminal) untouched. + """ + from pywal.backends import colorz + + colors = colorz.get(str(image), 16) + # Slot 0 trends near-black and the upper slots near-white; the signature + # color of an image lives in the middle. + return max(colors[1:7], key=_chroma) + + +def write_accent(name): + """Write the accent rasi file atomically.""" + hexval = ACCENTS[name] + content = ( + "/* Generated by udt-accent. Do not edit. */\n" + f"* {{ accent: {hexval}ff; }}\n" + ) + + OUTPUT.parent.mkdir(parents=True, exist_ok=True) + # Write-then-rename: a rofi launch during a wallpaper change must never + # read a half-written file. + fd, tmp = tempfile.mkstemp(dir=str(OUTPUT.parent), suffix=".tmp") + try: + with os.fdopen(fd, "w") as handle: + handle.write(content) + os.replace(tmp, OUTPUT) + except BaseException: + if os.path.exists(tmp): + os.unlink(tmp) + raise + + +def main(image): + try: + name = snap(signature_color(image)) + except Exception as exc: + # A broken image must still leave a working theme. + print(f"udt-accent: {exc}, falling back to {FALLBACK}", file=sys.stderr) + name = FALLBACK + + write_accent(name) + print(f"{name} {ACCENTS[name]}") +``` + +Replace the existing `__main__` block with: + +```python +if __name__ == "__main__": + if len(sys.argv) == 2 and sys.argv[1] == "--selftest": + selftest() + elif len(sys.argv) == 2: + main(sys.argv[1]) + else: + print(__doc__, file=sys.stderr) + sys.exit(2) +``` + +- [ ] **Step 2: Snapshot the pywal cache, then run against real wallpapers** + +The critical property is that this does NOT disturb the terminal colors. + +```bash +cp ~/.cache/wal/colors.json /tmp/udt-check/before.json +for w in $(find ~/Pictures/wallpapers -type f \( -name '*.jpg' -o -name '*.png' \) | head -6); do + printf '%-40s ' "$(basename "$w")" + python3 bin/udt-accent "$w" +done +``` + +Expected: six lines, each naming an accent and its hex, with different wallpapers giving different accents. + +- [ ] **Step 3: Verify the pywal cache was not touched** + +```bash +diff ~/.cache/wal/colors.json /tmp/udt-check/before.json && echo "ISOLATION OK" +``` + +Expected: `ISOLATION OK` with no diff output. **If this prints a diff, stop.** It means the terminal colors were modified, which is the exact regression this design exists to prevent. + +- [ ] **Step 4: Verify the fallback path** + +```bash +python3 bin/udt-accent /nonexistent/image.png +cat ~/.cache/wal/udt-accent.rasi +``` + +Expected: a warning on stderr, `mauve #c6a0f6` on stdout, and the file containing `* { accent: #c6a0f6ff; }`. + +- [ ] **Step 5: Re-run the self-check** + +```bash +python3 bin/udt-accent --selftest +``` + +Expected: `selftest OK` + +- [ ] **Step 6: Commit** + +```bash +git add bin/udt-accent +git commit -m "feat: extract wallpaper accent without touching the pywal cache" +``` + +--- + +### Task 5: The shared common.rasi + +**Files:** +- Create: `rofi/udt/common.rasi` + +- [ ] **Step 1: Write common.rasi** + +This holds everything the three layouts share. Fonts are the Qt/GTK ones, verified to resolve via `fc-match`. + +```css +/* + * Shared identity for all udt rofi layouts. + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * Licensed under the GNU General Public License v2 only. + */ + +@import "palette.rasi" +@import "accent.rasi" + +* { + font: "Noto Sans 11"; + monospace-font: "Inconsolata Nerd Font Mono 11"; + + background-color: transparent; + text-color: @text; + + margin: 0; + padding: 0; + spacing: 0; + + radius: 10px; + bar-height: 36px; +} + +window { + background-color: @base; + border: 2px; + border-color: @accent; + border-radius: @radius; + padding: 16px; +} + +mainbox { + spacing: 12px; +} + +inputbar { + background-color: @surface0; + border-radius: 6px; + padding: 10px 12px; + spacing: 8px; + children: [ prompt, entry ]; +} + +prompt { + text-color: @accent; +} + +entry { + placeholder: "search"; + placeholder-color: @overlay0; + cursor: text; +} + +listview { + scrollbar: false; + cycle: true; + dynamic: true; + spacing: 4px; +} + +element { + border-radius: 6px; + padding: 8px 10px; + spacing: 10px; + cursor: pointer; +} + +element normal.normal { text-color: @text; } +element alternate.normal{ text-color: @text; } +element normal.urgent { text-color: @red; } +element normal.active { text-color: @accent; } + +element selected.normal { + background-color: @accent; + text-color: @base; +} + +element selected.urgent { + background-color: @red; + text-color: @base; +} + +element selected.active { + background-color: @accent; + text-color: @base; +} + +element-icon { + size: 1.2em; + vertical-align: 0.5; + background-color: transparent; +} + +element-text { + vertical-align: 0.5; + background-color: transparent; + text-color: inherit; +} + +message { + background-color: @surface0; + border-radius: 6px; + padding: 10px; +} + +textbox { + text-color: @text; +} + +error-message { + background-color: @base; + text-color: @red; + padding: 12px; +} +``` + +- [ ] **Step 2: Create a placeholder accent so imports resolve** + +`accent.rasi` will be a symlink to the generated file (Task 9), but the file must exist now for the layouts to parse. + +```bash +mkdir -p rofi/udt +python3 bin/udt-accent --selftest >/dev/null && \ + printf '/* Generated by udt-accent. Do not edit. */\n* { accent: #c6a0f6ff; }\n' > rofi/udt/accent.rasi +cat rofi/udt/accent.rasi +``` + +Expected: the file prints with a mauve accent. + +- [ ] **Step 3: Commit** + +```bash +git add rofi/udt/common.rasi rofi/udt/accent.rasi +git commit -m "feat: add shared rofi styling common to all udt layouts" +``` + +--- + +### Task 6: The list layout + +`list.rasi` is built first because it serves eight of the eleven call sites. + +**Files:** +- Create: `rofi/udt/list.rasi` + +- [ ] **Step 1: Write list.rasi** + +```css +/* + * udt list layout: a tall, searchable, single-column list. + * For ssh hosts, passwords, emoji, VMs, repos, windows, clipboard. + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * Licensed under the GNU General Public License v2 only. + */ + +@import "common.rasi" + +window { + width: 680px; + anchor: center; + location: center; +} + +mainbox { + children: [ inputbar, listview ]; +} + +listview { + columns: 1; + lines: 12; + fixed-height: false; +} +``` + +- [ ] **Step 2: Verify it renders** + +```bash +printf 'alpha\nbravo\ncharlie\ndelta\n' | \ + rofi -dmenu -p "list" -theme "$PWD/rofi/udt/list.rasi" +``` + +Expected: a centered 680px window, Macchiato background, accent border, a search bar, and four rows. The selected row has an accent background with dark text. Press Escape. + +- [ ] **Step 3: Commit** + +```bash +git add rofi/udt/list.rasi +git commit -m "feat: add udt list layout" +``` + +--- + +### Task 7: The menu layout + +**Files:** +- Create: `rofi/udt/menu.rasi` + +- [ ] **Step 1: Write menu.rasi** + +A small box for a handful of fixed choices. No search bar: with four options, typing to filter is pointless and the bar only adds bulk. + +```css +/* + * udt menu layout: a small box for a few fixed options. + * For the screenshot menu, notes actions, the power menu. + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * Licensed under the GNU General Public License v2 only. + */ + +@import "common.rasi" + +window { + width: 380px; + anchor: center; + location: center; +} + +mainbox { + children: [ listview ]; +} + +listview { + columns: 1; + lines: 6; + fixed-height: false; +} + +element { + padding: 10px 12px; +} +``` + +- [ ] **Step 2: Verify it renders** + +```bash +printf 'lock\nlogout\nreboot\nshutdown\n' | \ + rofi -dmenu -p "power" -theme "$PWD/rofi/udt/menu.rasi" +``` + +Expected: a compact 380px window with four rows and no search bar, matching the list layout's colors and border. Press Escape. + +- [ ] **Step 3: Commit** + +```bash +git add rofi/udt/menu.rasi +git commit -m "feat: add udt menu layout" +``` + +--- + +### Task 8: The launcher layout + +**Files:** +- Create: `rofi/udt/launcher.rasi` + +- [ ] **Step 1: Write launcher.rasi** + +```css +/* + * udt launcher layout: a searchable icon grid for launching applications. + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * Licensed under the GNU General Public License v2 only. + */ + +@import "common.rasi" + +window { + width: 880px; + anchor: center; + location: center; +} + +mainbox { + children: [ inputbar, listview ]; +} + +listview { + columns: 3; + lines: 6; + fixed-height: false; +} + +element { + padding: 10px; + spacing: 12px; +} + +element-icon { + size: 2.2em; +} +``` + +- [ ] **Step 2: Verify it renders with real applications** + +```bash +rofi -show drun -theme "$PWD/rofi/udt/launcher.rasi" +``` + +Expected: an 880px window, three columns of applications with icons at a larger size, and a working search bar. Typing filters the grid. Press Escape. + +- [ ] **Step 3: Commit** + +```bash +git add rofi/udt/launcher.rasi +git commit -m "feat: add udt launcher layout" +``` + +--- + +### Task 9: Install script and wallp integration + +**Files:** +- Create: `install.sh` +- Modify: `~/bin/wallp` (the `finalize` function) + +- [ ] **Step 1: Write install.sh** + +```bash +#!/bin/bash +# Install udt by symlink, so the repo stays canonical. +# Copyright (C) 2026 Danilo M. <danix@danix.xyz> +# Licensed under the GNU General Public License v2 only. +set -euo pipefail + +repo="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +target="$HOME/.config/rofi/udt" + +mkdir -p "$target" "$HOME/bin" "$HOME/.cache/wal" + +# Theme files are symlinked individually; accent.rasi is NOT, it points at the +# generated file in the wal cache instead. +for f in palette.rasi common.rasi launcher.rasi menu.rasi list.rasi; do + ln -sfn "$repo/rofi/udt/$f" "$target/$f" +done + +# Seed the generated accent if it does not exist yet, so themes parse on a +# fresh install before any wallpaper has been set. +if [ ! -f "$HOME/.cache/wal/udt-accent.rasi" ]; then + cp "$repo/rofi/udt/accent.rasi" "$HOME/.cache/wal/udt-accent.rasi" +fi +ln -sfn "$HOME/.cache/wal/udt-accent.rasi" "$target/accent.rasi" + +ln -sfn "$repo/bin/udt-accent" "$HOME/bin/udt-accent" + +echo "installed:" +ls -l "$target" "$HOME/bin/udt-accent" +``` + +- [ ] **Step 2: Run it and verify the symlinks** + +```bash +chmod +x install.sh +./install.sh +``` + +Expected: `palette.rasi`, `common.rasi`, `launcher.rasi`, `menu.rasi` and `list.rasi` point into the repo; `accent.rasi` points into `~/.cache/wal/`; `~/bin/udt-accent` points into the repo. + +- [ ] **Step 3: Verify the installed path renders** + +```bash +printf 'one\ntwo\nthree\n' | rofi -dmenu -theme ~/.config/rofi/udt/list.rasi +``` + +Expected: renders exactly as in Task 6, now through the installed path. Press Escape. + +- [ ] **Step 4: Hook udt-accent into wallp** + +`finalize` is currently three lines: + +```bash +finalize() { + update_wpaper + apply_theme "$1" +} +``` + +`update_wpaper` maintains `~/.cache/wal/wpaper` as a symlink to the current +horizontal wallpaper, so that path is the simplest stable handle on the image +and needs no re-resolution. Add the accent refresh after it: + +```bash +finalize() { + update_wpaper + apply_theme "$1" + # Refresh the rofi accent from the wallpaper. Never fatal: a failure here + # must not stop the wallpaper from being set. + if command -v udt-accent >/dev/null 2>&1 && [ -e "$HOME/.cache/wal/wpaper" ]; then + udt-accent "$HOME/.cache/wal/wpaper" >/dev/null 2>&1 || true + fi +} +``` + +- [ ] **Step 5: Verify the integration end to end** + +```bash +cp ~/.cache/wal/colors.json /tmp/udt-check/before-wallp.json +cat ~/.cache/wal/udt-accent.rasi +wallp --restore +echo "--- accent after ---" +cat ~/.cache/wal/udt-accent.rasi +echo "--- terminal colors unchanged? ---" +diff ~/.cache/wal/colors.json /tmp/udt-check/before-wallp.json && echo "ISOLATION OK" +``` + +Expected: the accent file reflects the restored wallpaper, and `ISOLATION OK` confirms the terminal palette did not move. **If the diff is non-empty, stop and investigate before continuing.** + +- [ ] **Step 6: Commit** + +```bash +git add install.sh +git commit -m "feat: add symlink installer and wire accent refresh into wallp" +``` + +Note: `~/bin/wallp` is outside this repo, so it is not part of this commit. Mention the edit in the final summary so it is not lost. + +--- + +### Task 10: Migrate the list-layout call sites + +Eight call sites. Each is a one-line change. `~/bin` is not this repo, so these are edits to the live scripts; no commit here. + +**Files:** +- Modify: `~/bin/blackpearl-sshmenu.sh:4-7` +- Modify: `~/bin/blackpearl-emoji.sh:3` +- Modify: `~/bin/rofi-qemu.sh:68` +- Modify: `~/bin/github-repos.sh:22-23` +- Modify: `~/bin/hypr-windows.sh:5-6,17` +- Modify: `~/bin/rofipass:111` +- Modify: `~/bin/ddgr_search.py` +- Modify: `~/.config/hypr/sections/keybindings.lua` + +- [ ] **Step 1: Back up every file to be edited** + +```bash +mkdir -p /tmp/udt-check/backup +cp ~/bin/blackpearl-sshmenu.sh ~/bin/blackpearl-emoji.sh ~/bin/rofi-qemu.sh \ + ~/bin/github-repos.sh ~/bin/hypr-windows.sh ~/bin/rofipass \ + ~/bin/ddgr_search.py /tmp/udt-check/backup/ +cp ~/.config/hypr/sections/keybindings.lua /tmp/udt-check/backup/ +ls /tmp/udt-check/backup/ +``` + +- [ ] **Step 2: Repoint the three scripts that use the `dir`/`theme` variable pair** + +`blackpearl-sshmenu.sh`, `github-repos.sh` and `hypr-windows.sh` each define `dir=` and `theme=` and then reference `${dir}/${theme}.rasi`. Replace both variable lines in each file so the existing reference resolves to the new theme: + +```bash +for f in ~/bin/blackpearl-sshmenu.sh ~/bin/github-repos.sh ~/bin/hypr-windows.sh; do + sed -i \ + -e 's|^dir=.*|dir="$HOME/.config/rofi/udt"|' \ + -e "s|^theme=.*|theme='list'|" "$f" + echo "--- $f ---" + grep -nE '^dir=|^theme=|\$\{dir\}' "$f" +done +``` + +Expected: each file shows `dir="$HOME/.config/rofi/udt"`, `theme='list'`, and an unchanged `${dir}/${theme}.rasi` reference. + +- [ ] **Step 3: Repoint the scripts with an inline theme path** + +```bash +sed -i 's|-theme darknix/runner.rasi|-theme ~/.config/rofi/udt/list.rasi|' \ + ~/bin/blackpearl-emoji.sh ~/bin/rofi-qemu.sh +sed -i 's|-theme elegantVagrant/elegantvagrant-dark|-theme ~/.config/rofi/udt/list.rasi|' \ + ~/bin/rofipass +grep -n 'udt' ~/bin/blackpearl-emoji.sh ~/bin/rofi-qemu.sh ~/bin/rofipass +``` + +Expected: one match in each of the three files. + +- [ ] **Step 4: Repoint the cliphist keybinding** + +```bash +sed -i 's|-theme ~/.config/rofi/launchers/type-2/style-1.rasi|-theme ~/.config/rofi/udt/list.rasi|' \ + ~/.config/hypr/sections/keybindings.lua +grep -n 'cliphist' ~/.config/hypr/sections/keybindings.lua +``` + +Expected: the binding now references `udt/list.rasi`. + +- [ ] **Step 5: Give ddgr_search.py a theme** + +This script currently passes no `-theme` and uses rofi's default. Read it to find the rofi invocation, then add `-theme ~/.config/rofi/udt/list.rasi` to that command: + +```bash +grep -n 'rofi' ~/bin/ddgr_search.py +``` + +Apply the edit to the line that actually builds the rofi command, not to the docstring at the top that shows example usage. + +- [ ] **Step 6: Verify each one launches** + +Run each and confirm it renders in the new theme. These are interactive; press Escape to dismiss each. + +```bash +~/bin/blackpearl-sshmenu.sh +~/bin/hypr-windows.sh +~/bin/github-repos.sh +~/bin/blackpearl-emoji.sh +~/bin/rofi-qemu.sh +~/bin/rofipass +``` + +Expected: all six show the Macchiato list layout with the accent border. Test the cliphist binding with CTRL+SHIFT+L and the ddgr script per its own usage. + +--- + +### Task 11: Migrate the menu- and launcher-layout call sites + +**Files:** +- Modify: `~/bin/qar-scrotmenu.sh:3` +- Modify: `~/bin/blackpearl-notes.sh:3` +- Modify: `~/.config/rofi/powermenu/type-4/powermenu.sh` +- Modify: `~/bin/blackpearl-appsmenu.sh:5` +- Modify: `~/.config/rofi/launchers/type-1/launcher.sh` + +- [ ] **Step 1: Back up the remaining files** + +```bash +cp ~/bin/qar-scrotmenu.sh ~/bin/blackpearl-notes.sh ~/bin/blackpearl-appsmenu.sh \ + /tmp/udt-check/backup/ +cp ~/.config/rofi/powermenu/type-4/powermenu.sh /tmp/udt-check/backup/powermenu.sh +cp ~/.config/rofi/launchers/type-1/launcher.sh /tmp/udt-check/backup/launcher.sh +``` + +- [ ] **Step 2: Repoint the two menu scripts** + +```bash +sed -i 's|-theme darknix/scrotmenu.rasi|-theme ~/.config/rofi/udt/menu.rasi|' \ + ~/bin/qar-scrotmenu.sh +sed -i 's|-theme darknix/notes.rasi|-theme ~/.config/rofi/udt/menu.rasi|' \ + ~/bin/blackpearl-notes.sh +grep -n 'udt' ~/bin/qar-scrotmenu.sh ~/bin/blackpearl-notes.sh +``` + +Expected: one match in each. + +- [ ] **Step 3: Repoint the appsmenu launcher** + +```bash +sed -i 's|-theme darknix/appmenu.rasi|-theme ~/.config/rofi/udt/launcher.rasi|' \ + ~/bin/blackpearl-appsmenu.sh +grep -n 'udt' ~/bin/blackpearl-appsmenu.sh +``` + +- [ ] **Step 4: Repoint the two adi1090x scripts** + +These build a theme path from their own directory layout rather than taking a simple `-theme` argument. Read each one first: + +```bash +grep -nE 'theme|rasi|dir=' ~/.config/rofi/powermenu/type-4/powermenu.sh | head -20 +grep -nE 'theme|rasi|dir=' ~/.config/rofi/launchers/type-1/launcher.sh | head -20 +``` + +In each, replace the constructed theme path with the fixed new one: `~/.config/rofi/udt/menu.rasi` for the power menu, `~/.config/rofi/udt/launcher.rasi` for the launcher. Do not restructure these scripts, change only the theme path they pass to rofi. + +- [ ] **Step 5: Verify each one launches** + +```bash +~/bin/qar-scrotmenu.sh +~/bin/blackpearl-notes.sh +~/bin/blackpearl-appsmenu.sh +~/.config/rofi/powermenu/type-4/powermenu.sh +~/.config/rofi/launchers/type-1/launcher.sh +``` + +Expected: the two menu scripts and the power menu show the compact menu layout; appsmenu and the ALT+F2 launcher show the icon grid. Press Escape on each. **Take care with the power menu, it contains real shutdown and reboot entries. Dismiss it with Escape rather than selecting a row.** + +--- + +### Task 12: Final verification and documentation + +**Files:** +- Create: `docs/MIGRATION.md` + +- [ ] **Step 1: Confirm nothing still references the old themes** + +```bash +grep -rn 'darknix\|elegantVagrant\|launchers/type-2\|colors/catppuccin' \ + ~/bin/*.sh ~/bin/rofipass ~/bin/ddgr_search.py \ + ~/.config/hypr/sections/keybindings.lua 2>/dev/null | grep -v archive/ +``` + +Expected: no output. Any match is a call site that was missed. Commented-out lines are acceptable but should be noted. + +- [ ] **Step 2: Confirm the accent still tracks wallpapers and isolation holds** + +```bash +cp ~/.cache/wal/colors.json /tmp/udt-check/final.json +for w in $(find ~/Pictures/wallpapers -type f \( -name '*.jpg' -o -name '*.png' \) | head -4); do + printf '%-40s ' "$(basename "$w")" + udt-accent "$w" +done +diff ~/.cache/wal/colors.json /tmp/udt-check/final.json && echo "ISOLATION OK" +python3 ~/bin/udt-accent --selftest +``` + +Expected: four accents, `ISOLATION OK`, and `selftest OK`. + +- [ ] **Step 3: Write docs/MIGRATION.md** + +Record what changed outside this repo, since those edits are not under version control here: + +```markdown +# Migration record, phase 1 (rofi) + +Files edited outside this repository. Backups from the migration run are in +`/tmp/udt-check/backup/` and do not survive a reboot; if these need reverting +later, use the table below rather than the backups. + +## Call sites repointed + +| File | Was | Now | +| --- | --- | --- | +| `~/bin/blackpearl-sshmenu.sh` | `launchers/type-2/style-1` | `udt/list.rasi` | +| `~/bin/github-repos.sh` | `launchers/type-2/style-1` | `udt/list.rasi` | +| `~/bin/hypr-windows.sh` | `launchers/type-2/style-1` | `udt/list.rasi` | +| `~/bin/blackpearl-emoji.sh` | `darknix/runner.rasi` | `udt/list.rasi` | +| `~/bin/rofi-qemu.sh` | `darknix/runner.rasi` | `udt/list.rasi` | +| `~/bin/rofipass` | `elegantVagrant/elegantvagrant-dark` | `udt/list.rasi` | +| `~/bin/ddgr_search.py` | (rofi default) | `udt/list.rasi` | +| `~/.config/hypr/sections/keybindings.lua` | `launchers/type-2/style-1` | `udt/list.rasi` | +| `~/bin/qar-scrotmenu.sh` | `darknix/scrotmenu.rasi` | `udt/menu.rasi` | +| `~/bin/blackpearl-notes.sh` | `darknix/notes.rasi` | `udt/menu.rasi` | +| `~/.config/rofi/powermenu/type-4/powermenu.sh` | own theme dir | `udt/menu.rasi` | +| `~/bin/blackpearl-appsmenu.sh` | `darknix/appmenu.rasi` | `udt/launcher.rasi` | +| `~/.config/rofi/launchers/type-1/launcher.sh` | own theme dir | `udt/launcher.rasi` | + +## Other edits + +- `~/bin/wallp`: `finalize()` now calls `udt-accent` with the horizontal + wallpaper, guarded so a failure cannot stop the wallpaper being set. + +## Not removed + +The old theme directories are still on disk and untouched: +`~/.config/rofi/darknix/`, `elegantVagrant/`, `launchers/`, `applets/`, +`powermenu/`. Deleting them is a separate decision, deferred until the new +themes have been lived with. + +Note that `~/.config/rofi/applets/` still has its own launchers referenced from +waybar (`applets/bin/mpd.sh`) and is therefore still in use. It is out of scope +for phase 1 and will be handled with waybar in a later phase. + +## Out of scope + +`ronema` (rofi NetworkManager applet) was excluded: no longer used, pending +archival. +``` + +- [ ] **Step 4: Commit** + +```bash +git add docs/MIGRATION.md +git commit -m "docs: record phase 1 migration of rofi call sites" +``` + +- [ ] **Step 5: Final review with the user** + +Phase 1 is visual, so the user is the test. Ask them to exercise each menu over normal use and report anything that looks wrong: colors off the Macchiato palette, fonts that are not Noto Sans or Inconsolata, a layout that does not suit its job, or an accent that reads poorly against the background. + +--- + +## Deferred to later phases + +- waybar, dunst, conky, hyprland borders, quickshell (see the spec's "Later phases"). +- Deleting the five superseded rofi theme directories, once the new themes have proven themselves. +- `~/.config/rofi/applets/`, still live via waybar's mpd button, to be handled alongside waybar. +- `~/.config/rofi/config.rasi` still sets `font: "Mono 12"` and an `icon-theme`. The udt themes override the font, so this is harmless, but it is worth cleaning up when rofi's global config is next touched. |
