161ebb0da6
Gitea (and any case-sensitive filesystem) was rendering the [Top]
nav links in /docs as broken because of two bugs:
1. Case-sensitivity: 22 links used '../README.md' (all-uppercase)
but the actual file is 'docs/Readme.md' (capital R, lowercase
rest). 21 guide_*.md nav bars were affected, plus 1 internal
cross-link in Readme.md itself. Works on Windows (case-
insensitive) but broken on Linux/Gitea.
Fix: 22 occurrences across 22 files changed
'../README.md' -> '../Readme.md'
2. Wrong relative-path level: 16 links used '../../conductor/...'
from 'docs/guide_*.md' to reach 'conductor/'. This goes up 2
levels to 'projects/', which doesn't exist. The correct path
from 'docs/guide_*.md' to 'conductor/' is 1 level up
('../conductor/...'). 12 unique patterns across 10 files
affected.
Fix: 16 occurrences across 10 files changed
'../../conductor/' -> '../conductor/'
3. Bonus: 1 planned-guide link in guide_context_curation.md
referenced a never-written 'guide_context_presets.md'. The
ContextPreset schema is now fully covered in the new
'guide_context_aggregation.md' (per the 2026-06-08 docs
refresh). Fix: link target updated.
No content was changed, only link paths. 24 files, 37 link
replacements, 37 deletions.
Verification:
- All .md links in docs/ now resolve to existing files
(validated by path-resolution check from each file's directory)
- The 3 new guides from the previous docs refresh commit
(guide_discussions.md, guide_state_lifecycle.md,
guide_context_aggregation.md) had the case bug inherited from
guide_architecture.md's existing nav pattern; their top-of-file
nav bars are now correct
- The 21 pre-existing guide nav bars that had the same bug
(all 21 of them, except the 3 that used the correct case:
guide_mma.md, guide_simulations.md, guide_tools.md) are now
also fixed
- Inter-guide links (e.g. [Discussions](guide_discussions.md))
were not affected; they were always correct because both the
link text and the actual filename are lowercase
This is a docs-only fix. No code modified.
149 lines
7.2 KiB
Markdown
149 lines
7.2 KiB
Markdown
# Themes — Authoring Guide
|
|
|
|
## File Layout
|
|
|
|
- **Global themes:** `themes/<name>.toml` — one file per theme, in a directory at the project root.
|
|
- **Project-specific overrides:** `<project>/project_themes.toml` — a single bundled TOML file with one `[themes.<name>]` table per theme.
|
|
- **Override the global path** via the `SLOP_GLOBAL_THEMES` environment variable (must be a directory).
|
|
|
|
Both layouts are scanned and merged; project themes with the same name as a global theme override it.
|
|
|
|
## Schema
|
|
|
|
```toml
|
|
# human-readable label (optional)
|
|
description = "Solarized Dark by Ethan Schoonover"
|
|
|
|
# one of: dark | light | mariana | retro_blue
|
|
# selects which built-in imgui_color_text_edit palette to apply
|
|
# to code blocks in markdown viewers
|
|
syntax_palette = "dark"
|
|
|
|
[colors]
|
|
# RGB triples, 0-255
|
|
window_bg = [ 0, 43, 54]
|
|
text = [147, 161, 161]
|
|
button_hovered = [ 38, 139, 210]
|
|
# ... any imgui.Col_ key is accepted
|
|
```
|
|
|
|
- **`syntax_palette`** is required for TOML-defined themes. Unknown values fall back to `"dark"`.
|
|
- **`[colors]`** is required. Missing it is a hard error (logged to stderr, theme skipped).
|
|
- **Color keys** are imgui `Col_` enum members in snake_case. The loader does best-effort mapping; unknown keys are silently ignored.
|
|
|
|
### Common Color Keys
|
|
|
|
| Key | ImGui `Col_` | Use |
|
|
|---|---|---|
|
|
| `window_bg` | `WindowBg` | Panel/window background |
|
|
| `child_bg` | `ChildBg` | Nested child regions |
|
|
| `popup_bg` | `PopupBg` | Modal/popup backdrop |
|
|
| `border` | `Border` | Separator/border |
|
|
| `frame_bg` | `FrameBg` | Input field background |
|
|
| `title_bg` | `TitleBg` | Window title bar |
|
|
| `menu_bar_bg` | `MenuBarBg` | Top menu strip |
|
|
| `scrollbar_bg` | `ScrollbarBg` | Scrollbar track |
|
|
| `button` | `Button` | Standard button |
|
|
| `header` | `Header` | Collapsible section header |
|
|
| `separator` | `Separator` | Divider line |
|
|
| `tab` | `Tab` | Tab bar item |
|
|
| `text` | `Text` | Default text |
|
|
| `text_disabled` | `TextDisabled` | Greyed-out text |
|
|
| `check_mark` | `CheckMark` | Checkbox/radio check |
|
|
| `slider_grab` | `SliderGrab` | Slider thumb |
|
|
| `table_header_bg` | `TableHeaderBg` | Table column headers |
|
|
| `status_info` | (semantic) | Informational accent |
|
|
| `status_success` | (semantic) | Success/positive accent |
|
|
| `status_warning` | (semantic) | Warning accent |
|
|
| `status_error` | (semantic) | Error/negative accent |
|
|
|
|
The `status_*` keys are **semantic** — they map to the theme's accent colors and are used by the `C_*` color helpers in `src/gui_2.py:80-92`.
|
|
|
|
## The 4-Syntax-Palette Upstream Limit
|
|
|
|
`imgui-bundle` ships **four** built-in `imgui_color_text_edit` palettes and exposes no API to define new ones:
|
|
|
|
| Palette | Style |
|
|
|---|---|
|
|
| `dark` | Default dark; balanced contrast |
|
|
| `light` | Default light; balanced contrast |
|
|
| `mariana` | VS Code Mariana-inspired; muted blues |
|
|
| `retro_blue` | High-contrast blue-on-black retro CRT |
|
|
|
|
You select which one your theme uses by setting the `syntax_palette` field. The system picks the closest match for you when you omit the field (built-in non-TOML themes get `dark` by default). To get a different palette, set the field explicitly.
|
|
|
|
This is a hard upstream limit; there is no way to define a 5th palette without forking imgui-bundle. If you find yourself wanting to, the answer is to pick the closest of the four and adjust your UI theme's colors to harmonize.
|
|
|
|
## Public API (`src/theme_2.py`)
|
|
|
|
The system exposes three functions for runtime use:
|
|
|
|
| Function | Purpose |
|
|
|---|---|
|
|
| `load_themes_from_disk() -> None` | Re-scan the global themes directory and `<project>/project_themes.toml`, re-parse, and refresh the palette registry. Call this after dropping a new `.toml` file into `themes/`. |
|
|
| `get_syntax_palette_for_theme(theme_name: str) -> str` | Return the syntax palette name (`dark`/`light`/`mariana`/`retro_blue`) associated with a UI theme. Returns `"dark"` for unknown themes. |
|
|
| `apply_syntax_palette(palette_name: str) -> None` | Set the active `imgui_color_text_edit` default palette. No-op for unknown names. |
|
|
|
|
The `MarkdownRenderer.__init__` (`src/markdown_helper.py`) automatically calls `apply_syntax_palette(get_syntax_palette_for_theme(get_current_palette()))`, so code blocks in markdown viewers track the active theme. When the user switches themes, new `TextEditor` instances pick up the new palette; cached editors keep their previous palette until the next block renders.
|
|
|
|
### Usage Examples
|
|
|
|
```python
|
|
from src import theme_2 as theme
|
|
|
|
# Re-scan disk (e.g. after dropping a new theme file)
|
|
theme.load_themes_from_disk()
|
|
|
|
# Look up the syntax palette for a UI theme name
|
|
syntax = theme.get_syntax_palette_for_theme("solarized_dark") # "dark"
|
|
|
|
# Force a specific syntax palette (e.g. for a one-off preview)
|
|
theme.apply_syntax_palette("mariana")
|
|
```
|
|
|
|
## The C_* Color-Callable Convention
|
|
|
|
`src/gui_2.py:80-92` defines 13 module-level getter functions for semantic colors used throughout the GUI:
|
|
|
|
```python
|
|
def C_LBL() -> imgui.ImVec4: return theme.get_color("text_disabled")
|
|
def C_VAL() -> imgui.ImVec4: return theme.get_color("text")
|
|
def C_OUT() -> imgui.ImVec4: return theme.get_color("status_info")
|
|
def C_IN() -> imgui.ImVec4: return theme.get_color("status_success")
|
|
# ... and 9 more (C_REQ, C_RES, C_TC, C_TR, C_TRS, C_KEY, C_NUM, C_TRM, C_SUB)
|
|
```
|
|
|
|
**These are callables, not `ImVec4` values.** They resample the current theme's color on each call, so theme switches take effect on the next render frame.
|
|
|
|
**Correct usage** — call the function at the use site:
|
|
```python
|
|
imgui.text_colored(C_LBL(), "Completed:")
|
|
imgui.text_colored(C_VAL(), str(value))
|
|
```
|
|
|
|
**Common bug** — storing the function in a dict keyed by name, then passing the function (not its result) to imgui:
|
|
```python
|
|
DIR_COLORS = {"request": C_OUT, "response": C_IN}
|
|
d_col_fn = DIR_COLORS.get(direction, C_VAL) # stores the function
|
|
imgui.text_colored(d_col_fn(), direction) # CORRECT: call it
|
|
```
|
|
|
|
The bug shipped in commit `7ea52cbb` (multi-themes track) at `src/gui_2.py:3705-3707` and was fixed in `1469ecac`. When writing tests that assert theme color usage, **patch `src.theme_2.imgui`** so `theme.get_color()` returns the mock's `ImVec4`, and assert with `C_LBL()` (called), not `C_LBL` (the function).
|
|
|
|
## Hot Reload
|
|
|
|
Theme TOMLs are loaded once at module init **and** can be reloaded on demand via `theme.load_themes_from_disk()`. The function is safe to call from the GUI thread; it mutates the global registry atomically.
|
|
|
|
**Typical workflow** when authoring a new theme:
|
|
1. Drop a new file into `themes/`.
|
|
2. From the AI Settings panel's theme dropdown, the new theme is not yet visible — the registry is cached.
|
|
3. To see it without restarting, call `theme.load_themes_from_disk()` from a Python console hooked into the running process, OR add a "Refresh Themes" button that calls it, OR restart the app.
|
|
|
|
`project_themes.toml` is scanned for every project load, so changes there are picked up automatically when you switch projects.
|
|
|
|
## Cross-References
|
|
|
|
- **[guide_gui_2.md](guide_gui_2.md#theme-color-callable-pattern)** — The C_* callables in detail; the DIR_COLORS bug history.
|
|
- **[guide_testing.md](guide_testing.md#known-gotchas-2026-06-05)** — How to test theme color usage without crashing `imgui.color()`.
|
|
- **[conductor/tracks.md](../conductor/tracks.md)** — The `multi_themes_20260604` track entry (the 8 shipped themes and the API design).
|