# Hot Reloader Design Spec **Date:** 2026-05-14 **Author:** Tier 2 Tech Lead **Status:** Draft ## Overview Implement a selective, state-preserving hot-reload system for the Manual Slop `./src` Python codebase. This follows the "data in stable memory, code in reloadable modules" pattern pioneered by Casey's Handmade Hero hot-reload in C. ## Goals - Enable hot-reloading of `src/gui_2.py` and `src/app_controller.py` initially - Preserve App instance state across reloads (scroll position, form inputs, open windows) - Extensible architecture — future hot modules (e.g., `ai_client.py`) can be added via registry - Manual trigger only (Ctrl+Alt+R keyboard shortcut + GUI button) - Silent fallback on failure with visual error tint ## Architecture ### Delegation Pattern Code is separated into **delegators** (thin wrappers in stable modules) and **delegation targets** (actual logic in reloadable modules). ```python # src/gui_2.py (reloadable) def render_main_interface(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_main_interface") app._render_window_if_open("Project Settings", app._render_project_settings_hub) # ... all render logic here # src/app_controller.py (stable) — App class stays stable class App: def _render_main_interface(self) -> None: import src.gui_2 as gui2 gui2.render_main_interface(self) ``` ### HotModule Registry Entry ```python @dataclass class HotModule: name: str # "src.gui_2" file_path: str # Absolute path to .py state_keys: list[str] # App attrs to preserve during reload delegation_targets: list[str] # Method names on App that delegate here ``` ### HotReloader Core API ```python class HotReloader: HOT_MODULES: dict[str, HotModule] # Module registry @classmethod def register(cls, module: HotModule) -> None: ... @classmethod def reload(cls, module_name: str, app: App) -> bool: """Reload a single module. Returns True on success, False on failure.""" @classmethod def reload_all(cls, app: App) -> bool: """Reload all registered modules in dependency order.""" @classmethod def capture_state(cls, app: App, state_keys: list[str]) -> dict: ... @classmethod def restore_state(cls, app: App, state: dict) -> None: ... ``` ### State Capture/Restore Before reload, `HotReloader.capture_state()` serializes App attributes listed in `state_keys`. After reload (success or failure), `restore_state()` writes them back. State is captured as a dict of `{attr_name: copy.deepcopy(value)}`. Restoration uses `setattr` with re-imported module reference. ### Error Handling Flow ``` Reload triggered ↓ capture_state(app) ↓ attempt importlib.reload(module) ↓ on Exception: restore_state(app) # revert to pre-reload state cls.last_error = traceback cls.is_error_state = True tint GUI red return False ↓ on success: clear last_error clear error tint return True ``` ### Trigger Mechanism - `Ctrl+Alt+R` keyboard shortcut captured in main input loop - GUI button in MMA Dashboard: "Hot Reload" with icon - Both call `HotReloader.reload_all(self)` on the App instance ### Visual Error Tint When `HotReloader.is_error_state` is True: - If NERV theme active: overlay with NERV red (rgba 255, 72, 64, alpha) - Else: overlay with red tint - Tint cleared on next successful reload ## Module Registry (Initial) ```python HOT_MODULES = { "src.gui_2": HotModule( name="src.gui_2", file_path=str(Path(__file__).parent / "gui_2.py"), state_keys=[ "_active_discussion", "_disc_entries", "_disc_roles", "show_windows", "ui_discussion_split_h", "active_tickets", # ... more keys TBD during implementation ], delegation_targets=[ "_render_main_interface", "_render_discussion_hub", "_render_discussion_panel", "_render_discussion_selector", # ... more targets TBD during implementation ], ), } ``` ## Delegation Refactoring Phases ### Phase 1: GUI Methods Extract render methods from `App` in `app_controller.py` into delegation targets in `gui_2.py`. ### Phase 2: State Keys Inventory Catalog all `App` instance attributes that need preservation. ### Phase 3: HotReloader Implementation Implement `src/hot_reloader.py` with capture/restore, registry, and error handling. ### Phase 4: Trigger Integration Add Ctrl+Alt+R handler and GUI button. ### Phase 5: Visual Tint Implement error tint overlay. ## Extensibility: Adding Future Hot Modules ```python # Add ai_client as hot module: HotReloader.register(HotModule( name="src.ai_client", file_path=str(Path(__file__).parent / "ai_client.py"), state_keys=["_pending_requests", "_api_key"], delegation_targets=["_send_request", "_stream_response"], )) # Or via decorator on the delegation target: @hot_module(state_keys=["_pending_requests"]) def send_request(app: App) -> None: ... ``` ## Files Affected | File | Change | |------|--------| | `src/hot_reloader.py` | New — HotReloader class | | `src/gui_2.py` | Refactor render methods to module-level functions | | `src/app_controller.py` | Refactor App methods to delegation wrappers | | `src/imgui_scopes.py` | Unchanged (still used by refactored code) | ## Success Criteria 1. Pressing Ctrl+Alt+R reloads `src.gui_2` without losing App state 2. GUI button triggers same reload 3. Failed reload shows error tint, preserves last-good state 4. New hot modules can be added via `HotReloader.register()` without modifying core 5. No performance impact when not reloading (< 1ms overhead per frame) ## Out of Scope - Automatic file watching (manual trigger only per design) - Hot-reloading of C extensions or native code - Cross-platform reload support (Windows focus for now)