diff --git a/docs/guide_gui_2.md b/docs/guide_gui_2.md index 0ff9d79a..5f0a212b 100644 --- a/docs/guide_gui_2.md +++ b/docs/guide_gui_2.md @@ -429,7 +429,9 @@ The `App` class (around line 478-487) defines two descriptor hooks that delegate def __getattr__(self, name: str) -> Any: if name == 'controller': raise AttributeError(name) - return getattr(self.controller, name) + if hasattr(self, 'controller') and hasattr(self.controller, name): + return getattr(self.controller, name) + raise AttributeError(name) def __setattr__(self, name: str, value: Any) -> None: if name != 'controller' and hasattr(self, 'controller') and hasattr(self.controller, name): @@ -438,6 +440,8 @@ def __setattr__(self, name: str, value: Any) -> None: object.__setattr__(self, name, value) ``` +> **Critical (bcdc26d0):** The current code includes the `hasattr(self.controller, name)` guard in `__getattr__`. The previous version (without this guard) was a silent-None bug: any uninitialized `ui_` attribute on the App would have called `getattr(self.controller, name)` → raised `AttributeError` from Python → the ImGui code would catch that and return `None` → the GUI would render blanks silently instead of crashing. The `hasattr` guard makes the `AttributeError` propagate correctly so the bug surfaces during development. The fix is in `src/gui_2.py:688-693`. + **Why this matters:** - The `Controller` is the single source of truth for settable state (e.g. `ui_ai_input`, `ui_separate_tier1`, `show_windows`, `temperature`). - The `App` is a thin view layer that delegates reads (`__getattr__`) and writes (`__setattr__`) to the Controller. @@ -461,9 +465,19 @@ uv run python -c "import ast; tree = ast.parse(open('src/gui_2.py').read()); [pr **How to fix:** Re-indent the affected method to 2-space class level. This bit the project in 2026-06-05 during a cleanup commit: `_capture_workspace_profile` was being parsed as nested inside `_apply_snapshot` due to a 1-space indentation drift, breaking 3 live_gui tests (test_auto_switch_sim, test_workspace_profiles_restoration, test_undo_redo_lifecycle). ---- +### Startup Architecture (Lazy Imports, Profiler, Refresh Rate) +The 2026-06-06 `startup_speedup_20260606` track restructured `gui_2.py` for ~2400ms faster startup. The key components: +**`_LazyModule` proxies** (`np`, `filedialog`, `Tk`, `win32gui`, `win32con`): Defer `import numpy`, `import tkinter.filedialog`, `import tkinter`, `import win32gui`, `import win32con` until first attribute access. The first `gui_2` import drops from ~1770ms to ~341ms. + +**`_FiledialogStub`**: No-op fallback for tkinter-less environments (e.g., headless CI). Sets `available = False` so the GUI can detect and skip file dialogs gracefully. + +**`startup_profiler` + `render_warmup_status_indicator(app)`**: `AppController(defer_warmup=True)` defers heavy SDK warmup (google.genai, anthropic, openai, fastapi) to a background thread. `startup_profiler.phase(name)` wraps each init phase and reports per-phase duration. `render_warmup_status_indicator` is called per-frame during the warmup window to show a progress indicator in the UI. The warmup completes asynchronously; `_on_warmup_complete_callback` is invoked when done. See [guide_architecture.md](guide_architecture.md#warmup-architecture) for the full mechanism. + +**Native refresh rate detection** (`_detect_refresh_rate_win32`): The old implementation used a PowerShell/WMI subprocess (~350ms blocking). The new implementation uses `ctypes.windll.user32.EnumDisplaySettingsW` directly (~0.3ms, 1000x faster). Used by the ImGui IO setup to set the display refresh rate. + +**`immapp.run` error handling**: `immapp.run` is wrapped in try/except catching `RuntimeError` from native ImGui bundle assertions. On native crash, `_gui_degraded_reason` and `_last_imgui_assert` are set on the controller, and the GUI enters a degraded mode (rendering a static error panel instead of the live UI). This prevents the Python process from being killed by an uncatchable Windows access violation (`0xc0000005`). ---