From ee39bd927e056c2dae9d61230a95a05715b3a8c6 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 11 Sep 2026 18:45:15 +0200 Subject: feat(palette): add five more schemes, and parse-check them all Adds Catppuccin Frappe and Mocha, which share Macchiato's colour names so their role maps are the same mapping against different values, and Material Ocean, Palenight and Darker, which bring their own names and their own maps. Nine schemes now, all dark. Material ships one accent per hue and three greys where the roles want five depths, so each Material palette carries four interpolated shades, marked as not upstream, and its bright_* terminal slots name the same colours as the normal ones. The selftest now renders every scheme to a temp directory and has rofi parse every theme, not just check that roles resolve. Resolving was never the property that mattered: the Tokyo Night breakage resolved fine and still emitted a palette the themes could not use. Verified the check fails when a name is removed. Catppuccin Latte is deliberately absent. It is light, and that is not a role remap: homepage is pinned theme: dark and the generated card overrides target its dark-mode class. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015gbjA2bmswN8jyPDKzrvqe --- bin/udt-palette | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) (limited to 'bin') diff --git a/bin/udt-palette b/bin/udt-palette index 1df0c4f..18dc0b1 100755 --- a/bin/udt-palette +++ b/bin/udt-palette @@ -417,8 +417,8 @@ TARGETS = [ ] -def generate(roles_path, out_dir): - scheme, palette, res, snap_names = load(roles_path) +def generate(roles_path, out_dir, scheme=None): + scheme, palette, res, snap_names = load(roles_path, scheme=scheme) written = [] for target, fn, template in TARGETS: dest = out_dir / target @@ -437,6 +437,44 @@ def generate(roles_path, out_dir): return scheme, written +def check_rofi_parses(scheme): + """Render `scheme` to a temp dir and have rofi parse every theme. + + The role checks below prove a scheme resolves, not that what it emits is + valid. A palette missing a name the themes reference parses as a broken + theme and every launcher stops opening, which is how exactly that bug + shipped once. rofi needs no display for -dump-theme. + """ + import shutil + import subprocess + import tempfile + + if not shutil.which("rofi"): + return None + + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) + generate(None, out, scheme=scheme) + udt = out / "rofi" / "udt" + # The themes @import siblings, so they need the whole directory. + for extra in (REPO / "rofi" / "udt").glob("*.rasi"): + if not (udt / extra.name).exists(): + shutil.copy(extra, udt / extra.name) + # accent.rasi is generated per wallpaper; seed it so themes resolve. + (udt / "accent.rasi").write_text("* { accent: #ffffffff; }\n") + + for theme in sorted(udt.glob("*.rasi")): + if theme.name in ("palette.rasi", "accent.rasi", "common.rasi"): + continue + proc = subprocess.run( + ["rofi", "-no-config", "-theme", str(theme), "-dump-theme"], + capture_output=True, text=True) + if "Failed to parse" in proc.stderr: + raise AssertionError( + f"{scheme}: rofi cannot parse {theme.name}\n{proc.stderr.strip()}") + return True + + def selftest(): # Every shipped scheme must satisfy every role, or switching to it breaks # at install time. load() raises on any role the palette cannot supply. @@ -449,7 +487,9 @@ def selftest(): assert res["bg"] != res["fg"], f"{scheme}: bg and fg are the same colour" assert len(snap) >= 5, f"{scheme}: only {len(snap)} snap candidates" seen[scheme] = set(res) - print(f" {scheme}: {len(res)} roles, {len(snap)} accents") + parsed = check_rofi_parses(scheme) + note = "" if parsed else " (rofi not installed, parse unchecked)" + print(f" {scheme}: {len(res)} roles, {len(snap)} accents{note}") # Every scheme must define the SAME roles: a generator asks for a role by # name, so one scheme missing it would fail only once that scheme was -- cgit v1.2.3