TUI: move every colour behind a named-role palette #118

Closed
opened 2026-08-25 12:17:17 +01:00 by naps62-yolo · 1 comment
Collaborator

The TUI's colours are hardcoded named ratatui colours at roughly 26 sites, mostly in crates/maestro-tui/src/sidebar.rs, with a few in render.rs, pane.rs, and draft.rs. Two problems follow from that.

First, contrast is broken on the selected card. card_base(true) in sidebar.rs:38 returns black-on-cyan, and meta_row then patches the activity state's own colour onto that background — so an AwaitingInput session under the highlight renders yellow on light blue, which is close to unreadable. The activity dot must keep carrying state, but a state colour chosen for a dark card background has no business being painted onto the selection background.

Second, nothing can be themed while the values are spelled out at each use site.

Move every colour behind a palette of named roles. Roles are the vocabulary the rest of the milestone builds on, so name them for what they mean, not what they look like: the card background, the selection background and its foreground, the panel border in its focused and unfocused forms, the muted and dim text styles, and one role per activity state (Active, Idle, AwaitingInput, Error). Add the roles the milestone's other issues need — a group-row style for the sidebar grouping, and whatever the focus work requires — rather than forcing those issues to reach around the palette.

Values are Color::Rgb, so contrast is a property of the palette and can be asserted in a test rather than left to the user's terminal theme. Ship one default palette in this issue; selecting between presets from config is the next issue.

Fixing the selected-card contrast is part of this issue, not a follow-up: pick selection roles that stay legible against every activity-state colour, and add a test that fails if a state colour is painted directly onto the selection background.

This amends docs/SIDEBAR.md. Its "Colour per activity state" section currently pairs each state with a plain ratatui colour and instructs: "The TUI uses the plain ratatui named colours above; do not introduce RGB values or a new palette module for this." That instruction is what this issue overturns. Rewrite the table's TUI column to name palette roles instead of colours, and record that the role's value comes from the active palette. The web column and the rule that colour alone never carries state both stay exactly as they are.

Non-goals: no config surface, no preset list, no focus-indicator changes. Those are separate issues in this milestone.

The TUI's colours are hardcoded named ratatui colours at roughly 26 sites, mostly in `crates/maestro-tui/src/sidebar.rs`, with a few in `render.rs`, `pane.rs`, and `draft.rs`. Two problems follow from that. First, contrast is broken on the selected card. `card_base(true)` in `sidebar.rs:38` returns black-on-cyan, and `meta_row` then patches the activity state's own colour onto that background — so an `AwaitingInput` session under the highlight renders yellow on light blue, which is close to unreadable. The activity dot must keep carrying state, but a state colour chosen for a dark card background has no business being painted onto the selection background. Second, nothing can be themed while the values are spelled out at each use site. Move every colour behind a palette of named roles. Roles are the vocabulary the rest of the milestone builds on, so name them for what they mean, not what they look like: the card background, the selection background and its foreground, the panel border in its focused and unfocused forms, the muted and dim text styles, and one role per activity state (`Active`, `Idle`, `AwaitingInput`, `Error`). Add the roles the milestone's other issues need — a group-row style for the sidebar grouping, and whatever the focus work requires — rather than forcing those issues to reach around the palette. Values are `Color::Rgb`, so contrast is a property of the palette and can be asserted in a test rather than left to the user's terminal theme. Ship one default palette in this issue; selecting between presets from config is the next issue. Fixing the selected-card contrast is part of this issue, not a follow-up: pick selection roles that stay legible against every activity-state colour, and add a test that fails if a state colour is painted directly onto the selection background. This amends `docs/SIDEBAR.md`. Its "Colour per activity state" section currently pairs each state with a plain ratatui colour and instructs: "The TUI uses the plain ratatui named colours above; do not introduce RGB values or a new palette module for this." That instruction is what this issue overturns. Rewrite the table's TUI column to name palette roles instead of colours, and record that the role's value comes from the active palette. The web column and the rule that colour alone never carries state both stay exactly as they are. Non-goals: no config surface, no preset list, no focus-indicator changes. Those are separate issues in this milestone.
naps62-yolo added this to the TUI shell milestone 2026-08-25 12:17:17 +01:00
naps62-yolo added the area/tuitype/refactordifficulty/medium labels 2026-08-25 12:17:17 +01:00
naps62-yolo added a new dependency 2026-08-25 12:18:57 +01:00
naps62-yolo added a new dependency 2026-08-25 12:18:57 +01:00
naps62-yolo added a new dependency 2026-08-25 12:22:14 +01:00
Author
Collaborator

Out of scope, found while running the suite for this issue: retained_store_survives_a_daemon_restart (crates/maestro-e2e/tests/e2e.rs:872) fails under a full parallel run and passes when run alone.

list after restart: Api(Http(... url: "http://127.0.0.1:37475/api/worktrees",
source: hyper::Error(Io, Os { code: 104, kind: ConnectionReset })))

The GET /api/worktrees right after the daemon restart hits a socket that is accepting but not yet serving, so the test races the restart rather than testing retention. Nothing in this issue touches the daemon. Left unfixed; worth its own issue.

Out of scope, found while running the suite for this issue: `retained_store_survives_a_daemon_restart` (`crates/maestro-e2e/tests/e2e.rs:872`) fails under a full parallel run and passes when run alone. ``` list after restart: Api(Http(... url: "http://127.0.0.1:37475/api/worktrees", source: hyper::Error(Io, Os { code: 104, kind: ConnectionReset }))) ``` The `GET /api/worktrees` right after the daemon restart hits a socket that is accepting but not yet serving, so the test races the restart rather than testing retention. Nothing in this issue touches the daemon. Left unfixed; worth its own issue.
This repo is archived. You cannot comment on issues.
1 Participants
Due Date
No due date set.
Reference: naps62/maestro#118