Comprehensive guide for the main GUI file (~260KB, 5400 lines): - Architecture (UI delegation pattern: module-level render functions + App class) - App class breakdown (init, key state, render entry point) - The render_main_interface flow (calls ~15 hub renderers) - Modal pattern via _render_window_if_open helper - Keyboard shortcuts (Ctrl+Shift+P, Ctrl+Alt+R, Ctrl+L, Ctrl+Enter) - Key patterns: hot reload, snapshots, window state, modal - Public methods worth knowing table - Performance considerations (60 FPS target, perf monitor) - Testing approaches (pure, integration via live_gui, mock_app) - Common operations: adding toggleable window, modal, hook API exposure
15 KiB
src/gui_2.py — Main ImGui Application
Top | Architecture | Testing
Overview
src/gui_2.py is the largest file in the project (~260KB, ~5400 lines). It contains the App class — the main ImGui application orchestrator — and ~90 module-level render functions that draw the GUI.
The file is divided into:
- Module-level render functions (~90 functions): pure
def render_xxx(app: App) -> Nonefunctions that draw individual panels Appclass (~1500 lines): the main application with state, lifecycle, and the per-frame render loop in_gui_func
Architecture
The file follows the project's UI delegation pattern (see guide_architecture.md):
- Module-level functions do the drawing. They take
app: Appas their first parameter. - The
Appclass holds state and dispatches. - Thin wrapper methods on
App(e.g.,_render_message_panel(self)) just call the module-level function.
This pattern enables:
- Hot Reload: module-level functions can be reloaded via
importlib.reloadwithout breaking the App class - Testability: pure functions can be tested in isolation with a mock
App - Consistency: every render function follows the same calling convention
┌────────────────────────────────────────────────────┐
│ _gui_func (App method, ~50 lines) │
│ - Detects Ctrl+Shift+P, Ctrl+Alt+R, etc. │
│ - Renders background shader │
│ - Renders custom title bar │
│ - Calls render_main_interface(self) ← thin wrapper │
└──────────────────┬─────────────────────────────────┘
│ calls
▼
┌────────────────────────────────────────────────────┐
│ render_main_interface(app) │
│ - Iterates over the major panels │
│ - Calls render_<hub>(app) for each hub │
│ - Handles modal popups │
└──────────────────┬─────────────────────────────────┘
│ calls
▼
┌────────────────────────────────────────────────────┐
│ render_<panel>(app) (~80 functions) │
│ - Each draws one panel (Context, AI, Discussion, │
│ Operations, MMA Dashboard) │
│ - Reads from app state, writes to app state │
│ - Returns nothing; mutates via side effects │
└────────────────────────────────────────────────────┘
The App Class
App.__init__
The constructor (line 131) is long. It:
- Creates the
AppController(the headless counterpart) - Initializes
history.HistoryManager(max_capacity=100)for undo/redo - Sets up UI state flags:
show_windows,show_*_modalflags - Initializes the workspace manager
- Starts services via
controller.start_services(self) - Registers predefined callbacks on the controller for the Hook API:
self.controller._predefined_callbacks['save_context_preset'] = self.save_context_preset self.controller._predefined_callbacks['_toggle_command_palette'] = self._toggle_command_palette # ... ~10 more - Registers gettable fields for the Hook API:
self.controller._gettable_fields['show_command_palette'] = 'show_command_palette'
Key State
The App holds dozens of state attributes. The most important:
| Attribute | Type | Purpose |
|---|---|---|
self.controller |
AppController |
Headless counterpart; services and callbacks |
self.disc_entries |
list[dict] |
Current discussion entries |
self.disc_roles |
list[str] |
Roles per entry (User, AI, System, etc.) |
self.discussion_history |
list |
Legacy alias for disc_entries |
self.context_files |
list[FileItem] |
Files in the current context |
self.ui_file_paths |
list[str] |
Tracked file paths |
self.ai_status |
str |
Current AI state ("idle", "sending...", etc.) |
self.ai_response |
str |
Latest AI response text |
self.last_md |
str |
Last generated markdown (for MD-only mode) |
self.show_windows |
dict[str, bool] |
Toggle state for each window |
self.workspace_profiles |
dict |
Loaded workspace profiles |
self._comms_log |
deque |
Communication log (in-memory) |
self._tool_log |
deque |
Tool call log |
show_command_palette |
bool |
Command palette visibility |
Render Entry Point: _gui_func (line 754)
The main render loop, called by imgui-bundle's Hello ImGui runner every frame:
def _gui_func(self) -> None:
io = imgui.get_io()
# Keyboard shortcuts
if io.key_ctrl and io.key_alt and imgui.is_key_down(imgui.Key.r):
self._trigger_hot_reload()
if (io.key_ctrl and io.key_shift
and not io.key_alt and not io.key_super
and imgui.is_key_pressed(imgui.Key.p)):
self.show_command_palette = not self.show_command_palette
# Reset per-open state...
# Render background shader (if enabled)
render_custom_title_bar(self)
render_shader_live_editor(self)
render_history_window(self)
# ... background rendering ...
# Main content
render_main_interface(self)
# Error tint on hot-reload failure
render_error_tint(self)
render_main_interface (line 1259)
The "main content" renderer. Iterates over the major panels and calls the right render function for each:
def render_main_interface(app: App) -> None:
# Background panels (always rendered if shown)
render_history_window(app)
render_track_proposal_modal(app)
render_patch_modal(app)
# ... all the modals ...
# Hubs
render_project_settings_hub(app)
render_ai_settings_hub(app)
render_files_and_media(app)
render_discussion_hub(app)
render_operations_hub(app)
render_mma_dashboard(app)
# ... etc
Modal Helpers
_render_window_if_open(name, render_func, flag_condition=True)(line 800): helper that renders a window only if its toggle is active. Usesimscope.windowcontext manager.imscopefromsrc/imgui_scopes.py: stack-style context managers forimgui.begin/end,imgui.push_style/pop_style, etc. — replaces the legacy push/pop pattern with Pythonicwithstatements.
Keyboard Shortcuts
Implemented in _gui_func:
| Shortcut | Action |
|---|---|
Ctrl+Alt+R |
Hot Reload the GUI module |
Ctrl+Shift+P |
Toggle Command Palette |
Ctrl+L |
Clear AI input field |
Ctrl+Enter |
Generate + Send |
Escape (in modals) |
Close the modal |
Other shortcuts are handled in individual render functions (e.g., Ctrl+S in the project settings).
Key Patterns
Module-Level Render Functions
Every render function follows this signature:
def render_<thing>(app: App) -> None:
"""One-line docstring describing what this draws."""
if app.perf_profiling_enabled:
app.perf_monitor.start_component("<thing>")
# ... draw ImGui widgets, reading from and writing to app state ...
if app.perf_profiling_enabled:
app.perf_monitor.end_component("<thing>")
- Takes
app: Appas the only positional arg - Returns
None - Defensive: checks
hasattr(app, "...")before touching state, in case the state was never initialized
Custom Title Bar
render_custom_title_bar (line 1358) draws a custom ImGui-drawn title bar (instead of the OS title bar) for the cross-platform PyOpenGL-backed window. The Windows-specific close/min/max/close buttons are added via ctypes in the same function (lines 858-865).
Hot Reload Hook
The Hot Reload module (src/hot_reloader.py) registers src.gui_2 as a hot-reloadable module. The state_keys list (line 155) tells the reloader which App attributes to snapshot and restore:
state_keys=['active_discussion', 'show_windows', 'ui_file_paths',
'ui_screenshot_paths', 'disc_entries', 'disc_roles']
delegation_targets (line 156) lists the module-level functions the App calls into:
delegation_targets=['_render_main_interface', '_render_discussion_hub',
'_render_files_and_media', '_render_ai_settings_hub',
'_render_operations_hub', '_render_mma_dashboard']
The user presses Ctrl+Alt+R → _trigger_hot_reload() → HotReloader.reload("src.gui_2", app). The module is re-imported, the App's state is restored, and the next frame uses the new render functions.
Snapshots (Undo/Redo)
App._take_snapshot and App._apply_snapshot (lines 548, 567) capture and restore UI state for the undo/redo system. Used by the discussion view's edit-in-place operations.
Snapshots include: ai_input, project_system_prompt, global_system_prompt, base_system_prompt, use_default_base_prompt, temperature, top_p, max_tokens, auto_add_history, disc_entries, files, screenshots.
Window State Management
The app.show_windows: dict[str, bool] is the central toggle for all toggleable windows:
self.show_windows.setdefault("Text Viewer", False)
self.show_windows.setdefault("Diagnostics", False)
self.show_windows.setdefault("Usage Analytics", False)
self.show_windows.setdefault("Context Preview", False)
self.show_windows.setdefault("Tier 1: Strategy", False)
self.show_windows.setdefault("Tier 2: Tech Lead", False)
self.show_windows.setdefault("Tier 3: Workers", False)
self.show_windows.setdefault("Tier 4: QA", False)
self.show_windows.setdefault('External Tools', False)
self.show_windows.setdefault('Shader Editor', False)
self.show_windows.setdefault('Undo/Redo History', False)
The Command Palette exposes all of these via the toggle_* commands (32 total in src/commands.py).
Modal Pattern
Modals use imgui.begin_popup_modal or the if imgui.begin(...): ... imgui.end() pattern. The codebase has moved toward using imscope.window (line 800) for cleaner scoping:
def _render_window_if_open(self, name, render_func, flag_condition=True):
if not flag_condition or not self.show_windows.get(name, False):
return
with imscope.window(name, self.show_windows[name]) as (exp, opened):
if not opened:
self.show_windows[name] = False
if exp:
render_func(self)
Modals that need explicit state (e.g., "command palette" or "approve script") use app.show_<thing>_modal: bool directly.
Public Methods Worth Knowing
| Method | Line | Purpose |
|---|---|---|
__init__ |
131 | Construct the App and all subsystems |
run |
~1252 | Launch the ImGui render loop |
reset_session |
~500 | Clear the AI session and discussion |
save_context_preset / load_context_preset |
varies | Preset CRUD |
_handle_generate_send |
492 | The "Generate + Send" button handler |
_handle_md_only |
499 | The "Generate MD Only" button handler |
_take_snapshot / _apply_snapshot |
548 / 567 | Undo/redo snapshot system |
_capture_workspace_profile |
602 | Capture the current layout as a profile |
_apply_workspace_profile |
625 | Apply a saved profile |
_handle_undo / _handle_redo |
632 / 652 | Undo/redo handlers |
_toggle_command_palette |
732 | Test helper for opening the palette via the hook API |
_gui_func |
754 | The main per-frame render function |
_show_menus |
807 | The menu bar (File, Windows, Project, Layout) |
Performance Considerations
- Frame budget: target 60 FPS (16.67ms/frame). Heavy panels like the MMA dashboard and tier stream panels use
imscopeto skip work when not visible. - Performance monitor:
app.perf_monitortracks per-component timing. Setapp.perf_profiling_enabled = Trueto enable. - Heavy text:
render_heavy_text(line 4589) is the wrapper for displaying large blocks of text (used by text viewer, logs). - Background rendering: Background shader (
bg_shader.py) and ImGui draw lists are batched per frame.
Testing
The App class is the integration target for most live_gui tests. Common patterns:
Pure Tests (no app)
def test_my_helper():
from src.gui_2 import render_some_thing
# ... test the render function with a mock app ...
Integration via live_gui
def test_my_thing(live_gui):
client = ApiHookClient()
client.push_event("custom_callback", {
"callback": "_my_method",
"args": [],
})
time.sleep(0.5)
assert client.get_value("my_field") == "expected"
Mock App
def test_with_mock(mock_app):
mock_app.some_attr = "test"
from src.gui_2 import render_main_interface
render_main_interface(mock_app)
# Assert on side effects
See guide_testing.md for the full test infrastructure.
Common Operations
Adding a New Toggleable Window
- Add the key to
show_windowsin__init__:self.show_windows.setdefault("My Window", False) - Add a render function:
def render_my_window(app: App) -> None: if not app.show_windows.get("My Window", False): return with imscope.window("My Window", app.show_windows["My Window"]) as (exp, opened): if not opened: app.show_windows["My Window"] = False if exp: # ... draw the window ... - Call it from
render_main_interface. - Add a command in
src/commands.pyfor keyboard access:@registry.register def toggle_my_window(app: "App") -> None: from src.commands import _toggle_window _toggle_window(app, "My Window")
Adding a New Modal
- Add a state flag in
__init__:self.show_my_modal: bool = False - Add a render function that uses
imgui.begin_popup_modalor the begin/end pattern. - Call it from
render_main_interface. - Optionally add a Hook API gettable field for testing.
Exposing a New Method via the Hook API
In __init__:
self.controller._predefined_callbacks['_my_method'] = self._my_method
self.controller._gettable_fields['show_my_thing'] = 'show_my_thing'
The test can then invoke via:
client.push_event("custom_callback", {"callback": "_my_method", "args": []})
value = client.get_value("show_my_thing")
See Also
- guide_architecture.md — Threading model that the App respects
- guide_command_palette.md — The 32 commands accessible via Ctrl+Shift+P
- guide_testing.md — Test infrastructure for GUI tests
- guide_hot_reload.md — How Ctrl+Alt+R reloads this file
- conductor/product-guidelines.md — The UI delegation pattern rules