feat(theme): make Boreal the distro default
Some checks failed
Check / eval (push) Has been cancelled

Seed theme-state.json and the downstream template from themes/boreal.json
(including border + empty settings). Point eval/schema fallbacks at Boreal
(theme.nix preset, plymouth and live-ISO splash colors, theme-shot, sync
error examples). Docs/TESTING and RECOVERY follow the new default.

Verified: state matches boreal palette; theme-contrast all pass.
preview.png for boreal still missing (picker plain-name; V3 capture).
This commit is contained in:
Bernardo Magri
2026-07-09 09:57:32 +01:00
parent 70501b566a
commit 351b7adb8e
13 changed files with 122 additions and 113 deletions

View File

@@ -28,15 +28,6 @@ in [`docs/ROADMAP.md`](../docs/ROADMAP.md); map in
## NEXT ## NEXT
### 42. Default theme → boreal (`VISION § B` / v1.0 bar)
Ship Boreal as the distro default instead of Tokyo Night: root
`theme-state.json`, `templates/downstream/theme-state.json` (and any
greeter/plymouth fallbacks that hardcode Tokyo Night when state is
missing). Preset already exists under `themes/boreal/`. Capture
`preview.png` if still missing so the picker looks first-class.
Cost: copy/apply palette into seed state files + grep for tokyo-night
defaults; V0 contrast, V1/V2 theme apply, V3 visual on hardware.
### 43. System Firmware — fwupd product surface (`VISION § A`) ### 43. System Firmware — fwupd product surface (`VISION § A`)
`services.fwupd.enable` is already default-on and never auto-flashes, `services.fwupd.enable` is already default-on and never auto-flashes,
but users only learn about `fwupdmgr` from a README sentence. Add a but users only learn about `fwupdmgr` from a README sentence. Add a
@@ -131,10 +122,12 @@ implement. Bernardo moves accepted items into a tier.*
### v1.0 track — see `docs/VISION.md` ### v1.0 track — see `docs/VISION.md`
Product themes and the release bar live in **VISION**. Promoted to Product themes and the release bar live in **VISION**. **NEXT:**
**NEXT:** #42 boreal default · #43 System Firmware · #44 doctor #43 System Firmware · #44 doctor hardware. #42 boreal default
hardware. Remaining candidate promotions: fingerprint menu · shipped (seed state + eval fallbacks); still missing
first-boot tips · human rebuild errors · neon-glass quarantine. `themes/boreal/preview.png` (picker plain-name until captured).
Remaining candidate promotions: fingerprint menu · first-boot tips ·
human rebuild errors · neon-glass quarantine.
### Pre-existing ### Pre-existing
@@ -283,11 +276,12 @@ first-boot tips · human rebuild errors · neon-glass quarantine.
### Theme polish / completeness (codebase exam 2026-07-09) ### Theme polish / completeness (codebase exam 2026-07-09)
- **Missing theme picker previews + hand btop for identity themes** - **Missing theme picker previews + hand btop for identity themes**
`boreal`, `executive-slate`, and `neon-glass` lack `preview.png` `boreal` (now the default — higher priority), `executive-slate`, and
(picker falls back to plain-name rows) and `btop.theme` (generated `neon-glass` lack `preview.png` (picker plain-name rows) and
btop only). CSS/jsonc for boreal/executive-slate are otherwise `btop.theme` (generated btop only). CSS/jsonc for boreal/executive-slate
solid. Capture previews after neon-glass is fixed/quarantined. are solid. Capture boreal preview ASAP for v1.0 bar; others after
Cost: asset capture + optional hand btop; V3 visual. neon-glass is fixed/quarantined. Cost: asset capture + optional hand
btop; V3 visual.
- **Generated Waybar missing identity-bar affordances** - **Generated Waybar missing identity-bar affordances**
Whole-swap bars ship `custom/powermenu` and a left logo → main menu; Whole-swap bars ship `custom/powermenu` and a left logo → main menu;

View File

@@ -17,6 +17,13 @@ Template:
--- ---
## 2026-07-09 — Default theme → Boreal (item #42)
- **Task:** NEXT #42 — distro default theme Tokyo Night → Boreal (`VISION § B` / v1.0 bar).
- **Did:** Seeded `theme-state.json` + `templates/downstream/theme-state.json` from `themes/boreal.json` (incl. border + settings). Eval fallbacks: `theme.nix` preset → boreal.json; plymouth + live ISO splash hex fallbacks; theme-shot / theme-sync examples; TESTING/RECOVERY wording.
- **Verified:** V0 — state JSON matches boreal palette/fonts; `check-theme-contrast` 24×7 pass; py_compile theme-sync. Full `nix flake check --no-build` started but not waited (slow ISO eval). No V2 apply / V3 session.
- **Pending:** `themes/boreal/preview.png` still missing (picker name-only) — left in PROPOSED theme polish; V2/V3 visual on hardware. Queue head now #43 Firmware.
- **Next suggestion:** #43 System Firmware or #44 doctor hardware.
## 2026-07-09 — Install contracts: swap=0 + unattended LUKS (workflow test run) ## 2026-07-09 — Install contracts: swap=0 + unattended LUKS (workflow test run)
- **Task:** VISION § v1.0 install golden path — PROPOSED install P0s (NEXT head blocked: item 14 wait-cron, 20 `[human]`, 33 V3 visual, 41 hw). Test of VISION→BACKLOG→verify→commit loop. - **Task:** VISION § v1.0 install golden path — PROPOSED install P0s (NEXT head blocked: item 14 wait-cron, 20 `[human]`, 33 V3 visual, 41 hw). Test of VISION→BACKLOG→verify→commit loop.
- **Did:** (1) `nomarchy-install` passes `swapSize=0` when swap disabled (was always `"${SWAP_GB}G"``"0G"` still created `@swap`). (2) `disko-config.nix` treats `"0"` and `"0G"` as no-swap. (3) Unattended LUKS fail-closed: require `NOMARCHY_LUKS_PASSPHRASE` or `NOMARCHY_NO_LUKS=1`. - **Did:** (1) `nomarchy-install` passes `swapSize=0` when swap disabled (was always `"${SWAP_GB}G"``"0G"` still created `@swap`). (2) `disko-config.nix` treats `"0"` and `"0G"` as no-swap. (3) Unattended LUKS fail-closed: require `NOMARCHY_LUKS_PASSPHRASE` or `NOMARCHY_NO_LUKS=1`.

View File

@@ -25,7 +25,7 @@ The same picker lives in the menu: **System Rollback** lists the
recent desktop generations — pick one and it activates. recent desktop generations — pick one and it activates.
Or simply apply a theme you know is good: `nomarchy-theme-sync apply Or simply apply a theme you know is good: `nomarchy-theme-sync apply
tokyo-night`. If a switch failed halfway, the state file is written boreal` (or any preset). If a switch failed halfway, the state file is written
*before* the rebuild — fix the cause and re-run *before* the rebuild — fix the cause and re-run
`home-manager switch --flake ~/.nomarchy` (or `home-update`). `home-manager switch --flake ~/.nomarchy` (or `home-update`).

View File

@@ -65,13 +65,13 @@ Work through these in order; each one exercises a different layer.
| # | Check | Verifies | | # | Check | Verifies |
|---|---|---| |---|---|---|
| 1 | Boots to Hyprland with Tokyo Night wallpaper visible | greetd autologin, awww, session start | | 1 | Boots to Hyprland with the default theme wallpaper visible (Boreal) | greetd autologin, awww, session start |
| 2 | Waybar shows at top, themed (blue accent on dark) | HM waybar module, palette baking | | 2 | Waybar shows at top, themed (blue accent on dark) | HM waybar module, palette baking |
| 3 | `SUPER+Return` opens Ghostty with Tokyo Night colors | terminal default, ANSI palette | | 3 | `SUPER+Return` opens Ghostty with the default theme colors (Boreal) | terminal default, ANSI palette |
| 4 | `btop` in the terminal is themed | per-theme asset baking | | 4 | `btop` in the terminal is themed | per-theme asset baking |
| 5 | `nomarchy-theme-sync list` prints 21 presets | package, baked themes dir | | 5 | `nomarchy-theme-sync list` prints 21 presets | package, baked themes dir |
| 6 | `nomarchy-theme-sync apply gruvbox` → state written, `home-manager switch` runs, desktop re-themes, wallpaper changes | the whole engine: state write, pure eval, HM rebuild, wallpaper hook | | 6 | `nomarchy-theme-sync apply gruvbox` → state written, `home-manager switch` runs, desktop re-themes, wallpaper changes | the whole engine: state write, pure eval, HM rebuild, wallpaper hook |
| 7 | `SUPER+SHIFT+T` cycles wallpapers instantly (try `tokyo-night`: 4 of them) | the runtime wallpaper path | | 7 | `SUPER+SHIFT+T` cycles wallpapers instantly (try `tokyo-night` for 4, or `boreal` for its set) | the runtime wallpaper path |
| 8 | `nomarchy-theme-sync apply summer-night` → after the switch the bar has its own identity (light bar, different styling) | whole-swap waybar.css assets | | 8 | `nomarchy-theme-sync apply summer-night` → after the switch the bar has its own identity (light bar, different styling) | whole-swap waybar.css assets |
| 9 | Open a GTK app — dark theme matching the palette | Stylix layer | | 9 | Open a GTK app — dark theme matching the palette | Stylix layer |
| 10 | `home-manager generations` lists one generation per theme change; activating an older one rolls the theme back | atomicity / rollback story | | 10 | `home-manager generations` lists one generation per theme change; activating an older one rolls the theme back | atomicity / rollback story |

