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:
- Attribute Pruning: Juna uses
lib.optionalAttrsto ensure that if a theme or icon package is missing, the key is never defined. - Priority Pre-emption: By using
lib.mkDefaultspecifically 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.