redbear-tui-theme 0.1.0

Default TUI skin palette for Red Bear OS — single source of truth for the Red Bear brand colours used by every TUI application
Documentation
# Red Bear OS — Default TUI Skin Palette

`redbear-tui-theme` is the **single source of truth** for the Red Bear brand colours used by every TUI application on Red Bear OS:

- `tlc` (file manager)
- `cub` (package manager)
- `redbear-info` (system dashboard)
- `redbear-netctl-console` (network control)
- `redbear-wifictl`, `redbear-btctl` (device control)
- `redbear-mtr`, `redbear-traceroute`, `redbear-nmap`, `redbear-hwutils` (diagnostics)
- any future TUI app

The palette is pure-Rust (no `ratatui` dep at the core), builds offline, and ships with both dark and light presets.

## Quick start

```toml
# Cargo.toml
[dependencies]
redbear-tui-theme = { path = "../../../tui/redbear-tui-theme/source" }
```

```rust
use redbear_tui_theme::{REDBEAR_DARK, Theme};

// Render a selection row.
let bg = REDBEAR_DARK.selection_bg;  // #3652A0
let fg = REDBEAR_DARK.selection_fg;  // #F4F4F4

// Or pick a theme from the environment (REDBEAR_TUI_THEME or COLORFGBG).
let theme = Theme::from_env();
```

## What's in the palette

The `Theme` struct has **33 slots** organized into six families:

| Family | Slots | Purpose |
|---|---|---|
| Surface | `background`, `surface`, `overlay_bg`, `title_bg`, `status_bg`, `gauge_bg` | 6 slots; 3-tier raised surface; chrome bars |
| Text | `text`, `muted`, `dim`, `border` | 4 slots; body text + de-emphasis |
| Accent | `accent`, `accent_soft`, `title_accent`, `pink` | 4 slots; brand red + secondary accents |
| Semantic | `success`, `warning`, `error`, `info` | 4 slots; status colours (must pair with glyphs) |
| File types | `directory`, `executable`, `symlink`, `device`, `device_warn`, `hidden` | 6 slots; tlc file-type colours |
| Selection | `selection_{bg,fg}`, `cursor_{bg,fg}`, `marked_{bg,fg}`, `buttonbar_{bg,fg}` | 8 slots; tlc selection/cursor/marked/buttonbar |

All 33 slots are sourced from the shared `Theme` — no consumer hard-codes brand hex values.

## Brand red

`#B52430` is the Red Bear OS brand red. It is **identical** in both dark and light themes. This matches the convention of macOS, KDE, GNOME, and Windows: the brand stays constant; only the background flips.

## WCAG 2.1 AA contrast (verified)

| Pair | Dark ratio | Light ratio | Pass |
|---|---|---|---|
| `text` on `background` | 17.51 | 15.86 | AA-body ✓ |
| `text` on `accent` | 5.86 | 6.44 | AA-body ✓ |
| `muted` on `background` | 8.33 | 5.82 | AA-body ✓ |
| `border` on `background` | 4.53 | 2.27 | AA-body (dark) / AA-large (light) |
| `dim` on `background` | 2.91 | 3.81 | **Intentionally below AA-body** (de-emphasis) |

The only intentional AA-body failure is `dim`/`hidden`. By design: de-emphasis is the function. Apps must not use `dim` for body text.

## Color-blindness

- `success` vs `error`: distinguishable for all three common deficiencies.
- `warning` vs `error`: **confusable** for protanopia and deuteranopia. **Mitigation**: every warning must be accompanied by a `` glyph prefix, every error by ``.
- `info` vs `success`: confusable for tritanopia. **Mitigation**: keep `info` and `success` on separate lines in status bars.
- `pink` is for highlights (favorites, active link). **Mitigation**: when used, also prepend `` (U+2605).

Apps render these as styles with `Modifier::BOLD` on the semantic colours; the styles module (planned v0.2) will provide ready-made `success_style()`, `warning_style()`, etc. as one-liners.

