5.8 KiB
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.pyandsrc/app_controller.pyinitially - 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).
# 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
@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
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+Rkeyboard 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)
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
# 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
- Pressing Ctrl+Alt+R reloads
src.gui_2without losing App state - GUI button triggers same reload
- Failed reload shows error tint, preserves last-good state
- New hot modules can be added via
HotReloader.register()without modifying core - 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)