Private
Public Access
0
0
Files
manual_slop/docs/superpowers/specs/2026-06-04-theme-syntax-modularization.md
T

5.7 KiB

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)

# 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