Skip to content

refactor(overlays): split custom-packages-overlay into per-app gated modules#196

Merged
Bad3r merged 4 commits into
mainfrom
refactor/custom-overlays
May 8, 2026
Merged

refactor(overlays): split custom-packages-overlay into per-app gated modules#196
Bad3r merged 4 commits into
mainfrom
refactor/custom-overlays

Conversation

@Bad3r
Copy link
Copy Markdown
Owner

@Bad3r Bad3r commented May 8, 2026

Summary

  • Splits modules/base/custom-packages-overlay.nix into 28 per-package files under modules/custom-overlays/, each gated on programs.<name>.extended.enable of the matching flake.nixosModules.apps.<name>.
  • Adds the option flake.customOverlays (attrsOf deferredModule) in modules/meta/flake-output.nix so overlay submodules survive merge as first-class entries instead of collapsing under flake.nixosModules.* (same rationale already documented on flake.csec).
  • Adds four new NixOS app modules for the i3wm helpers (snixembed, i3-focus-or-launch, i3-scratchpad-show-or-create, monitor-query) and enables them on both hosts via apps-enable.nix. monitor-query's app module deliberately omits environment.systemPackages since the package is a writeText shell library sourced via pkgs.monitor-query.
  • Introduces per-host aggregators modules/system76/custom-overlays-base.nix and modules/tpnix/custom-overlays-base.nix that auto-discover overlays via lib.attrValues (config.flake.customOverlays or { }), mirroring the existing apps-base.nix pattern.
  • Renames modules/system76/custom-packages-overlay.nix to system76-power-overlay.nix (only the system76-power skip-non-alpm-scsi-hosts.patch override survives). Deletes modules/base/custom-packages-overlay.nix and modules/tpnix/custom-packages-overlay.nix; flake.lib.overlays.customPackages is no longer defined or referenced.

Test plan

  • nix fmt
  • nix develop -c pre-commit run --all-files --hook-stage manual (all hooks pass)
  • nix flake check --accept-flake-config --no-build (clean)
  • nix build .#nixosConfigurations.system76.config.system.build.toplevel
  • nix build .#nixosConfigurations.tpnix.config.system.build.toplevel
  • nix eval confirms snixembed carries icon-resolution.patch and librepods reports version 0.2.5
  • Confirm runtime parity for librepods, marktext, wfuzz, snixembed (i3 tray), charles, and an i3 keybinding using i3-scratchpad-show-or-create after activation

…modules

The combined overlay in modules/base/custom-packages-overlay.nix loaded all
28 entries on every host regardless of whether the matching app was enabled.
Splits it into per-package files under modules/custom-overlays/, each gated
on programs.<name>.extended.enable of the corresponding
flake.nixosModules.apps.<name>. Hosts wire them in via a single auto-
discovering aggregator (modules/<host>/custom-overlays-base.nix), mirroring
the existing modules/<host>/apps-base.nix pattern that uses lib.attrValues
over a flake-output namespace.

Adds the option flake.customOverlays (attrsOf deferredModule, declared in
modules/meta/flake-output.nix) so overlay submodules survive merge as first-
class entries instead of collapsing under flake.nixosModules.* like the
existing comment on flake.csec already documents.

Adds four new app modules (snixembed, i3-focus-or-launch,
i3-scratchpad-show-or-create, monitor-query) so the i3wm helpers fit the
gating contract; both hosts now enable them in apps-enable.nix because both
run i3wm. The monitor-query module intentionally omits
environment.systemPackages because the package is a writeText shell library
sourced via pkgs.monitor-query, not a derivation with bin/ outputs.

Renames modules/system76/custom-packages-overlay.nix to
system76-power-overlay.nix because its only remaining content is the
system76-power skip-non-alpm-scsi-hosts.patch override, unrelated to the
splitter aggregate. Deletes modules/base/custom-packages-overlay.nix and
modules/tpnix/custom-packages-overlay.nix; the flake.lib.overlays.customPackages
symbol is no longer defined or referenced.

Validation: nix fmt; pre-commit run --all-files --hook-stage manual passes
all hooks; nix flake check --accept-flake-config --no-build is clean;
nix build of nixosConfigurations.system76 and nixosConfigurations.tpnix
toplevel both succeed; nix eval confirms snixembed carries the icon-
resolution patch and librepods reports version 0.2.5 once the matching app
modules are enabled.
@Bad3r Bad3r added host(system76) Specific to the System76 host or its runtime contract. area(packages) Package definitions, overrides, overlays, or package-source selection. type(refactor) Structural cleanup without intended behavior change. area(module-system) Import-tree behavior, evaluation order, or module composition mechanics. host(tpnix) Specific to the tpnix host or its runtime contract. labels May 8, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in ❄️ NixOS May 8, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 8, 2026

Claude finished @Bad3r's task in 5m 43s —— View job


Review

No issues found.

The strict-rules bar requires concrete defects or measurable enhancements; the actionable concerns on this PR (i3-helper gating, snixembed service vs. patched overlay, dead cfg.package/empty mkIf in monitor-query) were already addressed in d74b65b and 1ea799b. The remaining diff is internally consistent: every overlay in modules/custom-overlays/<n>.nix gates on programs.<n>.extended.enable, each gate has a matching option declared in modules/apps/<n>.nix, and the four i3 helpers auto-enable via lib.mkDefault true (priority 1000) inside i3SessionModule — which correctly beats the per-host catalog's lib.mkOverride 1100 false (priority 1100). The snixembed overlay preserves all three fixes from the deleted base file (icon-resolution patch, wrapGAppsHook3, librsvg).
· branch refactor/custom-overlays

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the monolithic customPackages overlay into granular, per-app overlays located in modules/custom-overlays/. These overlays are now conditionally applied to nixpkgs.overlays only when the corresponding app module is enabled via programs.<name>.extended.enable. The changes also include an auto-import mechanism for these overlays in host configurations. Feedback highlights two risks: first, core i3 utilities are now gated, which could cause evaluation failures if the i3wm module references them while the app modules are disabled; second, the snixembed service enablement is not synchronized with its new gated overlay, potentially resulting in the use of an unpatched version of the package.

Comment thread modules/apps/i3-focus-or-launch.nix
Comment thread modules/apps/snixembed.nix
…osts

The previous commit gated four overlays (snixembed, i3-focus-or-launch,
i3-scratchpad-show-or-create, monitor-query) on
`programs.<name>.extended.enable` and listed them under each host's
`apps-enable.nix`. That setup had two foot-guns flagged in the PR review:

  1. modules/apps/i3wm/{config,scratchpad,services}.nix reference
     `pkgs.<helper>` directly. Disabling any helper on an i3 host would
     remove the overlay and surface as "attribute missing" during eval,
     not as a clear "you turned off something i3 needs".
  2. The snixembed systemd user unit in modules/apps/i3wm/services.nix is
     gated only on `xsession.windowManager.i3.enable`. With the overlay
     gated on the app-module enable, a host that disabled snixembed but
     kept i3 on would launch the upstream nixpkgs snixembed without the
     ARGB stride / SVG-loader / icon-resolution patches, producing blank
     i3bar tray icons.

Couples the four helpers to i3 itself by setting
`programs.<helper>.extended.enable = lib.mkDefault true` inside the i3
NixOS module (modules/apps/i3wm/nixos.nix). Priority 1000 (mkDefault)
beats the 1100 used in apps-enable.nix, so the four redundant entries in
modules/system76/apps-enable.nix and modules/tpnix/apps-enable.nix are
removed. Hosts can still opt out with no-modifier or `lib.mkForce false`.

Validation: nix flake check --no-build clean; both host closures rebuild;
nix eval confirms each helper resolves to enable=true on system76 and
tpnix via the i3 mkDefault path.
@Bad3r
Copy link
Copy Markdown
Owner Author

Bad3r commented May 8, 2026

@gemini-code-assist Thanks for the review. Both threads addressed in d74b65b7.

What was accepted

Both inline comments. They flagged the same underlying issue under different surfaces:

  1. High (thread): the i3 HM modules unconditionally read pkgs.{i3-focus-or-launch,i3-scratchpad-show-or-create,monitor-query}, but the matching overlays were only registered when the per-app extended.enable flipped on. Disabling any of them on an i3 host would surface as attribute missing during evaluation rather than as a meaningful "you turned off something i3 needs".
  2. Medium (thread): modules/apps/i3wm/services.nix runs the snixembed systemd user unit gated only on xsession.windowManager.i3.enable, while the patched-snixembed overlay was gated on programs.snixembed.extended.enable. A host could legitimately end up running upstream nixpkgs snixembed without the ARGB stride, SVG-loader, or icon-resolution patches, producing the original blank-tray-icon symptom that PR fix(snixembed): bundle librsvg + wrap with wrapGAppsHook3 #195 was meant to fix.

What was rejected

Nothing in the substance was rejected. Of the two implementation options you proposed, neither was used directly:

  • Option 1 (graceful absence in i3wm) was rejected as a fix shape. lib.optional wrappers in home.packages and string-fallbacks in keybindings would silently mask the more serious case where pkgs.snixembed exists upstream but is the broken-icon version, and they push i3 toward a partial-startup mode that is hard to reason about.
  • Option 2 (un-gate the four overlays) was rejected because it breaks the gating contract for every other helper and re-introduces the "every overlay loads on every host" property the PR set out to remove.

How the accepted ones were implemented

A third path: bind the dependency at the module that owns it. modules/apps/i3wm/nixos.nix now declares the four helpers as i3 dependencies:

programs = {
  "i3-focus-or-launch".extended.enable = lib.mkDefault true;
  "i3-scratchpad-show-or-create".extended.enable = lib.mkDefault true;
  "monitor-query".extended.enable = lib.mkDefault true;
  snixembed.extended.enable = lib.mkDefault true;
};

Why this shape:

  • Prioritylib.mkDefault is priority 1000. The per-host apps-enable.nix catalog uses lib.mkOverride 1100 (priority 1100, lower than mkDefault), so the i3 module wins by design. A host can still opt out explicitly with = false (priority 100) or lib.mkForce false (priority 50) without touching the i3 module.
  • Locality — the dependency lives next to the consumer. If someone deletes the i3 module from a host, the helpers are no longer auto-enabled there. If someone keeps i3 but un-checks one of the helpers on purpose, the override still works through normal NixOS priority semantics.
  • No redundancy — the four entries previously added to modules/system76/apps-enable.nix and modules/tpnix/apps-enable.nix are removed in the same commit. The catalog stops carrying duplicate signals.

Validation

  • nix fmt, pre-commit run --all-files --hook-stage manual, and nix flake check --no-build all clean.
  • nix build for both nixosConfigurations.system76 and nixosConfigurations.tpnix succeeds.
  • nix eval confirms each helper still reports extended.enable = true on both hosts (now via the i3 mkDefault path rather than the per-host catalog).

@gemini-code-assist
Copy link
Copy Markdown

The approach of binding dependencies at the module level using lib.mkDefault is an excellent solution. It maintains the desired modularity while correctly handling the priority semantics required for host-specific overrides. This implementation effectively resolves the issues regarding evaluation errors and the gating contract for overlays. The validation steps taken confirm the robustness of this refactor.

The previous commit removed the four i3 helper entries
(`snixembed`, `i3-focus-or-launch`, `i3-scratchpad-show-or-create`,
`monitor-query`) from each host's `apps-enable.nix` because
`modules/apps/i3wm/nixos.nix` now sets them via `lib.mkDefault true`.
The `apps-catalog-sync` pre-push hook treats every `modules/apps/*.nix`
file as a required catalog entry and rejected the push.

Re-adds the entries as `lib.mkOverride 1100 false`, the conventional
"catalog default off" value for entries managed elsewhere. Priority
1100 is lower than the i3 module's `mkDefault` (1000), so the four
helpers still resolve to `true` on i3 hosts. On a hypothetical non-i3
host that imported the app modules directly, the catalog default
would correctly leave them off.
Comment thread modules/apps/monitor-query.nix Outdated
…block

`modules/apps/monitor-query.nix` previously declared
`programs.monitor-query.extended.package` via `lib.mkPackageOption` and an
empty `config = lib.mkIf cfg.enable { }` block. Neither served anything:

  * `cfg.package` is never read. The overlay in
    modules/custom-overlays/monitor-query.nix builds the package itself
    via `import lib/shell/monitor-query.nix`, and the only consumer
    (modules/apps/i3wm/scratchpad.nix) sources `pkgs.monitor-query`
    directly. No call site goes through the option.
  * `lib.mkIf cfg.enable { }` is a no-op regardless of `cfg.enable`.

Reduces the module to its actual job: declaring
`programs.monitor-query.extended.enable` so the gated overlay submodule
has something to read. No behavior change on either host (verified with
`nix build` for system76 and tpnix).

Flagged in PR review by claude[bot] on PR #196.
@Bad3r
Copy link
Copy Markdown
Owner Author

Bad3r commented May 8, 2026

@claude Thanks for catching the dead code. Addressed in 1ea799b3.

What was accepted

Your enhancement on modules/apps/monitor-query.nix. Confirmed by grep that programs.monitor-query.extended.package is read by zero call sites:

  • modules/custom-overlays/monitor-query.nix builds the package itself via import ../../lib/shell/monitor-query.nix { inherit (final) writeText; }. It does not go through cfg.package.
  • modules/apps/i3wm/scratchpad.nix sources pkgs.monitor-query directly.
  • No other module references the option.

config = lib.mkIf cfg.enable { } is also a literal no-op.

How it was implemented

Reduced the module to its actual contract — declaring programs.monitor-query.extended.enable so the overlay submodule has something to gate on. Dropped the package option, the empty config block, and the now-unused pkgs and config arguments to the inner module. Updated the docstring to make clear that the absence of package is intentional, not an oversight, so future readers do not re-add it for "consistency" with the other helper modules.

Validation: nix fmt, nix flake check --no-build, and nix build for both nixosConfigurations.system76 and nixosConfigurations.tpnix still succeed. nix eval confirms programs.monitor-query.extended.enable = true on both hosts via the i3 mkDefault path from f6373e8b.

Pushing now.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 8, 2026

Claude finished @Bad3r's task in 1m 5s —— View job


Verified 1ea799b — looks right

Pulled refactor/custom-overlays and inspected the commit. Everything you described is in place.

  • Pull latest commits and inspect 1ea799b
  • Confirm modules/apps/monitor-query.nix no longer declares package or an empty config block
  • Confirm cfg.package has zero readers across the tree
  • Confirm programs.monitor-query.extended.enable is still wired up (overlay gate, i3 mkDefault)

What changed in modules/apps/monitor-query.nix

The module now declares only the toggle and nothing else:

options.programs."monitor-query".extended.enable = lib.mkOption {
  type = lib.types.bool;
  default = false;
  description = "Whether to enable the monitor-query shell library overlay.";
};

Confirmed:

  • The package option (and its lib.mkPackageOption pkgs "monitor-query" { }) is gone.
  • The config = lib.mkIf cfg.enable { } no-op is gone.
  • The inner module's argument list is reduced to { lib, ... }pkgs and config are no longer pulled in.
  • The header docstring at modules/apps/monitor-query.nix:13-16 calls out the absence of package explicitly so the next reader does not re-add it for surface-level symmetry with the other helper modules. Nice touch.

Reader audit (matches what you found)

modules/apps/i3wm/scratchpad.nix:51   . "${pkgs.monitor-query}"
modules/custom-overlays/monitor-query.nix:6   cfg = config.programs."monitor-query".extended;
modules/custom-overlays/monitor-query.nix:12  "monitor-query" = import ../../lib/shell/monitor-query.nix { inherit (final) writeText; };

grep -rn "monitor-query.extended.package" across *.nix returns zero hits — no consumer is reaching for cfg.package, so removing the option is safe.

Gating chain still intact

The overlay in modules/custom-overlays/monitor-query.nix:9 still keys on cfg.enable, and the i3 module sets programs."monitor-query".extended.enable = lib.mkDefault true from commit d74b65b, so the helper continues to register on i3 hosts via the dependency-at-the-consumer path discussed in the earlier thread.

No further changes from me. Thanks for the writeup — the explicit "no package by design" comment is exactly the right shape for this kind of intentional asymmetry.
· branch: refactor/custom-overlays

