From fc06a4bf80faae747427f9be615e13c6468b6b1a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 29 Sep 2026 03:43:36 +0000 Subject: [PATCH 1/2] feat: add prelude process-compose themes Co-authored-by: cooper --- .../content/docs/modules/process-compose.mdx | 12 + .../modules/process-compose/module.nix | 39 ++- .../modules/process-compose/themes.nix | 225 ++++++++++++++++++ nix/stackpanel/modules/process-compose/ui.nix | 12 + 4 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 nix/stackpanel/modules/process-compose/themes.nix diff --git a/apps/docs/content/docs/modules/process-compose.mdx b/apps/docs/content/docs/modules/process-compose.mdx index ae84d02e..99d99a95 100644 --- a/apps/docs/content/docs/modules/process-compose.mdx +++ b/apps/docs/content/docs/modules/process-compose.mdx @@ -22,3 +22,15 @@ stackpanel.apps.web.path = "apps/web"; ```nix # Then run `dev` in the devshell ``` + +## Theme + +Process Compose uses the selected Prelude palette for its TUI custom theme. +By default it follows `stackpanel.prelude.theme` and falls back to `minted`. + +```nix +stackpanel.process-compose.theme = "nord"; +``` + +The generated theme is written to `.stack/gen/process-compose/theme.yaml` and +the `dev` wrapper starts Process Compose with `--theme "Custom Style"`. diff --git a/nix/stackpanel/modules/process-compose/module.nix b/nix/stackpanel/modules/process-compose/module.nix index d0df3c4d..0667816d 100644 --- a/nix/stackpanel/modules/process-compose/module.nix +++ b/nix/stackpanel/modules/process-compose/module.nix @@ -36,6 +36,8 @@ }: let meta = import ./meta.nix; + preludeProcessComposeThemes = import ./themes.nix { inherit lib; }; + preludeProcessComposeThemeNames = builtins.attrNames preludeProcessComposeThemes; cfg = config.stackpanel; pcCfg = cfg.process-compose; @@ -385,9 +387,15 @@ let processes, commandName, port ? 8080, + themeConfigDir ? null, }: let processCount = builtins.length (lib.attrNames processes); + themeArgs = + lib.optionalString (themeConfigDir != null) '' + export PROC_COMP_CONFIG="${themeConfigDir}" + theme_args=(--theme "Custom Style") + ''; in pkgs.writeShellScriptBin commandName '' if [[ ${toString processCount} -eq 0 ]]; then @@ -422,10 +430,12 @@ let # Set PC_PORT_NUM for the process-compose API server export PC_PORT_NUM="${toString port}" + theme_args=() + ${themeArgs} # Run process-compose - auto-detects process-compose.yaml in repo root # The --port flag sets the API server port - exec ${pkgs.process-compose}/bin/process-compose --port ${toString port} "$@" + exec ${pkgs.process-compose}/bin/process-compose --port ${toString port} "''${theme_args[@]}" "$@" ''; in @@ -465,6 +475,20 @@ in example = "start"; }; + theme = lib.mkOption { + type = lib.types.nullOr (lib.types.enum preludeProcessComposeThemeNames); + default = null; + description = '' + Prelude theme to render for the Process Compose TUI. + + Null follows `stackpanel.prelude.theme`; when that is also null, + Stackpanel uses Prelude's default `minted` theme. Process Compose only + supports one custom theme file, so this option selects which Prelude + palette is written to `.stack/gen/process-compose/theme.yaml`. + ''; + example = "nord"; + }; + formatWatcher = { enable = lib.mkEnableOption "format watcher process" // { default = true; @@ -606,10 +630,15 @@ in # Resolve port: explicit config > computed from base port + fixed offset # Uses base-port + 90 to avoid hash collisions with other services resolvedPort = if pcCfg.port != null then pcCfg.port else cfg.ports.base-port + 90; + preludeTheme = cfg.prelude.theme or null; + effectiveTheme = + if pcCfg.theme != null then pcCfg.theme else if preludeTheme != null then preludeTheme else "minted"; configFile = mkConfigFile { inherit (pcCfg) processes; }; + themeFile = (pkgs.formats.yaml { }).generate "process-compose-theme.yaml" preludeProcessComposeThemes.${effectiveTheme}; + themeConfigDir = "\${STACKPANEL_ROOT:-$PWD}/.stack/gen/process-compose"; in { # Set PC_PORT_NUM environment variable for the devshell @@ -621,6 +650,7 @@ in inherit (pcCfg) commandName; inherit (pcCfg) processes; port = resolvedPort; + inherit themeConfigDir; }) ]; @@ -633,6 +663,13 @@ in description = "Process-compose configuration (symlink to Nix store)"; }; + stackpanel.files.entries.".stack/gen/process-compose/theme.yaml" = { + enable = true; + format = "symlink"; + target = "${themeFile}"; + description = "Process Compose custom theme generated from Prelude theme `${effectiveTheme}`"; + }; + # Gitignore the symlink — it points to a Nix store path that changes on every rebuild stackpanel.gitignore.entries = [ "process-compose.yaml" ]; diff --git a/nix/stackpanel/modules/process-compose/themes.nix b/nix/stackpanel/modules/process-compose/themes.nix new file mode 100644 index 00000000..b437aac3 --- /dev/null +++ b/nix/stackpanel/modules/process-compose/themes.nix @@ -0,0 +1,225 @@ +{ lib }: +let + # Ported from darkmatter/prelude src/prelude/themes.nix at the pinned + # Stackpanel Prelude revision. Keep token names aligned with Prelude so + # stackpanel.prelude.theme can select the matching Process Compose style. + palettes = { + phosphor = { + bg = "#0c110e"; + surface = "#131715"; + secondary = "#202622"; + fg = "#d5e2d7"; + muted = "#7d8a81"; + dim = "#5d665f"; + border = "#1a201d"; + accent = "#68e371"; + accent2 = "#f9b64f"; + success = "#68e371"; + warning = "#f9b64f"; + info = "#5eb7ff"; + error = "#e6443d"; + selectionFg = "#0c110e"; + }; + + minted = { + bg = "#0c0c13"; + surface = "#161623"; + secondary = "#24243f"; + fg = "#979db5"; + muted = "#6a6c85"; + dim = "#4a5085"; + border = "#1d1d2f"; + accent = "#f2cdcd"; + accent2 = "#CC99FF"; + success = "#b7ce99"; + warning = "#f2c17d"; + info = "#89b4fa"; + error = "#ee848e"; + selectionFg = "#0c0c13"; + }; + + amber = { + bg = "#110c08"; + surface = "#19120e"; + secondary = "#291f18"; + fg = "#fcbe62"; + muted = "#a97c46"; + dim = "#8d6a43"; + border = "#261c14"; + accent = "#ffc761"; + accent2 = "#fadc7d"; + success = "#a8c66c"; + warning = "#fadc7d"; + info = "#7fb4ca"; + error = "#e9523f"; + selectionFg = "#110c08"; + }; + + solarized = { + bg = "#0d2323"; + surface = "#162e2d"; + secondary = "#203838"; + fg = "#8b9c9d"; + muted = "#6d7e7f"; + dim = "#566768"; + border = "#1c3232"; + accent = "#75a33e"; + accent2 = "#ba9232"; + success = "#859900"; + warning = "#b58900"; + info = "#268bd2"; + error = "#db4241"; + selectionFg = "#031a1a"; + }; + + nord = { + bg = "#2e333d"; + surface = "#383d48"; + secondary = "#424853"; + fg = "#d3d8e0"; + muted = "#979fab"; + dim = "#79818d"; + border = "#3e434e"; + accent = "#96ce9d"; + accent2 = "#e2cc91"; + success = "#a3be8c"; + warning = "#ebcb8b"; + info = "#88c0d0"; + error = "#d55753"; + selectionFg = "#292e38"; + }; + + gruvbox = { + bg = "#282622"; + surface = "#33302b"; + secondary = "#3e3a34"; + fg = "#e2d7ba"; + muted = "#968f7b"; + dim = "#817a66"; + border = "#3a3632"; + accent = "#b2cb52"; + accent2 = "#eebc4a"; + success = "#b8bb26"; + warning = "#fabd2f"; + info = "#83a598"; + error = "#e9483d"; + selectionFg = "#282622"; + }; + + mono = { + bg = "#0a0a0a"; + surface = "#121212"; + secondary = "#1c1c1c"; + fg = "#e5e5e5"; + muted = "#8a8a8a"; + dim = "#5c5c5c"; + border = "#1c1c1c"; + accent = "#ebebeb"; + accent2 = "#a3a3a3"; + success = "#ebebeb"; + warning = "#c7c7c7"; + info = "#a3a3a3"; + error = "#ffffff"; + selectionFg = "#0a0a0a"; + }; + + apathy = { + bg = "#0e0b13"; + surface = "#1b1629"; + secondary = "#2a2441"; + fg = "#aabbbb"; + muted = "#7d7a8b"; + dim = "#4d4a56"; + border = "#2a2630"; + accent = "#77f5c9"; + accent2 = "#ffcb6b"; + success = "#77f5c9"; + warning = "#ffcb6b"; + info = "#82aaff"; + error = "#e61f44"; + selectionFg = "#0e0b13"; + }; + + paper = { + bg = "#f4f2ec"; + surface = "#faf8f4"; + secondary = "#e6e4df"; + fg = "#252a27"; + muted = "#545a55"; + dim = "#6e736e"; + border = "#e2e0da"; + accent = "#1e7729"; + accent2 = "#9f6200"; + success = "#1e7729"; + warning = "#9f6200"; + info = "#2563a6"; + error = "#cc2827"; + selectionFg = "#f7f5f1"; + }; + + prelude = { + bg = "#0e0b13"; + surface = "#1b1621"; + secondary = "#8787af"; + fg = "#c0c0c0"; + muted = "#8787af"; + dim = "#444444"; + border = "#444444"; + accent = "#ff87d7"; + accent2 = "#c1f98e"; + success = "#c1f98e"; + warning = "#ffd787"; + info = "#87d7ff"; + error = "#ff005f"; + selectionFg = "#0e0b13"; + }; + }; + + mkProcessComposeTheme = + name: palette: + { + style = { + name = "Prelude ${name}"; + body = { + fgColor = palette.fg; + bgColor = palette.bg; + secondaryTextColor = palette.accent2; + tertiaryTextColor = palette.success; + borderColor = palette.border; + }; + stat_table = { + keyFgColor = palette.accent2; + valueFgColor = palette.fg; + logoColor = palette.accent; + }; + proc_table = { + fgColor = palette.info; + fgWarning = palette.warning; + fgPending = palette.dim; + fgCompleted = palette.success; + fgError = palette.error; + headerFgColor = palette.accent2; + }; + help = { + fgColor = palette.fg; + keyColor = palette.accent; + hlColor = palette.secondary; + categoryFgColor = palette.accent2; + }; + dialog = { + fgColor = palette.fg; + bgColor = palette.surface; + contrastBgColor = palette.secondary; + attentionBgColor = palette.error; + buttonFgColor = palette.selectionFg; + buttonBgColor = palette.accent; + buttonFocusFgColor = palette.selectionFg; + buttonFocusBgColor = palette.info; + labelFgColor = palette.accent2; + fieldFgColor = palette.fg; + fieldBgColor = palette.secondary; + }; + }; + }; +in +lib.mapAttrs mkProcessComposeTheme palettes diff --git a/nix/stackpanel/modules/process-compose/ui.nix b/nix/stackpanel/modules/process-compose/ui.nix index fec2da07..bc997c71 100644 --- a/nix/stackpanel/modules/process-compose/ui.nix +++ b/nix/stackpanel/modules/process-compose/ui.nix @@ -12,6 +12,7 @@ let meta = import ./meta.nix; cfg = config.stackpanel; pcCfg = cfg.process-compose or { }; + preludeTheme = cfg.prelude.theme or null; in lib.mkIf (cfg.enable && (pcCfg.enable or false)) { stackpanel.panels."${meta.id}-status" = { @@ -36,6 +37,17 @@ lib.mkIf (cfg.enable && (pcCfg.enable or false)) { type = "FIELD_TYPE_BOOLEAN"; value = if (pcCfg.formatWatcher.enable or true) then "true" else "false"; } + { + name = "theme"; + type = "FIELD_TYPE_STRING"; + value = + if pcCfg.theme != null then + pcCfg.theme + else if preludeTheme != null then + preludeTheme + else + "minted"; + } ]; }; } From 4e4409c1f0f33cc85abca34a99b5f412b2d9ab76 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 29 Sep 2026 03:45:53 +0000 Subject: [PATCH 2/2] refactor: source process-compose themes from prelude Co-authored-by: cooper --- .../content/docs/modules/process-compose.mdx | 3 + .../modules/process-compose/module.nix | 13 +- .../modules/process-compose/themes.nix | 179 +----------------- 3 files changed, 16 insertions(+), 179 deletions(-) diff --git a/apps/docs/content/docs/modules/process-compose.mdx b/apps/docs/content/docs/modules/process-compose.mdx index 99d99a95..497539bb 100644 --- a/apps/docs/content/docs/modules/process-compose.mdx +++ b/apps/docs/content/docs/modules/process-compose.mdx @@ -34,3 +34,6 @@ stackpanel.process-compose.theme = "nord"; The generated theme is written to `.stack/gen/process-compose/theme.yaml` and the `dev` wrapper starts Process Compose with `--theme "Custom Style"`. +`process-compose-flake` exposes generated process settings and CLI wrapper +options, but no theme-definition hook, so Stackpanel renders Prelude's Nix +theme attrset into Process Compose's required custom `theme.yaml` shape. diff --git a/nix/stackpanel/modules/process-compose/module.nix b/nix/stackpanel/modules/process-compose/module.nix index 0667816d..3d90ab32 100644 --- a/nix/stackpanel/modules/process-compose/module.nix +++ b/nix/stackpanel/modules/process-compose/module.nix @@ -29,6 +29,7 @@ # config.stackpanel.process-compose.package # ============================================================================== { + inputs ? { }, lib, config, pkgs, @@ -36,7 +37,11 @@ }: let meta = import ./meta.nix; - preludeProcessComposeThemes = import ./themes.nix { inherit lib; }; + hasPreludeThemes = inputs ? prelude && inputs.prelude ? lib && inputs.prelude.lib ? themes; + preludeThemes = if hasPreludeThemes then inputs.prelude.lib.themes else { }; + preludeProcessComposeThemes = import ./themes.nix { + inherit lib preludeThemes; + }; preludeProcessComposeThemeNames = builtins.attrNames preludeProcessComposeThemes; cfg = config.stackpanel; pcCfg = cfg.process-compose; @@ -637,7 +642,11 @@ in configFile = mkConfigFile { inherit (pcCfg) processes; }; - themeFile = (pkgs.formats.yaml { }).generate "process-compose-theme.yaml" preludeProcessComposeThemes.${effectiveTheme}; + themeFile = + if hasPreludeThemes then + (pkgs.formats.yaml { }).generate "process-compose-theme.yaml" preludeProcessComposeThemes.${effectiveTheme} + else + throw "process-compose: generated Prelude themes require the `prelude` flake input"; themeConfigDir = "\${STACKPANEL_ROOT:-$PWD}/.stack/gen/process-compose"; in { diff --git a/nix/stackpanel/modules/process-compose/themes.nix b/nix/stackpanel/modules/process-compose/themes.nix index b437aac3..19a52aca 100644 --- a/nix/stackpanel/modules/process-compose/themes.nix +++ b/nix/stackpanel/modules/process-compose/themes.nix @@ -1,180 +1,5 @@ -{ lib }: +{ lib, preludeThemes }: let - # Ported from darkmatter/prelude src/prelude/themes.nix at the pinned - # Stackpanel Prelude revision. Keep token names aligned with Prelude so - # stackpanel.prelude.theme can select the matching Process Compose style. - palettes = { - phosphor = { - bg = "#0c110e"; - surface = "#131715"; - secondary = "#202622"; - fg = "#d5e2d7"; - muted = "#7d8a81"; - dim = "#5d665f"; - border = "#1a201d"; - accent = "#68e371"; - accent2 = "#f9b64f"; - success = "#68e371"; - warning = "#f9b64f"; - info = "#5eb7ff"; - error = "#e6443d"; - selectionFg = "#0c110e"; - }; - - minted = { - bg = "#0c0c13"; - surface = "#161623"; - secondary = "#24243f"; - fg = "#979db5"; - muted = "#6a6c85"; - dim = "#4a5085"; - border = "#1d1d2f"; - accent = "#f2cdcd"; - accent2 = "#CC99FF"; - success = "#b7ce99"; - warning = "#f2c17d"; - info = "#89b4fa"; - error = "#ee848e"; - selectionFg = "#0c0c13"; - }; - - amber = { - bg = "#110c08"; - surface = "#19120e"; - secondary = "#291f18"; - fg = "#fcbe62"; - muted = "#a97c46"; - dim = "#8d6a43"; - border = "#261c14"; - accent = "#ffc761"; - accent2 = "#fadc7d"; - success = "#a8c66c"; - warning = "#fadc7d"; - info = "#7fb4ca"; - error = "#e9523f"; - selectionFg = "#110c08"; - }; - - solarized = { - bg = "#0d2323"; - surface = "#162e2d"; - secondary = "#203838"; - fg = "#8b9c9d"; - muted = "#6d7e7f"; - dim = "#566768"; - border = "#1c3232"; - accent = "#75a33e"; - accent2 = "#ba9232"; - success = "#859900"; - warning = "#b58900"; - info = "#268bd2"; - error = "#db4241"; - selectionFg = "#031a1a"; - }; - - nord = { - bg = "#2e333d"; - surface = "#383d48"; - secondary = "#424853"; - fg = "#d3d8e0"; - muted = "#979fab"; - dim = "#79818d"; - border = "#3e434e"; - accent = "#96ce9d"; - accent2 = "#e2cc91"; - success = "#a3be8c"; - warning = "#ebcb8b"; - info = "#88c0d0"; - error = "#d55753"; - selectionFg = "#292e38"; - }; - - gruvbox = { - bg = "#282622"; - surface = "#33302b"; - secondary = "#3e3a34"; - fg = "#e2d7ba"; - muted = "#968f7b"; - dim = "#817a66"; - border = "#3a3632"; - accent = "#b2cb52"; - accent2 = "#eebc4a"; - success = "#b8bb26"; - warning = "#fabd2f"; - info = "#83a598"; - error = "#e9483d"; - selectionFg = "#282622"; - }; - - mono = { - bg = "#0a0a0a"; - surface = "#121212"; - secondary = "#1c1c1c"; - fg = "#e5e5e5"; - muted = "#8a8a8a"; - dim = "#5c5c5c"; - border = "#1c1c1c"; - accent = "#ebebeb"; - accent2 = "#a3a3a3"; - success = "#ebebeb"; - warning = "#c7c7c7"; - info = "#a3a3a3"; - error = "#ffffff"; - selectionFg = "#0a0a0a"; - }; - - apathy = { - bg = "#0e0b13"; - surface = "#1b1629"; - secondary = "#2a2441"; - fg = "#aabbbb"; - muted = "#7d7a8b"; - dim = "#4d4a56"; - border = "#2a2630"; - accent = "#77f5c9"; - accent2 = "#ffcb6b"; - success = "#77f5c9"; - warning = "#ffcb6b"; - info = "#82aaff"; - error = "#e61f44"; - selectionFg = "#0e0b13"; - }; - - paper = { - bg = "#f4f2ec"; - surface = "#faf8f4"; - secondary = "#e6e4df"; - fg = "#252a27"; - muted = "#545a55"; - dim = "#6e736e"; - border = "#e2e0da"; - accent = "#1e7729"; - accent2 = "#9f6200"; - success = "#1e7729"; - warning = "#9f6200"; - info = "#2563a6"; - error = "#cc2827"; - selectionFg = "#f7f5f1"; - }; - - prelude = { - bg = "#0e0b13"; - surface = "#1b1621"; - secondary = "#8787af"; - fg = "#c0c0c0"; - muted = "#8787af"; - dim = "#444444"; - border = "#444444"; - accent = "#ff87d7"; - accent2 = "#c1f98e"; - success = "#c1f98e"; - warning = "#ffd787"; - info = "#87d7ff"; - error = "#ff005f"; - selectionFg = "#0e0b13"; - }; - }; - mkProcessComposeTheme = name: palette: { @@ -222,4 +47,4 @@ let }; }; in -lib.mapAttrs mkProcessComposeTheme palettes +lib.mapAttrs mkProcessComposeTheme preludeThemes