# Theme & Syntax Highlighting Modularization

## Problem

The current theming system in `src/theme_2.py` has three limitations:

1. **Themes are hardcoded as a Python dict.** Users cannot author new themes without editing Python source and recompiling. This is inconsistent with the rest of the project (presets, personas, tool_presets, context_presets, bias profiles, workspace profiles all use TOML).

2. **Syntax highlighting is hardcoded.** The `MarkdownRenderer._lang_map` in `src/markdown_helper.py` uses `imgui-bundle`'s `imgui_color_text_edit` language definitions whose token colors are baked into the C++ library. There is no way to align syntax token colors with the active UI theme.

3. **No way to bundle new themes with a release or share them between projects.**

## Goals

- **TOML-based theme authoring.** Themes live in `themes/<name>.toml` (global) and `<project>/project_themes.toml` (project override). Schema mirrors the existing `_PALETTES` dict shape.

- **Authoring without recompiling.** Drop a new `.toml` file in `themes/` and it appears in the palette selector after the next load (or hot-reload, future).

- **Syntax palette mapping.** Each theme TOML declares a `syntax_palette` field that maps to one of the four built-in `imgui_color_text_edit` palettes (`dark`, `light`, `mariana`, `retro_blue`). The renderer calls `editor.set_default_palette(...)` whenever the active theme changes.

- **Scope-based merging** matches the existing pattern: project themes override global themes with the same name.

## Constraints

- `imgui-bundle` only ships 4 built-in syntax palettes and exposes no API to define new ones or override individual token colors. This is a hard upstream limit. The plan accepts the limit and works around it via palette mapping.

- We do NOT attempt to wrap or shadow `imgui_color_text_edit`. The C++ library owns the per-language token regexes and default token colors. We pick the closest of the 4 palettes for each theme and let users override the mapping per theme.

## Out of scope

- Defining new `imgui_color_text_edit` palettes or overriding token colors per language (blocked by upstream API).
- Hot-reload of theme changes (the user can re-apply from the selector).
- Per-language color customization (e.g., Python `keyword` color distinct from C `keyword`).

## File structure

| File | Action | Responsibility |
|---|---|---|
| `src/theme_2.py` | Modify | Replace hardcoded `_PALETTES` dict with a load-from-TOML pipeline. Keep `apply()` public API. Expose new helpers `get_syntax_palette_for_theme(name)` and `apply_syntax_palette(palette_id)`. |
| `src/paths.py` | Modify | Add `get_global_themes_path()` and `get_project_themes_path(project_root)`. Defaults: `themes.toml` (global) and `project_themes.toml` (project). Override via `SLOP_GLOBAL_THEMES` env var. |
| `src/theme_models.py` | Create | Pydantic/dataclass schema for theme TOML files. `ThemePalette` has all `imgui.Col_` keys, `syntax_palette` is a string (one of the 4 IDs). `to_dict()` / `from_dict()` round-trip. |
| `themes/solarized_dark.toml` | Create | Authoring artifact. RGB triples in standard `#RRGGBB` form. |
| `themes/solarized_light.toml` | Create | Same. |
| `themes/gruvbox_dark.toml` | Create | Same. |
| `themes/moss.toml` | Create | Same. |
| `tests/test_theme_models.py` | Create | Round-trip tests for `ThemePalette` from/to TOML. |
| `tests/test_theme.py` | Modify | Add tests for the 4 new palettes, TOML loading, scope merge, and syntax palette mapping. |
| `tests/fixtures/themes/minimal.toml` | Create | Minimal valid TOML fixture for loader tests. |
| `tests/fixtures/themes/missing_keys.toml` | Create | TOML missing required keys — should raise a clear error. |
| `docs/guide_themes.md` | Create | Authoring guide: schema, file locations, scope rules, syntax palette mapping, env vars. |

## Theme TOML schema (reference, not implementation in this plan)

```toml
# theme name (informational)
name = "Solarized Dark"

# optional: which built-in imgui_color_text_edit palette to use
# one of: dark | light | mariana | retro_blue
syntax_palette = "dark"

# which imgui style colors this theme overrides
# any key not listed falls back to the base imgui dark/light defaults
[colors]
window_bg         = [ 0,  43,  54]   # 0x002b36 base03
child_bg          = [ 7,  54,  66]   # 0x073642 base02
text              = [147, 161, 161] # 0x93a1a1 base1
text_disabled     = [ 88, 110, 117] # 0x586e75 base01
button_hovered    = [ 38, 139, 210] # 0x268bd2 blue
check_mark        = [ 38, 139, 210]
slider_grab       = [ 38, 139, 210]
tab_selected      = [ 88, 110, 117]
tab_hovered       = [ 38, 139, 210]
# ... remaining colors omitted
```

Values are 3-element RGB arrays (0-255) for the body and the syntax palette is a string identifier.

## Syntax palette mapping (built-in only)

| Theme | Syntax palette |
|---|---|
| Solarized Dark | `dark` (closest dark base) |
| Solarized Light | `light` |
| Gruvbox Dark | `retro_blue` (warm retro feel) |
| Moss | `mariana` (deep blue-green base) |
| 10x Dark | `dark` |
| Nord Dark | `dark` |
| Monokai | `dark` |
| Binks | `light` |
| ImGui Dark | `dark` |
| NERV | `dark` (NERV's own custom palette via `theme_nerv.apply_nerv()`) |

The mapping lives in `src/theme_2.py` as a small dict and is overridable per theme via the TOML `syntax_palette` field.

## Public API

Existing `src.theme_2` callsites must continue to work. New surface:

- `theme.get_palette_names() -> list[str]` — already exists, now also returns TOML-loaded themes
- `theme.apply(name) -> None` — already exists, applies the named theme (built-in OR TOML)
- `theme.get_syntax_palette_for_theme(name) -> PaletteId` — new
- `theme.apply_syntax_palette(palette_id) -> None` — new, calls `editor.set_default_palette(palette_id)`
- `theme.load_themes_from_disk() -> None` — new, public for hot-reload
