Commit 12b1012
Changed files (3)
modules
users
modules/users/hpcesia/default.nix
@@ -22,6 +22,7 @@
};
system = {
hashedPasswordAged = ./hashed-password.age;
+ themes.theme = "catppuccin";
};
};
}
modules/users/default.nix
@@ -1,4 +1,81 @@
-{den, ...}: {
+{
+ den,
+ lib,
+ ...
+}: let
+ # Walk den.aspects (at pipeline time) for .themes sub-aspects
+ # and include the ones selected in user.system.themes.
+ # user.system.themes.theme = global default; per-aspect keys override it.
+ themeIncludes = {user, ...}: let
+ themesCfg = user.system.themes or {};
+ globalTheme = themesCfg.theme or null;
+
+ inherit (den.lib.aspects.fx.keyClassification) structuralKeysSet;
+ classKeys = den.classes or {};
+ quirkKeys = den.quirks or {};
+ skipKey = k: lib.hasPrefix "__" k || structuralKeysSet ? ${k} || classKeys ? ${k} || quirkKeys ? ${k} || k == "settings" || k == "provides";
+
+ # Collect paths of aspects that have .themes (non-recursive flat walk).
+ collectThemePaths = let
+ go = prefix: node:
+ if !builtins.isAttrs node
+ then []
+ else let
+ hasThemes = node ? themes && builtins.isAttrs node.themes;
+ pathStr = lib.concatStringsSep "." prefix;
+ here =
+ if hasThemes
+ then [pathStr]
+ else [];
+ children = lib.concatMap (
+ k:
+ if skipKey k
+ then []
+ else go (prefix ++ [k]) node.${k}
+ ) (builtins.attrNames node);
+ in
+ here ++ children;
+ in
+ go [] (den.aspects or {});
+
+ # Resolve effective theme for an aspect path — walks the tree structure.
+ effectiveTheme = path: let
+ segs = lib.splitString "." path;
+ explicit = lib.attrByPath segs null themesCfg;
+ in
+ if explicit != null
+ then explicit
+ else globalTheme;
+
+ includeThemeAspect = path: theme: let
+ segs = lib.splitString "." path;
+ parentAspect = lib.attrByPath segs null den.aspects;
+ themeAspect = lib.attrByPath (segs ++ ["themes" theme]) null den.aspects;
+ # some den.aspects.<path> node carries no name/meta.provider
+ parentRef = {
+ name = lib.last segs;
+ meta.provider = lib.init segs;
+ };
+ in
+ lib.optionals (themeAspect != null && parentAspect != null) (
+ den.lib.policy.when
+ ({user, ...}: user.hasAspect parentRef)
+ [themeAspect]
+ );
+ in {
+ name = "users/themes";
+ includes =
+ lib.concatMap (
+ path: let
+ t = effectiveTheme path;
+ in
+ if t != null
+ then includeThemeAspect path t
+ else []
+ )
+ collectThemePaths;
+ };
+in {
den.default.includes = [
den.batteries.define-user
({user, ...}: {
@@ -15,5 +92,6 @@
vaultix.beforeUserborn = [secretName];
};
})
+ themeIncludes
];
}
modules/users/schema.nix
@@ -59,6 +59,22 @@ in {
default = {};
description = "Per-user feature settings (freeform nested namespace)";
};
+ themes = mkOption {
+ type = types.attrs;
+ default = {};
+ description = ''
+ Per-aspect theme selection (tree structure mirroring aspect tree).
+ Leaf values are theme names (strings). The special key `theme` at the
+ root sets the global default theme applied to all aspects that do not
+ have a per-aspect override.
+ '';
+ example = lib.literalExpression ''
+ {
+ theme = "catppuccin";
+ develop.editor.helix = "tokyo-night";
+ }
+ '';
+ };
};
};
default = {};