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:
@@ -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.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user