edamame 0.1.2

A fast TUI Markdown editor and viewer
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Built-in themes and indexed-color fallback

> Part of the edamame contributor deep-dives. Index and project-wide conventions: [`AGENTS.md`](../../AGENTS.md). Sibling docs live in [`docs/dev/`](.).


`BUILTIN_THEMES` in `src/config/theme.rs` lists every compiled-in theme (Edamame, Dracula, Nord, Gruvbox, Catppuccin, …); each constructor lives in its own file under `src/config/themes/`. `Config::ensure_default_files` creates `themes/` empty — built-in theme files are never written to disk, and `read_theme_named` short-circuits to `Palette::builtin(name)` before any disk read, so a user file with a built-in name is ignored. To add a built-in: write `src/config/themes/<name>.rs` with a `pub fn theme()` and add it to `BUILTIN_THEMES`; the load path and the theme-picker / settings cycle (via `list_theme_names`) pick it up automatically. Custom user themes go in `~/.config/edamame/themes/<name>.toml` under any name not in the registry.

**Indexed-color substitution.** `256 Dark` / `256 Light` are the only built-ins authored against the xterm-256 cube; every other theme picks 24-bit colors that an indexed terminal quantizes, routinely landing fg and bg on the same entry. So without truecolor, `app::theme_fallback::apply` swaps `config.theme` for the matching indexed built-in (dark/light follows the *current theme's* appearance, not just the `appearance` key) and `ThemeDowngradeModal` reports it.

- **Exempt themes** are `theme::INDEXED_SAFE_THEMES`, for which `indexed_fallback_theme` returns `None`: the two `256 *` targets (which also makes the swap idempotent across reloads) plus `Monochrome Dark`, whose every palette slot is `Color::Reset`. The exemption covers the warning as much as the swap; `indexed_safe_themes_are_registered` pins the list against `BUILTIN_THEMES`.
- **One terminal change, one modal.** `ThemeDowngradeModal` carries the news except when the capabilities notice fires on the same launch, where the notice absorbs the prose via `with_theme_downgrade`. Both render `ui::cap_summary::theme_downgrade_lines`, and both hand `ModalView` *paragraphs*, not pre-broken lines.
- **Three invariants.** (1) The swap happens **before the first frame** in `App::new` — a modal explaining unreadable colors that is itself unreadable is worse than useless. (2) It is **never persisted**: the user's choice is stashed in `Config::theme_downgraded_from` and written back in `theme`'s place by `Config::save`, because one `config.toml` is typically shared with a truecolor machine. (3) Both paths resolving a theme from a freshly-read `Config` must call `theme_fallback::apply` — startup *and* the external-editor reload.
- **`NoColor` is exempt at the *depth* end**: `App::new` passes `monochrome` to `Theme::from_file`, which strips every color whatever the theme, so a swap there would be invisible. Below 24-bit color `App::media_renderable` also refuses images and diagrams outright — same quantization argument, likewise session-only, so a persisted `Always` survives for the user's capable terminal. **Enforced at the write sites:** `WelcomeModal::save_outcome` writes the three media fields only when `image_capable`, so the `Never` the modal *displays* below truecolor is never persisted. And when the notice carries the downgrade it drops its "Adjust settings" button — with theme, images and diagrams all disabled, the welcome modal has nothing left but the vim toggle.
- **While the stash is set, `config.theme` is effectively unwritable** (`Config::save` puts `theme_downgraded_from` in its place), so every path committing a theme *choice* must go through `Config::set_theme`, which clears it: the theme picker's selection and the export-theme modal's new theme. A bare `app.config.theme = …` paints the screen but silently drops the choice at save time. The only legitimate direct assignments are `theme_fallback::apply` itself and the picker's transient preview / revert, which deliberately do *not* clear the stash — `Esc` must restore the substitution.