diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index 6b2db11..905d19c 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -83,10 +83,6 @@ the *same* outputs. **Plan (designed 2026-07-05, iteration #27):** - **Non-goals:** no kanshi (fights Hyprland's output management — decided in ROADMAP), nwg-displays stays a find-the-values helper. -### 16. Greeter theming from the JSON -ROADMAP § Greeter. tuigreet themed from theme-state.json at system -rebuild (same model as Plymouth's tint). Scope: tuigreet only (no SDDM). - ### 17. Launch-or-focus UX scripts ROADMAP § launch-or-focus. Hyprland dispatch scripts: a bind launches an app or focuses its existing window. Curate which apps get binds; diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index fd5efca..2c3944b 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -153,6 +153,13 @@ QA machine), the **T14s** (webcam case). applies the matching profile within ~3s (toast names it); unplugging switches back; Auto-switch off stops that; with two profiles naming the same outputs, no flapping (ties do nothing). +- [ ] **Themed greeter + console (item 16)** — after `sys-rebuild` + + logout: tuigreet renders in theme colors (dark container, accent + border, themed prompt/time) and a raw tty (CTRL+ALT+F2) shows the + theme's ANSI palette; on a LUKS machine the passphrase prompt + follows too. Then switch theme + `sys-rebuild` + logout → the + greeter follows the new palette. (tuigreet can't run under the + VM harness — see MEMORY.md — so rendering is hardware-tier.) - [ ] **Color picker (item 13, final slice)** — SUPER+CTRL+P (or Tools › Color picker): hyprpicker's zoom loupe appears; click a pixel → toast shows the hex and `wl-paste` yields it; Esc cancels with no diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index b2863e6..67ff795 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -17,6 +17,26 @@ Template: --- +## 2026-07-05 — greeter theming from the JSON (iteration #30, item 16) +- **Task:** BACKLOG NEXT#16 — tuigreet themed from theme-state.json at + system rebuild (the Plymouth model). Item deleted. +- **Did:** new modules/nixos/greeter.nix owns the greetd block (moved + out of default.nix, one concern one file) + the theming: (1) + console.colors = the theme's ansi[] (mkDefault; lands as + vt.default_* kernel params — also themes raw ttys + LUKS prompt); + (2) tuigreet --theme on NAMED slots (its parser is ratatui + Color::from_str, verified in source; names hit the standard indexes + the retinted VT palette now serves). Sparse state w/o ansi → skips + cleanly. +- **Verified:** V0; V1 — template-system toplevel: baked greetd.toml + carries the full --theme spec, kernel-params carry all three + vt.default_{red,grn,blu}. A checks.greeter VM test was attempted and + DROPPED: tuigreet dies under runNixOSTest even bare (harness limit, + not our flag — bisected; nixpkgs tests use agreety) → MEMORY.md. +- **Pending:** V3 queued (rendering + theme-follows-rebuild). +- **Next suggestion:** #17 launch-or-focus, or the new #28 UI-review + audit slice once Bernardo confirms its placement. + ## 2026-07-05 — display profiles auto-switch (iteration #29, item 15b) - **Task:** BACKLOG NEXT#15 slice b — opt-in hotplug auto-switch. - **Did:** nomarchy-display-profile gains `match ` (exact set diff --git a/agent/MEMORY.md b/agent/MEMORY.md index e14849b..014417b 100644 --- a/agent/MEMORY.md +++ b/agent/MEMORY.md @@ -7,6 +7,14 @@ here the moment a debugging session teaches you something a future iteration would otherwise rediscover. ## Testing & VM recipes +- **tuigreet dies silently under runNixOSTest** (even bare, no theme + flag: greetd sits as "(greetd)" with no child, nothing in the + journal — its stderr goes to the VT) — nixpkgs' own greetd test uses + agreety instead. Greeter *rendering* is interactive-ISO/hardware + tier; don't burn another session on a checks.greeter VM test. +- In VM tests `pgrep -f PATTERN` can match the test backdoor's own + `bash -c` wrapper (the pattern is in its cmdline) — use `pgrep -x` + or a `[t]uigreet`-style bracket pattern. - A checks.* fixture CANNOT be a writeText/toFile state file read at eval time ("path … is not valid" — flake check's eval store won't realise it): extract the logic into a pure importable file and diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 4c29f0a..c58c6e0 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -22,7 +22,7 @@ let ''; in { - imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ./hardware.nix ./timezone.nix ./oom.nix ]; + imports = [ ./options.nix ./plymouth.nix ./greeter.nix ./file-manager.nix ./power.nix ./services.nix ./hardware.nix ./timezone.nix ./oom.nix ]; config = { # Distro branding. distroName flows into /etc/os-release PRETTY_NAME, @@ -99,22 +99,8 @@ in extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; # file pickers, etc. }; - services.greetd = lib.mkIf cfg.greeter.enable { - enable = lib.mkDefault true; - settings = { - default_session = { - # start-hyprland is Hyprland 0.55's watchdog launcher; running - # the bare binary makes every session print a warning. - command = lib.mkDefault "${pkgs.tuigreet}/bin/tuigreet --time --remember --greeting 'Welcome to ${distroName}' --cmd start-hyprland"; - user = "greeter"; - }; - # Boot straight into the session once; logout → normal greeter. - initial_session = lib.mkIf (cfg.greeter.autoLogin != null) { - command = "start-hyprland"; - user = cfg.greeter.autoLogin; - }; - }; - }; + # The greetd/tuigreet login screen lives in ./greeter.nix — themed + # from the state JSON (console.colors + --theme) at system rebuild. # ── Audio: Pipewire ────────────────────────────────────────────── security.rtkit.enable = lib.mkDefault cfg.audio.enable; diff --git a/modules/nixos/greeter.nix b/modules/nixos/greeter.nix new file mode 100644 index 0000000..54ea9ad --- /dev/null +++ b/modules/nixos/greeter.nix @@ -0,0 +1,70 @@ +# Greeter — greetd/tuigreet, themed from the same theme-state.json that +# drives the desktop (nomarchy.system.stateFile; the Plymouth model: +# baked at SYSTEM rebuild, so it follows the theme as of the last +# sys-update, not the last instant apply). +# +# tuigreet draws on the virtual console with the 16 ANSI slots, so the +# theming is two-part: +# 1. console.colors — the VT palette becomes the theme's ansi[] hexes +# (which also themes raw ttys and the LUKS passphrase prompt: the +# same JSON reaches every pre-session surface). +# 2. --theme — tuigreet components on NAMED slots (its parser is +# ratatui Color::from_str; names map to the standard indexes, e.g. +# blue=4, gray=7, white=15, so the palette above hands them the +# theme's colors). ANSI "black" stays dark even in light themes — +# the greeter reads terminal-dark there, the same convention every +# terminal applies to ANSI colors. +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy.system; + distroName = config.system.nixos.distroName; + + state = + if cfg.stateFile != null + then builtins.fromJSON (builtins.readFile cfg.stateFile) + else { }; + # A sparse/hand-rolled state without a proper ansi block just skips the + # theming (stock tuigreet grey) — never an eval error. + ansi = state.ansi or [ ]; + themed = builtins.isList ansi && builtins.length ansi == 16; + + tuigreetTheme = lib.concatStringsSep ";" [ + "container=black" # ansi[0] — the theme's terminal background + "border=blue" # ansi[4] — the accent family in every shipped palette + "title=cyan" + "greet=cyan" + "prompt=green" + "input=white" # ansi[15] — bright foreground + "action=blue" + "button=yellow" + "time=cyan" + "text=gray" # ansi[7] — muted foreground + ]; +in +{ + config = { + # VT palette from the theme (RRGGBB, no #; lands as vt.default_* kernel + # params). mkDefault so a downstream console.colors wins. + console.colors = lib.mkIf themed (lib.mkDefault (map (lib.removePrefix "#") ansi)); + + services.greetd = lib.mkIf cfg.greeter.enable { + enable = lib.mkDefault true; + settings = { + default_session = { + # start-hyprland is Hyprland 0.55's watchdog launcher; running + # the bare binary makes every session print a warning. + command = lib.mkDefault ("${pkgs.tuigreet}/bin/tuigreet --time --remember --greeting 'Welcome to ${distroName}'" + + lib.optionalString themed " --theme '${tuigreetTheme}'" + + " --cmd start-hyprland"); + user = "greeter"; + }; + # Boot straight into the session once; logout → normal greeter. + initial_session = lib.mkIf (cfg.greeter.autoLogin != null) { + command = "start-hyprland"; + user = cfg.greeter.autoLogin; + }; + }; + }; + }; +}