···7assignees: ''
89---
01011If you have found a bug, please share a reproduction repository with us.
12···20```
2122Your repository will help us verify that we can reproduce the bug in a minimal environment - Your repo has CI actions enabled. When the bug has been fixed we can use your same code as a non-regression test to ensure bugs do not appear again.
002324### Description
25
···7assignees: ''
89---
10+Please read https://den.oeiuwq.com/tutorials/bogus/ first.
1112If you have found a bug, please share a reproduction repository with us.
13···21```
2223Your repository will help us verify that we can reproduce the bug in a minimal environment - Your repo has CI actions enabled. When the bug has been fixed we can use your same code as a non-regression test to ensure bugs do not appear again.
24+25+If you found a bug regression please edit the CI workflow to include main and any other revision to test and show something was working before main.
2627### Description
28
+1-1
README.md
···27- [Dendritic](https://den.oeiuwq.com/explanation/core-principles/): **same** concern, **different** Nix classes.
28- [Flake optional](https://den.oeiuwq.com/guides/no-flakes/). Stable/unstable Nix, with/without flake-parts.
29- [DRY](modules/aspects/provides/unfree/unfree.nix) & [`class`-generic](modules/aspects/provides/primary-user.nix) — [parametric](https://den.oeiuwq.com/explanation/parametric/) over `host`/`home`/`user`.
30-- Context-aware [dependencies](https://den.oeiuwq.com/explanation/context-system/); `host<->user` [bidirectional](https://den.oeiuwq.com/guides/bidirectional/) contributions.
31- [Share](https://den.oeiuwq.com/guides/namespaces/) aspects across repos. [Routable](templates/example/modules/aspects/eg/routes.nix) configs.
32- Custom [factories](https://github.com/vic/den/blob/f5c44098e4855e07bf5cbcec00509e75ddde4220/templates/ci/modules/homes.nix#L20) for any Nix `class`. Per-host `stable`/`unstable` channels.
33- Freeform [schemas](https://den.oeiuwq.com/reference/schema/) (no `specialArgs`) with [base](https://github.com/vic/den/pull/119) modules.
···27- [Dendritic](https://den.oeiuwq.com/explanation/core-principles/): **same** concern, **different** Nix classes.
28- [Flake optional](https://den.oeiuwq.com/guides/no-flakes/). Stable/unstable Nix, with/without flake-parts.
29- [DRY](modules/aspects/provides/unfree/unfree.nix) & [`class`-generic](modules/aspects/provides/primary-user.nix) — [parametric](https://den.oeiuwq.com/explanation/parametric/) over `host`/`home`/`user`.
30+- Context-aware [dependencies](https://den.oeiuwq.com/explanation/context-system/); `host<->user` [bidirectional](https://den.oeiuwq.com/guides/configure-aspects/) contributions.
31- [Share](https://den.oeiuwq.com/guides/namespaces/) aspects across repos. [Routable](templates/example/modules/aspects/eg/routes.nix) configs.
32- Custom [factories](https://github.com/vic/den/blob/f5c44098e4855e07bf5cbcec00509e75ddde4220/templates/ci/modules/homes.nix#L20) for any Nix `class`. Per-host `stable`/`unstable` channels.
33- Freeform [schemas](https://den.oeiuwq.com/reference/schema/) (no `specialArgs`) with [base](https://github.com/vic/den/pull/119) modules.
···3description: How aspects use the __functor pattern for context-awareness.
4---
50067-> Use the source, Luke: [`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · Built on [flake-aspects](https://github.com/vic/flake-aspects)
0089## The __functor Pattern
10
···3description: How aspects use the __functor pattern for context-awareness.
4---
56+import { Aside } from '@astrojs/starlight/components';
7+89+<Aside title="Use the Source, Luke" icon="github">
10+[`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · Built on [flake-aspects](https://github.com/vic/flake-aspects)
11+</Aside>
1213## The __functor Pattern
14
···1----
2-title: OS Context Pipeline
3-description: The complete data flow from host declaration to final configuration.
4----
5-6-> Use the source, Luke: [`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) · [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) · HM: [`hm-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-os.nix) · [`hm-integration.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-integration.nix)
7-8-## The NixOS/nix-Darwin Pipeline
9-10-When Den evaluates a host configuration, data flows through a pipeline
11-of context transformations. Here is the complete picture:
12-13-```mermaid
14-graph TD
15- Host["den.hosts.x86_64-linux.igloo"]
16- Host -->|"initial context"| CtxHost["ctx.host { host }"]
17-18- CtxHost -->|"conf"| HA["den.aspects.igloo<br/>(fixedTo { host })"]
19- CtxHost -->|"into.default"| CtxDef1["<b>ctx.default</b> { host }"]
20- CtxHost -->|"into.user<br/>(per user)"| CtxUser["ctx.user { host, user }"]
21- CtxHost -->|"into.hm-host<br/>(if HM detected)"| CtxHM["ctx.hm-host { host }"]
22-23- CtxUser -->|"conf"| UA["den.aspects.tux<br/>(fixedTo { host, user })"]
24- CtxUser -->|"into.default"| CtxDef2["<b>ctx.default</b> { host, user }"]
25-26- CtxHM -->|"conf"| HMmod["Import HM module"]
27- CtxHM -->|"into.hm-user<br/>(per HM user)"| CtxHMU["ctx.hm-user { host, user }"]
28-29- CtxHMU -->|"forward homeManager<br/>into host"| FW["home-manager.users.tux"]
30-31- CtxDef1 -->|"includes"| DI1["<b>den.default.{class}</b> ()<br/>den.default.includes<br/>(host-context funcs)"]
32- CtxDef2 -->|"includes"| DI2["den.default.includes<br/>(user-context funcs)"]
33-```
34-35-## Stage by Stage
36-37-### 1. Host Entry
38-39-Den reads `den.hosts.x86_64-linux.igloo` and applies the initial context `den.ctx.host`:
40-41-```nix
42-den.ctx.host { host = den.hosts.x86_64-linux.igloo; }
43-```
44-45-### 2. Host Aspect Resolution
46-47-`den.ctx.host.conf` locates `den.aspects.igloo` and fixes it to the host context.
48-All owned configs and static includes from the host aspect are collected.
49-50-### 3. Default configs (host-level)
51-52-`den.ctx.host.into.default` produces `{ host }` for `den.ctx.default`, which
53-activates `den.default.includes` functions matching `{ host, ... }`.
54-55-### 4. User Enumeration
56-57-`den.ctx.host.into.user` maps over `host.users`, producing one
58-`den.ctx.user { host, user }` per user.
59-60-### 5. User Aspect Resolution
61-62-`den.ctx.user.conf` locates both the user's aspect (`den.aspects.tux`) and the
63-host's aspect, collecting contributions from both directions.
64-65-### 6. Default configs (user-level)
66-67-`ctx.user.into.default` activates `den.default.includes` again, this time
68-with `{ host, user }` — functions needing user context now match.
69-70-### 7. Home-Manager Detection
71-72-`den.ctx.host.into.hm-host` checks if the host has users with `homeManager`
73-class and a supported OS. If so, it activates `den.ctx.hm-host`.
74-75-### 8. HM Module Import (host-level)
76-77-`den.ctx.hm-host.conf` imports the Home-Manager NixOS/Darwin module.
78-79-### 9. HM User config collection (user-level)
80-81-For each HM user, `ctx.hm-user` uses `den._.forward` to take
82-`homeManager` class configs and insert them into
83-`home-manager.users.<name>` on the host.
84-85-## Home-Manager Detection Criteria
86-87-`ctx.host.into.hm-host` does not always activate. It checks three conditions
88-(see [`hm-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-os.nix)):
89-90-1. **OS class is supported** — the host's class is `nixos` or `darwin`
91-2. **HM users exist** — at least one user has `class = "homeManager"`
92-3. **HM module available** — `inputs.home-manager` exists, or the host has a custom `hm-module`
93-94-All three must be true. Hosts without users, or with only non-HM users,
95-skip the entire HM pipeline.
96-97-## Duplication Caveat with den.default
98-99-`den.default` (alias for `den.ctx.default`) is included at **{host}**, **{host, user}**, **{home}**
100-context stages — once for the host context and once __per each__ user.
101-102-This means:
103-104-- **Owned** configs and **static** includes from `den.default` can appear
105- multiple times in the final configuration
106-- For `mkMerge`-compatible options (most NixOS options), this is harmless
107-- For options of **types.listOf** or **types.package**, you may get duplicate entries.
108-109-To avoid duplication, use `den.lib.take.exactly` to restrict which
110-context stages a function matches:
111-112-```nix
113-den.default.includes = [
114- (den.lib.take.exactly ({ host }: { nixos.x = 1; }))
115-];
116-```
117-118-This function runs only in the `{ host }` context, not in `{ host, user }`.
119-120-:::tip
121-Prefer attaching configurations directly to specific host or user aspects, or use
122-`den.ctx.host` / `den.ctx.user` includes, rather than overloading `den.default`
123-for everything.
124-:::
125-126-## Standalone Home-Manager
127-128-For `den.ctx.home`, the pipeline is shorter:
129-130-```mermaid
131-graph TD
132- Home["den.homes.x86_64-linux.tux"]
133- Home -->|"creates"| CtxHome["ctx.home { home }"]
134- CtxHome -->|"conf"| HomeA["den.aspects.tux<br/>(fixedTo { home })"]
135- CtxHome -->|"into.default"| CtxDef["ctx.default { home }"]
136-```
137-138-## den.default Is an Alias
139-140-`den.default` is an alias for `den.ctx.default`. When you write:
141-142-```nix
143-den.default.homeManager.home.stateVersion = "25.11";
144-den.default.includes = [ den._.define-user ];
145-```
146-147-You are actually setting `den.ctx.default.homeManager...` and
148-`den.ctx.default.includes`. This means `den.default` is a full
149-context type — it has `conf`, `into`, `includes`, and owned attributes.
150-151-### How den.default Receives Data
152-153-`host`, `user`, and `home` aspects **do not** include `den.default` directly.
154-Instead, each context type transforms **into** `default`:
155-156-```nix
157-den.ctx.host.into.default = lib.singleton; # passes { host }
158-den.ctx.user.into.default = lib.singleton; # passes { host, user }
159-den.ctx.home.into.default = lib.singleton; # passes { home }
160-```
161-162-This means `den.default` is reached through the declarative context
163-pipeline, not by direct inclusion. The data flowing into `den.default`
164-is whatever the source context provides.
165-166-### Best Practices
167-168-`den.default` is useful for global settings like `home.stateVersion`.
169-However, prefer attaching parametric includes to the appropriate
170-context type instead:
171-172-| Instead of | Use |
173-|-----------|-----|
174-| `den.default.includes = [ hostFunc ]` | `den.ctx.host.includes = [ hostFunc ]` |
175-| `den.default.includes = [ hmFunc ]` | `den.ctx.hm-host.includes = [ hmFunc ]` |
176-| `den.default.nixos.x = 1` | `den.ctx.host.nixos.x = 1` |
177-178-## Why This Design?
179-180-Each context type is **independent** and **composable**. You can:
181-182-- Add new context types without modifying existing ones
183-- Attach aspects to any stage of the pipeline
184-- Create custom transformations for domain-specific needs
185-- Override any built-in context behavior
186-187-The pipeline is not hardcoded — it's declared through `den.ctx` definitions
188-that you can inspect, extend, and customize.
···1+---
2+title: OS Context Pipeline
3+description: The complete data flow from host declaration to final configuration.
4+---
5+6+7+import { Aside } from '@astrojs/starlight/components';
8+9+<Aside title="Use the Source, Luke" icon="github">
10+[`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) · [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) · HM: [`hm-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-os.nix) · [`hm-integration.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-integration.nix)
11+</Aside>
12+13+## The NixOS/nix-Darwin Pipeline
14+15+When Den evaluates a host configuration, data flows through a pipeline
16+of context transformations. Here is the complete picture:
17+18+```mermaid
19+graph TD
20+ Host["den.hosts.x86_64-linux.igloo"]
21+ Host -->|"initial context"| CtxHost["ctx.host { host }"]
22+23+ CtxHost -->|"_.host"| HA["den.aspects.igloo<br/>(fixedTo { host })"]
24+ CtxHost -->|"into.default"| CtxDef1["<b>ctx.default</b> { host }"]
25+ CtxHost -->|"into.user<br/>(per user)"| CtxUser["ctx.user { host, user }"]
26+ CtxHost -->|"into.hm-host<br/>(if HM detected)"| CtxHM["ctx.hm-host { host }"]
27+28+ CtxUser -->|"_.user (self)"| UA["den.aspects.tux<br/>(fixedTo { host, user })"]
29+ CtxHost -->|"_.user (cross)"| XP["host aspect includes<br/>matching { host, user }"]
30+ CtxUser -->|"into.default"| CtxDef2["<b>ctx.default</b> { host, user }"]
31+32+ CtxHM -->|"_.hm-host"| HMmod["Import HM module"]
33+ CtxHM -->|"into.hm-user<br/>(per HM user)"| CtxHMU["ctx.hm-user { host, user }"]
34+35+ CtxHMU -->|"forward homeManager<br/>into host"| FW["home-manager.users.tux"]
36+37+ CtxDef1 -->|"includes"| DI1["<b>den.default.{class}</b> ()<br/>den.default.includes<br/>(host-context funcs)"]
38+ CtxDef2 -->|"includes"| DI2["den.default.includes<br/>(user-context funcs)"]
39+```
40+41+## Stage by Stage
42+43+### 1. Host Entry
44+45+Den reads `den.hosts.x86_64-linux.igloo` and applies the initial context `den.ctx.host`:
46+47+```nix
48+den.ctx.host { host = den.hosts.x86_64-linux.igloo; }
49+```
50+51+### 2. Host Aspect Resolution
52+53+`den.ctx.host.provides.host` (aliased as `_.host`) locates `den.aspects.igloo`
54+and fixes it to the host context. All owned configs and static includes from the
55+host aspect are collected.
56+57+### 3. Default configs (host-level)
58+59+`den.ctx.host.into.default` produces `{ host }` for `den.ctx.default`, which
60+activates `den.default.includes` functions matching `{ host, ... }`.
61+62+### 4. User Enumeration
63+64+`den.ctx.host.into.user` maps over `host.users`, producing one
65+`den.ctx.user { host, user }` per user.
66+67+### 5. User Aspect Resolution
68+69+Two providers contribute to user context:
70+- `den.ctx.user.provides.user` (self-provider) — locates the user's own aspect (`den.aspects.tux`)
71+- `den.ctx.host.provides.user` (cross-provider) — the host's contribution, dispatching host aspect includes matching `{ host, user }`
72+73+### 6. Default configs (user-level)
74+75+`ctx.user.into.default` activates `den.default.includes` again, this time
76+with `{ host, user }` — functions needing user context now match.
77+Because `den.ctx.default` was already visited in stage 3, only parametric
78+includes are dispatched (owned and static configs are **deduplicated**).
79+80+### 7. Home-Manager Detection
81+82+`den.ctx.host.into.hm-host` checks if the host has users with `homeManager`
83+class and a supported OS. If so, it activates `den.ctx.hm-host`.
84+85+### 8. HM Module Import (host-level)
86+87+`den.ctx.hm-host.provides.hm-host` imports the Home-Manager NixOS/Darwin module.
88+89+### 9. HM User config collection (user-level)
90+91+For each HM user, `ctx.hm-user` uses `den._.forward` to take
92+`homeManager` class configs and insert them into
93+`home-manager.users.<name>` on the host.
94+95+## Home-Manager Detection Criteria
96+97+`ctx.host.into.hm-host` does not always activate. It checks three conditions
98+(see [`hm-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-os.nix)):
99+100+1. **OS class is supported** — the host's class is `nixos` or `darwin`
101+2. **HM users exist** — at least one user has `class = "homeManager"`
102+3. **HM module available** — `inputs.home-manager` exists, or the host has a custom `hm-module`
103+104+All three must be true. Hosts without users, or with only non-HM users, skip the entire HM pipeline.
105+106+## Automatic Deduplication
107+108+`den.default` (alias for `den.ctx.default`) is reached at **`{host}`**, **`{host, user}`**,
109+and **`{home}`** context stages — once for the host context and once per user.
110+111+Den's `ctxApply` automatically deduplicates includes using a seen-set:
112+113+- **First visit** to a context type: includes **owned + static + parametric** configs via `fixedTo`
114+- **Subsequent visits**: includes only **parametric** configs via `atLeast`
115+116+Additionally, for each context, two providers are called:
117+- **Self-provider** (`provides.${name}`) — the target's own aspect lookup
118+- **Cross-provider** (`source.provides.${target}`) — the source's contribution (if defined)
119+120+```mermaid
121+graph TD
122+ H["ctx.host { host }"] -->|"into.default (1st visit)"| D1["ctx.default { host }<br/><b>fixedTo</b>: owned + statics + parametric<br/>+ self-provider + cross-provider"]
123+ H -->|"into.user"| U["ctx.user { host, user }"]
124+ U -->|"into.default (2nd visit)"| D2["ctx.default { host, user }<br/><b>atLeast</b>: parametric only<br/>+ self-provider + cross-provider"]
125+```
126+127+This means:
128+129+- **Owned** configs and **static** includes from `den.default` appear **exactly once**
130+- **Parametric** functions in `den.default.includes` still run at every stage
131+ (each may match different context shapes)
132+- Options of `types.package` or `types.listOf` no longer get duplicate entries
133+134+<Aside>
135+For parametric includes in `den.default.includes`, use `den.lib.take.exactly`
136+to restrict which context stages a function matches:
137+138+```nix
139+den.default.includes = [ den.aspects.foo ];
140+den.aspects.foo = take.exactly ({ host }: { nixos.x = 1; });
141+```
142+143+This function runs only with `{ host }` context, not with `{ host, user }`.
144+</Aside>
145+146+## Standalone Home-Manager
147+148+For `den.ctx.home`, the pipeline is shorter:
149+150+```mermaid
151+graph TD
152+ Home["den.homes.x86_64-linux.tux"]
153+ Home -->|"creates"| CtxHome["ctx.home { home }"]
154+ CtxHome -->|"_.home"| HomeA["den.aspects.tux<br/>(fixedTo { home })"]
155+ CtxHome -->|"into.default"| CtxDef["ctx.default { home }"]
156+```
157+158+## den.default Is an Alias
159+160+`den.default` is an alias for `den.ctx.default`. When you write:
161+162+```nix
163+den.default.homeManager.home.stateVersion = "25.11";
164+den.default.includes = [ den._.define-user ];
165+```
166+167+You are actually setting `den.ctx.default.homeManager...` and
168+`den.ctx.default.includes`. This means `den.default` is a full
169+context type — it has self-named providers, `into`, `includes`, and owned attributes.
170+171+### How den.default Receives Data
172+173+`host`, `user`, and `home` aspects **do not** include `den.default` directly.
174+Instead, each context type transforms **into** `default`:
175+176+```nix
177+den.ctx.host.into.default = lib.singleton; # passes { host }
178+den.ctx.user.into.default = lib.singleton; # passes { host, user }
179+den.ctx.home.into.default = lib.singleton; # passes { home }
180+```
181+182+This means `den.default` is reached through the declarative context
183+pipeline, not by direct inclusion. The data flowing into `den.default`
184+is whatever the source context provides.
185+186+### Best Practices
187+188+`den.default` is useful for global settings like `home.stateVersion`.
189+However, prefer attaching parametric includes to the appropriate
190+context type instead:
191+192+| Instead of | Use |
193+|-----------|-----|
194+| `den.default.includes = [ hostFunc ]` | `den.ctx.host.includes = [ hostFunc ]` |
195+| `den.default.includes = [ hmFunc ]` | `den.ctx.hm-host.includes = [ hmFunc ]` |
196+| `den.default.nixos.x = 1` | `den.ctx.host.nixos.x = 1` |
197+198+## Why This Design?
199+200+Each context type is **independent** and **composable**. You can:
201+202+- Add new context types without modifying existing ones
203+- Attach aspects to any stage of the pipeline
204+- Create custom transformations for domain-specific needs
205+- Override any built-in context behavior
206+207+The pipeline is not hardcoded — it's declared through `den.ctx` definitions
208+that you can inspect, extend, and customize.
···29(hosts, users, homes) and context types (shapes holding them). Den transforms these through
30a graph of context stages, each producing richer or conditioned data.
3132-The key insight: **the flow of data is declared separately from the
33-configurations it produces**.
3435### 2. Context-Aware Aspects
36···39adapts to every host-user combination. A function requiring `{ other }` is
40not used.
4142-The key insight: **the shape of the function arguments determines when
43-configs are produced, without explicit conditionals**.
4445## Why This Matters
46
···29(hosts, users, homes) and context types (shapes holding them). Den transforms these through
30a graph of context stages, each producing richer or conditioned data.
3132+**the flow of data is declared separately from the configurations it produces**.
03334### 2. Context-Aware Aspects
35···38adapts to every host-user combination. A function requiring `{ other }` is
39not used.
4041+**the shape of the function arguments determines when configs are produced, without explicit conditionals**.
04243## Why This Matters
44
···3description: Understanding Den's dual nature as both a library and a framework.
4---
506## Den the Library
78At its core, Den is a **library** for activating configuration aspects via context transformations.
···45Here is a deployment pipeline example:
4647```nix
48-den.ctx.deploy.conf = { target }:
49 den.aspects.${target.name};
5051den.ctx.deploy.into.service = { target }:
52 map (s: { service = s; target = target; })
53 target.services;
5455-den.ctx.service.conf = { service, target }:
56 { terraform.resource.${service.name} = { ... }; };
57```
58···612. Targets enumerate their services
623. Each service produces Terraform configurations
6364-The same `ctxApply` mechanics — owned configs, `conf` lookup,
65`into` transformations — drive this pipeline without any OS-specific code.
6667You can even design and test custom context flows independently of
···3description: Understanding Den's dual nature as both a library and a framework.
4---
56+7## Den the Library
89At its core, Den is a **library** for activating configuration aspects via context transformations.
···46Here is a deployment pipeline example:
4748```nix
49+den.ctx.deploy._.deploy = { target }:
50 den.aspects.${target.name};
5152den.ctx.deploy.into.service = { target }:
53 map (s: { service = s; target = target; })
54 target.services;
5556+den.ctx.service._.service = { service, target }:
57 { terraform.resource.${service.name} = { ... }; };
58```
59···622. Targets enumerate their services
633. Each service produces Terraform configurations
6465+The same `ctxApply` mechanics — owned configs, self-named provider lookup,
66`into` transformations — drive this pipeline without any OS-specific code.
6768You can even design and test custom context flows independently of
···3description: How parametric functors enable context forwarding and adaptation.
4---
56-> Use the source, Luke: [`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · [`nix/fn-can-take.nix`](https://github.com/vic/den/blob/main/nix/fn-can-take.nix)
000078## What Is a Parametric Aspect?
9···35Den provides several parametric functors in
36`den.lib.parametric`. Each of them provides a
37different `__functor` beaviour.
00000000000000003839## den.lib.parametric
40···176177## take.exactly and take.atLeast
178179-For individual functions (not whole aspects), use [`den.lib.take`](/reference/lib/#denlibatake):
180181182```nix
···3description: How parametric functors enable context forwarding and adaptation.
4---
56+import { Aside } from '@astrojs/starlight/components';
7+8+<Aside title="Use the Source, Luke" icon="github">
9+[`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · [`nix/fn-can-take.nix`](https://github.com/vic/den/blob/main/nix/fn-can-take.nix)
10+</Aside>
1112## What Is a Parametric Aspect?
13···39Den provides several parametric functors in
40`den.lib.parametric`. Each of them provides a
41different `__functor` beaviour.
42+43+<Aside >
44+Aspects created by Den from your host/user/home definitions are already `parametric`.
45+They will forward context to their includes functions.
46+47+However for new aspects you define manually, they are not parametric unless you say so.
48+If you get the following error:
49+50+```error
51+error: function 'anonymous lambda' called without required argument 'user'
52+```
53+54+It likely means you are trying to forward context from an aspect that is not `parametric`.
55+See also [#182](https://github.com/vic/den/discussions/182)
56+</Aside>
57+5859## den.lib.parametric
60···196197## take.exactly and take.atLeast
198199+For individual functions (not whole aspects), use [`den.lib.take`](/reference/lib/#denlibtake):
200201202```nix
···56import { Aside } from '@astrojs/starlight/components';
78-<Aside type="tip">Source: [`modules/aspects/provides/`](https://github.com/vic/den/tree/main/modules/aspects/provides) · Reference: [Batteries](/reference/batteries/)</Aside>
00910## What Are Batteries?
11···109```
110111This is how Home-Manager integration is implemented internally.
112-113-## Writing Your Own
114-115-Any aspect can serve as a battery. Publish it in a namespace for others:
116-117-```nix
118-{ den, ... }: {
119- den.provides.my-battery = den.lib.parametric {
120- includes = [
121- ({ host, ... }: { nixos.my-setting = host.name; })
122- ];
123- };
124-}
125-```
···56import { Aside } from '@astrojs/starlight/components';
78+<Aside title="Use the Source, Luke" icon="github">
9+[`modules/aspects/provides/`](https://github.com/vic/den/tree/main/modules/aspects/provides) · Reference: [Batteries](/reference/batteries/)
10+</Aside>
1112## What Are Batteries?
13···111```
112113This is how Home-Manager integration is implemented internally.
00000000000000
-95
docs/src/content/docs/guides/bidirectional.md
···1----
2-title: Bidirectional Dependencies
3-description: Hosts configure users and users configure hosts — automatically.
4----
5-6-## Host → User Configuration
7-8-A host aspect's `homeManager` settings apply to **all users** on that host:
9-10-```nix
11-den.aspects.igloo.homeManager.programs.direnv.enable = true;
12-```
13-14-If `igloo` has users `tux` and `pingu`, both get direnv enabled.
15-16-This also works with static includes:
17-18-```nix
19-den.aspects.igloo.includes = [
20- { homeManager.programs.direnv.enable = true; }
21-];
22-```
23-24-And with parametric includes that receive context:
25-26-```nix
27-den.aspects.igloo.includes = [
28- ({ host, user }: {
29- homeManager.programs.direnv.enable = true;
30- })
31-];
32-```
33-34-## User → Host Configuration
35-36-A user aspect's `nixos` or `darwin` settings apply to **every host** with that user:
37-38-```nix
39-den.aspects.tux.nixos.programs.fish.enable = true;
40-```
41-42-If `tux` is on both `igloo` and `iceberg`, both hosts get fish enabled.
43-44-User includes work the same way:
45-46-```nix
47-den.aspects.tux.includes = [
48- ({ host, ... }: {
49- nixos.users.users.tux.description = "Tux on ${host.name}";
50- })
51-];
52-```
53-54-## Context-Conditional Bidirectional
55-56-Combine host and user context for precise control:
57-58-```nix
59-let
60- git-on-linux = { user, host, ... }:
61- if !lib.hasSuffix "darwin" host.system
62- then { homeManager.programs.git.enable = true; }
63- else { };
64-in {
65- den.aspects.tux.includes = [ git-on-linux ];
66-}
67-```
68-69-User `tux` gets git only on Linux hosts, not on Darwin.
70-71-## How It Works
72-73-```mermaid
74-graph TD
75- HA["den.aspects.igloo (host)"] -->|"homeManager class"| U1["tux Home-Manager"]
76- HA -->|"homeManager class"| U2["pingu Home-Manager"]
77- UA["den.aspects.tux (user)"] -->|"nixos class"| H1["igloo NixOS"]
78- UA -->|"nixos class"| H2["iceberg NixOS"]
79-```
80-81-Den’s [context system](/explanation/context-system/) handles the routing:
82-- Host aspect configs flow to all users on that host
83-- User aspect configs flow to all hosts with that user
84-- Context functions get called with the specific `{ host, user }` pair
85-86-## The `den.default` Backbone
87-88-Settings in [`den.default`](/explanation/context-pipeline/#dendefault-is-an-alias) apply to **everything** — all hosts and all users:
89-90-```nix
91-den.default.homeManager.home.stateVersion = "25.11";
92-den.default.includes = [
93- ({ host, ... }: { ${host.class}.networking.hostName = host.name; })
94-];
95-```
···56import { Aside } from '@astrojs/starlight/components';
78-<Aside type="tip">Source: [`modules/aspects/provides/forward.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/forward.nix) · [CI tests](https://github.com/vic/den/blob/main/templates/ci/modules/forward.nix)</Aside>
910## What Are Custom Classes?
11···2021## Example: Custom Class to NixOS
2223-Forward a `custom` class into NixOS top-level:
2425```nix
26{ den, lib, ... }:
27let
28- forwarded = { class, aspect-chain }:
29 den._.forward {
30- each = lib.singleton class;
31- fromClass = _: "custom";
32- intoClass = _: "nixos";
33- intoPath = _: [ ];
34- fromAspect = _: lib.head aspect-chain;
35 };
36in {
37- den.aspects.igloo = {
38- includes = [ forwarded ];
39- custom.networking.hostName = "from-custom-class";
40- };
041}
42```
4344-## Example: Forward into a Subpath
45-46-Insert configs into a nested submodule:
47-48-```nix
49-den.aspects.igloo = {
50- includes = [ forwarded ];
51- nixos.imports = [
52- { options.fwd-box = lib.mkOption {
53- type = lib.types.submoduleWith { modules = [ myModule ]; };
54- }; }
55- ];
56- src.items = [ "from-src-class" ];
57-};
58-```
59-60-With `intoPath = _: [ "fwd-box" ]`, the `src` class configs merge into
61-`nixos.fwd-box`.
6263## How Home-Manager Uses Forward
64
···56import { Aside } from '@astrojs/starlight/components';
78+<Aside title="Use the Source, Luke" icon="github">[`modules/aspects/provides/forward.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/forward.nix) · [CI tests](https://github.com/vic/den/blob/main/templates/ci/modules/forward.nix)</Aside>
910## What Are Custom Classes?
11···2021## Example: Custom Class to NixOS
2223+Forward a new `hjem` class into NixOS top-level:
2425```nix
26{ den, lib, ... }:
27let
28+ hjem = { host }: { class, aspect-chain }:
29 den._.forward {
30+ each = host.users;
31+ fromClass = user: "hjem";
32+ intoClass = user: class;
33+ intoPath = user: [ "hjem" "users" user.userName ]
34+ fromAspect = user: den.aspects.${user.aspect};
35 };
36in {
37+ # host forwards user hjem modules into nixos level.
38+ den.aspects.igloo = { includes = [ hjem ]; };
39+40+ # user aspect has hjem class
41+ den.aspects.tux.hjem = { ... };
42}
43```
440000000000000000004546## How Home-Manager Uses Forward
47
+4-11
docs/src/content/docs/guides/debug.md
···25 });
26```
2728-## Trace Context Keys
29-30-See which contexts are being applied:
31-32-```nix
33-den.default.includes = [
34- (context: builtins.trace (builtins.attrNames context) { })
35-];
36-```
3738## REPL Inspection
39···8990## Common Issues
9192-**Duplicate values in lists**: Your function matches too many contexts.
93-Use `den.lib.take.exactly` to restrict matching:
009495```nix
96den.lib.take.exactly ({ host }: { nixos.x = 1; })
···25 });
26```
270000000002829## REPL Inspection
30···8081## Common Issues
8283+**Duplicate values in lists**: Den automatically deduplicates owned and
84+static configs from `den.default`, but parametric functions in
85+`den.default.includes` still run at every context stage. Use
86+`den.lib.take.exactly` to restrict matching:
8788```nix
89den.lib.take.exactly ({ host }: { nixos.x = 1; })
···56import { Aside } from '@astrojs/starlight/components';
78-<Aside type="tip">Source: [`nix/namespace.nix`](https://github.com/vic/den/blob/main/nix/namespace.nix)</Aside>
910## What Are Namespaces?
1112Namespaces let you organize aspects into named collections that can be
13shared across flakes and consumed by others.
140000015## Define a Local Namespace
1617```nix
···21}
22```
23000024The second argument controls output:
25- `false` — local only, not exposed as flake output
26- `true` — exposed at `flake.denful.ns`
···2829## Consume Remote Namespaces
3031-Import aspects from another flake's `denful` output:
3233```nix
34{ inputs, ... }: {
···40}
41```
4243-Multiple sources are merged. Local definitions override remote ones.
4445## Nested Provides in Namespaces
46···92angle brackets:
9394```nix
95-{ __findFile, ns, ... }: {
96 _module.args.__findFile = den.lib.__findFile;
097 den.aspects.igloo.includes = [ <ns/tools> ];
98}
99```
100101## Real-World: denful
102103-[denful](https://github.com/vic/denful) is a community aspect distribution
104built on Den namespaces — a lazyvim-like approach to Nix configurations.
···56import { Aside } from '@astrojs/starlight/components';
78+<Aside title="Use the Source, Luke" icon="github">[`nix/namespace.nix`](https://github.com/vic/den/blob/main/nix/namespace.nix)</Aside>
910## What Are Namespaces?
1112Namespaces let you organize aspects into named collections that can be
13shared across flakes and consumed by others.
1415+This is unlike your `den.aspects.<name>` namespace which is private,
16+because you wont likely want to share your user/host configurations.
17+18+Namespaces are for re-usable aspects you are willing to share with others.
19+20## Define a Local Namespace
2122```nix
···26}
27```
2829+The first argument is the aspect namespace name.
30+It will be provided as module argument: `{ lib, ns, ... }`
31+so you can access aspects from the namespace.
32+33The second argument controls output:
34- `false` — local only, not exposed as flake output
35- `true` — exposed at `flake.denful.ns`
···3738## Consume Remote Namespaces
3940+Import aspects from another flake's `flake.denful.provider` output:
4142```nix
43{ inputs, ... }: {
···49}
50```
5152+Multiple sources are merged by the module system.
5354## Nested Provides in Namespaces
55···101angle brackets:
102103```nix
104+{ __findFile, ... }: {
105 _module.args.__findFile = den.lib.__findFile;
106+107 den.aspects.igloo.includes = [ <ns/tools> ];
108}
109```
110111## Real-World: denful
112113+[denful](https://github.com/vic/denful) is a (WIP) community aspect distribution
114built on Den namespaces — a lazyvim-like approach to Nix configurations.
-91
docs/src/content/docs/guides/no-flakes.md
···1----
2-title: Use Without Flakes
3-description: Den works with stable Nix, npins, and without flake-parts.
4----
5-6-## Den is Flake-Agnostic
7-8-Den does **not** require Nix flakes. It works with:
9-10-- Nix flakes + flake-parts (most common)
11-- Nix flakes without flake-parts
12-- No flakes at all (stable Nix + npins or fetchTarball)
13-14-## Without Flakes
15-16-Use `flake-compat` to evaluate Den from a non-flake setup:
17-18-```nix
19-# default.nix
20-let
21- flake = import (fetchTarball {
22- url = "https://github.com/edolstra/flake-compat/archive/...tar.gz";
23- }) { src = ./.; };
24-in
25- flake.outputs
26-```
27-28-See the [`noflake` template](https://github.com/vic/den/tree/main/templates/noflake)
29-for a complete working example with npins.
30-31-## Without Flake-Parts
32-33-Den provides its own minimal `flake` option when flake-parts is not present.
34-Simply import `inputs.den.flakeModule`:
35-36-```nix
37-let
38- denCfg = (lib.evalModules {
39- modules = [
40- (import-tree ./modules)
41- inputs.den.flakeModule
42- ];
43- specialArgs = { inherit inputs; };
44- }).config;
45-in {
46- nixosConfigurations.igloo =
47- denCfg.flake.nixosConfigurations.igloo;
48-}
49-```
50-51-## With Flake-Parts
52-53-The standard setup — Den integrates seamlessly:
54-55-```nix
56-{
57- outputs = inputs:
58- inputs.flake-parts.lib.mkFlake { inherit inputs; }
59- (inputs.import-tree ./modules);
60-61- inputs = {
62- den.url = "github:vic/den";
63- flake-aspects.url = "github:vic/flake-aspects";
64- import-tree.url = "github:vic/import-tree";
65- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
66- flake-parts.url = "github:hercules-ci/flake-parts";
67- };
68-}
69-```
70-71-## The Dendritic Module
72-73-For flake-parts users who want automatic Den + flake-aspects setup:
74-75-```nix
76-imports = [ inputs.den.flakeModules.dendritic ];
77-```
78-79-This auto-configures `flake-file` inputs for `den` and `flake-aspects`.
80-81-## Minimal Dependencies
82-83-Den's only hard dependency is `flake-aspects`. Everything else is optional:
84-85-| Dependency | Required? | Purpose |
86-|-----------|-----------|---------|
87-| `flake-aspects` | **Yes** | [Aspect composition](https://github.com/vic/flake-aspects) |
88-| `import-tree` | Optional | [Auto-import module directories](https://github.com/vic/import-tree) |
89-| `flake-parts` | Optional | Flake structuring |
90-| `nixpkgs` | Optional | Only if building NixOS/HM configs |
91-| `home-manager` | Optional | Only if using HM integration |
···3description: The motivation and fundamental idea behind Den.
4---
5067-> This section is about how and why Den came to be.
8-> You can safely skip this unless you are interested in how Den was shaped.
9->
10-> However you might need to already be familiar with what [Dendritic](https://github.com/mightyiam/dendritic) means
11-> you might also want to read the [FAQ](https://github.com/Doc-Steve/dendritic-design-with-flake-parts/wiki/FAQ)
001213Den is basically about a single idea:
1415-> Being able to share re-usable (parametric) cross-class Nix configurations.
001617Den was born not to be yet-another zillionth way to wire-up configurations.
18···32At that time [unify](https://codeberg.org/quasigod/unify) - the first dendritic framework I know of- entered my radar, and I really loved it! It was missing a couple of features that I needed in my existing infra, but I loved the `<aspect>.<class>` transposition that its author invented, and the cleaness of the code at the author's infra was a testament to how good-looking and organized a Dendritic setup could look. I certainly wanted that for mine.
3334However, before adopting `unify`, I realized that my re-usability problem was not about syntax, not about how things are loaded from filesystem nor how they are wired at the flake level.
35-36-> The problem was more about feature composability.
3738Being an fan of functional programming, the most composable things I know are functions.
39···59An important point is that parametric aspect's context **are not** _module.args, they do not depend on config. This allows using them for [conditional](https://github.com/vic/den/blob/main/templates/ci/modules/features/conditional-config.nix) imports.
606162-> Some [people](https://codeberg.org/FrdrCkII/nixconf/src/branch/main/modules/aspects.prt.nix) have found `flake-aspects` enough to implement their own Dendritic setups, or even for mixing with other non-dendritic infra.
006364## Den builds upon flake-aspects
65···7778 `den.default.includes = [ (den._.unfree ["vscode"]) ]`
7980-- How a `Host` can affect its `User`s configurations, and [viceversa](/guides/bidirectional).
8182- How to [mixin](/guides/namespaces) aspects from remote sources and enhance locally defined ones.
83···9899Den is more about giving to others (creating useful Nix software/configurations), not just about make things work locally.
100101-> I've been working over a year on these [dendritic libs](https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries), but time is our most scarce resource. If you like my work, consider [sponsoring](https://github.com/sponsors/vic)
00102103104## What's Next?
105106-Ready to try it? Head to the [Getting Started](/tutorials/getting-started/) tutorial.
107108Want to understand the architecture? Read about the [Core Principles](/explanation/core-principles/).
···3description: The motivation and fundamental idea behind Den.
4---
56+import { Aside } from '@astrojs/starlight/components';
78+<Aside>
9+This section is about how and why Den came to be.
10+You can safely skip this unless you are interested in how Den was shaped.
11+12+However you might need to already be familiar with what [Dendritic](https://github.com/mightyiam/dendritic) means
13+you might also want to read the [FAQ](https://github.com/Doc-Steve/dendritic-design-with-flake-parts/wiki/FAQ)
14+</Aside>
1516Den is basically about a single idea:
1718+<Aside type="tip" icon="star" title="Motivation">
19+Den was built for being able to share re-usable (parametric) cross-class Nix configurations.
20+</Aside>
2122Den was born not to be yet-another zillionth way to wire-up configurations.
23···37At that time [unify](https://codeberg.org/quasigod/unify) - the first dendritic framework I know of- entered my radar, and I really loved it! It was missing a couple of features that I needed in my existing infra, but I loved the `<aspect>.<class>` transposition that its author invented, and the cleaness of the code at the author's infra was a testament to how good-looking and organized a Dendritic setup could look. I certainly wanted that for mine.
3839However, before adopting `unify`, I realized that my re-usability problem was not about syntax, not about how things are loaded from filesystem nor how they are wired at the flake level.
40+**The problem was more about feature composability.**
04142Being an fan of functional programming, the most composable things I know are functions.
43···63An important point is that parametric aspect's context **are not** _module.args, they do not depend on config. This allows using them for [conditional](https://github.com/vic/den/blob/main/templates/ci/modules/features/conditional-config.nix) imports.
646566+<Aside>
67+Some [people](https://codeberg.org/FrdrCkII/nixconf/src/branch/main/modules/aspects.prt.nix) have found [`flake-aspects`](https://github.com/vic/flake-aspects) enough to implement their own Dendritic setups, or even for mixing with other non-dendritic infra.
68+</Aside>
6970## Den builds upon flake-aspects
71···8384 `den.default.includes = [ (den._.unfree ["vscode"]) ]`
8586+- How a `Host` can affect its `User`s configurations, and [viceversa](/guides/configure-aspects).
8788- How to [mixin](/guides/namespaces) aspects from remote sources and enhance locally defined ones.
89···104105Den is more about giving to others (creating useful Nix software/configurations), not just about make things work locally.
106107+<Aside type="tip" title="Support Den development" icon="heart">
108+I've been working over a year on these [dendritic libs](https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries), but time is our most scarce resource. If you like my work, consider [sponsoring](https://github.com/sponsors/vic)
109+</Aside>
110111112## What's Next?
113114+Ready to try it? Head to the [Templates overview](/tutorials/overview/) to pick a starting point.
115116Want to understand the architecture? Read about the [Core Principles](/explanation/core-principles/).
-127
docs/src/content/docs/reference/aspects.md
···1----
2-title: den.aspects Reference
3-description: Aspect structure, resolution, and configuration.
4----
5-6-import { Aside } from '@astrojs/starlight/components';
7-8-<Aside type="tip">Source: [`modules/aspects.nix`](https://github.com/vic/den/blob/main/modules/aspects.nix) · [`modules/aspects/definition.nix`](https://github.com/vic/den/blob/main/modules/aspects/definition.nix) · [`modules/aspects/provides.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides.nix)</Aside>
9-10-## Aspect Attributes
11-12-Every aspect is an attribute set with these recognized keys:
13-14-| Attribute | Type | Description |
15-|-----------|------|-------------|
16-| `nixos` | module | NixOS configuration module |
17-| `darwin` | module | nix-darwin configuration module |
18-| `homeManager` | module | Home-Manager configuration module |
19-| `<class>` | module | Any custom Nix class |
20-| `includes` | list | Dependencies on other aspects or functions |
21-| `provides` | attrset | Nested sub-aspects |
22-| `_` | alias | Shorthand for `provides` |
23-| `description` | str | Human-readable description |
24-| `__functor` | function | Context-aware behavior |
25-26-## Automatic Aspect Creation
27-28-Den creates an aspect for each host, user, and home you declare:
29-30-```nix
31-den.hosts.x86_64-linux.igloo.users.tux = { };
32-# Creates: den.aspects.igloo and den.aspects.tux
33-```
34-35-Aspects are created with `parametric { <class> = {}; }` functor
36-matching the entity's class.
37-38-## Aspect Resolution
39-40-To extract a class module from an aspect:
41-42-```nix
43-module = aspect.resolve { class = "nixos"; aspect-chain = []; };
44-```
45-46-Resolution collects the specified class from the aspect and all
47-transitive includes into a single merged Nix module.
48-49-## den.aspects (option)
50-51-```nix
52-options.den.aspects = lib.mkOption {
53- type = aspectsType;
54- default = { };
55-};
56-```
57-58-Contributions from any module are merged:
59-60-```nix
61-# file1.nix
62-den.aspects.igloo.nixos.networking.hostName = "igloo";
63-64-# file2.nix
65-den.aspects.igloo.homeManager.programs.vim.enable = true;
66-```
67-68-## den.provides (batteries)
69-70-```nix
71-options.den.provides = lib.mkOption {
72- type = lib.types.submodule {
73- freeformType = lib.types.attrsOf providerType;
74- };
75-};
76-```
77-78-Access via `den._.name` or `den.provides.name`.
79-See [Batteries Reference](/reference/batteries/) for all built-in aspects.
80-81-## den.default
82-83-The global dispatcher aspect:
84-85-```nix
86-den.default = den.lib.parametric.atLeast { };
87-```
88-89-All hosts, users, and homes include `den.default`. Set global
90-configs and shared [parametric](/explanation/parametric/) includes here.
91-92-Aliased from `den.ctx.default`.
93-94-## Including Aspects
95-96-```nix
97-den.aspects.igloo.includes = [
98- # another aspect
99- den.aspects.gaming
100-101- # nested provides
102- den.aspects.tools._.editors
103-104- # static config
105- { homeManager.programs.direnv.enable = true; }
106-107- # context function
108- ({ host, ... }: { nixos.time.timeZone = "UTC"; })
109-110- # battery
111- den._.define-user
112-113- # battery with args
114- (den._.user-shell "fish")
115- (den._.unfree [ "discord" ])
116-];
117-```
118-119-## Aspect as Module Argument
120-121-`den` is available as a module argument in all Den modules:
122-123-```nix
124-{ den, ... }: {
125- den.aspects.foo = den.lib.parametric { ... };
126-}
127-```
···1+---
2+title: den.aspects Reference
3+description: Aspect structure, resolution, and configuration.
4+---
5+6+import { Aside } from '@astrojs/starlight/components';
7+8+<Aside title="Use the Source, Luke" icon="github">[`modules/aspects.nix`](https://github.com/vic/den/blob/main/modules/aspects.nix) · [`modules/aspects/definition.nix`](https://github.com/vic/den/blob/main/modules/aspects/definition.nix) · [`modules/aspects/provides.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides.nix) · Tests: [`user-host-bidirectional-config.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/user-host-bidirectional-config.nix) · [`top-level-parametric.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/top-level-parametric.nix)</Aside>
9+10+## Aspect Attributes
11+12+Every aspect is an attribute set with these recognized keys:
13+14+| Attribute | Type | Description |
15+|-----------|------|-------------|
16+| `description` | str | Human-readable description |
17+| `__functor` | function | Context-aware behavior |
18+| `includes` | list | Dependencies on other aspects or functions |
19+| `provides` | attrset | Nested sub-aspects |
20+| `_` | alias | Shorthand for `provides` |
21+| `<class>` | module | Any Nix class, like the following |
22+| `nixos` | module | NixOS configuration module |
23+| `darwin` | module | nix-darwin configuration module |
24+| `homeManager` | module | Home-Manager configuration module |
25+26+## Automatic Aspect Creation
27+28+Den creates an aspect for each host, user, and home you declare:
29+30+```nix
31+den.hosts.x86_64-linux.igloo.users.tux = { };
32+33+# will create:
34+35+den.aspects.igloo = parametric { };
36+den.aspects.tux = parametric { };
37+```
38+39+## Aspect Resolution
40+41+To extract a class module from an aspect use the flake-aspects API:
42+43+```nix
44+module = aspect.resolve { class = "nixos"; aspect-chain = []; };
45+```
46+47+Resolution collects the specified class from the aspect and all
48+transitive includes into a single merged Nix module.
49+50+## den.aspects (namespace)
51+52+This namespace is where Den creates aspects for your host/user/home.
53+54+You can also use this namespace to create your own aspects.
55+Aspects in this namespace are not shared outside the flake,
56+for that use `den.namespace`.
57+58+Contributions from any module are merged:
59+60+```nix
61+# file1.nix
62+den.aspects.igloo.nixos.networking.hostName = "igloo";
63+64+# file2.nix
65+den.aspects.igloo.homeManager.programs.vim.enable = true;
66+```
67+68+## den.provides (namespace)
69+70+Den batteries-included re-usable aspects.
71+72+Access via `den._.name` or `den.provides.name`.
73+74+See [Batteries Reference](/reference/batteries/) for all built-in aspects.
···56import { Aside } from '@astrojs/starlight/components';
78-<Aside type="tip">Source: [`modules/aspects/provides/`](https://github.com/vic/den/tree/main/modules/aspects/provides)</Aside>
910## Overview
11···15## define-user
1617Defines a user at OS and Home-Manager levels.
18-([source](https://github.com/vic/den/blob/main/modules/aspects/provides/define-user.nix))
1920```nix
21den.default.includes = [ den._.define-user ];
···31## primary-user
3233Makes a user an administrator.
34-([source](https://github.com/vic/den/blob/main/modules/aspects/provides/primary-user.nix))
3536```nix
37den.aspects.vic.includes = [ den._.primary-user ];
···47## user-shell
4849Sets default shell at OS and HM levels.
50-([source](https://github.com/vic/den/blob/main/modules/aspects/provides/user-shell.nix))
5152```nix
53den.aspects.vic.includes = [ (den._.user-shell "fish") ];
···63## unfree
6465Enables unfree packages by name.
66-([source](https://github.com/vic/den/blob/main/modules/aspects/provides/unfree/unfree-predicate-builder.nix) · [predicate](https://github.com/vic/den/blob/main/modules/aspects/provides/unfree/unfree.nix))
6768```nix
69den.aspects.laptop.includes = [ (den._.unfree [ "discord" ]) ];
···82## tty-autologin
8384Automatic tty login.
08586```nix
87den.aspects.laptop.includes = [ (den._.tty-autologin "root") ];
···94## import-tree
9596Auto-imports non-dendritic Nix files by class directory.
97-([source](https://github.com/vic/den/blob/main/modules/aspects/provides/import-tree.nix))
9899```nix
100den.aspects.laptop.includes = [ (den._.import-tree ./path) ];
···115## inputs' (flake-parts)
116117Provides per-system `inputs'` as a module argument.
0118119```nix
120den.default.includes = [ den._.inputs' ];
···125## self' (flake-parts)
126127Provides per-system `self'` as a module argument.
0128129```nix
130den.default.includes = [ den._.self' ];
···135## forward
136137Creates custom Nix classes by forwarding configs between classes.
138-([source](https://github.com/vic/den/blob/main/modules/aspects/provides/forward.nix) · [usage guide](/guides/custom-classes/))
139140```nix
141den._.forward {
···154HM integration is handled by
155`den.ctx.hm-host` and `den.ctx.hm-user` context types, which are
156activated automatically when hosts have HM users.
0157158All `homeManager` class settings are forwarded to os-level `home-manager.users.<userName>`.
159160## user
161162The `user` class is automatically handled by Den and forwards to os-level `users.users.<userName>`
0163164You can write:
165
···56import { Aside } from '@astrojs/starlight/components';
78+<Aside title="Use the Source, Luke" icon="github">[`modules/aspects/provides/`](https://github.com/vic/den/tree/main/modules/aspects/provides) · Tests: [`batteries/`](https://github.com/vic/den/tree/main/templates/ci/modules/features/batteries)</Aside>
910## Overview
11···15## define-user
1617Defines a user at OS and Home-Manager levels.
18+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/define-user.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/define-user.nix))
1920```nix
21den.default.includes = [ den._.define-user ];
···31## primary-user
3233Makes a user an administrator.
34+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/primary-user.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/primary-user.nix))
3536```nix
37den.aspects.vic.includes = [ den._.primary-user ];
···47## user-shell
4849Sets default shell at OS and HM levels.
50+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/user-shell.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/user-shell.nix))
5152```nix
53den.aspects.vic.includes = [ (den._.user-shell "fish") ];
···63## unfree
6465Enables unfree packages by name.
66+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/unfree/unfree-predicate-builder.nix) · [predicate](https://github.com/vic/den/blob/main/modules/aspects/provides/unfree/unfree.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/unfree.nix))
6768```nix
69den.aspects.laptop.includes = [ (den._.unfree [ "discord" ]) ];
···82## tty-autologin
8384Automatic tty login.
85+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/tty-autologin.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/tty-autologin.nix))
8687```nix
88den.aspects.laptop.includes = [ (den._.tty-autologin "root") ];
···95## import-tree
9697Auto-imports non-dendritic Nix files by class directory.
98+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/import-tree.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/import-tree.nix))
99100```nix
101den.aspects.laptop.includes = [ (den._.import-tree ./path) ];
···116## inputs' (flake-parts)
117118Provides per-system `inputs'` as a module argument.
119+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/flake-parts/inputs.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/flake-parts.nix))
120121```nix
122den.default.includes = [ den._.inputs' ];
···127## self' (flake-parts)
128129Provides per-system `self'` as a module argument.
130+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/flake-parts/self.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/flake-parts.nix))
131132```nix
133den.default.includes = [ den._.self' ];
···138## forward
139140Creates custom Nix classes by forwarding configs between classes.
141+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/forward.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/forward.nix) · [usage guide](/guides/custom-classes/))
142143```nix
144den._.forward {
···157HM integration is handled by
158`den.ctx.hm-host` and `den.ctx.hm-user` context types, which are
159activated automatically when hosts have HM users.
160+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/home-manager/))
161162All `homeManager` class settings are forwarded to os-level `home-manager.users.<userName>`.
163164## user
165166The `user` class is automatically handled by Den and forwards to os-level `users.users.<userName>`
167+([source](https://github.com/vic/den/blob/main/modules/aspects/provides/os-user.nix))
168169You can write:
170
-180
docs/src/content/docs/reference/ctx.md
···1----
2-title: den.ctx Reference
3-description: Context type definitions and their built-in implementations.
4----
5-6-import { Aside } from '@astrojs/starlight/components';
7-8-<Aside type="tip">Source: [`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) · [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) · [`modules/aspects/defaults.nix`](https://github.com/vic/den/blob/main/modules/aspects/defaults.nix)</Aside>
9-10-## Context Type Schema
11-12-Each `den.ctx.<name>` is a submodule with these options:
13-14-| Option | Type | Description |
15-|--------|------|-------------|
16-| `desc` | `str` | Human-readable description |
17-| `conf` | `ctx → aspect` | Locates the aspect for this context |
18-| `into` | `attrset of (ctx → list)` | Transformations to other contexts |
19-| `includes` | `list of aspect` | Parametric aspects activated for this context |
20-21-Context types are also functors — callable as functions:
22-23-```nix
24-aspect = den.ctx.host { host = den.hosts.x86_64-linux.igloo; };
25-```
26-27-## ctxApply (internal)
28-29-When a context type is applied, `ctxApply` executes:
30-31-```nix
32-ctxApply = self: ctx: {
33- includes = lib.flatten [
34- (parametric.fixedTo ctx (cleanCtx self)) # owned configs
35- (self.conf ctx) # located aspect
36- (mapAttrsToList (name: into: # transformations
37- map den.ctx.${name} (into ctx)
38- ) self.into)
39- ];
40-};
41-```
42-43-## Transformation Types
44-45-All `into` transformations have the type `source → [ target ]`:
46-47-- **Fan-out**: `{ host }: map (u: { inherit host user; }) users` — one host produces N contexts
48-- **Conditional**: `{ host }: lib.optional (test host) { inherit host; }` — zero or one
49-- **Pass-through**: `lib.singleton` — forward the same data as-is
50-51-An empty list means the target context is not created. This enables
52-conditional activation like HM detection without any explicit `if` logic
53-in the pipeline.
54-55-## Contexts as Cutting-Points
56-57-Context types have their own owned configs and `includes`, making them
58-aspect-like cutting-points in the pipeline:
59-60-```nix
61-den.ctx.hm-host.nixos.home-manager.useGlobalPkgs = true;
62-den.ctx.hm-host.includes = [
63- ({ host, ... }: { nixos.home-manager.backupFileExtension = "bak"; })
64-];
65-```
66-67-These only activate for validated HM hosts — more precise than
68-`den.default.includes` which runs at every pipeline stage.
69-70-## Extending Context Flow
71-72-Add transformations to existing context types from any module:
73-74-```nix
75-den.ctx.hm-host.into.foo = { host }: [ { foo = host.name; } ];
76-den.ctx.foo.conf = { foo }: { funny.names = [ foo ]; };
77-```
78-79-The module system merges these definitions. You can also override a
80-host's `mainModule` to use a completely custom context flow.
81-82-## Built-in Context Types
83-84-### den.ctx.host
85-86-| Field | Value |
87-|-------|-------|
88-| `desc` | OS |
89-| `conf` | `{ host }:` fixedTo host aspect |
90-| `into.default` | `lib.singleton` (pass-through) |
91-| `into.user` | Enumerate `host.users` |
92-| `into.hm-host` | Detect HM support (from `hm-detect.nix`) |
93-94-### den.ctx.user
95-96-| Field | Value |
97-|-------|-------|
98-| `desc` | OS user |
99-| `conf` | `{ host, user }:` includes user + host aspects |
100-| `into.default` | `lib.singleton` (pass-through) |
101-102-### den.ctx.default
103-104-| Field | Value |
105-|-------|-------|
106-| `conf` | `_: { }` (empty — just a dispatcher) |
107-108-Aliased as `den.default` via `lib.mkAliasOptionModule`.
109-Writing `den.default.foo` is identical to `den.ctx.default.foo`.
110-111-Every context type transforms into `default`, so `den.default.includes`
112-functions run at every pipeline stage. Use `take.exactly` to restrict
113-matching if you see duplicate values.
114-115-### den.ctx.home
116-117-| Field | Value |
118-|-------|-------|
119-| `desc` | Standalone Home-Manager config |
120-| `conf` | `{ home }:` fixedTo home aspect |
121-| `into.default` | `lib.singleton` |
122-123-### den.ctx.hm-host
124-125-| Field | Value |
126-|-------|-------|
127-| `desc` | Host with HM-supported OS and HM users |
128-| `conf` | `{ host }:` imports HM NixOS/Darwin module |
129-| `into.hm-user` | Enumerate HM-class users |
130-131-**Detection criteria** (all must be true):
132-1. Host class is `nixos` or `darwin`
133-2. At least one user has `class = "homeManager"`
134-3. `inputs.home-manager` exists, or host has a custom `hm-module` attribute
135-136-If detection fails, the HM pipeline is skipped entirely.
137-138-### den.ctx.hm-user
139-140-| Field | Value |
141-|-------|-------|
142-| `desc` | Internal — forwards HM class to host |
143-| `conf` | `{ host, user }:` forward homeManager into host |
144-145-### den.ctx.hm-internal-user (internal)
146-147-An internal context type used by `hm-user`. It combines:
148-- The user context (`den.ctx.user { host, user }`)
149-- Owned configs from the host aspect
150-- Static includes from the host aspect
151-- Parametric includes from the host aspect matching `{ host, user }`
152-153-This ensures that when the `homeManager` class is forwarded into
154-`home-manager.users.<name>`, it receives contributions from both
155-the user's aspect and the host's aspect.
156-157-## Defining Custom Context Types
158-159-Context types are independent of NixOS. Use them for any domain:
160-161-```nix
162-den.ctx.myctx = {
163- desc = "{x, y} context";
164- conf = { x, y }: { funny.names = [ x y ]; };
165- includes = [
166- ({ x, ... }: { funny.names = [ "include-${x}" ]; })
167- ];
168- into = {
169- other = { x, y }: [{ z = x + y; }];
170- };
171-};
172-```
173-174-Owned attributes (anything not `desc`, `conf`, `into`, `includes`)
175-are included when the context is applied.
176-177-Custom context flows can be designed and tested in isolation — Den's
178-CI tests use a `funny.names` class that has nothing to do with NixOS
179-to verify the context mechanics independently.See the [CI test suite](https://github.com/vic/den/tree/main/templates/ci/modules)
180-for examples.
···1+---
2+title: den.ctx Reference
3+description: Context type definitions and their built-in implementations.
4+---
5+6+import { Aside } from '@astrojs/starlight/components';
7+8+<Aside title="Use the Source, Luke" icon="github">[`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) · [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) · [`modules/aspects/defaults.nix`](https://github.com/vic/den/blob/main/modules/aspects/defaults.nix) · Tests: [`context/`](https://github.com/vic/den/tree/main/templates/ci/modules/features/context) · [`host-propagation.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/host-propagation.nix)</Aside>
9+10+## Context Type Schema
11+12+Each `den.ctx.<name>` is an [aspect submodule](/explanation/aspects/) extended
13+with context transformations. It inherits all options from `flake-aspects`'s
14+`aspect`-type and adds `into`, and a couple of conventions:
15+16+| Option | Type | Description |
17+|--------|------|-------------|
18+| `description` | `str` | Human-readable description |
19+| `provides.${name}` | `ctx → aspect` | Self-named provider: locates the aspect for this context |
20+| `provides.${target}` | `ctx → aspect` | Cross-provider: source's contribution to target contexts |
21+| `into` | `attrset of (ctx → list)` | Transformations to other contexts |
22+| `includes` | `list of aspect` | Parametric aspects activated for this context |
23+24+Context types are also functors — callable as functions:
25+26+```nix
27+aspect = den.ctx.host { host = den.hosts.x86_64-linux.igloo; };
28+```
29+30+## ctxApply (internal)
31+32+When a context type is applied, `ctxApply` walks the full `into` graph
33+and deduplicates includes using a seen-set:
34+35+```nix
36+ctxApply = ctxName: _self: ctx:
37+ let
38+ pairs = collectPairs null den.ctx.${ctxName} ctx;
39+ in
40+ { includes = dedupIncludes pairs; };
41+```
42+43+**collectPairs** recursively walks `into` transformations, producing
44+`(source, ctxDef, ctx)` triples for every reachable context type.
45+The `source` tracks which context type produced the transformation.
46+47+**dedupIncludes** processes triples with a seen-set keyed by context name:
48+49+- **First visit**: `fixedTo ctx (cleanCtx ctxDef)` — dispatches owned, static, and parametric includes — plus self-provider and cross-provider
50+- **Subsequent visits**: `atLeast (cleanCtx ctxDef) ctx` — dispatches only parametric includes — plus self-provider and cross-provider
51+52+For each triple, three providers are called:
53+1. **Self-provider** `ctxDef.provides.${ctxDef.name}` — the target's own aspect lookup
54+2. **Cross-provider** `source.provides.${ctxDef.name}` — the source's contribution to this target (if defined)
55+56+## Transformation Types
57+58+All `into` transformations have the type `source → [ target ]`:
59+60+- **Fan-out**: `{ host }: map (u: { inherit host user; }) users` — one host produces N contexts
61+- **Conditional**: `{ host }: lib.optional (test host) { inherit host; }` — zero or one
62+- **Pass-through**: `lib.singleton` — forward the same data as-is
63+64+An empty list means the target context is not created. This enables
65+conditional activation like HM detection without any explicit `if` logic
66+in the pipeline.
67+68+## Contexts as Cutting-Points
69+70+Context types have their own owned configs and `includes`, making them
71+aspect-like cutting-points in the pipeline:
72+73+```nix
74+den.ctx.hm-host.nixos.home-manager.useGlobalPkgs = true;
75+den.ctx.hm-host.includes = [
76+ ({ host, ... }: { nixos.home-manager.backupFileExtension = "bak"; })
77+];
78+```
79+80+81+## Extending Context Flow
82+83+Add transformations to existing context types from any module:
84+85+```nix
86+den.ctx.hm-host.into.foo = { host }: [ { foo = host.name; } ];
87+den.ctx.foo._.foo = { foo }: { funny.names = [ foo ]; };
88+```
89+90+The module system merges these definitions. You can also override a
91+host's `mainModule` to use a completely custom context flow.
92+93+## Built-in Context Types
94+95+### den.ctx.host
96+97+| Field | Value |
98+|-------|-------|
99+| `description` | OS |
100+| `provides.host` | `{ host }:` fixedTo host aspect |
101+| `provides.user` | `{ host, user }:` host's contribution to user contexts (cross-provider) |
102+| `into.default` | `lib.singleton` (pass-through) |
103+| `into.user` | Enumerate `host.users` |
104+| `into.hm-host` | Detect HM support (from `hm-detect.nix`) |
105+106+### den.ctx.user
107+108+| Field | Value |
109+|-------|-------|
110+| `description` | OS user |
111+| `provides.user` | `{ host, user }:` fixedTo user aspect |
112+| `into.default` | `lib.singleton` (pass-through) |
113+114+### den.ctx.default
115+116+| Field | Value |
117+|-------|-------|
118+| `provides.default` | `_: { }` (empty — just a dispatcher) |
119+120+Aliased as `den.default` via `lib.mkAliasOptionModule`.
121+Writing `den.default.foo` is identical to `den.ctx.default.foo`.
122+123+Host/user/home context types transforms into `default`, so `den.default.includes`
124+functions run at each of their pipeline stages. However, owned and static configs
125+are **automatically deduplicated** — only the first visit gets them.
126+Parametric functions still run at every stage; use `take.exactly` to
127+restrict matching if needed.
128+129+### den.ctx.home
130+131+| Field | Value |
132+|-------|-------|
133+| `description` | Standalone Home-Manager config |
134+| `provides.home` | `{ home }:` fixedTo home aspect |
135+| `into.default` | `lib.singleton` |
136+137+### den.ctx.hm-host
138+139+| Field | Value |
140+|-------|-------|
141+| `description` | Host with HM-supported OS and HM users |
142+| `provides.hm-host` | `{ host }:` imports HM NixOS/Darwin module |
143+| `into.hm-user` | Enumerate HM-class users |
144+145+**Detection criteria** (all must be true):
146+1. Host class is `nixos` or `darwin`
147+2. At least one user has `class = "homeManager"`
148+3. `inputs.home-manager` exists, or host has a custom `hm-module` attribute
149+150+If detection fails, the HM pipeline is skipped entirely.
151+152+### den.ctx.hm-user
153+154+| Field | Value |
155+|-------|-------|
156+| `description` | Internal — forwards HM class to host |
157+| `provides.hm-user` | `{ host, user }:` forward homeManager into host |
158+159+### den.ctx.hm-internal-user (internal)
160+161+An internal context type used by `hm-user`. It combines:
162+- The user context (`den.ctx.user { host, user }`)
163+- Owned configs from the host aspect
164+- Static includes from the host aspect
165+- Parametric includes from the host aspect matching `{ host, user }`
166+167+This ensures that when the `homeManager` class is forwarded into
168+`home-manager.users.<name>`, it receives contributions from both
169+the user's aspect and the host's aspect.
···56import { Aside } from '@astrojs/starlight/components';
78-<Aside type="tip">Source: [`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · [`nix/fn-can-take.nix`](https://github.com/vic/den/blob/main/nix/fn-can-take.nix) · [`nix/den-brackets.nix`](https://github.com/vic/den/blob/main/nix/den-brackets.nix)</Aside>
910## den.lib.parametric
1112-Creates a parametric aspect. Alias for `parametric.withOwn parametric.atLeast`.
1314```nix
15den.lib.parametric { nixos.x = 1; includes = [ f ]; }
···2122Dispatches **only** to functions whose required args are a subset of context:
230024```nix
25F = parametric.atLeast { includes = [ a b ]; };
26```
···28### parametric.exactly
2930Dispatches **only** to functions whose args match context exactly:
003132```nix
33F = parametric.exactly { includes = [ a b ]; };
···3738Replaces context with a fixed attribute set:
3900040```nix
41F = parametric.fixedTo { x = 1; } aspect;
42```
···44### parametric.expands
4546Merges extra attributes into received context:
04748```nix
49F = parametric.expands { extra = 1; } aspect;
···52### parametric.withOwn
5354Combinator: adds owned + statics on top of a dispatch functor:
05556```nix
057F = parametric.withOwn parametric.exactly aspect;
58```
59···6970Wraps function to only be called when context has at least the required args.
710072### take.exactly
7374```nix
···76```
7778Only called when context has exactly these args, no more.
007980### take.unused
81···8990Function signature introspection:
910092```nix
93den.lib.canTake { x = 1; } someFunction
94# => true if someFunction can take at least { x }
···99100## den.lib.aspects
101102-Re-export of `flake-aspects` library. Provides:
103- `aspects.types.aspectsType` — module type for aspect trees
104- `aspects.types.providerType` — type for aspect providers
105- `aspects.forward` — class forwarding implementation
···114115## den.lib.owned
116117-Extracts only owned configs from an aspect (removes `includes`, `__functor`):
118119```nix
120den.lib.owned someAspect # => { nixos = ...; darwin = ...; }
···124125Creates a functor that only resolves static includes from an aspect:
126000127```nix
128-den.lib.statics someAspect { class = "nixos"; aspect-chain = []; }
129```
130131## den.lib.isStatic
···56import { Aside } from '@astrojs/starlight/components';
78+<Aside title="Use the Source, Luke" icon="github">[`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · [`nix/fn-can-take.nix`](https://github.com/vic/den/blob/main/nix/fn-can-take.nix) · [`nix/den-brackets.nix`](https://github.com/vic/den/blob/main/nix/den-brackets.nix) · Tests: [`parametric.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/parametric.nix) · [`angle-brackets.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/angle-brackets.nix)</Aside>
910## den.lib.parametric
1112+Creates a parametric aspect. Alias for `(parametric.withOwn parametric.atLeast)`.
1314```nix
15den.lib.parametric { nixos.x = 1; includes = [ f ]; }
···2122Dispatches **only** to functions whose required args are a subset of context:
2324+**does include owned config, atLeast only forwards context**.
25+26```nix
27F = parametric.atLeast { includes = [ a b ]; };
28```
···30### parametric.exactly
3132Dispatches **only** to functions whose args match context exactly:
33+34+**does include owned config, atLeast only forwards context**.
3536```nix
37F = parametric.exactly { includes = [ a b ]; };
···4142Replaces context with a fixed attribute set:
4344+Ignores any given context at call site, and replaces with a fixed one.
45+Behaves like `parametric`, includes owned + static includes + function includes.
46+47```nix
48F = parametric.fixedTo { x = 1; } aspect;
49```
···51### parametric.expands
5253Merges extra attributes into received context:
54+Behaves like `parametric`, includes owned + static includes + function includes.
5556```nix
57F = parametric.expands { extra = 1; } aspect;
···60### parametric.withOwn
6162Combinator: adds owned + statics on top of a dispatch functor:
63+This is how `parametric` function itself uses `parametric.atLeast`.
6465```nix
66+# an exactly variant of parametric.
67F = parametric.withOwn parametric.exactly aspect;
68```
69···7980Wraps function to only be called when context has at least the required args.
8182+Produces an empty attrset if the context has not atLeast the expected args.
83+84### take.exactly
8586```nix
···88```
8990Only called when context has exactly these args, no more.
91+92+Produces an empty attrset otherwise.
9394### take.unused
95···103104Function signature introspection:
105106+Does not care about values, only about attribute names.
107+108```nix
109den.lib.canTake { x = 1; } someFunction
110# => true if someFunction can take at least { x }
···115116## den.lib.aspects
117118+Re-export of [`flake-aspects`](https://github.com/vic/flake-aspects) library. Provides:
119- `aspects.types.aspectsType` — module type for aspect trees
120- `aspects.types.providerType` — type for aspect providers
121- `aspects.forward` — class forwarding implementation
···130131## den.lib.owned
132133+Extracts only owned configs from an aspect (**removes** `includes`, `__functor` from someAspect):
134135```nix
136den.lib.owned someAspect # => { nixos = ...; darwin = ...; }
···140141Creates a functor that only resolves static includes from an aspect:
142143+The new aspect ignores owned configs, and functional includes that are not
144+of type (ground flake-aspects): `{ class, aspect-chain }: {...}`
145+146```nix
147+den.lib.statics someAspect # => <aspect with only static includes>
148```
149150## den.lib.isStatic
···1+---
2+title: "Template: Bug Reproduction"
3+description: Create minimal reproductions for Den bug reports using nix-unit.
4+---
5+6+The bogus template helps you create minimal bug reproductions. Use it when reporting issues or contributing fixes.
7+8+## Initialize
9+10+```console
11+mkdir bogus && cd bogus
12+nix flake init -t github:vic/den#bogus
13+nix flake update den
14+```
15+16+## Project Structure
17+18+```
19+flake.nix
20+modules/
21+ bug.nix # your bug reproduction
22+ test-base.nix # test infrastructure (DO NOT EDIT)
23+```
24+25+## Writing a Bug Reproduction
26+27+Edit `modules/bug.nix` with a minimal test case:
28+29+```nix
30+{ denTest, ... }:
31+{
32+ flake.tests.bogus = {
33+ test-something = denTest (
34+ { den, lib, igloo, tuxHm, ... }:
35+ {
36+ den.hosts.x86_64-linux.igloo.users.tux = { };
37+38+ # set up the scenario
39+ den.aspects.igloo.nixos.something = true;
40+41+ # what you get
42+ expr = igloo.something;
43+ # what you expect
44+ expected = true;
45+ }
46+ );
47+ };
48+}
49+```
50+51+### How denTest Works
52+53+`denTest` is a helper that:
54+1. Creates a fresh Den evaluation with your module
55+2. Provides helpers like `igloo` (host config), `tuxHm` (user's HM config)
56+3. Compares `expr` against `expected` using [nix-unit](https://github.com/nix-community/nix-unit)
57+58+Available test helpers (from `test-base.nix`):
59+60+| Helper | Description |
61+|--------|-------------|
62+| `igloo` | `nixosConfigurations.igloo.config` |
63+| `iceberg` | `nixosConfigurations.iceberg.config` |
64+| `tuxHm` | `igloo.home-manager.users.tux` |
65+| `pinguHm` | `igloo.home-manager.users.pingu` |
66+| `funnyNames` | Resolves an aspect for class `"funny"` and collects `.names` |
67+| `show` | `builtins.trace` helper for debugging |
68+69+## Run Tests
70+71+```console
72+nix flake check
73+```
74+75+## Testing Against Different Den Versions
76+77+Edit `.github/workflows/test.yml` to test against multiple Den versions:
78+79+```yaml
80+strategy:
81+ matrix:
82+ rev: ["main", "v1.0.0", "abc1234"]
83+```
84+85+This helps identify regressions — include `"main"` and any release tag or commit.
86+87+## Contributing a Fix
88+89+If you're submitting a fix to Den, test against your local checkout:
90+91+```console
92+cd <den-working-copy>
93+nix flake check --override-input den . ./templates/bogus
94+```
95+96+## What It Provides
97+98+| Feature | Provided |
99+|---------|:--------:|
100+| nix-unit test infrastructure | ✓ |
101+| Pre-configured denTest helper | ✓ |
102+| Version matrix testing | ✓ |
103+| Common test helpers | ✓ |
104+105+## Next Steps
106+107+- Share your reproduction repo on [GitHub Discussions](https://github.com/vic/den/discussions)
108+- Read [Debug Configurations](/guides/debug/) for debugging techniques
109+- See the [CI Tests template](/tutorials/ci/) for Den's own comprehensive test suite
···1+---
2+title: "Template: CI Tests"
3+description: Den's own test suite — the definitive reference for every feature.
4+---
5+6+The CI template is Den's comprehensive test suite. It tests every feature using [nix-unit](https://github.com/nix-community/nix-unit). This is the **best learning resource** for understanding exactly how Den behaves.
7+8+## Structure
9+10+```
11+flake.nix
12+modules/
13+ empty.nix # example test skeleton
14+ test-support/
15+ eval-den.nix # denTest + evalDen helpers
16+ nix-unit.nix # nix-unit integration
17+ features/
18+ angle-brackets.nix # <den/...> syntax
19+ conditional-config.nix # conditional imports/configs
20+ default-includes.nix # den.default behavior
21+ forward.nix # den._.forward
22+ homes.nix # standalone HM
23+ host-options.nix # host/user schema options
24+ namespaces.nix # namespace define/merge/export
25+ os-user-class.nix # user class forwarding
26+ parametric.nix # parametric functors
27+ schema-base-modules.nix # den.base modules
28+ special-args-custom-instantiate.nix # custom instantiation
29+ top-level-parametric.nix # top-level context aspects
30+ user-host-bidirectional-config.nix # bidirectional providers
31+ batteries/
32+ define-user.nix # define-user battery
33+ flake-parts.nix # inputs' and self'
34+ import-tree.nix # import-tree battery
35+ primary-user.nix # primary-user battery
36+ tty-autologin.nix # tty-autologin battery
37+ unfree.nix # unfree packages
38+ user-shell.nix # user-shell battery
39+ context/
40+ apply.nix # ctx application
41+ apply-non-exact.nix # non-exact matching
42+ cross-provider.nix # cross-provider mechanism
43+ custom-ctx.nix # custom context types
44+ den-default.nix # den.default as context
45+ host-propagation.nix # full host pipeline
46+ named-provider.nix # self-named providers
47+ deadbugs/
48+ _external-namespace-deep-aspect.nix
49+ static-include-dup-package.nix
50+ home-manager/
51+ home-managed-home.nix
52+ use-global-pkgs.nix
53+ non-dendritic/ # non-den files for import-tree tests
54+ provider/ # external namespace provider flake
55+```
56+57+## Test Categories
58+59+### Core Features
60+61+| Test File | What It Tests |
62+|-----------|---------------|
63+| [conditional-config.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/conditional-config.nix) | Conditional imports using host/user attributes |
64+| [default-includes.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/default-includes.nix) | `den.default` applying to all hosts/users |
65+| [host-options.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/host-options.nix) | Custom host attributes, hostName, aspect names |
66+| [top-level-parametric.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/top-level-parametric.nix) | Context-aware top-level aspects |
67+| [parametric.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/parametric.nix) | All parametric functors (atLeast, fixedTo, expands) |
68+69+### Bidirectional & Providers
70+71+| Test File | What It Tests |
72+|-----------|---------------|
73+| [user-host-bidirectional-config.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/user-host-bidirectional-config.nix) | Host→user and user→host config flow |
74+| [context/cross-provider.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/cross-provider.nix) | Source providing config to target context |
75+| [context/named-provider.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/named-provider.nix) | Self-named provider mechanism |
76+77+### Context System
78+79+| Test File | What It Tests |
80+|-----------|---------------|
81+| [context/apply.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/apply.nix) | Context application mechanics |
82+| [context/apply-non-exact.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/apply-non-exact.nix) | Non-exact context matching |
83+| [context/custom-ctx.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/custom-ctx.nix) | User-defined context types with `into` |
84+| [context/den-default.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/den-default.nix) | `den.default` as a context type |
85+| [context/host-propagation.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/host-propagation.nix) | Full host pipeline with all contributions |
86+87+### Batteries
88+89+| Test File | What It Tests |
90+|-----------|---------------|
91+| [batteries/define-user.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/define-user.nix) | User definition across contexts |
92+| [batteries/primary-user.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/primary-user.nix) | Primary user groups |
93+| [batteries/user-shell.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/user-shell.nix) | Shell configuration |
94+| [batteries/unfree.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/unfree.nix) | Unfree package predicates |
95+| [batteries/tty-autologin.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/tty-autologin.nix) | TTY autologin service |
96+| [batteries/import-tree.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/import-tree.nix) | Auto-importing class dirs |
97+| [batteries/flake-parts.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/flake-parts.nix) | `inputs'` and `self'` providers |
98+99+### Advanced
100+101+| Test File | What It Tests |
102+|-----------|---------------|
103+| [angle-brackets.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/angle-brackets.nix) | All bracket resolution paths |
104+| [namespaces.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/namespaces.nix) | Local, remote, merged namespaces |
105+| [forward.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/forward.nix) | Custom class forwarding |
106+| [homes.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/homes.nix) | Standalone Home-Manager configs |
107+| [schema-base-modules.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/schema-base-modules.nix) | `den.base.{host,user,home,conf}` |
108+109+### Bug Regressions
110+111+| Test File | What It Tests |
112+|-----------|---------------|
113+| [deadbugs/static-include-dup-package.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/deadbugs/static-include-dup-package.nix) | Duplicate deduplication for packages/lists |
114+| [deadbugs/_external-namespace-deep-aspect.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/deadbugs/_external-namespace-deep-aspect.nix) | Deep aspect access from external flakes |
115+116+### External Provider
117+118+The `provider/` subdirectory is a **separate flake** that defines a namespace `provider` with aspects. It's used by the deadbugs test to verify cross-flake namespace consumption:
119+120+```nix
121+# provider/modules/den.nix
122+{ inputs, ... }:
123+{
124+ imports = [ inputs.den.flakeModule (inputs.den.namespace "provider" true) ];
125+ provider.tools._.dev._.editors = {
126+ nixos.programs.vim.enable = true;
127+ };
128+}
129+```
130+131+## Running CI Tests
132+133+From the Den root against your local checkout:
134+135+```console
136+nix flake check --override-input den . ./templates/ci
137+```
138+139+You can also run a single or a subset of tests using:
140+141+```console
142+# You can use any attr-path bellow flake.tests after system-agnositc to run those specific tests:
143+nix-unit --override-input den . --flake ./templates/ci#.tests.systems.x86_64-linux.system-agnostic
144+```
145+146+## Writing New Tests
147+148+Copy `modules/empty.nix` as a starting point:
149+150+```nix
151+{ denTest, ... }:
152+{
153+ flake.tests.my-feature = {
154+ test-name = denTest (
155+ { den, igloo, ... }:
156+ {
157+ den.hosts.x86_64-linux.igloo.users.tux = { };
158+ expr = /* what you get */;
159+ expected = /* what you expect */;
160+ }
161+ );
162+ };
163+}
164+```
-115
docs/src/content/docs/tutorials/context-aware.md
···1----
2-title: Context-Aware Configurations
3-description: Make aspects produce conditional config based on host and user context.
4----
5-6-## From Static to Dynamic
7-8-So far, aspects have been static attribute sets. But Den's power comes from
9-making them **functions of context**. When an aspect is a function, Den
10-passes it the current context — host, user, platform — and the function
11-decides what to produce.
12-13-## Step 1: A Simple Context Function
14-15-Instead of a static aspect, write a function:
16-17-```nix
18-{ den, ... }: {
19- den.aspects.igloo.includes = [
20- ({ host, ... }: {
21- nixos.networking.hostName = host.name;
22- })
23- ];
24-}
25-```
26-27-Den calls this function with `{ host }` containing the host's attributes.
28-The result sets the hostname dynamically from the host's name.
29-30-## Step 2: React to User Context
31-32-Functions receiving `{ host, user, ... }` run once per user on each host:
33-34-```nix
35-{ den, ... }: {
36- den.default.includes = [
37- ({ host, user, ... }: {
38- ${host.class}.users.users.${user.userName}.description =
39- "${user.userName} on ${host.name}";
40- })
41- ];
42-}
43-```
44-45-Notice `${host.class}` — this dynamically targets `nixos` or `darwin`
46-depending on the host's platform. The same function works on both.
47-48-## Step 3: Conditional Configuration
49-50-Use standard Nix conditionals inside context functions:
51-52-```nix
53-{ den, lib, ... }:
54-let
55- git-for-linux = { user, host, ... }:
56- if !lib.hasSuffix "darwin" host.system
57- then { homeManager.programs.git.enable = true; }
58- else { };
59-in {
60- den.aspects.tux.includes = [ git-for-linux ];
61-}
62-```
63-64-Git is enabled only for users on Linux hosts. On Darwin, the function
65-returns an empty set — no effect.
66-67-## Step 4: Use den.lib.parametric
68-69-For aspects that need to forward context to their own includes,
70-wrap them with `den.lib.parametric`:
71-72-```nix
73-{ den, ... }:
74-let
75- workspace = den.lib.parametric {
76- nixos.networking.hostName = "from-parametric";
77- includes = [
78- ({ host, ... }: { nixos.time.timeZone = "UTC"; })
79- ];
80- };
81-in {
82- den.aspects.igloo.includes = [ workspace ];
83-}
84-```
85-86-The `parametric` functor ensures that:
87-1. **Owned** config (`nixos.networking.hostName`) is always included
88-2. **Functions** in `includes` receive the forwarded context
89-3. Functions whose parameters don't match are **silently skipped**
90-91-## Step 5: Understand Matching
92-93-Den matches context by **argument names**, not values:
94-95-| Function signature | `{ host }` | `{ host, user }` |
96-|--------------------|:---:|:---:|
97-| `{ host, ... }: ...` | ✓ | ✓ |
98-| `{ host, user }: ...` | ✗ | ✓ |
99-| `{ never, ... }: ...` | ✗ | ✗ |
100-101-- `atLeast` — function is called if context has **at least** the required args
102-- `exactly` — function is called only if context has **exactly** those args
103-104-## What You've Learned
105-106-- Aspects can be functions that receive `{ host, user, home, ... }`
107-- Context functions produce conditional, platform-aware configurations
108-- [`den.lib.parametric`](/explanation/parametric/) forwards context to nested includes
109-- Unmatched functions are [silently skipped](/explanation/parametric/#matching-rules-summary) — no errors
110-- `${host.class}` makes configs target the right Nix class dynamically
111-112-## Next
113-114-- [Bidirectional Dependencies](/guides/bidirectional/) — hosts and users configure each other
115-- [Context System](/explanation/context-system/) — deep dive into `den.ctx`
···1+---
2+title: "Template: Default"
3+description: Recommended starting point with flake-parts, Home-Manager, and VM testing.
4+---
5+6+The default template is the recommended way to start a new Den project. It includes flake-parts, Home-Manager, dendritic flake-file, and a VM for testing.
7+8+## Initialize
9+10+```console
11+mkdir my-nix && cd my-nix
12+nix flake init -t github:vic/den#default
13+nix flake update den
14+```
15+16+## Project Structure
17+18+```
19+flake.nix # auto-generated by flake-file
20+modules/
21+ dendritic.nix # flake-file + den dendritic setup
22+ hosts.nix # host and user declarations
23+ defaults.nix # global settings (stateVersion, etc.)
24+ igloo.nix # host aspect
25+ tux.nix # user aspect
26+ vm.nix # VM runner
27+```
28+29+## File by File
30+31+### hosts.nix — Declare Your Infrastructure
32+33+```nix
34+{
35+ den.hosts.x86_64-linux.igloo.users.tux = { };
36+}
37+```
38+39+This single line declares:
40+- A host `igloo` on `x86_64-linux` (class: `nixos`)
41+- A user `tux` on that host (class: `homeManager`)
42+43+The host and user each get an aspect (`den.aspects.igloo` and `den.aspects.tux`) that you configure in separate files.
44+45+### igloo.nix — Host Aspect
46+47+```nix
48+{
49+ den.aspects.igloo = {
50+ nixos = { pkgs, ... }: {
51+ environment.systemPackages = [ pkgs.hello ];
52+ };
53+ homeManager = { pkgs, ... }: {
54+ home.packages = [ pkgs.vim ];
55+ };
56+ };
57+}
58+```
59+60+The host aspect provides:
61+- **NixOS config** — system packages available to all users
62+- **Home-Manager config** — default home environment for every user on this host
63+64+### tux.nix — User Aspect
65+66+```nix
67+{ den, ... }:
68+{
69+ den.aspects.tux = {
70+ includes = [
71+ den.provides.primary-user
72+ (den.provides.user-shell "fish")
73+ ];
74+ homeManager = { pkgs, ... }: {
75+ home.packages = [ pkgs.htop ];
76+ };
77+ };
78+}
79+```
80+81+The user aspect:
82+- Includes [`primary-user`](/reference/batteries/#primary-user) — adds wheel + networkmanager groups
83+- Includes [`user-shell`](/reference/batteries/#user-shell) — sets fish as default shell at OS and HM level
84+- Provides personal Home-Manager packages
85+86+### defaults.nix — Global Settings
87+88+```nix
89+{
90+ den.default.nixos.system.stateVersion = "25.11";
91+ den.default.homeManager.home.stateVersion = "25.11";
92+}
93+```
94+95+`den.default` applies settings to **all** hosts, users, and homes. This is the right place for `stateVersion` and other global policies.
96+97+### vm.nix — Test in a VM
98+99+```nix
100+{ inputs, den, ... }:
101+{
102+ den.aspects.igloo.includes = [ (den.provides.tty-autologin "tux") ];
103+104+ perSystem = { pkgs, ... }: {
105+ packages.vm = pkgs.writeShellApplication {
106+ name = "vm";
107+ text = let
108+ host = inputs.self.nixosConfigurations.igloo.config;
109+ in ''
110+ ${host.system.build.vm}/bin/run-${host.networking.hostName}-vm "$@"
111+ '';
112+ };
113+ };
114+}
115+```
116+117+Run the VM with:
118+119+```console
120+nix run .#vm
121+```
122+123+### dendritic.nix — Flake Wiring
124+125+```nix
126+{ inputs, ... }:
127+{
128+ imports = [
129+ (inputs.flake-file.flakeModules.dendritic or { })
130+ (inputs.den.flakeModules.dendritic or { })
131+ ];
132+ flake-file.inputs = {
133+ den.url = "github:vic/den";
134+ flake-file.url = "github:vic/flake-file";
135+ home-manager = {
136+ url = "github:nix-community/home-manager";
137+ inputs.nixpkgs.follows = "nixpkgs";
138+ };
139+ };
140+}
141+```
142+143+This uses [flake-file](https://github.com/vic/flake-file) so inputs can be defined close to where they are used. Run `nix run .#write-flake` to regenerate `flake.nix` after changing inputs.
144+145+## Data Flow
146+147+```mermaid
148+graph TD
149+ H["hosts.nix: igloo + tux"] --> CH["ctx.host {host=igloo}"]
150+ CH --> AI["den.aspects.igloo<br/>nixos + homeManager"]
151+ CH --> CU["ctx.user {host=igloo, user=tux}"]
152+ CU --> AT["den.aspects.tux<br/>primary-user + fish + htop"]
153+ CH --> HM["ctx.hm-host: import HM module"]
154+ HM --> HMU["ctx.hm-user: forward homeManager<br/>into home-manager.users.tux"]
155+ AI --> NixOS["nixosConfigurations.igloo"]
156+ AT --> NixOS
157+ HMU --> NixOS
158+```
159+160+## What It Provides
161+162+| Feature | Provided |
163+|---------|:--------:|
164+| NixOS host configuration | ✓ |
165+| Home-Manager integration | ✓ |
166+| Dendritic flake-file | ✓ |
167+| VM testing | ✓ |
168+| flake-parts | ✓ |
169+| Darwin support | Add input |
170+| Namespaces | Add manually |
171+172+## Next Steps
173+174+- Edit `hosts.nix` to add more hosts or users
175+- Create new aspect files under `modules/`
176+- Add Darwin support by adding `nix-darwin` input
177+- Explore the [Example template](/tutorials/example/) for namespaces and advanced features
···1+---
2+title: "Template: Example"
3+description: Feature showcase with namespaces, angle brackets, cross-platform aspects, and providers.
4+---
5+6+The example template demonstrates Den's advanced features: namespaces, angle brackets, cross-platform hosts, bidirectional providers, and custom aspect libraries.
7+8+## Initialize
9+10+```console
11+mkdir my-nix && cd my-nix
12+nix flake init -t github:vic/den#example
13+nix flake update den
14+```
15+16+## Project Structure
17+18+```
19+flake.nix
20+modules/
21+ den.nix # host/home declarations
22+ dendritic.nix # flake-file + den wiring
23+ inputs.nix # flake inputs
24+ namespace.nix # creates the "eg" namespace
25+ tests.nix # CI checks
26+ vm.nix # VM runner
27+ aspects/
28+ defaults.nix # global config + angle brackets demo
29+ alice.nix # user aspect
30+ igloo.nix # host aspect
31+ eg/ # namespace aspects
32+ autologin.nix
33+ ci-no-boot.nix
34+ routes.nix
35+ vm.nix
36+ vm-bootable.nix
37+ xfce-desktop.nix
38+```
39+40+## Key Features Demonstrated
41+42+### Cross-Platform Hosts
43+44+```nix
45+# modules/den.nix
46+{
47+ den.hosts.x86_64-linux.igloo.users.alice = { };
48+ den.hosts.aarch64-darwin.apple.users.alice = { };
49+ den.homes.x86_64-linux.alice = { };
50+}
51+```
52+53+One user (`alice`) across a NixOS host, a Darwin host, and a standalone Home-Manager config. The same aspects produce appropriate configs for each platform.
54+55+### Namespaces
56+57+```nix
58+# modules/namespace.nix
59+{ inputs, den, ... }:
60+{
61+ imports = [ (inputs.den.namespace "eg" true) ];
62+ _module.args.__findFile = den.lib.__findFile;
63+}
64+```
65+66+Creates a local namespace `eg` accessible as a module argument. The `true` flag exposes it as a flake output (`flake.denful.eg`). Angle brackets are enabled via `__findFile`.
67+68+### Namespace Aspects
69+70+The `eg/` directory defines reusable aspects under the `eg` namespace:
71+72+```nix
73+# modules/aspects/eg/vm.nix
74+{ eg, ... }:
75+{
76+ eg.vm.provides = {
77+ gui.includes = [ eg.vm eg.vm-bootable._.gui eg.xfce-desktop ];
78+ tui.includes = [ eg.vm eg.vm-bootable._.tui ];
79+ };
80+}
81+```
82+83+Aspects can have nested **provides** — `eg.vm._.gui` and `eg.vm._.tui` are sub-aspects accessed via the `_.` provider syntax.
84+85+### Angle Brackets
86+87+```nix
88+# modules/aspects/defaults.nix
89+{ den, __findFile ? __findFile, ... }:
90+{
91+ den.default.includes = [
92+ <eg/routes> # resolves to eg.routes
93+ <den/define-user> # resolves to den.provides.define-user
94+ ];
95+}
96+```
97+98+The `<name>` syntax is shorthand for aspect lookup. `<den/define-user>` resolves to `den.provides.define-user`. See [Angle Brackets](/guides/angle-brackets/).
99+100+### Bidirectional Providers
101+102+```nix
103+# modules/aspects/alice.nix — user provides config TO the host
104+den.aspects.alice = {
105+ provides.igloo = { host, ... }: {
106+ nixos.programs.nh.enable = host.name == "igloo";
107+ };
108+};
109+110+# modules/aspects/igloo.nix — host provides config TO the user
111+den.aspects.igloo = {
112+ provides.alice = { user, ... }: {
113+ homeManager.programs.helix.enable = user.name == "alice";
114+ };
115+};
116+```
117+118+Hosts and users can contribute configuration **to each other** through `provides`. Alice enables `nh` on igloo, and igloo enables `helix` for alice. These are cross-providers activated during context transformation.
119+120+### Custom Routes
121+122+```nix
123+# modules/aspects/eg/routes.nix
124+{ den, ... }:
125+{
126+ eg.routes = let
127+ inherit (den.lib) parametric;
128+ mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { };
129+ routes = { host, user, ... }@ctx:
130+ parametric.fixedTo ctx {
131+ includes = [ (mutual user host) (mutual host user) ];
132+ };
133+ in routes;
134+}
135+```
136+137+This aspect **wires bidirectional providers** between hosts and users automatically. Including `<eg/routes>` in `den.default` activates all `provides` declarations.
138+139+### Tests
140+141+```nix
142+# modules/tests.nix — verifies the template works
143+{
144+ checks."alice enabled igloo nh" = checkCond "..." igloo.programs.nh.enable;
145+ checks."igloo enabled alice helix" = checkCond "..." alice-at-igloo.programs.helix.enable;
146+}
147+```
148+149+## Data Flow
150+151+```mermaid
152+graph TD
153+ D["den.nix: igloo/apple + alice"] --> CH["ctx.host"]
154+ CH --> AI["aspects.igloo<br/>nixos + provides.alice"]
155+ CH --> CU["ctx.user"]
156+ CU --> AA["aspects.alice<br/>includes + provides.igloo"]
157+ AA -->|"routes: mutual"| AI
158+ AI -->|"routes: mutual"| AA
159+ CH --> HM["ctx.hm-host"]
160+ AI --> NixOS["nixosConfigurations.igloo"]
161+ AA --> NixOS
162+ AI --> Darwin["darwinConfigurations.apple"]
163+ AA --> Darwin
164+```
165+166+## What It Provides
167+168+| Feature | Provided |
169+|---------|:--------:|
170+| NixOS + Darwin hosts | ✓ |
171+| Home-Manager | ✓ |
172+| Standalone HM config | ✓ |
173+| Namespaces (`eg`) | ✓ |
174+| Angle brackets | ✓ |
175+| Bidirectional providers | ✓ |
176+| VM testing | ✓ |
177+| CI checks | ✓ |
178+179+## Next Steps
180+181+- Study the `eg/` namespace aspects as examples for your own libraries
182+- Read [Namespaces](/guides/namespaces/) and [Angle Brackets](/guides/angle-brackets/)
183+- Explore the [CI Tests template](/tutorials/ci/) for comprehensive feature coverage
-114
docs/src/content/docs/tutorials/first-aspect.md
···1----
2-title: Your First Aspect
3-description: Create a cross-class, reusable Nix configuration aspect.
4----
5-6-## What is an Aspect?
7-8-An aspect is a composable bundle of Nix configurations that can span multiple
9-classes (NixOS, Darwin, Home-Manager). Each host, user, and home you declare
10-automatically gets an aspect.
11-12-## Step 1: Declare Your Infrastructure
13-14-Start with a host and user:
15-16-```nix
17-# modules/hosts.nix
18-{
19- den.hosts.x86_64-linux.igloo.users.tux = { };
20-}
21-```
22-23-Den creates `den.aspects.igloo` (host) and `den.aspects.tux` (user) automatically.
24-25-## Step 2: Configure the Host Aspect
26-27-Any module file can contribute to any aspect. Create a file for your host:
28-29-```nix
30-# modules/igloo.nix
31-{ den, ... }: {
32- den.aspects.igloo = {
33- nixos.networking.hostName = "igloo";
34- nixos.time.timeZone = "UTC";
35- homeManager.programs.direnv.enable = true;
36- };
37-}
38-```
39-40-The `nixos` settings apply to NixOS. The `homeManager` settings apply
41-to **every user** on this host.
42-43-## Step 3: Configure the User Aspect
44-45-```nix
46-# modules/vic.nix
47-{ den, ... }: {
48- den.aspects.tux = {
49- homeManager.programs.fish.enable = true;
50- nixos.users.users.tux.description = "Tux the Penguin";
51- };
52-}
53-```
54-55-User aspects contribute to **every host** that has this user.
56-If `tux` exists on both `igloo` and another host, both get the fish config.
57-58-## Step 4: Use Includes for Composition
59-60-Aspects can include other aspects:
61-62-```nix
63-# modules/gaming.nix
64-{ den, ... }: {
65- den.aspects.gaming = {
66- nixos.programs.steam.enable = true;
67- homeManager.programs.mangohud.enable = true;
68- };
69-70- den.aspects.igloo.includes = [ den.aspects.gaming ];
71-}
72-```
73-74-## Step 5: Use Provides for Nesting
75-76-Organize related aspects in a tree:
77-78-```nix
79-{ den, ... }: {
80- den.aspects.tools.provides.editors = {
81- homeManager.programs.vim.enable = true;
82- };
83-84- den.aspects.tux.includes = [ den.aspects.tools._.editors ];
85-}
86-```
87-88-The `._. ` syntax is shorthand for `.provides.`.
89-90-## Step 6: Set Global Defaults
91-92-Use `den.default` for settings shared across all hosts and users:
93-94-```nix
95-# modules/defaults.nix
96-{
97- den.default.homeManager.home.stateVersion = "25.11";
98- den.default.nixos.system.stateVersion = "25.11";
99-}
100-```
101-102-## What You've Learned
103-104-- [Aspects](/explanation/aspects/) bundle cross-class configs (nixos, darwin, homeManager)
105-- Host aspects apply to all users on that host
106-- User aspects apply to all hosts with that user
107-- `includes` compose aspects together
108-- `provides` creates nested aspect trees
109-- [`den.default`](/explanation/context-pipeline/#dendefault-is-an-alias) sets global shared values
110-111-## Next
112-113-[Context-Aware Configs](/tutorials/context-aware/) — make your aspects
114-respond dynamically to their host and user context.
···1----
2-title: Getting Started
3-description: Set up your first Den project from scratch.
4----
5-6-## Prerequisites
7-8-- [Nix](https://nixos.org/download) with flakes enabled (or use [without flakes](/guides/no-flakes/))
9-- Basic familiarity with Nix modules
10-11-## Quick Start — Launch the Demo VM
12-13-Try Den instantly without installing anything:
14-15-```console
16-nix run github:vic/den
17-```
18-19-## Initialize a New Project
20-21-Create a fresh Den-based flake:
22-23-```console
24-mkdir my-infra && cd my-infra
25-nix flake init -t github:vic/den
26-nix flake update den
27-```
28-29-This creates a project with:
30-31-```
32-flake.nix # inputs and entry point
33-modules/ # your Den modules go here
34- hosts.nix # host and user declarations
35- aspects/ # aspect definitions
36-```
37-38-## Your flake.nix
39-40-The generated `flake.nix` imports Den and uses `import-tree` to load all modules:
41-42-```nix
43-{
44- outputs = inputs:
45- inputs.flake-parts.lib.mkFlake { inherit inputs; }
46- (inputs.import-tree ./modules);
47-48- inputs = {
49- den.url = "github:vic/den";
50- flake-aspects.url = "github:vic/flake-aspects";
51- import-tree.url = "github:vic/import-tree";
52- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
53- flake-parts.url = "github:hercules-ci/flake-parts";
54- };
55-}
56-```
57-58-## Declare a Host
59-60-In `modules/hosts.nix`, declare your first host:
61-62-```nix
63-{
64- den.hosts.x86_64-linux.my-laptop.users.vic = { };
65-}
66-```
67-68-This single line creates:
69-- A NixOS host named `my-laptop` on `x86_64-linux`
70-- A user `vic` with Home-Manager support
71-- Aspects `den.aspects.my-laptop` and `den.aspects.vic`
72-73-## Build It
74-75-```console
76-nixos-rebuild switch --flake .#my-laptop
77-```
78-79-## Available Templates
80-81-| Template | Description |
82-|----------|-------------|
83-| `default` | Batteries-included layout |
84-| `minimal` | Minimalistic Den flake |
85-| `noflake` | No flakes, no flake-parts |
86-| `example` | Examples and patterns |
87-| `ci` | Feature test suite |
88-| `bogus` | Bug reproduction |
89-90-```console
91-nix flake init -t github:vic/den#minimal
92-```
93-94-## Next Steps
95-96-- [Your First Aspect](/tutorials/first-aspect/) — write cross-class configs
97-- [Context-Aware Configs](/tutorials/context-aware/) — make aspects react to context
···1+---
2+title: Templates Overview
3+description: All available Den templates and when to use each one.
4+---
5+6+Den ships six templates that cover progressively complex setups. Use `nix flake init` to scaffold a new project:
7+8+```console
9+nix flake init -t github:vic/den#<template>
10+```
11+12+## Choosing a Template
13+14+| Template | Use case | Flakes | flake-parts | Home-Manager |
15+|----------|----------|:------:|:-----------:|:------------:|
16+| [**minimal**](/tutorials/minimal/) | Smallest possible Den setup | ✓ | ✗ | ✗ |
17+| [**default**](/tutorials/default/) | Recommended starting point | ✓ | ✓ | ✓ |
18+| [**example**](/tutorials/example/) | Feature showcase with namespaces | ✓ | ✓ | ✓ |
19+| [**noflake**](/tutorials/noflake/) | Stable Nix, no flakes | ✗ | ✗ | ✗ |
20+| [**bogus**](/tutorials/bogus/) | Bug reproduction | ✓ | ✓ | ✓ |
21+| [**ci**](/tutorials/ci/) | Den's own test suite | ✓ | ✓ | ✓ |
22+23+## Quick Start
24+25+```console
26+mkdir my-nix && cd my-nix
27+nix flake init -t github:vic/den
28+nix flake update den
29+```
30+31+This clones the **default** template. Edit `modules/hosts.nix` to declare your machines, then:
32+33+```console
34+nix run .#vm
35+```
36+37+## Project Structure
38+39+Every template follows the same pattern:
40+41+```
42+flake.nix # or default.nix for noflake
43+modules/
44+ den.nix # host/user declarations + den.flakeModule import
45+ *.nix # aspect definitions, one concern per file
46+```
47+48+Den uses [import-tree](https://github.com/vic/import-tree) to recursively load all `.nix` files under `modules/`. You never need to manually list imports — just create files.
49+50+## What Each Template Demonstrates
51+52+- **minimal** — The absolute minimum: one host, one user, no extra dependencies
53+- **default** — Production-ready structure with Home-Manager, VM testing, dendritic flake-file
54+- **example** — Namespaces, angle brackets, cross-platform (NixOS + Darwin), providers
55+- **noflake** — Using Den with npins instead of flakes
56+- **bogus** — Creating minimal reproductions for bug reports with nix-unit
57+- **ci** — Comprehensive tests covering every Den feature (your best learning resource)
58+59+## Next Steps
60+61+Start with the [Minimal template](/tutorials/minimal/) to understand Den's core, then graduate to [Default](/tutorials/default/) for a real setup.
···3 flake.tests.ctx-non-exact.test-apply-non-exact-less = denTest (
4 { den, funnyNames, ... }:
5 {
6- den.ctx.foobar.desc = "{foo,bar} context";
7- den.ctx.foobar.conf =
8 # use atLeast if you get error: function called with unexpected argument
9 den.lib.take.atLeast (
10 { foo, bar }:
···30 flake.tests.ctx-non-exact.test-apply-non-exact-more = denTest (
31 { den, funnyNames, ... }:
32 {
33- den.ctx.foobar.desc = "{foo,bar} context";
34- den.ctx.foobar.conf =
35 # use exactly if you want to restrict to not having more args
36 den.lib.take.exactly (
37 { foo, bar }:
···3 flake.tests.ctx-non-exact.test-apply-non-exact-less = denTest (
4 { den, funnyNames, ... }:
5 {
6+ den.ctx.foobar.description = "{foo,bar} context";
7+ den.ctx.foobar._.foobar =
8 # use atLeast if you get error: function called with unexpected argument
9 den.lib.take.atLeast (
10 { foo, bar }:
···30 flake.tests.ctx-non-exact.test-apply-non-exact-more = denTest (
31 { den, funnyNames, ... }:
32 {
33+ den.ctx.foobar.description = "{foo,bar} context";
34+ den.ctx.foobar._.foobar =
35 # use exactly if you want to restrict to not having more args
36 den.lib.take.exactly (
37 { foo, bar }: