git.lucas.co / cce-status-interface
status bar
git clone https://git.lucas.co/cce-status-interface.git

commit8be4ba6146d2565bf578e299522429c9f6a64c08
parent59d2822699
authorLucas Galante <[email protected]>
date2026-08-28 11:00
docs: correct the notes the last few features left behind

An audit of CLAUDE.md against the code, and of the code against itself,
after the contrast work landed.

CLAUDE.md had the status feed subscribing to `layout`, `title`,
`modifiers` and `dismiss` on a flat 1s reconnect. Three of those four
facts had moved: `layout`/`title` are subscribed only by the process
owning the window module, `backdrop` joins `dismiss` in every process,
and reconnects have backed off 1s-to-30s since d35e2b5. `modifiers` is
the interesting one — the compositor still offers it, so the line read
as plausible, but nothing here subscribes and the topic match ends in
`unreachable!()`, so following the doc would have panicked a module
process. It now says to add the arm first.

Also there: `get_module_side` was documented without its first branch,
so `light_source` looked like it honored a `layout { status_bar }` entry
it actually reads and discards; and both cited test names were wrong —
`test_status_config` is in config.rs, and the color-space spec has no
`test_` prefix. Neither would grep.

The other direction, the code disagreeing with the doc: two comments
still called `disabled_color` black. It has been a light red since
2026-08-28, picked so the muted volume run keeps the same dark pool as
its neighbors, which is exactly what the comments cited it as the
counterexample to.

Finally the halo, deleted in ce0e113, was still all over the prose that
outlived it — three test names, `scrim_alpha`'s and `text_scrim`'s docs,
and the worst-case backdrop rationale in two places. The scrim is the
one treatment now, so the tests say scrim and the comments explain it on
its own terms rather than as the halo's successor. CLAUDE.md keeps its
one deliberate mention, the note recording the deletion.

Comments and test names only; 40 tests pass unchanged.

Co-Authored-By: Claude Opus 5 <[email protected]>

 CLAUDE.md        | 28 +++++++++++++++++++---------
 src/config.rs    | 13 ++++++-------
 src/listeners.rs |  4 ++--
 src/main.rs      | 36 +++++++++++++++++++-----------------
 4 files changed, 46 insertions(+), 35 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index f487318..6cff256 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -18,7 +18,7 @@ and the `cce-ui` toolkit this app is built on. This crate is deliberately small:
 
 ```sh
 cargo build --release                 # standalone build (or `-p cce-status-interface` from the workspace root)
-cargo test                            # the tests live in main.rs (e.g. test_status_config)
+cargo test                            # 40 tests: main.rs (contrast, parsers), config.rs, tray.rs
 make install                          # installs ../target/release/cce-status-interface to ~/.local/bin
 ```
 
@@ -48,7 +48,10 @@ The compositor places each segment by its Wayland `app_id`, computed in
 prefix is used instead — keep both spellings in mind when matching app_ids. A module's
 side comes from the config (`get_module_side`, which also maps snap positions like
 `top-left`/`bottom-right` to left/right); default is `window` → left, everything else →
-right.
+right. **`light_source` is the exception**: it short-circuits ahead of all of
+that and takes its side from `/window_manager/light_source_position` — the
+angle points at a side — so a `layout { status_bar light_source=… }` entry is
+read and then ignored, which looks like the key not working.
 
 ## Rendering
 
@@ -76,9 +79,16 @@ in `new()` — which tasks run depends on the selected module, so a clock proces
 listen to tray D-Bus, etc.:
 
 - **Compositor status feed** (`spawn_status_listener`): connects to
-  `/tmp/cce-status[-interface]-{WAYLAND_DISPLAY}.sock`, subscribes to `layout`,
-  `title`, `modifiers`, `dismiss` (line-oriented, auto-reconnects every 1s).
-  (The old `viewport` topic is gone with the viewport-tag feature.)
+  `/tmp/cce-status[-interface]-{WAYLAND_DISPLAY}.sock` and subscribes, one task
+  per topic, line-oriented — `layout` and `title` only in the process that owns
+  the window module, `dismiss` and `backdrop` in every one. Reconnects back off
+  1s doubling to 30s, reset the moment a connection delivers a line: a
+  compositor that does not know a topic drops the subscription on sight, so a
+  flat retry made a bar running ahead of its compositor reconnect once a second
+  from every module process, forever. The compositor also offers `modifiers`
+  (`status_server.rs`), but nothing here subscribes to it and the match over
+  topics ends in `unreachable!()` — adding a subscription means adding its arm
+  first. (The old `viewport` topic is gone with the viewport-tag feature.)
 - **System stats** (`spawn_system_stats`): `/proc/stat`, `/proc/meminfo`,
   `/sys/class/power_supply/BAT*`, `/sys/class/backlight`, and `pactl` for volume/mute.
 - **Tray** (`spawn_status_tray`): a full StatusNotifierItem/Watcher host over `zbus`,
@@ -193,9 +203,9 @@ Color space (one rule, enforced in `config.rs`): **text colors stay raw sRGB**
 (`text_color_from` — cosmic-text consumes sRGB `[u8; 3]`), **quad/box colors
 are linearized** (`quad_color_from` via `cce_ui::color::parse_hex_rgba_linear`,
 for the Vulkan pipeline). No local gamma math — the old scattered `.powf(2.2)`
-is gone; `test_text_colors_stay_srgb_and_quad_colors_are_linearized` is the
-spec. Config changes are picked up by polling the file mtime in `tick()`, so
-there is no reload event to wire up.
+is gone; `text_colors_stay_srgb_and_quad_colors_are_linearized`, in
+`config.rs`, is the spec. Config changes are picked up by polling the file
+mtime in `tick()`, so there is no reload event to wire up.
 
 ## Adaptive text contrast
 
@@ -254,7 +264,7 @@ cannot read (no committed buffer, an unsupported read format).
 
 Failures resolve toward legible in every direction: an unparseable or absent
 line reads as `(50, 100)` — mid luminance, full spread — which drives the
-outline rather than switching it off.
+scrim rather than switching it off.
 
 ## Interactions worth knowing before touching input code
 
diff --git a/src/config.rs b/src/config.rs
index 8b16381..42498aa 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -205,14 +205,13 @@ pub(crate) fn read_text_contrast_from_config() -> f32 {
 
 /// `module { text_scrim }` — opacity 0-1 of a dark feathered pool drawn
 /// inside each module box, beneath everything the module paints (0 = off,
-/// the default). Where the halo outlines the glyphs, this darkens the ground
-/// they sit on, which is the treatment that survives a busy backdrop without
-/// putting a rim on every letterform.
+/// the default). It darkens the ground the glyphs sit on rather than
+/// decorating the letterforms, which is what survives a busy backdrop
+/// without putting a rim on every letterform.
 ///
-/// It SUPERSEDES the halo when set — two contrast treatments at once is one
-/// too many — and `module { text_contrast }` still applies on top: the scrim
-/// rests at this opacity and deepens toward opaque as the measured backdrop
-/// demands more.
+/// The DE's one text-contrast treatment, and `module { text_contrast }`
+/// applies on top: the scrim rests at this opacity and deepens toward
+/// opaque as the measured backdrop demands more.
 pub(crate) fn read_text_scrim_from_config() -> f32 {
     cfg_f32("/module/text_scrim").unwrap_or(0.0).clamp(0.0, 1.0)
 }
diff --git a/src/listeners.rs b/src/listeners.rs
index 483c0af..03350f4 100644
--- a/src/listeners.rs
+++ b/src/listeners.rs
@@ -79,9 +79,9 @@ pub(crate) async fn spawn_status_listener(sub: String, sender: calloop::channel:
 ///
 /// Anything unreadable — the literal "unknown", a truncated line, a future
 /// compositor's extra fields — reports the worst case: mid luminance and full
-/// spread, which drives the outline. Guessing "uniform and bright" from a
+/// spread, which drives the scrim. Guessing "uniform and bright" from a
 /// line we failed to understand would silently turn the treatment OFF, and
-/// unreadable text is a worse failure than an unnecessary halo.
+/// unreadable text is a worse failure than an unnecessary scrim.
 pub(crate) fn parse_backdrop(line: &str) -> (u8, u8) {
     const UNKNOWN: (u8, u8) = (50, 100);
     let mut parts = line.split_whitespace();
diff --git a/src/main.rs b/src/main.rs
index 7a1de19..7542a36 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -167,11 +167,10 @@ fn contrast_demand(text_luma: f32, (luma, spread): (u8, u8)) -> f32 {
 /// The color a treatment behind or around `rgb` text should be drawn in:
 /// whichever of black/white that text reads against.
 ///
-/// Shared by the halo and the scrim, and applied PER RUN rather than from the
-/// configured module color, because a module may paint a run in something
-/// else entirely — the volume module's muted state uses the shared
-/// `disabled_color`, which on this DE is black. A black pool behind black
-/// text is the same mistake as a white halo around white text.
+/// Used by the scrim, and applied PER RUN rather than from the configured
+/// module color, because a module may paint a run in something else entirely
+/// — the volume module's muted state uses the shared `disabled_color`. A
+/// black pool behind black text is not a weaker treatment, it is an eraser.
 fn treatment_rgb(rgb: [u8; 3]) -> [f32; 3] {
     let luma = relative_luminance([rgb[0] as f32 / 255.0, rgb[1] as f32 / 255.0, rgb[2] as f32 / 255.0, 1.0]);
     if contrast_ratio(luma, 0.0) >= contrast_ratio(luma, 1.0) {
@@ -182,10 +181,10 @@ fn treatment_rgb(rgb: [u8; 3]) -> [f32; 3] {
 }
 
 /// The scrim's opacity: it rests at the configured `base` and deepens toward
-/// opaque as the measured backdrop demands more. `demand` is the same eased
-/// value the halo would have used, which is already zero when
-/// `module { text_contrast }` is off — so without that knob the scrim is a
-/// constant, which is the point of having it.
+/// opaque as the measured backdrop demands more. `demand` is the eased
+/// `contrast_now`, which is already zero when `module { text_contrast }` is
+/// off — so without that knob the scrim is a constant, which is the point of
+/// having it.
 fn scrim_alpha(base: f32, demand: f32) -> f32 {
     (base + (1.0 - base) * demand.clamp(0.0, 1.0)).clamp(0.0, 1.0)
 }
@@ -387,7 +386,7 @@ struct StatusApp {
     /// frame and the color changes about never.
     text_luma: f32,
     /// Dark feathered pool behind each module's content (0 = off) — see
-    /// `read_text_scrim_from_config`. Supersedes the halo when set.
+    /// `read_text_scrim_from_config`. The DE's one text-contrast treatment.
     text_scrim: f32,
     /// Feather distance for that pool, logical px; None derives it from the
     /// box height.
@@ -2188,14 +2187,14 @@ mod tests {
     const WHITE_TEXT: f32 = 1.0;
 
     #[test]
-    fn dark_text_on_a_light_uniform_backdrop_wants_no_halo() {
+    fn dark_text_on_a_light_uniform_backdrop_wants_no_scrim() {
         // The case that must stay untouched: the bar already reads fine, so
         // an adaptive scheme that decorates it anyway is worse than nothing.
         assert_eq!(contrast_demand(BLACK_TEXT, (100, 0)), 0.0);
     }
 
     #[test]
-    fn dark_text_on_a_dark_uniform_backdrop_wants_a_full_halo() {
+    fn dark_text_on_a_dark_uniform_backdrop_wants_a_full_scrim() {
         // Black text over a black grid cell — invisible, and the whole
         // reason for the feature.
         assert_eq!(contrast_demand(BLACK_TEXT, (0, 0)), 1.0);
@@ -2210,7 +2209,7 @@ mod tests {
     }
 
     #[test]
-    fn a_comfortable_mean_over_a_split_backdrop_still_wants_a_halo() {
+    fn a_comfortable_mean_over_a_split_backdrop_still_wants_a_scrim() {
         // Half black cell, half light gap: the mean alone says "mid-gray,
         // fine" while the text is invisible over one half. Checking the
         // spread's ends is what catches it.
@@ -2243,8 +2242,11 @@ mod tests {
     #[test]
     fn the_treatment_is_chosen_per_run_so_an_odd_colored_module_is_safe() {
         // The volume module paints its muted state in the shared
-        // disabled_color, which on this DE is black, while every other run is
-        // the configured white. One bar, both answers.
+        // disabled_color while every other run is the configured white, and
+        // the two need not land on the same answer. They happen to today —
+        // disabled_color is a light red, picked so the muted run keeps the
+        // same dark pool as its neighbors — so the endpoints below stand in
+        // for a palette that could part them again.
         assert_eq!(treatment_rgb([255, 255, 255]), BLACK);
         assert_eq!(treatment_rgb([0, 0, 0]), WHITE);
         // A mid accent color still resolves rather than landing in between.
@@ -2265,7 +2267,7 @@ mod tests {
     fn the_scrim_is_constant_without_the_adaptive_knob() {
         // text_contrast off leaves `demand` at zero, and the scrim is then
         // exactly what was configured — a fixed dark ground, which is the
-        // whole reason to prefer it to the halo.
+        // whole reason it can stand alone as a treatment.
         assert_eq!(scrim_alpha(0.55, 0.0), 0.55);
         assert_eq!(scrim_alpha(0.0, 0.0), 0.0);
     }
@@ -2349,7 +2351,7 @@ mod tests {
     #[test]
     fn parse_backdrop_rejects_out_of_range_but_tolerates_extra_fields() {
         // Out of protocol is unknown, not clamped — clamping a bad luma to
-        // 100 would read as "bright and uniform" and switch the halo off.
+        // 100 would read as "bright and uniform" and switch the scrim off.
         assert_eq!(crate::listeners::parse_backdrop("200 200"), (50, 100));
         assert_eq!(crate::listeners::parse_backdrop("101 0"), (50, 100));
         // Room for the compositor to grow the line without the bar