From 237f572592123fc2482d2b2982c09b5b5aff1572 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 10 Jun 2026 20:07:08 -0400 Subject: [PATCH] docs(app_controller): replace fictional __init__ + register_hooks with real flow The previous doc showed: - A fictional AppState dataclass (does not exist) - A fictional __init__ that creates manager objects in __init__ (managers are lazy via __getattr__, created in _load_active_project) - A fictional register_hooks(app) method (real flow is _init_actions called from init_state populates _predefined_callbacks) - A fictional enable_test_hooks parameter (real signature is defer_warmup: bool = False, log_to_stderr: Optional[bool] = None; --enable-test-hooks is parsed by sloppy.py for HookServer, not here) The new doc describes the real init flow (timeline anchors, 12 locks, GUI health state, io_pool, warmup manager, flags) and points to the actual line numbers in src/app_controller.py. --- docs/guide_app_controller.md | 67 +++++++++--------------------------- 1 file changed, 16 insertions(+), 51 deletions(-) diff --git a/docs/guide_app_controller.md b/docs/guide_app_controller.md index 827eb32a..c1567f60 100644 --- a/docs/guide_app_controller.md +++ b/docs/guide_app_controller.md @@ -54,64 +54,29 @@ When `--enable-test-hooks` is passed, the controller also spins up the HookServe ## The `AppController` Class -### `__init__(self, enable_test_hooks: bool = False)` +### `__init__(self, defer_warmup: bool = False, log_to_stderr: Optional[bool] = None)` -Initializes the controller. Key state: +> **Important:** The `__init__` does NOT create manager objects, does NOT register hooks, and does NOT start the HookServer. The previous documentation in this section was **fictional** (a fabricated `AppState` dataclass, fabricated `enable_test_hooks` parameter, fabricated `register_hooks` method, and manager objects that don't exist on the controller). -```python -class AppController: - def __init__(self, enable_test_hooks: bool = False): - # 1. Path resolution (src/paths.py) - self.paths = PathManager() +Initializes the controller. Real state created here: - # 2. State container - self.app_state = AppState() +The actual `__init__` (`src/app_controller.py:778-836`) does the following: - # 3. Subsystem managers - self.presets = PresetManager(self.paths) - self.personas = PersonaManager(self.paths) - self.context_presets = ContextPresetManager(self.paths) - self.tool_presets = ToolPresetManager(self.paths) - self.tool_bias = ToolBiasEngine() - self.history = HistoryManager(self.paths) - self.workspace = WorkspaceManager(self.paths) - self.rag_engine = RAGEngine(self.paths) # Lazy +1. **Startup timeline anchors** — Captures `_init_start_ts` for the `startup_timeline()` diagnostics. Other timeline anchors are filled in lazily as events occur. +2. **Locks** — Creates 12 thread-safety locks (`_send_thread_lock`, `_disc_entries_lock`, `_pending_*_lock` for comms/tool_calls/history/gui_tasks/dialog/api_event_queue, `_rag_engine_lock`, `_rag_sync_lock`/`_rag_sync_token`/`_rag_sync_dirty` for FR3 coalescing, `_project_switch_lock`/`_project_switch_in_progress`/`_project_switch_pending_path`/`_project_switch_error`). +3. **GUI health state** — `_gui_degraded_reason` and `_last_imgui_assert` (set when `immapp.run` raises `RuntimeError`; see [guide_gui_2.md](guide_gui_2.md#startup-architecture-lazy-imports-profiler-refresh-rate)). +4. **Shared io_pool** — `make_io_pool()` creates a 4-thread `ThreadPoolExecutor` named `controller-io-N`. This is the SOLE background pool for all async work (no `threading.Thread()` calls anywhere else in `src/`). +5. **Warmup manager** — `WarmupManager(self._io_pool, log_to_stderr=log_to_stderr)` with an on-complete callback to stamp `warmup_done_ts`. `defer_warmup=True` defers the actual `start_warmup()` call until the first frame is painted (the desktop GUI pattern; headless mode starts immediately). +6. **Various flags** — `_warmup_started`, `_pending_fetch_provider`, `_defer_warmup`. - # 4. Hook API surface - self._predefined_callbacks: dict[str, Callable] = {} - self._gettable_fields: dict[str, str] = {} +**Manager objects** (`preset_manager`, `persona_manager`, `context_preset_manager`, `tool_preset_manager`, `tool_bias_engine`, `history_manager`, `workspace_manager`, `rag_engine`) are **NOT created in `__init__`**. They are lazy attributes accessed via `__getattr__` and created on first reference (typically from `_load_active_project` at `src/app_controller.py:2150` or from `App._post_init` at `src/gui_2.py:3995`). - # 5. AI client (lazy) - self.ai_client = None +**Hook API surface** is NOT populated by a `register_hooks` method. The actual flow: +1. `AppController._init_actions()` (called from `init_state` at `src/app_controller.py:1740`) populates `self._predefined_callbacks` and `self._gettable_fields` registries via module-level handler registration functions. +2. `src/api_hooks.py:HookHandler.do_GET` / `do_POST` reads from these registries to expose App methods as `/api/gui` `custom_callback` actions. +3. The `sloppy.py` CLI parses `--enable-test-hooks` and passes it to `HookServer` (a separate class, not the controller). - # 6. MMA conductor (lazy) - self.mma_conductor = None - - # 7. Sync event queue (daemon <-> UI bridge) - self.event_queue = SyncEventQueue() - - # 8. Optional hook server - if enable_test_hooks: - self.hook_server = HookServer() - self.hook_server.start() -``` - -The `App` (in `gui_2.py`) then reads `controller.app_state`, `controller.presets`, etc. for rendering. - -### `register_hooks(app: App)` - -Called by `gui_2.py` after instantiation. The controller populates the predefined callbacks and gettable fields that the Hook API can invoke. - -```python -def register_hooks(self, app: 'App') -> None: - """Register App methods as predefined callbacks for the Hook API.""" - self._predefined_callbacks['_toggle_command_palette'] = app._toggle_command_palette - self._predefined_callbacks['_open_command_palette'] = app._open_command_palette - # ... etc, many more ... - self._gettable_fields['show_command_palette'] = 'show_command_palette' - self._gettable_fields['current_provider'] = 'current_provider' - # ... etc ... -``` +For the actual init flow, read `src/app_controller.py:778-836` (`__init__`), `:1606` (`_init_actions`), `:1740` (`init_state`), and `:2150` (`_load_active_project`). This is the **only** bridge between the GUI's app methods and the external Hook API. If a method is not in `_predefined_callbacks`, external callers cannot invoke it.