Background
ADR-028 (docs/decisions/ADR-028-stylix-foundation-and-desktop-env.md)
sets out a six-slice plan to land Stylix in foundation and the first
desktop host on metis. Slice 2 has landed (issue #4, closed) — Stylix is
in foundation, per-host palettes for nixos-vm / mercury / metis
exist in lib/host-palettes.nix, and TUI tools render in those palettes.
Slice 3 is the first desktop slice. It scaffolds the niri + greetd +
DMS stack as importable bundles and wires metis to import them. It does
not activate the desktop — that's slice 5 (separate issue). It does
not theme DMS — that's slice 4 (separate issue). This slice ends at a
green nix build.
The point of the explicit scaffold-vs-activate split is to land the
closure-size and schema-shape risks (Qt6 / Quickshell / matugen / niri
all enter the metis evaluation) in a slice that's easy to back out from
without touching display-manager state.
Prereqs
Hard dependency: ADR-028 slice 2 (issue #4) — landed. The Stylix
input, lib/host-palettes.nix, and HM theming bundle already exist; this
slice consumes none of it directly, but slice 4 (next) does, and the
ADR-028 slice train assumes the prior slice's invariants hold.
No runtime prereqs — metis is not yet a desktop host. The bundle imports
add inert configuration until slice 5 activates it.
Scope
In scope:
- New flake inputs:
niri-flake and dank-material-shell.
- New system bundle
modules/core/nixos/bundles/desktop-env.nix — niri
enablement, greetd session entry, any desktop-only fonts not already
covered by Stylix's fonts.monospace in foundation.
- New home bundle
home/core/nixos/bundles/desktop-env.nix — niri user
config, Ghostty terminal, DMS. First occupant of
home/core/nixos/bundles/ — directory doesn't exist yet.
hosts/metis/default.nix imports both bundles (system bundle in
imports, home bundle in hostContext.extraHomeModules).
Out of scope:
- DMS↔Stylix theme wiring — slice 4 (separate issue).
- First activation (
nh os switch on metis) — slice 5 (separate issue).
- GTK / Qt / cursor / wallpaper Stylix targets — additive, can layer in
after slice 5.
- Any change on
mercury (work, headless) or nixos-vm (UTM, no DRM) —
they do not import the desktop bundle.
- App-level desktop packages (Firefox, 1Password GUI, etc.) — ADR-027
flags a future desktop-apps bundle alongside desktop-env; that
split is deferred per the rule-of-two-with-intent-to-grow.
Architectural constraints
Home-side bundle lives under nixos/, not shared/. Per the
shared-purity lint (parts/checks), Darwin-incompatible modules must
not live under shared/. Niri, greetd, DMS, and Ghostty's
Linux-specific paths are all Linux-only. The directory
home/core/nixos/bundles/ is created as part of this slice; existing
HM bundles all live under home/core/shared/bundles/.
System bundle does not import DMS. DMS is a home-manager module
(programs.dank-material-shell). The system bundle handles niri
enablement (which has both a system and HM side) and greetd. The home
bundle handles DMS + Ghostty + niri user config.
programs.dank-material-shell.enable = true; in slice 3 will draw DMS
in its default Material You palette. This is fine — slice 3 only
verifies the build closure resolves. Slice 4 wires the custom theme
sourced from config.lib.stylix.colors.
Niri imports follow upstream's module exposure. The
sodiboo/niri-flake input exposes nixosModules.niri (system) and
homeModules.niri (home). DMS's home module is exposed by its own
flake (see Outstanding Q2).
Outstanding decisions
| # |
Decision |
Options |
Notes |
| 1 |
Niri input source |
(a) github:sodiboo/niri-flake — most-used community flake; exposes both nixosModules.niri and homeModules.niri; offers niri-stable / niri-unstable package set. (b) Nixpkgs programs.niri only — no community flake, lose access to the typed HM module. |
Lean (a). Community flake is the documented Niri-on-NixOS path; nixpkgs-only is bare metal. |
| 2 |
DMS input |
(a) github:AvengeMedia/DankMaterialShell — upstream master. (b) Pin to a specific tag/rev. |
Lean (a) + weekly update_flake_lock_action (already running per .github/workflows). DMS pushes daily but its 16-key custom-theme schema has been stable across recent releases; ADR-028 records this as migration trigger 1. |
| 3 |
greetd session list |
(a) niri-session only. (b) niri + TTY fallback session. |
Lean (b). CLAUDE.md break-glass posture says "physical console (monitor + keyboard); once ADR-028 lands, the greetd login is the same entry point" — keep a TTY login alongside niri so a broken niri session doesn't lock the operator out. |
| 4 |
Niri user-config source |
(a) Typed via programs.niri.settings.* (niri-flake's options surface). (b) Raw ~/.config/niri/config.kdl via home.file. |
Lean (a). Fall back to (b) only for options niri-flake hasn't typed yet. |
| 5 |
Ghostty package source |
(a) nixpkgs ghostty. (b) github:ghostty-org/ghostty flake. |
Lean (a) unless slice 5 surfaces a feature gap. The shared ghostty-terminfo.nix module already exists and is reused as-is. |
| 6 |
Default-fonts surface |
Stylix's fonts.monospace already sets JetBrains Mono Nerd Font in foundation. Desktop adds: sans-serif (Inter? Roboto?) and emoji (Noto Color Emoji?). |
Pick one sans + one emoji. Stylix's fonts.sansSerif / fonts.emoji options are the right surface — set in the desktop bundle so headless hosts don't carry the closure. |
Implementation plan
Each sub-slice peer-reviewed on staged diff with operator sign-off before
commit, per workflow_peer_review_per_slice.md.
Slice 3a — flake inputs
Add niri-flake and dank-material-shell to flake.nix. Both with
inputs.nixpkgs.follows = "nixpkgs". Run nix flake update to lock.
Build check: nix flake check passes.
Commit: flake: add niri + DMS inputs (ADR-028 slice 3a)
Slice 3b — system bundle
Create modules/core/nixos/bundles/desktop-env.nix:
# desktop-env — host runs a Wayland desktop session.
#
# Niri compositor + greetd session entry + desktop fonts. Home-side
# pieces (DMS, Ghostty, niri user config) live in
# home/core/nixos/bundles/desktop-env.nix. Per ADR-028.
{ inputs, pkgs, ... }: {
imports = [ inputs.niri-flake.nixosModules.niri ];
programs.niri.enable = true;
services.greetd = {
enable = true;
settings = {
default_session = {
command = "${pkgs.greetd.tuigreet}/bin/tuigreet --time --cmd niri-session";
user = "greeter";
};
};
};
# Desktop-only fonts. Monospace is set by Stylix in foundation.
stylix.fonts = {
sansSerif = { ... }; # per Outstanding Q6
emoji = { ... }; # per Outstanding Q6
};
}
Build check: nix build .#nixosConfigurations.metis.config.system.build.toplevel --no-link (bundle not yet imported by any host).
Commit: modules/bundles: scaffold desktop-env (niri + greetd + fonts) (ADR-028 slice 3b)
Slice 3c — home bundle
Create home/core/nixos/bundles/ directory (first occupant). Create
home/core/nixos/bundles/desktop-env.nix:
# desktop-env — home-manager pieces for a Wayland desktop session.
#
# DMS shell + Ghostty terminal + Niri user config. System-side pieces
# (niri enable, greetd, desktop fonts) live in
# modules/core/nixos/bundles/desktop-env.nix. Per ADR-028.
{ inputs, ... }: {
imports = [
inputs.niri-flake.homeModules.niri
inputs.dank-material-shell.homeModules.default
];
programs.niri = {
enable = true;
settings = { ... }; # per Outstanding Q4
};
programs.ghostty.enable = true;
programs.dank-material-shell.enable = true;
# Theming deliberately not wired here — slice 4 owns
# programs.dank-material-shell.settings.customThemeFile.
}
Commit: home/bundles: scaffold desktop-env (niri + ghostty + DMS) (ADR-028 slice 3c)
Slice 3d — metis composition
In hosts/metis/default.nix:
- Add
../../modules/core/nixos/bundles/desktop-env.nix to imports
alongside remote-access.nix.
- Add
../../home/core/nixos/bundles/desktop-env.nix to
hostContext.extraHomeModules.
Build check: nix build .#nixosConfigurations.metis.config.system.build.toplevel --no-link.
Diff closure size against the pre-slice generation
(nix store diff-closures) for visibility — ADR-028 calls Qt6 +
Quickshell + matugen out as the expected delta.
Verify no regression on the other hosts: nix build .#nixosConfigurations.{mercury,nixos-vm}.config.system.build.toplevel --no-link.
Commit: metis: import desktop-env bundles (ADR-028 slice 3d)
Acceptance criteria
Risks / caveats
- DMS upstream pushes daily. Schema drift is migration trigger 1
in ADR-028. Mitigation: weekly flake-lock bump surfaces drift for
manual review; CI catches eval failures before activation.
- niri-flake is a community flake. Pin or master-track tradeoff
is Outstanding Q1. Either is reversible via lockfile.
- First home/core/nixos/bundles/ entry. No existing precedent in
this repo for HM bundles under nixos/ — but shared-purity lint
forces this placement and ADR-028 explicitly authorises it.
- Closure grows on metis only. Mercury and nixos-vm are
unaffected. Verify via the acceptance-criteria diff.
- DMS in default Material You palette during slice 3. Looks
wrong against TUI tools rendering in metis's rose-pine. Acceptable
— slice 3 doesn't activate; slice 4 reconciles.
References
Context for a fresh Claude Code session
- This is the first desktop slice. Three slices in the desktop
series: 3 (this one) scaffolds, 4 wires DMS↔Stylix, 5 activates.
Each is a separate issue.
- Do not activate in this slice. No
nh os switch. Slice 3 ends
at a green nix build. If a tempting moment arrives to "just check
it works", that's slice 5's job — and slice 5 has the verification
matrix to do it properly.
- DMS theming is slice 4, not this one. Resist the urge to wire
programs.dank-material-shell.settings.customThemeFile here. The
bundle in slice 3 enables DMS in its default palette; that's
intentional. Slice 4 introduces the Stylix-driven JSON.
- Home bundle placement is load-bearing. It MUST live under
home/core/nixos/bundles/, not home/core/shared/bundles/. The
shared-purity lint will reject the latter — DMS / niri / greetd are
Linux-only. This is the first time this directory exists; create it.
- Hard rule: peer review on staged diff before each commit; never
commit without user sign-off
(workflow_peer_review_per_slice.md).
Background
ADR-028 (docs/decisions/ADR-028-stylix-foundation-and-desktop-env.md)
sets out a six-slice plan to land Stylix in foundation and the first
desktop host on metis. Slice 2 has landed (issue #4, closed) — Stylix is
in foundation, per-host palettes for
nixos-vm/mercury/metisexist in
lib/host-palettes.nix, and TUI tools render in those palettes.Slice 3 is the first desktop slice. It scaffolds the niri + greetd +
DMS stack as importable bundles and wires metis to import them. It does
not activate the desktop — that's slice 5 (separate issue). It does
not theme DMS — that's slice 4 (separate issue). This slice ends at a
green
nix build.The point of the explicit scaffold-vs-activate split is to land the
closure-size and schema-shape risks (Qt6 / Quickshell / matugen / niri
all enter the metis evaluation) in a slice that's easy to back out from
without touching display-manager state.
Prereqs
Hard dependency: ADR-028 slice 2 (issue #4) — landed. The Stylix
input,
lib/host-palettes.nix, and HM theming bundle already exist; thisslice consumes none of it directly, but slice 4 (next) does, and the
ADR-028 slice train assumes the prior slice's invariants hold.
No runtime prereqs — metis is not yet a desktop host. The bundle imports
add inert configuration until slice 5 activates it.
Scope
In scope:
niri-flakeanddank-material-shell.modules/core/nixos/bundles/desktop-env.nix— nirienablement, greetd session entry, any desktop-only fonts not already
covered by Stylix's
fonts.monospacein foundation.home/core/nixos/bundles/desktop-env.nix— niri userconfig, Ghostty terminal, DMS. First occupant of
home/core/nixos/bundles/— directory doesn't exist yet.hosts/metis/default.niximports both bundles (system bundle inimports, home bundle inhostContext.extraHomeModules).Out of scope:
nh os switchon metis) — slice 5 (separate issue).after slice 5.
mercury(work, headless) ornixos-vm(UTM, no DRM) —they do not import the desktop bundle.
flags a future
desktop-appsbundle alongsidedesktop-env; thatsplit is deferred per the rule-of-two-with-intent-to-grow.
Architectural constraints
Home-side bundle lives under
nixos/, notshared/. Per theshared-purity lint (
parts/checks), Darwin-incompatible modules mustnot live under
shared/. Niri, greetd, DMS, and Ghostty'sLinux-specific paths are all Linux-only. The directory
home/core/nixos/bundles/is created as part of this slice; existingHM bundles all live under
home/core/shared/bundles/.System bundle does not import DMS. DMS is a home-manager module
(
programs.dank-material-shell). The system bundle handles nirienablement (which has both a system and HM side) and greetd. The home
bundle handles DMS + Ghostty + niri user config.
programs.dank-material-shell.enable = true;in slice 3 will draw DMSin its default Material You palette. This is fine — slice 3 only
verifies the build closure resolves. Slice 4 wires the custom theme
sourced from
config.lib.stylix.colors.Niri imports follow upstream's module exposure. The
sodiboo/niri-flakeinput exposesnixosModules.niri(system) andhomeModules.niri(home). DMS's home module is exposed by its ownflake (see Outstanding Q2).
Outstanding decisions
github:sodiboo/niri-flake— most-used community flake; exposes bothnixosModules.niriandhomeModules.niri; offersniri-stable/niri-unstablepackage set.(b) Nixpkgs
programs.nirionly — no community flake, lose access to the typed HM module.github:AvengeMedia/DankMaterialShell— upstream master.(b) Pin to a specific tag/rev.
update_flake_lock_action(already running per.github/workflows). DMS pushes daily but its 16-key custom-theme schema has been stable across recent releases; ADR-028 records this as migration trigger 1.niri-sessiononly.(b) niri + TTY fallback session.
programs.niri.settings.*(niri-flake's options surface).(b) Raw
~/.config/niri/config.kdlviahome.file.(b)
github:ghostty-org/ghosttyflake.ghostty-terminfo.nixmodule already exists and is reused as-is.fonts.monospacealready sets JetBrains Mono Nerd Font in foundation. Desktop adds: sans-serif (Inter? Roboto?) and emoji (Noto Color Emoji?).fonts.sansSerif/fonts.emojioptions are the right surface — set in the desktop bundle so headless hosts don't carry the closure.Implementation plan
Each sub-slice peer-reviewed on staged diff with operator sign-off before
commit, per
workflow_peer_review_per_slice.md.Slice 3a — flake inputs
Add
niri-flakeanddank-material-shelltoflake.nix. Both withinputs.nixpkgs.follows = "nixpkgs". Runnix flake updateto lock.Build check:
nix flake checkpasses.Commit:
flake: add niri + DMS inputs (ADR-028 slice 3a)Slice 3b — system bundle
Create
modules/core/nixos/bundles/desktop-env.nix:Build check:
nix build .#nixosConfigurations.metis.config.system.build.toplevel --no-link(bundle not yet imported by any host).Commit:
modules/bundles: scaffold desktop-env (niri + greetd + fonts) (ADR-028 slice 3b)Slice 3c — home bundle
Create
home/core/nixos/bundles/directory (first occupant). Createhome/core/nixos/bundles/desktop-env.nix:Commit:
home/bundles: scaffold desktop-env (niri + ghostty + DMS) (ADR-028 slice 3c)Slice 3d — metis composition
In
hosts/metis/default.nix:../../modules/core/nixos/bundles/desktop-env.nixtoimportsalongside
remote-access.nix.../../home/core/nixos/bundles/desktop-env.nixtohostContext.extraHomeModules.Build check:
nix build .#nixosConfigurations.metis.config.system.build.toplevel --no-link.Diff closure size against the pre-slice generation
(
nix store diff-closures) for visibility — ADR-028 calls Qt6 +Quickshell + matugen out as the expected delta.
Verify no regression on the other hosts:
nix build .#nixosConfigurations.{mercury,nixos-vm}.config.system.build.toplevel --no-link.Commit:
metis: import desktop-env bundles (ADR-028 slice 3d)Acceptance criteria
flake.nixhasniri-flakeanddank-material-shellinputs;lockfile updated; both follow nixpkgs.
modules/core/nixos/bundles/desktop-env.nixexists; niri +greetd + desktop fonts wired.
home/core/nixos/bundles/directory exists withdesktop-env.nixas first occupant; niri + Ghostty + DMS wiredwith no Stylix theme override.
fallback (per Q3).
hosts/metis/default.niximports the system bundle inimportsand the home bundle in
hostContext.extraHomeModules.nix build .#nixosConfigurations.metis.config.system.build.toplevel --no-linksucceeds.nix build .#nixosConfigurations.mercury.config.system.build.toplevel --no-linksucceeds (no regression).nix build .#nixosConfigurations.nixos-vm.config.system.build.toplevel --no-linksucceeds (no regression).nix flake checkpasses.parts/checks) passes — the home bundlecorrectly lives under
nixos/.matugen / niri / greetd / fonts — nothing unexpected. Record the
delta in the PR description.
Risks / caveats
in ADR-028. Mitigation: weekly flake-lock bump surfaces drift for
manual review; CI catches eval failures before activation.
is Outstanding Q1. Either is reversible via lockfile.
this repo for HM bundles under
nixos/— but shared-purity lintforces this placement and ADR-028 explicitly authorises it.
unaffected. Verify via the acceptance-criteria diff.
wrong against TUI tools rendering in metis's rose-pine. Acceptable
— slice 3 doesn't activate; slice 4 reconciles.
References
docs/decisions/ADR-028-stylix-foundation-and-desktop-env.md,especially §Implementation slice 3.
home.nix
is the canonical HM-module reference.
Context for a fresh Claude Code session
series: 3 (this one) scaffolds, 4 wires DMS↔Stylix, 5 activates.
Each is a separate issue.
nh os switch. Slice 3 endsat a green
nix build. If a tempting moment arrives to "just checkit works", that's slice 5's job — and slice 5 has the verification
matrix to do it properly.
programs.dank-material-shell.settings.customThemeFilehere. Thebundle in slice 3 enables DMS in its default palette; that's
intentional. Slice 4 introduces the Stylix-driven JSON.
home/core/nixos/bundles/, nothome/core/shared/bundles/. Theshared-purity lint will reject the latter — DMS / niri / greetd are
Linux-only. This is the first time this directory exists; create it.
commit without user sign-off
(
workflow_peer_review_per_slice.md).