diff --git a/.claude/tasks/0022-stylix-nord-theming.md b/.claude/tasks/0022-stylix-nord-theming.md index 4e4c1cf..21694d3 100644 --- a/.claude/tasks/0022-stylix-nord-theming.md +++ b/.claude/tasks/0022-stylix-nord-theming.md @@ -13,8 +13,34 @@ The theming is reversible per target, so individual surfaces can migrate toward ## Acceptance criteria -- [ ] Stylix is a flake input, wired into the host build. -- [ ] A desktop theming module resolves a single Nord base16 scheme and applies it to GTK, Qt, cursor, and system fonts. -- [ ] A single static Nord wallpaper is set by Stylix; no dynamic, animated, or cycling wallpaper. -- [ ] Stylix targets for nvim, tmux, and fish are off, leaving their existing themes untouched. -- [ ] neogaia builds green under `nix flake check`, and an eval probe confirms the resolved scheme is Nord. +- [x] Stylix is a flake input, wired into the host build. +- [x] A desktop theming module resolves a single Nord base16 scheme and applies it to GTK, Qt, cursor, and system fonts. +- [x] A single static Nord wallpaper is set by Stylix; no dynamic, animated, or cycling wallpaper. +- [x] Stylix targets for nvim, tmux, and fish are off, leaving their existing themes untouched. +- [x] neogaia builds green under `nix flake check`, and an eval probe confirms the resolved scheme is Nord. + +## Implementation Notes + +- **Wallpaper is a generated Nord gradient, not a shipped image.** + Stylix requires an `image`, and the spec asks only for "a single static Nord wallpaper". + Rather than commit a binary blob or fetch one over the network at build time, the module draws a vertical gradient across the Nord Polar Night shades (`#2E3440` → `#3B4252`) with ImageMagick. + It is static, genuinely Nord, and fully reproducible with no external dependency beyond a cached build tool. + Swapping in a picture later is a one-line change to `image`. + +- **The nvim target is `nixvim`, not `neovim`.** + nvim here is configured through nixvim, so the Stylix target that would theme it is `nixvim`. + Disabling `neovim` would have been a no-op and left nvim themed. + +- **fish is disabled at two levels.** + fish is enabled both at system level (`programs.fish`, for `/etc/shells` and vendor completions) and in home-manager, so its theming has a target in each scope. + Both are turned off, so the hand-written fish theme stands. + +- **Cursor generation switched on explicitly.** + home-manager now wants `home.pointerCursor.enable` set explicitly rather than inferring it from the presence of cursor settings, so the module sets it to silence the deprecation and keep the build warning-clean (bar the pre-existing benign nixvim `nixpkgs.follows` notice). + +- **System font pinned to JetBrains Mono.** + The acceptance criterion asks for "system fonts" without naming one, so the monospace is pinned to JetBrains Mono and the serif/sans/emoji families are left at Stylix's Nord-coherent defaults. + +- **Stylix module imported unconditionally.** + `lib.nix` adds `inputs.stylix.nixosModules.stylix` to every host's module set, matching how the other input modules are wired. + It stays inert until `stylix.enable` is set, which only the theming module does, only when the desktop is on. diff --git a/flake.lock b/flake.lock index 898aa4b..cbf26b4 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,73 @@ { "nodes": { + "base16": { + "inputs": { + "fromYaml": "fromYaml" + }, + "locked": { + "lastModified": 1755819240, + "narHash": "sha256-qcMhnL7aGAuFuutH4rq9fvAhCpJWVHLcHVZLtPctPlo=", + "owner": "SenchoPens", + "repo": "base16.nix", + "rev": "75ed5e5e3fce37df22e49125181fa37899c3ccd6", + "type": "github" + }, + "original": { + "owner": "SenchoPens", + "repo": "base16.nix", + "type": "github" + } + }, + "base16-fish": { + "flake": false, + "locked": { + "lastModified": 1765809053, + "narHash": "sha256-XCUQLoLfBJ8saWms2HCIj4NEN+xNsWBlU1NrEPcQG4s=", + "owner": "tomyun", + "repo": "base16-fish", + "rev": "86cbea4dca62e08fb7fd83a70e96472f92574782", + "type": "github" + }, + "original": { + "owner": "tomyun", + "repo": "base16-fish", + "rev": "86cbea4dca62e08fb7fd83a70e96472f92574782", + "type": "github" + } + }, + "base16-helix": { + "flake": false, + "locked": { + "lastModified": 1776754714, + "narHash": "sha256-E3OAK27smtATTmX45uoTSRsVD+Y+ZiVVfgM/tjpbtYg=", + "owner": "tinted-theming", + "repo": "base16-helix", + "rev": "4d508123037e7851ad36ebf7d9c48b0e9e1eb581", + "type": "github" + }, + "original": { + "owner": "tinted-theming", + "repo": "base16-helix", + "type": "github" + } + }, + "base16-vim": { + "flake": false, + "locked": { + "lastModified": 1732806396, + "narHash": "sha256-e0bpPySdJf0F68Ndanwm+KWHgQiZ0s7liLhvJSWDNsA=", + "owner": "tinted-theming", + "repo": "base16-vim", + "rev": "577fe8125d74ff456cf942c733a85d769afe58b7", + "type": "github" + }, + "original": { + "owner": "tinted-theming", + "repo": "base16-vim", + "rev": "577fe8125d74ff456cf942c733a85d769afe58b7", + "type": "github" + } + }, "chaotic": { "inputs": { "flake-schemas": "flake-schemas", @@ -41,6 +109,22 @@ "type": "github" } }, + "firefox-gnome-theme": { + "flake": false, + "locked": { + "lastModified": 1782007937, + "narHash": "sha256-PbnJr+eB+9Czol3ReI83dUgEhcn0sDK6TSy6ODTQm88=", + "owner": "rafaelmardojai", + "repo": "firefox-gnome-theme", + "rev": "981bd332015397fb1ca033fa982bd61635160c78", + "type": "github" + }, + "original": { + "owner": "rafaelmardojai", + "repo": "firefox-gnome-theme", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": [ @@ -62,6 +146,27 @@ "type": "github" } }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "stylix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1782949081, + "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-schemas": { "locked": { "lastModified": 1780327564, @@ -76,6 +181,22 @@ "url": "https://flakehub.com/f/DeterminateSystems/flake-schemas/%3D0.5.0.tar.gz" } }, + "fromYaml": { + "flake": false, + "locked": { + "lastModified": 1731966426, + "narHash": "sha256-lq95WydhbUTWig/JpqiB7oViTcHFP8Lv41IGtayokA8=", + "owner": "SenchoPens", + "repo": "fromYaml", + "rev": "106af9e2f715e2d828df706c386a685698f3223b", + "type": "github" + }, + "original": { + "owner": "SenchoPens", + "repo": "fromYaml", + "type": "github" + } + }, "gitea-axi": { "inputs": { "home-manager": "home-manager_2", @@ -97,6 +218,25 @@ "url": "https://git.alexion.dev/alexion/gitea-axi" } }, + "gnome-shell": { + "flake": false, + "locked": { + "host": "gitlab.gnome.org", + "lastModified": 1776175984, + "narHash": "sha256-RJFlFW8GiMei6oqUGrMkGEvVqOH8U7Q8abc1yK4VKD8=", + "owner": "GNOME", + "repo": "gnome-shell", + "rev": "e0fdc4c13250e9a9b8ea9594c83925274f4a5dca", + "type": "gitlab" + }, + "original": { + "host": "gitlab.gnome.org", + "owner": "GNOME", + "ref": "50.1", + "repo": "gnome-shell", + "type": "gitlab" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -265,6 +405,31 @@ "type": "github" } }, + "nur": { + "inputs": { + "flake-parts": [ + "stylix", + "flake-parts" + ], + "nixpkgs": [ + "stylix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1783439237, + "narHash": "sha256-WUr8JF2v3n4Y30E5dxv4sAgNJXpVDBoCQNoQ/V4+n4o=", + "owner": "nix-community", + "repo": "NUR", + "rev": "b70bb66c7bcd162642f3a609bc16843c7059f503", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "NUR", + "type": "github" + } + }, "root": { "inputs": { "chaotic": "chaotic", @@ -276,7 +441,8 @@ "nixpkgs-stable": "nixpkgs-stable", "nixpkgs-unstable": "nixpkgs-unstable", "nixvim": "nixvim", - "sops-nix": "sops-nix" + "sops-nix": "sops-nix", + "stylix": "stylix" } }, "sops-nix": { @@ -299,6 +465,39 @@ "type": "github" } }, + "stylix": { + "inputs": { + "base16": "base16", + "base16-fish": "base16-fish", + "base16-helix": "base16-helix", + "base16-vim": "base16-vim", + "firefox-gnome-theme": "firefox-gnome-theme", + "flake-parts": "flake-parts_2", + "gnome-shell": "gnome-shell", + "nixpkgs": [ + "nixpkgs" + ], + "nur": "nur", + "systems": "systems_2", + "tinted-kitty": "tinted-kitty", + "tinted-schemes": "tinted-schemes", + "tinted-tmux": "tinted-tmux", + "tinted-zed": "tinted-zed" + }, + "locked": { + "lastModified": 1784676123, + "narHash": "sha256-ndyanKzw90yX2nUVFmTuYqXidUNymtMfgmIHyNdhht0=", + "owner": "danth", + "repo": "stylix", + "rev": "66714e5ce44269ecc58c20d9196da8dbe1b27a31", + "type": "github" + }, + "original": { + "owner": "danth", + "repo": "stylix", + "type": "github" + } + }, "systems": { "locked": { "lastModified": 1774449309, @@ -314,6 +513,85 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "tinted-kitty": { + "flake": false, + "locked": { + "lastModified": 1735730497, + "narHash": "sha256-4KtB+FiUzIeK/4aHCKce3V9HwRvYaxX+F1edUrfgzb8=", + "owner": "tinted-theming", + "repo": "tinted-kitty", + "rev": "de6f888497f2c6b2279361bfc790f164bfd0f3fa", + "type": "github" + }, + "original": { + "owner": "tinted-theming", + "repo": "tinted-kitty", + "type": "github" + } + }, + "tinted-schemes": { + "flake": false, + "locked": { + "lastModified": 1781968807, + "narHash": "sha256-yYO3Vw2M0y3TAUqt+9+Mj0zwP3XDTF5/PXcPhhFQ1ZM=", + "owner": "tinted-theming", + "repo": "schemes", + "rev": "2ccef2f4b22e3cab5a9292811f7133a07eeba4a7", + "type": "github" + }, + "original": { + "owner": "tinted-theming", + "repo": "schemes", + "type": "github" + } + }, + "tinted-tmux": { + "flake": false, + "locked": { + "lastModified": 1782012462, + "narHash": "sha256-2iDiD8DQLwS1lGuD9TS8WlvNyDoTs6krWntJbtB2zGo=", + "owner": "tinted-theming", + "repo": "tinted-tmux", + "rev": "8c4e750f738a742bd73377ee41d3dadedebedef4", + "type": "github" + }, + "original": { + "owner": "tinted-theming", + "repo": "tinted-tmux", + "type": "github" + } + }, + "tinted-zed": { + "flake": false, + "locked": { + "lastModified": 1782009766, + "narHash": "sha256-VUhBjpGvWqHI7rWeyMYb/u87YJSXHKfVV6S+IelWeO8=", + "owner": "tinted-theming", + "repo": "base16-zed", + "rev": "5e8350bcd354e3241ab681a265fa6ef060c40be1", + "type": "github" + }, + "original": { + "owner": "tinted-theming", + "repo": "base16-zed", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 17f5390..3341974 100644 --- a/flake.nix +++ b/flake.nix @@ -40,6 +40,13 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + # Themes the graphical layer from one base16 scheme. + # Follows our nixpkgs so it themes the same package set the host builds. + stylix = { + url = "github:danth/stylix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # Agent-ergonomic CLI for Gitea, with its home-manager module wiring in the # Claude Code context where that harness is present. gitea-axi = { diff --git a/lib.nix b/lib.nix index 6447e30..b4a72be 100644 --- a/lib.nix +++ b/lib.nix @@ -51,6 +51,7 @@ let inputs.chaotic.nixosModules.default inputs.disko.nixosModules.disko inputs.sops-nix.nixosModules.sops + inputs.stylix.nixosModules.stylix (self + "/system.nix") (self + "/hosts/${hostName}") { networking.hostName = hostName; } diff --git a/modules/desktop/desktop.nix b/modules/desktop/desktop.nix index d1b823e..e0a8704 100644 --- a/modules/desktop/desktop.nix +++ b/modules/desktop/desktop.nix @@ -12,5 +12,6 @@ in modules.desktop.hyprland.enable = lib.mkDefault true; modules.desktop.login.enable = lib.mkDefault true; modules.desktop.terminal.enable = lib.mkDefault true; + modules.desktop.theming.enable = lib.mkDefault true; }; } diff --git a/modules/desktop/theming.nix b/modules/desktop/theming.nix new file mode 100644 index 0000000..24e1d34 --- /dev/null +++ b/modules/desktop/theming.nix @@ -0,0 +1,65 @@ +{ + config, + lib, + pkgs, + ... +}: +# Nord theming for the graphical layer, driven from one base16 scheme by Stylix. +let + cfg = config.modules.desktop.theming; + user = config.user.name; + + # A single static Nord wallpaper, drawn as a vertical gradient across the + # Polar Night shades so it needs no committed binary and no network fetch. + wallpaper = pkgs.runCommand "nord-wallpaper.png" { } '' + ${pkgs.imagemagick}/bin/magick \ + -size 2560x1440 gradient:'#2E3440'-'#3B4252' \ + "$out" + ''; +in +{ + options.modules.desktop.theming.enable = + lib.mkEnableOption "Nord theming of the graphical layer via Stylix"; + + config = lib.mkIf cfg.enable { + stylix = { + enable = true; + + # Nord is a dark scheme. + # The polarity steers Stylix's contrast choices. + polarity = "dark"; + base16Scheme = "${pkgs.base16-schemes}/share/themes/nord.yaml"; + + image = wallpaper; + + cursor = { + package = pkgs.bibata-cursors; + name = "Bibata-Modern-Ice"; + size = 24; + }; + + fonts.monospace = { + package = pkgs.jetbrains-mono; + name = "JetBrains Mono"; + }; + + # fish is also installed at system level, so its theming has a target + # here of its own. + targets.fish.enable = false; + }; + + home-manager.users.${user} = { + # Stylix drives the cursor through home-manager's pointer-cursor config, + # whose generation must be switched on explicitly. + home.pointerCursor.enable = true; + + # nvim, tmux, and fish are configured through home-manager and keep their + # own hand-written themes, so their Stylix targets stay off. + stylix.targets = { + fish.enable = false; + tmux.enable = false; + nixvim.enable = false; + }; + }; + }; +}