feat(theme): opt-in cross-pack icon overrides, no default bloat
All checks were successful
Check / eval (push) Successful in 3m39s

theme.nix now resolves nomarchy.theme.iconThemePackage from a small
icon-pack registry (Papirus/Tela/Qogir/Reversal/Numix, matched by name
prefix). Default themes name Papirus-*, so the resolved package is the
plain papirus-icon-theme derivation — byte-identical closure, zero added
MB. Only when a theme's `icons` names a set from another pack is that
pack (and only it) symlinkJoined alongside papirus. stylix.nix consumes
it via mkDefault. Adding a pack is one iconPacks row.

Deliberately NOT shipping more packs by default: papirus alone is already
~1 GiB and each extra pack adds ~hundreds of MB, so variety is opt-in per
install (set `icons` in theme-state.json), not a default closure cost.
Documented in templates/downstream/home.nix + ROADMAP § Icon themes.

Verification: V1 (HM activationPackage builds). Eval proofs — default
resolves to plain papirus (zero delta); icons="Tela-dark" ->
nomarchy-icon-themes symlinkJoin (papirus+tela) with the name flowing,
without building tela; all 5 pack attrs exist in the pinned nixpkgs;
template-sot + option-docs + flake check green. No default visual change
(same papirus), so no screenshot; the opt-in render is downstream.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-07-10 20:12:52 +01:00
parent aff50e6070
commit 7eb9b8ad4f
6 changed files with 77 additions and 6 deletions

View File

@@ -60,10 +60,12 @@ in
# GTK/file-manager/rofi icon theme, resolved from the JSON in
# theme.nix (per-theme `icons`, else Papirus-Dark/Light by mode).
# Stylix sets gtk.iconTheme from this; both dark/light point at the
# already-mode-resolved name.
# already-mode-resolved name. The package is papirus by default and
# only unions in another pack when a theme's `icons` names one
# (theme.nix — opt-in, no default closure bloat).
icons = {
enable = true;
package = lib.mkDefault pkgs.papirus-icon-theme;
package = lib.mkDefault t.iconThemePackage;
dark = t.iconTheme;
light = t.iconTheme;
};

View File

@@ -164,10 +164,43 @@ let
if parsed.icons != "" then parsed.icons
else if parsed.mode == "light" then "Papirus-Light"
else "Papirus-Dark";
# Icon-pack resolution — opt-in, no default bloat. Papirus is the shipped
# default and the ONLY icon pack in the closure unless a theme's `icons`
# names a set from another pack; then that pack (and only it) is
# union-joined alongside papirus. Because the default themes name
# `Papirus-*`, the resolved package below is byte-identical to
# papirus-icon-theme (no symlinkJoin, no added MB). To offer another set:
# add one row to `iconPacks` and set `icons = "<Name>"` in a theme JSON
# (see templates/downstream/home.nix). Referencing `pkgs.*` here is
# eval-only — a pack enters the closure solely when it is the matched one.
papirusPkg = pkgs.papirus-icon-theme;
iconPacks = [
# pkg + the theme-name prefixes it provides (packs ship many named
# variants under one prefix, e.g. Tela / Tela-dark / Tela-blue).
{ pkg = papirusPkg; prefixes = [ "Papirus" "breeze" ]; }
{ pkg = pkgs.tela-icon-theme; prefixes = [ "Tela" ]; }
{ pkg = pkgs.qogir-icon-theme; prefixes = [ "Qogir" ]; }
{ pkg = pkgs.reversal-icon-theme; prefixes = [ "Reversal" ]; }
{ pkg = pkgs.numix-icon-theme-circle; prefixes = [ "Numix" ]; }
];
# The pack whose prefix matches the resolved name (null = unknown name →
# GTK falls back gracefully; papirus is still present).
matchedPack =
let m = lib.findFirst
(p: lib.any (pre: lib.hasPrefix pre iconTheme) p.prefixes) null iconPacks;
in if m == null then null else m.pkg;
iconThemePackage =
if matchedPack == null || matchedPack == papirusPkg
then papirusPkg # single pack → identical store path, zero closure delta
else pkgs.symlinkJoin {
name = "nomarchy-icon-themes";
paths = [ papirusPkg matchedPack ];
};
in
{
config = {
nomarchy.theme = checked // { inherit iconTheme border; };
nomarchy.theme = checked // { inherit iconTheme border iconThemePackage; };
# Feature toggles the menu writes (settings.nightlight.enable, …), exposed
# alongside the appearance state. Feature modules mkDefault-read from here