Private
Public Access
0
0
Files
manual_slop/docs/guide_gui_2.md
T
ed 779eb006ba docs(gui_2): add guide_gui_2.md
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
2026-06-02 23:23:35 -04:00

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) -> None functions that draw individual panels
  • App class (~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: App as their first parameter.
  • The App class 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.reload without 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:

  1. Creates the AppController (the headless counterpart)
  2. Initializes history.HistoryManager(max_capacity=100) for undo/redo
  3. Sets up UI state flags: show_windows, show_*_modal flags
  4. Initializes the workspace manager
  5. Starts services via controller.start_services(self)
  6. 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
    
  7. 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. Uses imscope.window context manager.
  • imscope from src/imgui_scopes.py: stack-style context managers for imgui.begin/end, imgui.push_style/pop_style, etc. — replaces the legacy push/pop pattern with Pythonic with statements.

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: App as 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 imscope to skip work when not visible.
  • Performance monitor: app.perf_monitor tracks per-component timing. Set app.perf_profiling_enabled = True to 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

  1. Add the key to show_windows in __init__:
    self.show_windows.setdefault("My Window", False)
    
  2. 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 ...
    
  3. Call it from render_main_interface.
  4. Add a command in src/commands.py for 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

  1. Add a state flag in __init__:
    self.show_my_modal: bool = False
    
  2. Add a render function that uses imgui.begin_popup_modal or the begin/end pattern.
  3. Call it from render_main_interface.
  4. 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