Skip to content

ADR-028 slice 3: desktop-env bundle scaffolding (niri + greetd + DMS) #33

Description

@dannyfaris

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

  • flake.nix has niri-flake and dank-material-shell inputs;
    lockfile updated; both follow nixpkgs.
  • modules/core/nixos/bundles/desktop-env.nix exists; niri +
    greetd + desktop fonts wired.
  • home/core/nixos/bundles/ directory exists with
    desktop-env.nix as first occupant; niri + Ghostty + DMS wired
    with no Stylix theme override.
  • greetd session list includes both niri-session and a TTY
    fallback (per Q3).
  • hosts/metis/default.nix imports the system bundle in imports
    and the home bundle in hostContext.extraHomeModules.
  • nix build .#nixosConfigurations.metis.config.system.build.toplevel --no-link succeeds.
  • nix build .#nixosConfigurations.mercury.config.system.build.toplevel --no-link succeeds (no regression).
  • nix build .#nixosConfigurations.nixos-vm.config.system.build.toplevel --no-link succeeds (no regression).
  • nix flake check passes.
  • Shared-purity lint (parts/checks) passes — the home bundle
    correctly lives under nixos/.
  • Closure-size delta on metis is dominated by Qt6 / Quickshell /
    matugen / niri / greetd / fonts — nothing unexpected. Record the
    delta in the PR description.

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions