Private
Public Access
0
0

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.
This commit is contained in:
2026-06-10 20:07:08 -04:00
parent 5fa8a10ebf
commit 237f572592
+16 -51
View File
@@ -54,64 +54,29 @@ When `--enable-test-hooks` is passed, the controller also spins up the HookServe
## The `AppController` Class ## 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 Initializes the controller. Real state created here:
class AppController:
def __init__(self, enable_test_hooks: bool = False):
# 1. Path resolution (src/paths.py)
self.paths = PathManager()
# 2. State container The actual `__init__` (`src/app_controller.py:778-836`) does the following:
self.app_state = AppState()
# 3. Subsystem managers 1. **Startup timeline anchors** — Captures `_init_start_ts` for the `startup_timeline()` diagnostics. Other timeline anchors are filled in lazily as events occur.
self.presets = PresetManager(self.paths) 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`).
self.personas = PersonaManager(self.paths) 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)).
self.context_presets = ContextPresetManager(self.paths) 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/`).
self.tool_presets = ToolPresetManager(self.paths) 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).
self.tool_bias = ToolBiasEngine() 6. **Various flags**`_warmup_started`, `_pending_fetch_provider`, `_defer_warmup`.
self.history = HistoryManager(self.paths)
self.workspace = WorkspaceManager(self.paths)
self.rag_engine = RAGEngine(self.paths) # Lazy
# 4. Hook API surface **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`).
self._predefined_callbacks: dict[str, Callable] = {}
self._gettable_fields: dict[str, str] = {}
# 5. AI client (lazy) **Hook API surface** is NOT populated by a `register_hooks` method. The actual flow:
self.ai_client = None 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) For the actual init flow, read `src/app_controller.py:778-836` (`__init__`), `:1606` (`_init_actions`), `:1740` (`init_state`), and `:2150` (`_load_active_project`).
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 ...
```
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. 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.