View File

@@ -37,7 +37,7 @@ judgment; agents prepare, do not advance `v1`):
| Bar | Done looks like | | Bar | Done looks like |
|-----|-----------------| |-----|-----------------|
| **Install golden path** | Offline (or documented online) install → first boot themed desktop; swap=0 and unattended LUKS contracts correct | | **Install golden path** | Offline (or documented online) install → first boot themed desktop; swap=0 and unattended LUKS contracts correct |
| **Default identity** | Boreal (or chosen default) is the seed theme; picker has preview | | **Default identity** | Boreal is the seed theme (shipped 2026-07-09); picker should have `preview.png` |
| **Day-2 confidence** | Doctor covers real failure modes; firmware and fingerprint are discoverable without reading the README | | **Day-2 confidence** | Doctor covers real failure modes; firmware and fingerprint are discoverable without reading the README |
| **Hardware story** | `docs/HARDWARE.md` matches code; option-docs green; i2c/ddcci documented | | **Hardware story** | `docs/HARDWARE.md` matches code; option-docs green; i2c/ddcci documented |
| **Honesty** | No “done” claims past verified tier; HARDWARE-QUEUE cleared or consciously deferred | | **Honesty** | No “done” claims past verified tier; HARDWARE-QUEUE cleared or consciously deferred |

View File

@@ -6,14 +6,14 @@
let let
# ISO boot branding: the Nomarchy monogram recolored to the palette accent, # ISO boot branding: the Nomarchy monogram recolored to the palette accent,
# centred on the theme base. Built from the vendored vector logo and the # centred on the theme base. Built from the vendored vector logo and the
# live theme-state.json (tokyo-night by default). The same composed image # live theme-state.json (Boreal by default). The same composed image
# backs both the isolinux (BIOS) splash and the GRUB (UEFI) theme below, so # backs both the isolinux (BIOS) splash and the GRUB (UEFI) theme below, so
# the two boot paths match. # the two boot paths match.
state = builtins.fromJSON (builtins.readFile ../theme-state.json); state = builtins.fromJSON (builtins.readFile ../theme-state.json);
isoColor = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback); isoColor = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback);
accent = isoColor "accent" "7aa2f7"; accent = isoColor "accent" "B79BE8";
base = isoColor "base" "1a1b26"; base = isoColor "base" "21272F";
subtext = isoColor "subtext" "787c99"; subtext = isoColor "subtext" "97A3B2";
isoSplash = pkgs.runCommand "nomarchy-iso-splash.png" isoSplash = pkgs.runCommand "nomarchy-iso-splash.png"
{ nativeBuildInputs = [ pkgs.imagemagick pkgs.librsvg ]; } '' { nativeBuildInputs = [ pkgs.imagemagick pkgs.librsvg ]; } ''

View File

@@ -22,10 +22,10 @@ let
# Defaults guarantee evaluation succeeds on a sparse or older state # Defaults guarantee evaluation succeeds on a sparse or older state
# file (e.g. one written before a schema field was added). The shipped # file (e.g. one written before a schema field was added). The shipped
# Tokyo Night preset provides the color/ansi fallbacks; the path is # Boreal preset provides the color/ansi fallbacks; the path is
# relative to this module, so it resolves inside Nomarchy's own flake # relative to this module, so it resolves inside Nomarchy's own flake
# source even when consumed downstream. # source even when consumed downstream.
preset = builtins.fromJSON (builtins.readFile ../../themes/tokyo-night.json); preset = builtins.fromJSON (builtins.readFile ../../themes/boreal.json);
defaults = preset // { defaults = preset // {
fonts = { fonts = {
@@ -132,7 +132,7 @@ let
${toString cfg.stateFile} is a snapshot of it). The tool prints ${toString cfg.stateFile} is a snapshot of it). The tool prints
the same report with per-field fixes: `nomarchy-theme-sync the same report with per-field fixes: `nomarchy-theme-sync
validate`. Re-applying any preset resets all appearance fields: validate`. Re-applying any preset resets all appearance fields:
`nomarchy-theme-sync apply tokyo-night`.''; `nomarchy-theme-sync apply boreal`.'';
# A border value is a palette key (look it up in colors) unless it's # A border value is a palette key (look it up in colors) unless it's
# already a literal hex; unknown keys fall through to the raw string. # already a literal hex; unknown keys fall through to the raw string.

View File

@@ -33,7 +33,7 @@
description = '' description = ''
theme-state.json for the system-side consumers (currently the theme-state.json for the system-side consumers (currently the
Plymouth splash background). lib.mkFlake wires it automatically Plymouth splash background). lib.mkFlake wires it automatically
from your flake; null falls back to the Tokyo Night base color. from your flake; null falls back to the Boreal base color.
''; '';
}; };

View File

@@ -15,14 +15,15 @@ let
then builtins.fromJSON (builtins.readFile cfg.stateFile) then builtins.fromJSON (builtins.readFile cfg.stateFile)
else { }; else { };
colorOf = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback); colorOf = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback);
base = colorOf "base" "#1a1b26"; # Fallbacks match the distro default theme (Boreal) when stateFile is null.
base = colorOf "base" "#21272F";
# Splash elements are recolored from the palette so they read on any # Splash elements are recolored from the palette so they read on any
# base, light or dark: foreground glyphs → text, field/track boxes → # base, light or dark: foreground glyphs → text, field/track boxes →
# surface (raised from base in both polarities), the progress fill → # surface (raised from base in both polarities), the progress fill →
# accent. The shipped art is a fixed navy that vanished on dark themes. # accent. The shipped art is a fixed navy that vanished on dark themes.
text = colorOf "text" "#a9b1d6"; text = colorOf "text" "#D3DAE0";
surface = colorOf "surface" "#32344a"; surface = colorOf "surface" "#303A46";
accent = colorOf "accent" "#7aa2f7"; accent = colorOf "accent" "#B79BE8";
magick = lib.getExe' pkgs.imagemagick "magick"; magick = lib.getExe' pkgs.imagemagick "magick";
# Plymouth's Window.SetBackgroundTopColor takes three floats in # Plymouth's Window.SetBackgroundTopColor takes three floats in

View File

@@ -208,7 +208,7 @@ def validate_state(state) -> tuple:
if not isinstance(state, dict): if not isinstance(state, dict):
err("top level", "must be a JSON object", err("top level", "must be a JSON object",
"re-apply a preset: nomarchy-theme-sync apply tokyo-night") "re-apply a preset: nomarchy-theme-sync apply boreal")
return errors, warnings return errors, warnings
for k in state: for k in state:
@@ -221,7 +221,7 @@ def validate_state(state) -> tuple:
("icons", "an icon theme name (or \"\" for pick-by-mode)")): ("icons", "an icon theme name (or \"\" for pick-by-mode)")):
v = state.get(field) v = state.get(field)
if v is not None and not isinstance(v, str): if v is not None and not isinstance(v, str):
err(field, f"must be a string ({want}), got {v!r}", 'quote it, e.g. "tokyo-night"') err(field, f"must be a string ({want}), got {v!r}", 'quote it, e.g. "boreal"')
mode = state.get("mode") mode = state.get("mode")
if mode is not None and mode not in ("dark", "light"): if mode is not None and mode not in ("dark", "light"):

View File

@@ -1,55 +1,61 @@
{ {
"version": 1, "version": 1,
"name": "Tokyo Night", "name": "Boreal",
"slug": "tokyo-night", "slug": "boreal",
"mode": "dark", "mode": "dark",
"wallpaper": "", "wallpaper": "",
"colors": { "colors": {
"base": "#1a1b26", "base": "#21272F",
"mantle": "#161720", "mantle": "#1A1F26",
"surface": "#32344a", "surface": "#303A46",
"overlay": "#444b6a", "overlay": "#404D5C",
"text": "#a9b1d6", "text": "#D3DAE0",
"subtext": "#787c99", "subtext": "#97A3B2",
"muted": "#444b6a", "muted": "#5E6A78",
"accent": "#7aa2f7", "accent": "#B79BE8",
"accentAlt": "#ad8ee6", "accentAlt": "#86C7C0",
"good": "#9ece6a", "good": "#A3CF88",
"warn": "#e0af68", "warn": "#E6C384",
"bad": "#f7768e" "bad": "#E2818A"
},
"border": {
"active": "accent",
"inactive": "overlay"
}, },
"ansi": [ "ansi": [
"#32344a", "#2E3742",
"#f7768e", "#E2818A",
"#9ece6a", "#A3CF88",
"#e0af68", "#E6C384",
"#7aa2f7", "#86A9E0",
"#ad8ee6", "#B79BE8",
"#449dab", "#86C7C0",
"#787c99", "#D3DAE0",
"#444b6a", "#5E6A78",
"#ff7a93", "#EC98A0",
"#b9f27c", "#B4DB9C",
"#ff9e64", "#EFCF98",
"#7da6ff", "#9DBBEC",
"#bb9af7", "#C6AEEF",
"#0db9d7", "#99D6CF",
"#acb0d0" "#E8EDF2"
], ],
"fonts": { "fonts": {
"mono": "JetBrainsMono Nerd Font", "mono": "GeistMono Nerd Font",
"ui": "Inter", "ui": "Inter",
"size": 11 "size": 11
}, },
"ui": { "ui": {
"gapsIn": 5, "gapsIn": 6,
"gapsOut": 12, "gapsOut": 14,
"borderSize": 2, "borderSize": 3,
"rounding": 10, "rounding": 12,
"iconSize": 44,
"activeOpacity": 1.0, "activeOpacity": 1.0,
"inactiveOpacity": 0.95, "inactiveOpacity": 0.93,
"terminalOpacity": 0.96, "terminalOpacity": 0.9,
"blur": true, "blur": true,
"shadow": true "shadow": true
} },
"settings": {}
} }

View File

@@ -1,58 +1,59 @@
{ {
"version": 1, "version": 1,
"name": "Tokyo Night", "name": "Boreal",
"slug": "tokyo-night", "slug": "boreal",
"mode": "dark", "mode": "dark",
"wallpaper": "", "wallpaper": "",
"colors": { "colors": {
"base": "#1a1b26", "base": "#21272F",
"mantle": "#161720", "mantle": "#1A1F26",
"surface": "#32344a", "surface": "#303A46",
"overlay": "#444b6a", "overlay": "#404D5C",
"text": "#a9b1d6", "text": "#D3DAE0",
"subtext": "#787c99", "subtext": "#97A3B2",
"muted": "#444b6a", "muted": "#5E6A78",
"accent": "#7aa2f7", "accent": "#B79BE8",
"accentAlt": "#ad8ee6", "accentAlt": "#86C7C0",
"good": "#9ece6a", "good": "#A3CF88",
"warn": "#e0af68", "warn": "#E6C384",
"bad": "#f7768e" "bad": "#E2818A"
}, },
"border": { "border": {
"active": "accent", "active": "accent",
"inactive": "overlay" "inactive": "overlay"
}, },
"ansi": [ "ansi": [
"#32344a", "#2E3742",
"#f7768e", "#E2818A",
"#9ece6a", "#A3CF88",
"#e0af68", "#E6C384",
"#7aa2f7", "#86A9E0",
"#ad8ee6", "#B79BE8",
"#449dab", "#86C7C0",
"#787c99", "#D3DAE0",
"#444b6a", "#5E6A78",
"#ff7a93", "#EC98A0",
"#b9f27c", "#B4DB9C",
"#ff9e64", "#EFCF98",
"#7da6ff", "#9DBBEC",
"#bb9af7", "#C6AEEF",
"#0db9d7", "#99D6CF",
"#acb0d0" "#E8EDF2"
], ],
"fonts": { "fonts": {
"mono": "JetBrainsMono Nerd Font", "mono": "GeistMono Nerd Font",
"ui": "Inter", "ui": "Inter",
"size": 11 "size": 11
}, },
"ui": { "ui": {
"gapsIn": 5, "gapsIn": 6,
"gapsOut": 12, "gapsOut": 14,
"borderSize": 2, "borderSize": 3,
"rounding": 10, "rounding": 12,
"iconSize": 44,
"activeOpacity": 1.0, "activeOpacity": 1.0,
"inactiveOpacity": 0.95, "inactiveOpacity": 0.93,
"terminalOpacity": 0.96, "terminalOpacity": 0.9,
"blur": true, "blur": true,
"shadow": true "shadow": true
}, },

View File

@@ -17,7 +17,7 @@ let
overlays = [ flake.overlays.default ]; overlays = [ flake.overlays.default ];
config.allowUnfree = true; config.allowUnfree = true;
}; };
slug = let s = builtins.getEnv "THEME"; in if s == "" then "tokyo-night" else s; slug = let s = builtins.getEnv "THEME"; in if s == "" then "boreal" else s;
in in
pkgs.testers.runNixOSTest { pkgs.testers.runNixOSTest {
name = "theme-shot-${slug}"; name = "theme-shot-${slug}";