@Bad3r Bad3r merged commit ca0fc53 into main May 8, 2026
1 check passed
@Bad3r Bad3r deleted the refactor/custom-overlays branch May 8, 2026 19:01
@github-project-automation github-project-automation Bot moved this from Backlog to Done in ❄️ NixOS May 8, 2026
Bad3r added a commit that referenced this pull request May 10, 2026
…modules (#196)

* refactor(overlays): split custom-packages-overlay into per-app gated modules

The combined overlay in modules/base/custom-packages-overlay.nix loaded all
28 entries on every host regardless of whether the matching app was enabled.
Splits it into per-package files under modules/custom-overlays/, each gated
on programs.<name>.extended.enable of the corresponding
flake.nixosModules.apps.<name>. Hosts wire them in via a single auto-
discovering aggregator (modules/<host>/custom-overlays-base.nix), mirroring
the existing modules/<host>/apps-base.nix pattern that uses lib.attrValues
over a flake-output namespace.

Adds the option flake.customOverlays (attrsOf deferredModule, declared in
modules/meta/flake-output.nix) so overlay submodules survive merge as first-
class entries instead of collapsing under flake.nixosModules.* like the
existing comment on flake.csec already documents.

Adds four new app modules (snixembed, i3-focus-or-launch,
i3-scratchpad-show-or-create, monitor-query) so the i3wm helpers fit the
gating contract; both hosts now enable them in apps-enable.nix because both
run i3wm. The monitor-query module intentionally omits
environment.systemPackages because the package is a writeText shell library
sourced via pkgs.monitor-query, not a derivation with bin/ outputs.

Renames modules/system76/custom-packages-overlay.nix to
system76-power-overlay.nix because its only remaining content is the
system76-power skip-non-alpm-scsi-hosts.patch override, unrelated to the
splitter aggregate. Deletes modules/base/custom-packages-overlay.nix and
modules/tpnix/custom-packages-overlay.nix; the flake.lib.overlays.customPackages
symbol is no longer defined or referenced.

Validation: nix fmt; pre-commit run --all-files --hook-stage manual passes
all hooks; nix flake check --accept-flake-config --no-build is clean;
nix build of nixosConfigurations.system76 and nixosConfigurations.tpnix
toplevel both succeed; nix eval confirms snixembed carries the icon-
resolution patch and librepods reports version 0.2.5 once the matching app
modules are enabled.

* fix(i3wm): auto-enable helper apps so gated overlays register on i3 hosts

The previous commit gated four overlays (snixembed, i3-focus-or-launch,
i3-scratchpad-show-or-create, monitor-query) on
`programs.<name>.extended.enable` and listed them under each host's
`apps-enable.nix`. That setup had two foot-guns flagged in the PR review:

  1. modules/apps/i3wm/{config,scratchpad,services}.nix reference
     `pkgs.<helper>` directly. Disabling any helper on an i3 host would
     remove the overlay and surface as "attribute missing" during eval,
     not as a clear "you turned off something i3 needs".
  2. The snixembed systemd user unit in modules/apps/i3wm/services.nix is
     gated only on `xsession.windowManager.i3.enable`. With the overlay
     gated on the app-module enable, a host that disabled snixembed but
     kept i3 on would launch the upstream nixpkgs snixembed without the
     ARGB stride / SVG-loader / icon-resolution patches, producing blank
     i3bar tray icons.

Couples the four helpers to i3 itself by setting
`programs.<helper>.extended.enable = lib.mkDefault true` inside the i3
NixOS module (modules/apps/i3wm/nixos.nix). Priority 1000 (mkDefault)
beats the 1100 used in apps-enable.nix, so the four redundant entries in
modules/system76/apps-enable.nix and modules/tpnix/apps-enable.nix are
removed. Hosts can still opt out with no-modifier or `lib.mkForce false`.

Validation: nix flake check --no-build clean; both host closures rebuild;
nix eval confirms each helper resolves to enable=true on system76 and
tpnix via the i3 mkDefault path.

* chore(catalog): list i3 helper apps as catalog defaults

The previous commit removed the four i3 helper entries
(`snixembed`, `i3-focus-or-launch`, `i3-scratchpad-show-or-create`,
`monitor-query`) from each host's `apps-enable.nix` because
`modules/apps/i3wm/nixos.nix` now sets them via `lib.mkDefault true`.
The `apps-catalog-sync` pre-push hook treats every `modules/apps/*.nix`
file as a required catalog entry and rejected the push.

Re-adds the entries as `lib.mkOverride 1100 false`, the conventional
"catalog default off" value for entries managed elsewhere. Priority
1100 is lower than the i3 module's `mkDefault` (1000), so the four
helpers still resolve to `true` on i3 hosts. On a hypothetical non-i3
host that imported the app modules directly, the catalog default
would correctly leave them off.

* refactor(monitor-query): drop dead `package` option and no-op config block

`modules/apps/monitor-query.nix` previously declared
`programs.monitor-query.extended.package` via `lib.mkPackageOption` and an
empty `config = lib.mkIf cfg.enable { }` block. Neither served anything:

  * `cfg.package` is never read. The overlay in
    modules/custom-overlays/monitor-query.nix builds the package itself
    via `import lib/shell/monitor-query.nix`, and the only consumer
    (modules/apps/i3wm/scratchpad.nix) sources `pkgs.monitor-query`
    directly. No call site goes through the option.
  * `lib.mkIf cfg.enable { }` is a no-op regardless of `cfg.enable`.

Reduces the module to its actual job: declaring
`programs.monitor-query.extended.enable` so the gated overlay submodule
has something to read. No behavior change on either host (verified with
`nix build` for system76 and tpnix).

Flagged in PR review by claude[bot] on PR #196.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area(module-system) Import-tree behavior, evaluation order, or module composition mechanics. area(packages) Package definitions, overrides, overlays, or package-source selection. host(system76) Specific to the System76 host or its runtime contract. host(tpnix) Specific to the tpnix host or its runtime contract. type(refactor) Structural cleanup without intended behavior change.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant