From 964e2309d60845a5d6d87b4afc8d95a006dc9d6c Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 2 Jul 2026 14:37:52 -0400 Subject: [PATCH 1/8] feat(theme): add semantic colour role layer Introduce six @thm_role_{accent,info,notice,muted,alert,error} options, each a live reference to one @thm_* palette slot. Defined right after load_theme.sh resolves the active theme and before modules/*.conf are sourced, so module defaults can build on top of them in a follow-up change. Roles stay refs (not flattened hex) rather than pre-resolved values: verified on an isolated tmux socket that the existing #{E:} render pipeline fully resolves the module_color -> role -> palette chain in one pass, and that the harness-style theme-switch reset re-derives roles exactly like the rest of @thm_*. --- themux.conf | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/themux.conf b/themux.conf index de15adc..59c4ecd 100644 --- a/themux.conf +++ b/themux.conf @@ -21,6 +21,24 @@ run "#{d:current_file}/utils/load_theme.sh" %hidden TMX_MESSAGE_BACKGROUND="#{E:@themux_status_background}" %endif +# Semantic colour roles (DO NOT USE -o IN YOUR OWN CONFIGURATION). Each role maps +# to one @thm_* palette slot, so every module that accents through a role tracks +# a theme switch automatically instead of hardwiring a raw palette colour. Defined +# here — right after load_theme.sh has resolved @thm_* for the active theme, and +# before any modules/*.conf is sourced below — so every module accent default can +# reference a role that already exists. -ogq keeps the usual precedence: a role set +# BEFORE the plugin loads (in your config, or by a custom theme file) wins over +# this default. The stored value is a #{E:@thm_x} ref, not a flattened hex, so a +# later theme switch + reload re-resolves it exactly like any other @thm_* token +# (see docs/reference/configuration.md's "Colour roles" section for the mapping +# rationale and the full remap of module defaults built on top of these roles). +set -ogq @thm_role_accent "#{E:@thm_green}" +set -ogq @thm_role_info "#{E:@thm_sapphire}" +set -ogq @thm_role_notice "#{E:@thm_peach}" +set -ogq @thm_role_muted "#{E:@thm_lavender}" +set -ogq @thm_role_alert "#{E:@thm_yellow}" +set -ogq @thm_role_error "#{E:@thm_red}" + source -F "#{d:current_file}/modules/application.conf" source -F "#{d:current_file}/modules/battery.conf" source -F "#{d:current_file}/modules/clima.conf" From e4ba766c9c739b5c4448025866e3557b7d760282 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 2 Jul 2026 14:38:12 -0400 Subject: [PATCH 2/8] feat(modules)!: default module accents to colour roles Point every module's _color/_active_color/_context_color default at the new @thm_role_* layer instead of a hardwired palette slot, so all built-in themes read as one coherent set instead of clashing per module. kube_namespace_color, application_color and the cpu/ram live level-colour escalation are deliberately left untouched (no role counterpart, or a live threshold that a static role can't model). Regenerates the three existing *_expected.txt fixtures whose captured output embeds one of the changed defaults (git's resting/dirty accent, load's icon colour, session's active-prefix colour). BREAKING CHANGE: module accent defaults changed. - @themux_git_color / @themux_gitmux_color: teal -> role_accent (green) - @themux_git_active_color: peach -> role_notice (same hex, ref changed) - @themux_session_color: green -> role_accent (same hex, ref changed) - @themux_session_active_color: red -> role_alert (yellow) - @themux_clima_color / @themux_weather_color: yellow -> role_notice (peach) - @themux_battery_color: lavender -> role_muted (same hex, ref changed) - @themux_kube_color: blue -> role_info (sapphire) - @themux_kube_context_color: red -> role_info (sapphire) - @themux_directory_color: blue -> role_info (sapphire) - @themux_load_color: blue -> role_info (sapphire) - @themux_host_color: mauve -> role_info (sapphire) - @themux_date_time_color: sapphire -> role_info (same hex, ref changed) - @themux_time_color: sky -> role_info (sapphire) - @themux_zoom_color: yellow -> role_alert (same hex, ref changed) - @themux_pomodoro_plus_color: peach -> role_notice (same hex, ref changed) Override the affected @themux__color (or the role itself) before the plugin loads to keep the previous colour. --- modules/battery.conf | 2 +- modules/clima.conf | 2 +- modules/date_time.conf | 2 +- modules/directory.conf | 2 +- modules/git.conf | 4 ++-- modules/gitmux.conf | 2 +- modules/host.conf | 2 +- modules/kube.conf | 4 ++-- modules/load.conf | 2 +- modules/pomodoro_plus.conf | 2 +- modules/session.conf | 6 +++--- modules/time.conf | 2 +- modules/weather.conf | 2 +- modules/zoom.conf | 2 +- tests/git_module_expected.txt | 4 ++-- tests/load_module_expected.txt | 2 +- tests/naked_style_expected.txt | 2 +- 17 files changed, 22 insertions(+), 22 deletions(-) diff --git a/modules/battery.conf b/modules/battery.conf index f2b1fdc..1aeeb2f 100644 --- a/modules/battery.conf +++ b/modules/battery.conf @@ -17,7 +17,7 @@ set -ogq @batt_icon_status_attached "󱈑" set -ogq "@themux_${MODULE_NAME}_icon" "#{l:#{battery_icon}}" set -ogq "@themux_${MODULE_NAME}_label" "bat" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_lavender}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_muted}" set -ogq "@themux_${MODULE_NAME}_text" "#{l:#{battery_percentage}}" # tmux-battery only resolves #{battery_*} into status-left/right; mark the module diff --git a/modules/clima.conf b/modules/clima.conf index e4dbea0..83edec9 100644 --- a/modules/clima.conf +++ b/modules/clima.conf @@ -4,7 +4,7 @@ set -ogq "@themux_${MODULE_NAME}_icon" "" set -ogq "@themux_${MODULE_NAME}_label" "clima" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_yellow}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_notice}" set -ogq "@themux_${MODULE_NAME}_text" "#{l:#{clima}}" # Resolve the plugin's #{clima} literal in any status-line zone (see layout.sh). diff --git a/modules/date_time.conf b/modules/date_time.conf index 15e6daf..4e351f7 100644 --- a/modules/date_time.conf +++ b/modules/date_time.conf @@ -2,7 +2,7 @@ %hidden MODULE_NAME="date_time" set -ogq "@themux_${MODULE_NAME}_icon" "󰃰" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_sapphire}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_info}" set -ogq "@themux_${MODULE_NAME}_text" "%Y-%m-%d %H:%M" source -F "#{d:current_file}/../utils/module_block.conf" diff --git a/modules/directory.conf b/modules/directory.conf index cff588d..16ec199 100644 --- a/modules/directory.conf +++ b/modules/directory.conf @@ -2,7 +2,7 @@ %hidden MODULE_NAME="directory" set -ogq "@themux_${MODULE_NAME}_icon" "󰉋" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_blue}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_info}" set -ogq "@themux_${MODULE_NAME}_text" "#{b:pane_current_path}" source -F "#{d:current_file}/../utils/module_block.conf" diff --git a/modules/git.conf b/modules/git.conf index 3598fcb..8a9518c 100644 --- a/modules/git.conf +++ b/modules/git.conf @@ -6,7 +6,7 @@ # styled like any other module and every prop (variant, shape, padding, # leading_position) keeps working. No external binary required. set -ogq "@themux_${MODULE_NAME}_icon" "󰊢" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_teal}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_accent}" set -ogq "@themux_${MODULE_NAME}_symbol_clean" "✓" set -ogq "@themux_${MODULE_NAME}_symbol_dirty" "!" @@ -18,7 +18,7 @@ set -gq "@themux_${MODULE_NAME}_text" ' #("#{E:@_tmx_git_script}" "#{pane_curren # A dirty work tree warms the accent through the shared active machinery: the # probe prints 1 or 0 (tmux reads the string "0" as false) and checks any # change — tracked or untracked — so the accent always agrees with the text. -set -ogq "@themux_${MODULE_NAME}_active_color" "#{E:@thm_peach}" +set -ogq "@themux_${MODULE_NAME}_active_color" "#{E:@thm_role_notice}" set -gq "@_tmx_${MODULE_NAME}_dirty" '#("#{E:@_tmx_git_script}" --dirty "#{pane_current_path}")' set -ogq "@themux_${MODULE_NAME}_active_when" "#{E:@_tmx_${MODULE_NAME}_dirty}" diff --git a/modules/gitmux.conf b/modules/gitmux.conf index 0bcf7fd..efd8203 100644 --- a/modules/gitmux.conf +++ b/modules/gitmux.conf @@ -3,7 +3,7 @@ # Requires https://github.com/arl/gitmux (uses gitmux's own defaults). set -ogq "@themux_${MODULE_NAME}_icon" "󰊢" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_teal}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_accent}" set -gq "@themux_${MODULE_NAME}_text" ' #(gitmux "#{pane_current_path}")' # gitmux paints its own segments and resets the background between them (#[none], diff --git a/modules/host.conf b/modules/host.conf index f83b5d0..99145d7 100644 --- a/modules/host.conf +++ b/modules/host.conf @@ -2,7 +2,7 @@ %hidden MODULE_NAME="host" set -ogq "@themux_${MODULE_NAME}_icon" "󰒋" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_mauve}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_info}" set -ogq "@themux_${MODULE_NAME}_text" "#H" source -F "#{d:current_file}/../utils/module_block.conf" diff --git a/modules/kube.conf b/modules/kube.conf index fc3438b..c43c15f 100644 --- a/modules/kube.conf +++ b/modules/kube.conf @@ -6,8 +6,8 @@ set -ogq "@themux_${MODULE_NAME}_icon" "󱃾" set -ogq "@themux_${MODULE_NAME}_label" "kube" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_blue}" -set -ogq "@themux_kube_context_color" "#{E:@thm_red}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_info}" +set -ogq "@themux_kube_context_color" "#{E:@thm_role_info}" set -ogq "@themux_kube_namespace_color" "#{E:@thm_sky}" set -ogq "@themux_${MODULE_NAME}_text" \ " #{l:#[fg=#{@themux_kube_context_color}]#{kubectx_context}#[fg=default]:#[fg=#{@themux_kube_namespace_color}]#{kubectx_namespace}}" diff --git a/modules/load.conf b/modules/load.conf index d81c429..7474622 100644 --- a/modules/load.conf +++ b/modules/load.conf @@ -3,7 +3,7 @@ set -ogq "@themux_${MODULE_NAME}_icon" "󰊚" set -ogq "@themux_${MODULE_NAME}_label" "load" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_blue}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_info}" set -ogq "@themux_${MODULE_NAME}_text" "#(uptime | awk '{split(substr($0, index($0, \"load\")), a, \":\"); print a[2]}')" source -F "#{d:current_file}/../utils/module_block.conf" diff --git a/modules/pomodoro_plus.conf b/modules/pomodoro_plus.conf index 0222896..d154393 100644 --- a/modules/pomodoro_plus.conf +++ b/modules/pomodoro_plus.conf @@ -4,7 +4,7 @@ set -ogq "@themux_${MODULE_NAME}_icon" "" set -ogq "@themux_${MODULE_NAME}_label" "pomo" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_peach}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_notice}" set -ogq "@themux_${MODULE_NAME}_text" "#{l:#{pomodoro_status}}" # Resolve the plugin's #{pomodoro_status} literal in any status-line zone (layout.sh). diff --git a/modules/session.conf b/modules/session.conf index 36640c9..9f686e5 100644 --- a/modules/session.conf +++ b/modules/session.conf @@ -2,12 +2,12 @@ %hidden MODULE_NAME="session" set -ogq "@themux_${MODULE_NAME}_icon" "" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_green}" # resting (no prefix) +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_accent}" # resting (no prefix) set -ogq "@themux_${MODULE_NAME}_text" "#S" -# While the prefix is held the selected state target takes a red accent: +# While the prefix is held the selected state target takes the alert accent: # auto keeps it on the visible leading slot, or text/both/off can force it. -set -ogq "@themux_${MODULE_NAME}_active_color" "#{E:@thm_red}" +set -ogq "@themux_${MODULE_NAME}_active_color" "#{E:@thm_role_alert}" set -ogq "@themux_${MODULE_NAME}_active_when" "client_prefix" source -F "#{d:current_file}/../utils/module_block.conf" diff --git a/modules/time.conf b/modules/time.conf index 27cadc9..dc392d7 100644 --- a/modules/time.conf +++ b/modules/time.conf @@ -3,7 +3,7 @@ # Clock-only counterpart to date_time: just the time, no date. set -ogq "@themux_${MODULE_NAME}_icon" "" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_sky}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_info}" set -ogq "@themux_${MODULE_NAME}_text" "%H:%M" source -F "#{d:current_file}/../utils/module_block.conf" diff --git a/modules/weather.conf b/modules/weather.conf index 70db690..4044568 100644 --- a/modules/weather.conf +++ b/modules/weather.conf @@ -5,7 +5,7 @@ set -ogq @themux_${MODULE_NAME}_icon "" set -ogq "@themux_${MODULE_NAME}_label" "weather" -set -ogq @themux_${MODULE_NAME}_color "#{E:@thm_yellow}" +set -ogq @themux_${MODULE_NAME}_color "#{E:@thm_role_notice}" set -ogq @themux_${MODULE_NAME}_text "#{l:#{weather}}" # Resolve the plugin's #{weather} literal in any status-line zone (see layout.sh). diff --git a/modules/zoom.conf b/modules/zoom.conf index 2c78fa5..641e9ce 100644 --- a/modules/zoom.conf +++ b/modules/zoom.conf @@ -2,7 +2,7 @@ %hidden MODULE_NAME="zoom" set -ogq "@themux_${MODULE_NAME}_icon" "" -set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_yellow}" +set -ogq "@themux_${MODULE_NAME}_color" "#{E:@thm_role_alert}" set -ogq "@themux_${MODULE_NAME}_text" "zoom" # The zoom module only renders while the active pane is zoomed; module_render.sh diff --git a/tests/git_module_expected.txt b/tests/git_module_expected.txt index 92562b0..bd0ab82 100644 --- a/tests/git_module_expected.txt +++ b/tests/git_module_expected.txt @@ -3,9 +3,9 @@ @themux_git_active_when #{E:@_tmx_git_dirty} -@themux_git_active_color #{E:@thm_peach} +@themux_git_active_color #{E:@thm_role_notice} -@themux_git_color #{E:@thm_teal} +@themux_git_color #{E:@thm_role_accent} in_repo_probe rev-parse --is-inside-work-tree dirty_probe #("#{E:@_tmx_git_script}" --dirty diff --git a/tests/load_module_expected.txt b/tests/load_module_expected.txt index e69c386..d955d82 100644 --- a/tests/load_module_expected.txt +++ b/tests/load_module_expected.txt @@ -1,2 +1,2 @@ -E:@themux_module_load #[fg=#89b4fa]#[bg=default]█#[fg=#11111b,bg=#89b4fa] 󰊚 #[fg=#cdd6f4,bg=#313244] #[bg=#313244] #[fg=#313244]#[bg=default]█ +E:@themux_module_load #[fg=#74c7ec]#[bg=default]█#[fg=#11111b,bg=#74c7ec] 󰊚 #[fg=#cdd6f4,bg=#313244] #[bg=#313244] #[fg=#313244]#[bg=default]█ diff --git a/tests/naked_style_expected.txt b/tests/naked_style_expected.txt index ec67325..349cb2d 100644 --- a/tests/naked_style_expected.txt +++ b/tests/naked_style_expected.txt @@ -1,7 +1,7 @@ @themux_module_application #[fg=#eba0ac,bg=default]  #[fg=#eba0ac,bg=default] #{E:@themux_application_text}#[bg=default] -@themux_module_session #[fg=#{?client_prefix,#f38ba8,#a6e3a1},bg=default]  #[fg=#a6e3a1,bg=default] #{E:@themux_session_text}#[bg=default] +@themux_module_session #[fg=#{?client_prefix,#f9e2af,#a6e3a1},bg=default]  #[fg=#a6e3a1,bg=default] #{E:@themux_session_text}#[bg=default] @_tmx_module_divider #[fg=#{E:@themux_module_divider_color},bg=default]#{E:@themux_module_divider} From 1df941e3495e3b23a7ddedf9e837ada782a39ec3 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 2 Jul 2026 14:38:21 -0400 Subject: [PATCH 3/8] test(colors): cover the semantic colour role layer Add tests/color_roles.sh (+ expected fixture), registered in run_tests.sh. Covers: the six roles resolve to the correct default palette hex through the expanded #{E:} core (not the raw ref); kube's icon accent renders the role colour through the real module render path; overriding @thm_role_info before re-sourcing re-colours every info-family module at once (directory, host, date_time, time, load, kube); cpu/ram stay wired to tmux-cpu's live level colours, not to any role; session's active-prefix accent is now role_alert instead of the old hardwired red; and a harness-style theme switch re-resolves a role from the new theme's palette. Also documents (via kube_context_color_is_role_info / kube_namespace_color_untouched, without asserting on the rendered core) a pre-existing, unrelated quirk found while writing this test: kube's context/namespace fg colours are composed inside an #{l:...} (literal, never-expanded) block in @themux_kube_text, so they were already not resolving through the render pipeline before this change -- confirmed unaffected by the accent both being a raw @thm_* ref and being a @thm_role_* ref. --- run_tests.sh | 1 + tests/color_roles.sh | 112 +++++++++++++++++++++++++++++++++ tests/color_roles_expected.txt | 33 ++++++++++ 3 files changed, 146 insertions(+) create mode 100755 tests/color_roles.sh create mode 100644 tests/color_roles_expected.txt diff --git a/run_tests.sh b/run_tests.sh index c73cf3c..f949410 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -13,6 +13,7 @@ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) "${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/application_module.sh --expected "${script_dir}"/tests/application_module_expected.txt "$@" "${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/battery_module.sh --expected "${script_dir}"/tests/battery_module_expected.txt "$@" +"${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/color_roles.sh --expected "${script_dir}"/tests/color_roles_expected.txt "$@" "${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/cpu_module.sh --expected "${script_dir}"/tests/cpu_module_expected.txt "$@" "${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/git_module.sh --expected "${script_dir}"/tests/git_module_expected.txt "$@" "${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/load_module.sh --expected "${script_dir}"/tests/load_module_expected.txt "$@" diff --git a/tests/color_roles.sh b/tests/color_roles.sh new file mode 100755 index 0000000..ee8e5df --- /dev/null +++ b/tests/color_roles.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) +# shellcheck disable=SC1091 +source "${script_dir}/helpers.sh" + +core() { tmux show -gqv "@_tmx_module_$1_core"; } +core_e() { tmux display -p "#{E:@_tmx_module_$1_core}"; } # expanded: state routed, live colour in its final slot +src() { + tmux source "${script_dir}/../themux_options.conf" + tmux source "${script_dir}/../themux.conf" +} + +# --- 1. The six roles exist and resolve, through the EXPANDED #{E:} core (not +# the raw ref), to catppuccin-mocha's default palette hex. ------------------ +tmux set -g @themux_status_line_1 "directory host date_time time load kube session zoom" +src + +printf "role_accent "; tmux display -p '#{E:@thm_role_accent}' +printf "\nrole_info "; tmux display -p '#{E:@thm_role_info}' +printf "\nrole_notice "; tmux display -p '#{E:@thm_role_notice}' +printf "\nrole_muted "; tmux display -p '#{E:@thm_role_muted}' +printf "\nrole_alert "; tmux display -p '#{E:@thm_role_alert}' +printf "\nrole_error "; tmux display -p '#{E:@thm_role_error}' + +# The stored option is a #{E:@thm_x} REF, not a flattened hex (mechanism (a) — +# see themux.conf) — so it keeps tracking a theme switch instead of freezing. +printf "\nrole_info_raw_is_ref " +{ tmux show -gqv @thm_role_info | grep -qF '#{E:@thm_sapphire}'; } && printf "Y" || printf "n" + +# --- 2. A real module accent renders with the role's colour through the real +# module render path (module_render.sh's acc()/bake(), not a synthetic probe). +# kube changed blue -> role_info AND has three colour-bearing options; only its +# icon block (@themux_kube_color) actually routes through acc()/bake() into the +# baked core below. @themux_kube_context_color/_namespace_color are composed by +# hand inside an #{l:...} (literal, never-expanded) block in @themux_kube_text — +# a PRE-EXISTING characteristic of kube.conf, unrelated to and unchanged by the +# colour-role work (confirmed unchanged before/after: #{@themux_kube_context_color} +# leaks into @_tmx_module_kube_core's EXPANDED core literally either way). So this +# only exercises the icon block through the real render path, and separately +# confirms the two text-only options still got the correct role default assigned. +printf "\nkube_icon_role_info " +{ core kube | grep -qF 'bg=#74c7ec'; } && printf "Y" || printf "n" +printf "\nkube_context_color_is_role_info " +{ tmux show -gqv @themux_kube_context_color | grep -qF '@thm_role_info'; } && printf "Y" || printf "n" +printf "\nkube_namespace_color_untouched " +{ tmux show -gqv @themux_kube_namespace_color | grep -qF '@thm_sky'; } && printf "Y" || printf "n" + +# --- 3. Single mapping point: overriding @thm_role_info re-colours every +# info-family module at once (directory, host, date_time, time, load, kube). --- +before_directory=$(core directory) +tmux set -g @thm_role_info "#{E:@thm_red}" +src +printf "\noverride_propagates_directory " +{ core directory | grep -qF 'bg=#f38ba8' && ! echo "$before_directory" | grep -qF 'bg=#f38ba8'; } && printf "Y" || printf "n" +printf "\noverride_propagates_host " +{ core host | grep -qF 'bg=#f38ba8'; } && printf "Y" || printf "n" +printf "\noverride_propagates_date_time " +{ core date_time | grep -qF 'bg=#f38ba8'; } && printf "Y" || printf "n" +printf "\noverride_propagates_time " +{ core time | grep -qF 'bg=#f38ba8'; } && printf "Y" || printf "n" +printf "\noverride_propagates_load " +{ core load | grep -qF 'bg=#f38ba8'; } && printf "Y" || printf "n" +printf "\noverride_propagates_kube " +{ core kube | grep -qF 'bg=#f38ba8'; } && printf "Y" || printf "n" +tmux set -gu @thm_role_info +src + +# --- 4. cpu/ram keep their live level-colour escalation; they are NOT wired to +# any @thm_role_* (deliberate divergence — see modules/cpu.conf, ram.conf). ----- +tmux set -g @themux_status_line_1 "cpu ram" +src +printf "\ncpu_low_color_not_role_driven " +{ ! tmux show -gqv @themux_cpu_low_color | grep -q 'role'; } && printf "Y" || printf "n" +printf "\nram_low_color_not_role_driven " +{ ! tmux show -gqv @themux_ram_low_color | grep -q 'role'; } && printf "Y" || printf "n" +printf "\ncpu_live_escalation_still_wired " +core_e cpu | { grep -o 'cpu_bg_color' || true; } | wc -l | tr -d ' ' +printf "\nram_live_escalation_still_wired " +core_e ram | { grep -o 'ram_bg_color' || true; } | wc -l | tr -d ' ' + +# --- 5. session's active-prefix accent is now role_alert (yellow), not the old +# hardwired red. -------------------------------------------------------------- +tmux set -g @themux_status_line_1 "session" +src +printf "\nsession_active_color_is_role_alert " +{ tmux show -gqv @themux_session_active_color | grep -qF '@thm_role_alert'; } && printf "Y" || printf "n" +printf "\nsession_active_prefix_hex " +tmux display -p '#{E:@themux_session_active_color}' +printf "\nsession_resting_color_is_role_accent " +{ tmux show -gqv @themux_session_color | grep -qF '@thm_role_accent'; } && printf "Y" || printf "n" + +# --- 6. Reload/theme-switch path: a role re-derives from the NEW theme's +# palette after the harness-style reset (unset @thm_*/_tmx_*, then re-source — +# mirrors run_tests.sh's own internal reset and this repo's test convention). +# themux's real @themux_reload_key path only unsets @themux_* (never @thm_*), +# so it does NOT force a mid-session theme switch either — for roles OR for the +# palette itself; this is pre-existing, unrelated to the role layer, so it is +# only exercised here through the harness-style reset that this suite (and +# run_tests.sh) already relies on for every other theme-dependent assertion. --- +before_role_info=$(tmux display -p '#{E:@thm_role_info}') +tmux set -g @themux_theme "kanagawa_wave" +reset_args=() +add() { [ ${#reset_args[@]} -eq 0 ] || reset_args+=(';'); reset_args+=("$@"); } +while read -r o; do add set -gu "$o"; done \ + < <(tmux show -g | awk '$1 ~ /^@(thm_|_tmx_)/ { print $1 }') +[ ${#reset_args[@]} -gt 0 ] && tmux "${reset_args[@]}" +src +after_role_info=$(tmux display -p '#{E:@thm_role_info}') +printf "\ntheme_switch_reresolves_role " +{ [ "$before_role_info" != "$after_role_info" ]; } && printf "Y" || printf "n" +printf "\n" diff --git a/tests/color_roles_expected.txt b/tests/color_roles_expected.txt new file mode 100644 index 0000000..100cd4e --- /dev/null +++ b/tests/color_roles_expected.txt @@ -0,0 +1,33 @@ +role_accent #a6e3a1 + +role_info #74c7ec + +role_notice #fab387 + +role_muted #b4befe + +role_alert #f9e2af + +role_error #f38ba8 + +role_info_raw_is_ref Y +kube_icon_role_info Y +kube_context_color_is_role_info Y +kube_namespace_color_untouched Y +override_propagates_directory Y +override_propagates_host Y +override_propagates_date_time Y +override_propagates_time Y +override_propagates_load Y +override_propagates_kube Y +cpu_low_color_not_role_driven Y +ram_low_color_not_role_driven Y +cpu_live_escalation_still_wired 2 + +ram_live_escalation_still_wired 2 + +session_active_color_is_role_alert Y +session_active_prefix_hex #f9e2af + +session_resting_color_is_role_accent Y +theme_switch_reresolves_role Y From 04b854b29ad1ed2a87ab22f180b73ddded9a9e48 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 2 Jul 2026 14:38:26 -0400 Subject: [PATCH 4/8] docs(colors): document the colour role layer and breaking defaults Add a "Colour roles" section to configuration.md: the six roles, their default palette slot, override syntax, and a breaking-change callout enumerating every module accent default that changed (and why the three exceptions -- kube_namespace_color, application_color, cpu/ram -- were deliberately left out of the remap). Update status-line.md's git and kube examples, which showed the old raw @thm_teal/@thm_red defaults instead of the new role references. README.md was checked and has no stale per-module accent examples for the modules in the remap table. --- docs/reference/configuration.md | 64 +++++++++++++++++++++++++++++++++ docs/reference/status-line.md | 6 ++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 040aeec..beead58 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -44,6 +44,70 @@ its text block `subtle` (grey fill, accent text), so the two halves stand apart. | --- | --- | --- | | `@themux_theme` | `catppuccin_mocha` | Palette: `catppuccin_{latte,frappe,macchiato,mocha}`, `kanagawa_{wave,dragon,lotus}`, or `kanso_{zen,ink,mist,pearl}`. Loads `themes/.palette` (or a legacy `_tmux.conf`), which exposes the `@thm_*` colors. | +### Colour roles + +Module accents default to **six semantic roles**, not raw palette slots. Each +role maps to one `@thm_*` colour once; every module that accents through a role +follows that one mapping, so a single override re-colours every module that +shares the role instead of you hand-editing each one. + +| Role | Default palette slot | Meaning | +| --- | --- | --- | +| `@thm_role_accent` | `@thm_green` | Primary/VCS-family accent (git, session). | +| `@thm_role_info` | `@thm_sapphire` | Informational/system modules (directory, host, load, date/time, kube). | +| `@thm_role_notice` | `@thm_peach` | Attention without alarm (dirty git, weather/clima, pomodoro). | +| `@thm_role_muted` | `@thm_lavender` | Low-priority/background info (battery). | +| `@thm_role_alert` | `@thm_yellow` | Draws the eye, not an error (session prefix held, zoom). | +| `@thm_role_error` | `@thm_red` | Reserved for genuine error states (not assigned to any built-in module by default). | + +Roles are `-ogq` options, so **set them before the plugin loads** (same rule as +every other themux option) to override the default: + +```sh +set -g @thm_role_info "#{E:@thm_blue}" # re-colour every info-family module at once +``` + +Each role's stored value is itself a palette reference (`#{E:@thm_}`), not +a frozen hex, so it keeps tracking a theme switch exactly like `@thm_*` does. A +module's `_color` (and related `_active_color` / `_context_color` / +`_namespace_color`) options now default to `#{E:@thm_role_}` instead of a +raw `#{E:@thm_}` — set `@themux__color` (or the per-slot variant) as +usual to override a single module without touching its role. + +> [!WARNING] +> **Breaking change:** module accent defaults changed in this release — see the +> full list below. +> +> | Module option | Old default | New default | Visible colour change | +> | --- | --- | --- | --- | +> | `@themux_git_color`, `@themux_gitmux_color` | `@thm_teal` | `@thm_role_accent` (green) | Yes | +> | `@themux_git_active_color` | `@thm_peach` | `@thm_role_notice` (peach) | No (same hex) | +> | `@themux_session_color` | `@thm_green` | `@thm_role_accent` (green) | No (same hex) | +> | `@themux_session_active_color` | `@thm_red` | `@thm_role_alert` (yellow) | Yes | +> | `@themux_clima_color`, `@themux_weather_color` | `@thm_yellow` | `@thm_role_notice` (peach) | Yes | +> | `@themux_battery_color` | `@thm_lavender` | `@thm_role_muted` (lavender) | No (same hex) | +> | `@themux_kube_color` | `@thm_blue` | `@thm_role_info` (sapphire) | Yes | +> | `@themux_kube_context_color` | `@thm_red` | `@thm_role_info` (sapphire) | Yes | +> | `@themux_directory_color` | `@thm_blue` | `@thm_role_info` (sapphire) | Yes | +> | `@themux_load_color` | `@thm_blue` | `@thm_role_info` (sapphire) | Yes | +> | `@themux_host_color` | `@thm_mauve` | `@thm_role_info` (sapphire) | Yes | +> | `@themux_date_time_color` | `@thm_sapphire` | `@thm_role_info` (sapphire) | No (same hex) | +> | `@themux_time_color` | `@thm_sky` | `@thm_role_info` (sapphire) | Yes | +> | `@themux_zoom_color` | `@thm_yellow` | `@thm_role_alert` (yellow) | No (same hex) | +> | `@themux_pomodoro_plus_color` | `@thm_peach` | `@thm_role_notice` (peach) | No (same hex) | +> +> Even where the resolved colour is unchanged, the *option's stored value* +> changed from a raw `@thm_*` reference to a `@thm_role_*` reference — if you +> read that raw value back (rather than the rendered colour), account for it. +> +> **Not remapped (intentional):** `@themux_kube_namespace_color` keeps +> `@thm_sky` (an info-family nuance, kept distinct from `@themux_kube_context_color`). +> `@themux_application_color` keeps `@thm_maroon` (no equivalent role — there is +> nothing else in the "primary command" family to unify it with). `cpu`/`ram` +> keep tmux-cpu's live green→yellow→red level-colour escalation untouched — a +> role is a static per-theme mapping, and a live threshold cannot be modelled as +> one; see [Status modules](#status-modules). + ### Component props — per item Each item is styled by independent props, so any combination is valid: the diff --git a/docs/reference/status-line.md b/docs/reference/status-line.md index bb351b3..54e486c 100644 --- a/docs/reference/status-line.md +++ b/docs/reference/status-line.md @@ -216,8 +216,8 @@ through the module active state, so the pill warms natively under any variant and always agrees with the text. ```sh -set -g @themux_git_color "#{E:@thm_teal}" # resting (clean) -set -g @themux_git_active_color "#{E:@thm_peach}" # dirty +set -g @themux_git_color "#{E:@thm_role_accent}" # resting (clean) +set -g @themux_git_active_color "#{E:@thm_role_notice}" # dirty set -g @themux_git_symbol_clean "✓" set -g @themux_git_symbol_dirty "!" ``` @@ -297,7 +297,7 @@ run '~/.config/tmux/plugins/tpm/tpm' **Configure:** ```sh -set -g @themux_kube_context_color "#{@thm_red}" +set -g @themux_kube_context_color "#{E:@thm_role_info}" set -g @themux_kube_namespace_color "#{@thm_sky}" set -g @themux_status_line_1 "windows / kube" From c40effe856a7970a7193409ea9c528269c5dbf5e Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 2 Jul 2026 14:52:34 -0400 Subject: [PATCH 5/8] test(colors): attribute reset mirror to themux.tmux internal reset --- tests/color_roles.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/color_roles.sh b/tests/color_roles.sh index ee8e5df..c192f50 100755 --- a/tests/color_roles.sh +++ b/tests/color_roles.sh @@ -91,13 +91,14 @@ printf "\nsession_resting_color_is_role_accent " { tmux show -gqv @themux_session_color | grep -qF '@thm_role_accent'; } && printf "Y" || printf "n" # --- 6. Reload/theme-switch path: a role re-derives from the NEW theme's -# palette after the harness-style reset (unset @thm_*/_tmx_*, then re-source — -# mirrors run_tests.sh's own internal reset and this repo's test convention). -# themux's real @themux_reload_key path only unsets @themux_* (never @thm_*), -# so it does NOT force a mid-session theme switch either — for roles OR for the -# palette itself; this is pre-existing, unrelated to the role layer, so it is -# only exercised here through the harness-style reset that this suite (and -# run_tests.sh) already relies on for every other theme-dependent assertion. --- +# palette after a reset (unset @thm_*/_tmx_*, then re-source). The unset +# pattern below mirrors themux.tmux's own internal reset block, which is the +# production path that re-establishes the palette on load. +# themux's real @themux_reload_key path (utils/reset.sh) only unsets +# @themux_* (never @thm_*), so it does NOT force a mid-session theme switch +# either — for roles OR for the palette itself; this is pre-existing and +# unrelated to the role layer, so this test exercises the themux.tmux-style +# reset instead. --- before_role_info=$(tmux display -p '#{E:@thm_role_info}') tmux set -g @themux_theme "kanagawa_wave" reset_args=() From 5a1c3431ecfaa872fef2c7ea9d1215da35971732 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 2 Jul 2026 14:52:34 -0400 Subject: [PATCH 6/8] docs(colors): call out user/uptime as outside the role remap --- docs/reference/configuration.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index beead58..de201d3 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -106,7 +106,11 @@ usual to override a single module without touching its role. > nothing else in the "primary command" family to unify it with). `cpu`/`ram` > keep tmux-cpu's live green→yellow→red level-colour escalation untouched — a > role is a static per-theme mapping, and a live threshold cannot be modelled as -> one; see [Status modules](#status-modules). +> one; see [Status modules](#status-modules). `@themux_user_color` (`@thm_sky`) +> and `@themux_uptime_color` (`@thm_sapphire`) also keep their raw palette +> defaults — they are outside the remap, so overriding `@thm_role_info` does +> not affect them; point them at a role yourself (e.g. +> `set -g @themux_user_color "#{E:@thm_role_info}"`) if you want them to follow. ### Component props — per item From 3c83c91dfdc57a3b51f2c17e7d82f210bc8d39e7 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 2 Jul 2026 15:10:20 -0400 Subject: [PATCH 7/8] docs(colors): correct reset-path comment and align doc columns --- docs/reference/status-line.md | 4 ++-- tests/color_roles.sh | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/reference/status-line.md b/docs/reference/status-line.md index 54e486c..a99b04a 100644 --- a/docs/reference/status-line.md +++ b/docs/reference/status-line.md @@ -216,8 +216,8 @@ through the module active state, so the pill warms natively under any variant and always agrees with the text. ```sh -set -g @themux_git_color "#{E:@thm_role_accent}" # resting (clean) -set -g @themux_git_active_color "#{E:@thm_role_notice}" # dirty +set -g @themux_git_color "#{E:@thm_role_accent}" # resting (clean) +set -g @themux_git_active_color "#{E:@thm_role_notice}" # dirty set -g @themux_git_symbol_clean "✓" set -g @themux_git_symbol_dirty "!" ``` diff --git a/tests/color_roles.sh b/tests/color_roles.sh index c192f50..331356a 100755 --- a/tests/color_roles.sh +++ b/tests/color_roles.sh @@ -94,11 +94,11 @@ printf "\nsession_resting_color_is_role_accent " # palette after a reset (unset @thm_*/_tmx_*, then re-source). The unset # pattern below mirrors themux.tmux's own internal reset block, which is the # production path that re-establishes the palette on load. -# themux's real @themux_reload_key path (utils/reset.sh) only unsets -# @themux_* (never @thm_*), so it does NOT force a mid-session theme switch -# either — for roles OR for the palette itself; this is pre-existing and -# unrelated to the role layer, so this test exercises the themux.tmux-style -# reset instead. --- +# The real @themux_reload_key path (utils/reset.sh) reaches the same block: +# it re-sources the user's config, which re-runs themux.tmux, whose internal +# guard unsets @thm_*/@_tmx_* and re-derives palette and roles — so a +# mid-session theme switch works through it too. The harness has no user +# config to re-source, so this test drives the equivalent reset directly. --- before_role_info=$(tmux display -p '#{E:@thm_role_info}') tmux set -g @themux_theme "kanagawa_wave" reset_args=() From 2d2ea7f25230e3c9d25fff2c2e13af37915fc286 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 2 Jul 2026 15:23:20 -0400 Subject: [PATCH 8/8] feat(modules)!: fold user and uptime accents into the info role --- docs/reference/configuration.md | 9 ++++----- modules/uptime.conf | 2 +- modules/user.conf | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index de201d3..0c2df97 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -106,11 +106,10 @@ usual to override a single module without touching its role. > nothing else in the "primary command" family to unify it with). `cpu`/`ram` > keep tmux-cpu's live green→yellow→red level-colour escalation untouched — a > role is a static per-theme mapping, and a live threshold cannot be modelled as -> one; see [Status modules](#status-modules). `@themux_user_color` (`@thm_sky`) -> and `@themux_uptime_color` (`@thm_sapphire`) also keep their raw palette -> defaults — they are outside the remap, so overriding `@thm_role_info` does -> not affect them; point them at a role yourself (e.g. -> `set -g @themux_user_color "#{E:@thm_role_info}"`) if you want them to follow. +> one; see [Status modules](#status-modules). `@themux_user_color` and +> `@themux_uptime_color` default to `@thm_role_info` like the other neutral-data +> modules (for `user` that shifts the default from `@thm_sky` to the info role's +> sapphire; `uptime` was already sapphire, so only the semantics change). ### Component props — per item diff --git a/modules/uptime.conf b/modules/uptime.conf index adcb14a..69afc46 100644 --- a/modules/uptime.conf +++ b/modules/uptime.conf @@ -3,7 +3,7 @@ set -ogq @themux_${MODULE_NAME}_icon "󰔟" set -ogq "@themux_${MODULE_NAME}_label" "uptime" -set -ogq @themux_${MODULE_NAME}_color "#{E:@thm_sapphire}" +set -ogq @themux_${MODULE_NAME}_color "#{E:@thm_role_info}" set -ogq @themux_${MODULE_NAME}_text "#(uptime | sed 's/^[^,]*up *//; s/, *[[:digit:]]* user.*//; s/ day.*, */d /; s/ hr\\(s*\\).*/h/; s/ min\\(s*\\).*/m/; s/ sec\\(s*\\).*/s/; s/\\([0-9]\\{1,2\\}\\):\\([0-9]\\{1,2\\}\\)/\\1h \\2m/;')" source -F "#{d:current_file}/../utils/module_block.conf" diff --git a/modules/user.conf b/modules/user.conf index ca1e0f1..b2d8585 100644 --- a/modules/user.conf +++ b/modules/user.conf @@ -2,7 +2,7 @@ %hidden MODULE_NAME="user" set -ogq @themux_${MODULE_NAME}_icon "" -set -ogq @themux_${MODULE_NAME}_color "#{E:@thm_sky}" +set -ogq @themux_${MODULE_NAME}_color "#{E:@thm_role_info}" set -ogq @themux_${MODULE_NAME}_text "#(whoami)" source -F "#{d:current_file}/../utils/module_block.conf"