Files
manual_slop/docs/superpowers/specs/2026-05-14-hot-reloader-design.md
T

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.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).

# 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+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)

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

  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)