diff --git a/docs/guide_gui_2.md b/docs/guide_gui_2.md index 094d931b..634ef1e1 100644 --- a/docs/guide_gui_2.md +++ b/docs/guide_gui_2.md @@ -386,6 +386,39 @@ client.push_event("custom_callback", {"callback": "_my_method", "args": []}) value = client.get_value("show_my_thing") ``` +### Theme Color-Callable Pattern + +Theme color helpers in `src/theme_2.py` (`C_LBL`, `C_VAL`, `C_OUT`, `C_IN`, `C_OK`, `C_ERR`, etc.) are **callable functions, not `ImVec4` values**. This is intentional: it lets the active theme be swapped at runtime and have the new colors take effect on the next render frame, instead of capturing stale colors at module import time. + +**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.text_colored`: +```python +DIR_COLORS = { + "request": C_OUT, + "response": C_IN, +} +# ... later ... +d_col_fn = DIR_COLORS.get(direction, C_VAL) # WRONG: stores the function +imgui.text_colored(d_col_fn(), direction) # CORRECT: calls it +``` + +This pattern is used in `src/gui_2.py:3705-3707` (the `render_comms_history_panel` `DIR_COLORS`/`KIND_COLORS` dicts). The bug shipped in the multi-themes track commit `7ea52cbb` and was caught by `1469ecac` — `imgui.text_colored` was being passed a callable instead of an `ImVec4`, raising `TypeError` on every render frame. + +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). + +### Workspace Profile Defer-Not-Catch + +`_capture_workspace_profile` (line 601) calls `imgui.save_ini_settings_to_memory()` to serialize the current ImGui layout. This C function **crashes the Python process with `0xc0000005` access violation** when called in the first few render frames because ImGui's internal state (Fonts, DisplaySize, Settings) isn't yet fully initialized. The crash is **not catchable from Python** — it's a native access violation, not a Python exception. + +The fix uses a **defer-not-catch** pattern: a one-shot `_ini_capture_ready` flag in the instance state. The first call (during initial startup) returns an empty profile and flips the flag; subsequent calls (when the user actually clicks "Save Profile") invoke the C function. The user's workflow is unaffected because the first call is non-blocking and the user cannot have clicked "Save Profile" before the GUI was fully rendered. + +This pattern unblocks 4-5 live_gui tests that were crashing the GUI subprocess during the first render frames after a `save_workspace_profile` Hook API callback. See [guide_testing.md](guide_testing.md#known-gotchas-2026-06-05) for the broader pattern and how to recognize these crashes. + --- ## See Also @@ -394,4 +427,5 @@ value = client.get_value("show_my_thing") - **[guide_command_palette.md](guide_command_palette.md)** — The 32 commands accessible via Ctrl+Shift+P - **[guide_testing.md](guide_testing.md)** — Test infrastructure for GUI tests - **[guide_hot_reload.md](guide_hot_reload.md)** — How Ctrl+Alt+R reloads this file +- **[guide_themes.md](guide_themes.md)** — TOML theme system; defines the `C_*` callable color helpers used throughout `gui_2.py` - **[conductor/product-guidelines.md](../../conductor/product-guidelines.md)** — The UI delegation pattern rules