## 256-color + 16-color fallback

For terminals that don't support truecolor, the `fallback` module provides:

- `fallback_256(rgb: Rgb) -> u8` — closest XTerm-256 index (0..=255)
- `fallback_16_name(rgb: Rgb) -> &'static str` — closest ANSI-16 name

`fallback_256` uses a weighted Euclidean distance in sRGB space (small luminance penalty to keep the returned colour visually close). Verified mappings:

| Slot | Truecolor | 256-idx | 16-name |
|---|---|---|---|
| `background` | `#0E0E12` | 233 | Black |
| `text` | `#F4F4F4` | 255 | Bright White |
| `accent` | `#B52430` | 125 or 9 | Bright Red |
| `success` | `#74C793` | 114 | Bright Black |
| `warning` | `#FFBF57` | 215 | Bright Yellow |

## Migrating an existing TUI app

1. Add the dep to `Cargo.toml`:
   ```toml
   redbear-tui-theme = { path = "../../../tui/redbear-tui-theme/source" }
   ```
2. Replace local `Color::Rgb(r, g, b)` constants with `REDBEAR_DARK.<slot>` (or `Rgb(r,g,b)` if you need a custom value).
3. Convert to your TUI lib's colour type: `ratatui::style::Color::Rgb(t.bg.0, t.bg.1, t.bg.2)`.
4. Replace any local `Theme` struct with `pub use redbear_tui_theme::Theme;`.
5. Add `redbear-tui-theme` to the cookbook's cascade list in `local/scripts/rebuild-cascade.sh`.

## Build

The crate has zero required dependencies. It builds offline:

```bash
cd local/recipes/tui/redbear-tui-theme/source
cargo build --release
cargo test              # 11 unit tests (palette + fallback) + 1 doc test
```

The recipe lives at `local/recipes/tui/redbear-tui-theme/recipe.toml` and
stages the resulting `libredbear_tui_theme.rlib` into
`${COOKBOOK_STAGE}/usr/lib/` so the cookbook's repo artifact check passes.
The recipe is symlinked into the cookbook search path at
`recipes/tui/redbear-tui-theme` (per `local/AGENTS.md` "Local recipe priority
vs upstream WIP"). Downstream TUI apps depend on the crate via a Cargo
`path` dependency — they do **not** install it as a binary.

## Downstream consumers (as of 2026-06-13)

| App | Status | Notes |
|---|---|---|
| `tlc` | ✅ wired | `tlc/source/Cargo.toml` adds `redbear-tui-theme = { path = "../../../tui/redbear-tui-theme/source" }`. The 22-field `Theme` struct in `src/terminal/color.rs` is preserved (TOML skin deserializer still works); `DEFAULT_THEME` and `LIGHT_THEME` are now built from the shared `REDBEAR_DARK`/`REDBEAR_LIGHT` via a const `as_color(rgb)` adapter. |
| `cub` | ✅ wired | `local/recipes/system/cub/source/cub/src/tui/theme.rs` is now a 33-field `RedBearTheme` newtype constructed from `redbear_tui_theme::REDBEAR_DARK` via a const `as_color(rgb)` adapter. The 19 `*_style()` methods are preserved as inherent methods; all 33 slots are sourced from the shared palette. 2 new tests verify byte-identical brand red and 19 non-empty styles. |
| `redbear-info` | 🚧 TODO | `local/recipes/system/redbear-info/source/src/tui.rs` uses the legacy ANSI-16 palette (`Color::Red`, `Color::DarkGray`, etc.). Migration is non-trivial because the file uses semantic colors only, not slot names. |
| `redbear-netctl-console` | 🚧 TODO | `local/recipes/system/redbear-netctl-console/source/src/ui.rs` uses the legacy ANSI-16 palette. Same migration story as `redbear-info`. |

## License

MIT — same as Red Bear OS.