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