Juna Philosophy: The Ghost-Drizzle

Juna is built for developers who chase simplicity and crave control. While most Nix theming solutions act as Management Frameworks, Juna is a Transparent Overlay.


1. The Polite Guest Principle

In a typical NixOS configuration, the user "owns" their application settings. Most theming tools are aggressive; they take total ownership of an application's configuration set to apply a theme. If you want to change a single nested value the tool doesn't expose, you end up fighting the module system with lib.mkForce.

Juna is a Polite Guest. It provides the pigments but stays out of your way.


The Visual Proof

Imagine your configuration is a canvas. Other frameworks try to paint the whole thing. Juna just fills the gaps you left behind.

Your dotfiles Juna's pigments Final Result
gtk.font.size = 12; (nothing) 12 (User wins)
(nothing) gtk.theme.name = "Juna-Dark"; Juna-Dark (Juna fills)
gtk.theme.name = "Custom"; gtk.theme.name = "Juna-Dark"; Custom (Native Priority)

2. Transparent Overlay vs. Management Framework

A Management Framework (like Stylix) wraps your applications in an abstraction layer. You interact with the framework's API, which then generates the application config using internal logic like lib.recursiveUpdate.

Juna is a Transparent Overlay. It leverages the Nix module system's native merging capabilities.


Code Anatomy: Surgical vs. Aggressive

The Aggressive Way (Frameworks): Frameworks often use recursiveUpdate, which merges two sets entirely. If you want to change one sub-key, you must override the whole branch or use tool-specific override syntax.

The Juna Way (Surgical): Juna targets the "Leaf Nodes." We don't define the root gtk set; we define the specific strings and packages inside it using mkDefault.

# juna/modules/gtk.nix
let
  junaGtk =
    {}
    # Only "drizzle" the theme if it's actually defined
    // lib.optionalAttrs (cfg.themePackage != null && cfg.themeName != "") {
      theme = {
        package = cfg.themePackage;
        name = cfg.themeName;
      };
    }
    # Icons stay untouched unless Juna has a replacement
    // lib.optionalAttrs (cfg.iconPackage != null && cfg.iconName != "") {
      iconTheme = {
        package = cfg.iconPackage;
        name = cfg.iconName;
      };
    }
    // {
      gtk3.extraConfig.gtk-application-prefer-dark-theme =
        if cfg.preferDarkTheme
        then 1
        else 0;
      gtk4.extraConfig.gtk-application-prefer-dark-theme =
        if cfg.preferDarkTheme
        then 1
        else 0;
    };
in {
  # Apply mkDefault (1000) so your home.nix always wins natively
  gtk = junaLib.applySurgicalOverride cfg.forceJunaOverride junaGtk;
);

Because we target gtk.theme and not the root gtk attribute, your manual gtk.font or gtk3.bookmarks remains untouched. The Nix evaluator handles this natively, keeping your build times low and your control high.


3. Performance & Evaluation

Nix evaluation time matters, especially in complex flakes. Most engines run a mini-interpreter of lib.fix and lib.map just to decide your background color.

  • Lazy Evaluation: Juna leverages Nix's laziness. If a specific domain (like GTK or Alacritty) isn't enabled in Juna, the evaluator never touches those attributes.
  • Native C++ Merging: By avoiding recursive Nix-language functions to resolve configurations, Juna allows the Nix C++ evaluator to perform native attribute merging, leading to faster rebuilds.

4. The Base16 Contract

Juna doesn't hide the "magic." It provides a clean metadata contract based on the Base16 standard. This metadata is exposed directly, allowing you to build your own custom abstractions on top of Juna without having to dig through a complex library source.

Tip

Wizard Note: Priority Sovereignty

Juna uses priority 1000 (mkDefault). Since your standard home.nix definitions also sit at 1000, your user config naturally overrides Juna without needing mkForce. It’s not magic; it’s just respecting the module system.


Case Study: Submodule Sovereignty (GTK)

A common issue in Nix theming is the "null conflict." In Home Manager, the gtk.theme option defaults to null. If a module attempts to provide a theme set at the same priority level as the default, the Nix evaluator panics.

Juna solves this with Surgical Overrides:

  1. Attribute Pruning: Juna uses lib.optionalAttrs to ensure that if a theme or icon package is missing, the key is never defined.
  2. Priority Pre-emption: By using lib.mkDefault specifically for leaf nodes, Juna pre-empts the internal module defaults without blocking user-defined values.

This ensures that your custom gtk.font defined in your dotfiles remains completely untouched, while Juna "drizzles" the pigments onto the remaining empty slots.


How to use this Philosophy

If you find yourself using lib.mkForce to override Juna, Juna has failed. Juna is designed so that your manual configurations and automated theming coexist in a single, elegant attribute set.