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

PROPOSAL.md (9.9K)

  1 # Proposal: cce-status-interface cleanup
  2 
  3 > **Status (2026-08-19): closed.** Phases 0–3 and 5 are done; phase 4 became
  4 > moot when the cce-cloud popups were replaced by in-surface menus. Phase 2's
  5 > final step landed too: `json_find_key` and the warn-once fallback are
  6 > deleted — config lookup is explicit JSON pointers only. Phase 6: the
  7 > launcher backoff shipped (4353b94), the fuzzy upstream was rejected here
  8 > and is moot since the deletion, and the `cce-tray` extraction stays
  9 > deliberately gated on a second SNI consumer existing (user-confirmed
 10 > 2026-08-19) — `src/tray.rs` keeps that extraction cheap whenever one
 11 > appears. Nothing on this document remains actionable.
 12 
 13 Fixes for the issues identified in the 2026-07 review: `main.rs` carrying five jobs,
 14 a fuzzy config-lookup layer that partially duplicates `cce-ui`, stringly-typed
 15 coordination with the compositor, inconsistent color gamma handling, mixed
 16 `eprintln!`/`log::` output, and near-zero test coverage.
 17 
 18 Ordered so that each phase is independently commitable and the risky changes land on
 19 top of a test safety net. Phases 1, 2, 5, and 6 touch only this repo; phase 3 also
 20 touches `cce/`; phase 4 also touches `cce-ui/`. Per the multi-repo rules, each repo
 21 gets its own commits and must keep building standalone.
 22 
 23 ---
 24 
 25 ## Phase 0 — Characterization tests (safety net)
 26 
 27 Today there is one test (`test_status_config`). Before moving anything, pin down the
 28 behavior the later phases will refactor:
 29 
 30 - `parse_viewport_text`: pango spans, JSON-wrapped payloads, malformed/unterminated
 31   spans, plain-text fallback.
 32 - `json_find_key`: exact match, snake_case split across nesting
 33   (`status_background_color` → `status { background_color }`), collision/traversal
 34   order, miss → `None`. These tests become the spec for phase 2's replacement.
 35 - `get_module_side`: config side values, snap-position aliases
 36   (`top-left`/`bottom-right`/…), defaults (`window` → left, rest → right).
 37 - The `ccectl windows` line parser in `trigger_switcher` (extract the per-line parse
 38   into a free function first so it's testable): `app_id=`/`title="…"`/`focused=`/
 39   `window id=` extraction, filtered app_ids. These tests get retired in phase 3 when
 40   the parser is replaced by JSON, but until then they document the wire format.
 41 - Color parsing: one test asserting which keys are gamma-corrected and which are raw
 42   sRGB, so phase 2's fix is a deliberate, visible change rather than an accident.
 43 
 44 **Effort:** small. **Risk:** none (test-only).
 45 
 46 ## Phase 1 — Split `main.rs` (mechanical, no behavior change)
 47 
 48 `main.rs` is ~3,400 lines. Split by existing seams, keeping `modules.rs` as-is:
 49 
 50 | New file | Contents (moved, not rewritten) | ~lines |
 51 |---|---|---|
 52 | `src/tray.rs` | zbus proxies/traits, `StatusNotifierWatcher`/host impl, `spawn_status_tray`, `fetch_tray_item`, icon decode | ~850 |
 53 | `src/cloud.rs` | `show_cce_cloud_menu`, `MenuItem` parsing/paging, window-picker spawn body from `trigger_switcher` | ~500 |
 54 | `src/stats.rs` | `spawn_system_stats`, `read_cpu_ticks`, `read_memory_usage`, `read_battery_details`, `read_volume`, brightness | ~300 |
 55 | `src/config.rs` | all `read_*_from_config`, `parse_*_color_from_key`, `json_find_key`, `parse_font_for_alias`, `get_*_cmd` | ~350 |
 56 | `src/listeners.rs` | `spawn_status_listener`, `spawn_switcher_listener` | ~100 |
 57 | `main.rs` (remains) | `StatusApp`, `Application` impl, layout/input, `CustomEvent`, launcher-daemon `main()` | ~1,300 |
 58 
 59 Rule for the phase: `git diff` should show only moves, `use` changes, and visibility
 60 bumps (`fn` → `pub(crate) fn`). No logic edits — those come later, reviewable on their
 61 own.
 62 
 63 **Effort:** medium (mostly mechanical). **Risk:** low with phase 0 in place.
 64 
 65 ## Phase 2 — Config: replace the local layer with `cce-ui` accessors
 66 
 67 `cce_ui::config` already provides `cached_config()`, JSON-pointer accessors
 68 (`get_f32`, `get_bool`, `get_string`, `get_color`) and a recursive `find_key`. The
 69 local layer in this crate re-implements the lookup with an extra behavior — splitting
 70 snake_case keys across nesting — and hand-rolls gamma with `.powf(2.2)`.
 71 
 72 1. **Make each config key an explicit pointer.** Replace
 73    `json_find_key(&val, "status_background_color")` with
 74    `cce_ui::config::get_color("/style/status/background_color")` (etc.), encoding the
 75    real nesting once instead of discovering it by recursive search. Keys whose actual
 76    KDL location is unclear get resolved by looking at a real `config.kdl` and the
 77    compositor's reader — that's the point: today nobody can grep where a key lives.
 78 2. **Keep a thin fallback during migration.** A local
 79    `get_color_fuzzy(key)` that first tries the pointer, then falls back to the old
 80    `json_find_key`, with a `log::warn!` when only the fallback hits. After one release
 81    of quiet logs, delete the fallback and `json_find_key` entirely.
 82 3. **Fix gamma in one place.** `get_color` returns raw sRGB by its own doc; apply
 83    `cce_ui::color::srgb_to_linear` at the single point where colors enter the render
 84    state (`rebuild_layout`), replacing the scattered `.powf(2.2)` and the
 85    `status_normal_color`-is-linear-but-background-isn't inconsistency. Verify visually
 86    against the current bar before/after (screenshot compare) since this may shift
 87    perceived colors that users have tuned; if `status_normal_color` was correct as-is,
 88    document that in the code rather than leaving it implicit.
 89 4. **Delete local duplicates:** `parse_hex`, `parse_hex_rgba` wrappers,
 90    `parse_srgb_color_from_key`, `parse_rgba_color_from_key`, `parse_json` — all become
 91    calls into `cce_ui::config`/`cce_ui::color`.
 92 
 93 **Effort:** medium. **Risk:** medium (visible color shifts possible — mitigated by the
 94 phase-0 gamma test and a manual screenshot check). Cross-crate impact: none; other
 95 clients using the fuzzy pattern can migrate later on their own schedule.
 96 
 97 ## Phase 3 — Structured `ccectl` output (cross-repo: `cce/`)
 98 
 99 The window picker parses `ccectl windows` free text with `find("app_id=")` and
100 friends; titles containing `"` or spaces in unexpected places break it silently.
101 
102 1. **In `cce/` (owns `run_cce_ctl` and the control socket):** add `--json` to the
103    read commands this crate consumes — `windows` first; `viewports`/others as needed.
104    Output: one JSON object per line or a single array —
105    `{"id": …, "app_id": "…", "title": "…", "focused": bool}`. Text output stays the
106    default so nothing else breaks.
107 2. **In this crate:** replace the line parser with `serde_json` deserialization into a
108    small `WindowEntry` struct; fall back to the text parser if `--json` is rejected
109    (running against an older compositor), so the two repos can ship independently.
110 3. **Same pass, smaller items:**
111    - Replace `Command::new("kill").arg(pid)` with a direct `SIGTERM` via `libc::kill`
112      (or the `nix` crate) — no shell-out, and an error result instead of a silent
113      failure.
114    - Replace the `/tmp/cce-status-interface-adjust-mode` sentinel-file read in
115      `ToggleAdjustPositionMode` with a `ccectl` query, so mode state has one source of
116      truth (the compositor).
117 
118 **Effort:** medium, split across two repos. **Risk:** low (fallback keeps old/new
119 combinations working).
120 
121 ## Phase 4 — A shared popup helper (cross-repo: `cce-ui/`)
122 
123 The `cce-cloud` popup pattern (spawn, write JSON pages to stdin, track pid via
124 `CloudSpawned`/`CloudClosed`, toggle-off by kill, restore focus on close) is
125 hand-rolled here in three places (window picker, tray menus, layout menu) and will be
126 wanted by other clients.
127 
128 - Add `cce_ui::process::CloudPopup` (the crate already has `process.rs`):
129   `spawn(pages, position, source) -> CloudPopup`, `toggle_off()`, `is_running()`,
130   completion callback for focus-restore. Internals lifted from this crate's working
131   implementation — it's extraction, not redesign.
132 - Port the three call sites here; `active_cloud_pid`/`active_cloud_source` and the
133   `/proc/<pid>/comm` checks move behind the helper.
134 
135 Deliberately **not** proposing a socket protocol between bar and popups: the
136 pid-plus-stdin model is working, and the goal is to stop *re-implementing* it, not to
137 replace it. Revisit only if popups need richer two-way communication.
138 
139 **Effort:** medium. **Risk:** low-medium (behavior-preserving extraction, three call
140 sites to verify by hand: window picker, a tray menu, layout menu).
141 
142 ## Phase 5 — Logging unification
143 
144 51 `eprintln!` vs 12 `log::` calls today, and `env_logger` is already initialized.
145 
146 - Convert `eprintln!` → `log::debug!` (chatty per-event traces: `[status-listener]`,
147   `[cloud-event]`, stats updates) or `log::info!`/`log::warn!` (lifecycle, failures).
148 - Keep the existing bracketed subsystem tags as message prefixes; they're useful.
149 - Default filter stays `Info`, so the net effect is a much quieter stderr with
150   `RUST_LOG=debug` restoring today's firehose.
151 
152 **Effort:** small, mechanical. **Risk:** none.
153 
154 ## Phase 6 — Follow-ups (explicitly out of scope for now)
155 
156 - **Tray as its own crate** (`cce-tray`): worth doing only when a second client needs
157   SNI. Phase 1's `tray.rs` makes the later extraction cheap.
158 - **Upstreaming the fuzzy key-split into `cce_ui::config::find_key`**: rejected —
159   phase 2 goes the other way (explicit pointers), and two lookup semantics in the
160   toolkit is worse than one.
161 - **Launcher-daemon supervision polish** (backoff on crash-looping modules instead of
162   unconditional 500ms restarts): cheap, but wait until after phase 1 so it lands in a
163   small `main()`.
164 
165 ---
166 
167 ## Sequencing and verification
168 
169 ```
170 0 tests ─→ 1 split ─→ 2 config/gamma ─→ 5 logging
171                 └────→ 3 ccectl --json (needs a cce/ commit first)
172                 └────→ 4 CloudPopup    (needs a cce-ui/ commit first)
173 ```
174 
175 Phases 3 and 4 are independent of 2 and of each other; 5 can land any time after 1.
176 
177 Each phase ends with: `cargo test` green, `cargo build --release` standalone, and a
178 manual smoke test in a live session — bar renders in both orientations, viewport tabs
179 switch, a tray menu opens and closes, the window picker toggles, super+drag moves a
180 module, and a config edit hot-reloads.