conductor(plan): theme + syntax modularization plan/spec
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user