# Result Migration — Sub-Track 4 (gui_2.py) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use `mma-tier3-worker` (recommended) or `mma-tier2-tech-lead` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Migrate all 42 migration-target sites in `src/gui_2.py` (the immediate-mode ImGui rendering layer) to the data-oriented `Result[T]` convention, using the 8 controller state attributes added by sub-track 3 as the data plane and adding 3 new render functions as the drain plane.

**Architecture:** Per-site `_result` helpers return `Result[T]` where T is the data the caller needs. Callers check `.ok` and either accumulate in the appropriate controller attribute (data plane) or trigger a drain modal (Pattern 2 from `error_handling.md`). The drain plane (3 new render functions) reads from the controller's data plane and renders errors as popups/status indicators.

**Tech Stack:** Python 3.11+, imgui_bundle (immediate-mode ImGui), dearpygui (legacy), pytest. Existing infrastructure: `Result[T]` from `src/result_types.py:91-105`, audit script at `scripts/audit_exception_handling.py`, 5 regression-guard tests at `tests/test_audit_heuristics.py`, 8 controller state attributes (added by sub-track 3 Phase 6).

---

## Anti-Sliming Protocol (MANDATORY for every phase)

The previous sub-tracks slimed when scope felt tight (sub-track 2 Phase 10 slimed 21 of 26 sites via 5 laundering heuristics; sub-track 3 Phase 3 slimed 8 sites via logging.debug bodies). This protocol prevents sliming:

1. **Pre-phase styleguide re-read** (commit 1 of the phase): Read `conductor/code_styleguides/error_handling.md` end-to-end. Commit message MUST include "TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase N."

2. **Audit pre-check** (per site, before migration): Run `uv run python scripts/audit_exception_handling.py --src src/gui_2.py --json 2>&1 | grep -A 2 "L<line>"` to confirm the site's category BEFORE migration. Capture in commit body.

3. **Red** (1 commit per site): Write the unit test in `tests/test_gui_2_result.py`. Run test — MUST FAIL. Commit.

4. **Green** (1 commit per site): Migrate the site. Use the migration pattern for the phase. Run test — MUST PASS. Commit.

5. **Audit post-check** (per site, after migration): Same command. Confirm the site moved out of the violation category. Capture in commit body.

6. **Phase invariant test** (1 commit at end of phase): `test_phase_N_<phase>_invariant` verifies the per-phase count drop.

7. **If a site "resists migration":** DO NOT invent a heuristic. Report to the user (Tier 1). The user decides whether to fix forward or defer.

8. **Per-file atomic commits:** 1 site = 1 commit (per `workflow.md` "ATOMIC PER-TASK COMMITS").

---

## File Structure

**Files created (3):**
- `tests/test_gui_2_result.py` — the new test file (≥55 tests: 42 site tests + 13 invariant tests)
- `docs/reports/TRACK_COMPLETION_result_migration_gui_2_20260619.md` — the end-of-track report (Phase 13)
- `tests/artifacts/PHASE1_SITE_INVENTORY.md` — the site inventory (Phase 1)

**Files modified (4):**
- `src/gui_2.py` — the migration target (42 sites + 3 new render functions in Phase 2)
- `conductor/tracks.md` — add the new track row (Phase 0)
- `conductor/tracks/result_migration_gui_2_20260619/state.toml` — task statuses
- `conductor/tracks/result_migration_gui_2_20260619/metadata.json` — verification criteria

**Files NOT modified:**
- `scripts/audit_exception_handling.py` — the audit heuristic is correct from sub-track 3 Phase 7; do not change
- `src/app_controller.py` — the data plane is correct from sub-track 3 Phase 6; this track only consumes it
- `tests/test_audit_heuristics.py` — the 5 regression-guard tests are correct; do not change

---

## Phase 0: Setup + Styleguide Re-Read (3 tasks)

**Focus:** Initialize the track, update tracks.md, Tier 2 reads the styleguide end-to-end, acknowledge in commit message.

### Task 0.1: Update `conductor/tracks.md` [bf94fb2]

**Files:**
- Modify: `conductor/tracks.md` (add new row in the "Active Tracks (Current Queue)" table, after row 6d-3)

- [x] **Step 1: Read the current tracks.md row for sub-track 3**

Run: `grep -n "result_migration_app_controller_20260618" conductor/tracks.md | head -5`

- [x] **Step 2: Add the new row after sub-track 3**

Insert in the "Active Tracks (Current Queue)" table (between row 6d-3 and row 6e):

```
| 6d-4 | A | [Result Migration Sub-Track 4: gui_2.py](#track-result-migration-sub-track-4-gui_2py-20260619) | spec ✓, plan pending, **ready to start** | `result_migration_app_controller_20260618` (sub-track 3, SHIPPED 2026-06-19 with Phase 7) |
```

- [x] **Step 3: Commit**

```bash
git add conductor/tracks.md
git commit -m "conductor(tracks): add result_migration_gui_2_20260619 row"
```

### Task 0.2: Tier 2 reads the styleguide end-to-end [62188d6]

**Files:** (no file changes; verification is the commit message)

- [x] **Step 1: Read `conductor/code_styleguides/error_handling.md` end-to-end**

This file is 989 lines. Read all sections:
1. The 5 Patterns (lines 22-130)
2. The Data Model (lines 132-150)
3. Decision Tree (lines 152-171)
4. Anti-Patterns (lines 173-194)
5. Examples (lines 196-210)
6. Hard Rules (lines 212-251)
7. When to Use This Convention (lines 253-272)
8. Boundary Types (lines 274-355)
9. Drain Points (lines 356-518) ← CRITICAL: 5 drain patterns + heuristic D
10. Broad-Except Distinction (lines 520-583) ← CRITICAL: narrow+log = violation
11. Constructors Can Raise (lines 584-624)
12. Re-Raise Patterns (lines 625-693)
13. Audit Script (lines 695-774)
14. Migration Playbook (lines 776-792)
15. AI Agent Checklist (lines 809-974) ← CRITICAL: 5 MUST-DO + 7 MUST-NOT-DO + 3 boundary patterns

- [ ] **Step 2: Acknowledge the read in an empty commit**

```bash
git commit --allow-empty -m "chore: TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase 0"
```

### Task 0.3: Phase 0 checkpoint

- [x] **Step 1: Create empty commit marking Phase 0 complete**

```bash
git commit --allow-empty -m "conductor(plan): mark Phase 0 complete (setup + styleguide re-read)"
```

- [ ] **Step 2: Update state.toml Phase 0 status**

```toml
phase_0 = { status = "completed", checkpointsha = "<commit_sha>", name = "Setup + styleguide re-read" }
```

- [ ] **Step 3: Commit state.toml**

```bash
git add conductor/tracks/result_migration_gui_2_20260619/state.toml
git commit -m "conductor(state): mark Phase 0 complete"
```

---

## Phase 1: Site Inventory + Classification (3 tasks)

**Focus:** Run the audit, walk every finding, classify each of the 42 migration-target sites.

### Task 1.1: Run the audit + capture JSON output [a068934]

- [x] **Step 1: Run audit and save JSON**

```bash
uv run python scripts/audit_exception_handling.py --src src/gui_2.py --json > tests/artifacts/PHASE1_AUDIT.json
```

- [x] **Step 2: Verify JSON was generated**

```bash
ls -la tests/artifacts/PHASE1_AUDIT.json
```

Expected: file exists, size > 1KB

### Task 1.2: Walk the audit + write the inventory [a068934]

**Files:**
- Create: `tests/artifacts/PHASE1_SITE_INVENTORY.md`

- [x] **Step 1: Extract the 42 migration-target sites**

```bash
uv run python -c "
import json
data = json.load(open('tests/artifacts/PHASE1_AUDIT.json'))
gui = [f for f in data['files'] if 'gui_2' in f.get('filename', '')][0]
findings = gui['findings']
migration_targets = [f for f in findings if f.get('category') in ('INTERNAL_BROAD_CATCH', 'INTERNAL_SILENT_SWALLOW', 'INTERNAL_RETHROW', 'UNCLEAR')]
print(f'Total migration-target sites: {len(migration_targets)}')
for f in migration_targets:
    print(f\"L{f['line']}: [{f['category']}]\")
" > tests/artifacts/PHASE1_MIGRATION_TARGETS.txt
```

- [ ] **Step 2: Verify count = 42**

```bash
cat tests/artifacts/PHASE1_MIGRATION_TARGETS.txt | head -1
```

Expected: `Total migration-target sites: 42`

- [ ] **Step 3: For each migration-target site, write the inventory entry**

For each line in `PHASE1_MIGRATION_TARGETS.txt` (skip the first "Total" line), read the actual code at that line and write to `tests/artifacts/PHASE1_SITE_INVENTORY.md`:

```markdown
# Phase 1 Site Inventory — gui_2.py

Generated: <YYYY-MM-DD>
Total migration-target sites: 42

| Line | Category | Current code (5 lines around) | Target migration | Drain point |
|---|---|---|---|---|
| L<line> | <category> | <code excerpt> | <pattern> | <drain> |
| ... |
```

Use this command template to extract the code around each line:
```bash
sed -n '<line_minus_2>,<line_plus_5>p' src/gui_2.py
```

For "Target migration", reference the per-phase pattern (e.g., "render-loop Result[bool] helper" for Phase 3, "modal Result[bool] helper + drain modal" for Phase 4, etc.).

For "Drain point", specify one of:
- `app._last_request_errors.append(...)` — data plane
- `app._worker_errors.append(...)` + lock — data plane (thread-safe)
- `app._startup_timeline_errors.append(...)` — data plane
- `app._signal_handler_error = ...` — data plane
- `app._inject_preview_error = ...` — data plane
- `app._mcp_config_parse_error = ...` — data plane
- `app._save_project_error = ...` — data plane
- `app._model_fetch_errors[p] = ...` — data plane
- `imgui.open_popup("Error")` — immediate drain (Pattern 2)
- `sys.stderr.write(...) + sys.exit(1)` — Pattern 3 (app termination)

- [ ] **Step 4: Commit the inventory**

```bash
git add tests/artifacts/PHASE1_AUDIT.json tests/artifacts/PHASE1_MIGRATION_TARGETS.txt tests/artifacts/PHASE1_SITE_INVENTORY.md
git commit -m "conductor(plan): Phase 1 site inventory — 42 migration-target sites classified"
```

### Task 1.3: Phase 1 invariant test + checkpoint [554fbbd]

**Files:**
- Create: `tests/test_gui_2_result.py` (initial creation; will be extended each phase)
- Modify: `conductor/tracks/result_migration_gui_2_20260619/state.toml`

- [x] **Step 1: Create the test file with Phase 1 invariant test**

```python
"""Tests for gui_2.py Result[T] migration (sub-track 4 of result_migration_20260616).

Per the anti-sliming protocol, each phase has an invariant test that locks
the per-phase progress. Per-site tests are added per phase.
"""
import json
import subprocess
from pathlib import Path


def _load_gui_2_audit() -> dict:
    """Re-run the audit and return the gui_2.py findings."""
    audit_json = Path("tests/artifacts/PHASE1_AUDIT.json")
    if not audit_json.exists():
        # Re-run the audit if the cached JSON is missing
        subprocess.run(
            ["uv", "run", "python", "scripts/audit_exception_handling.py",
             "--src", "src/gui_2.py", "--json"],
            check=True, capture_output=True,
        )
        # (caller must re-read; this helper is for tests that assume the JSON exists)
    return json.loads(audit_json.read_text())


def test_phase_1_invariant_inventory_complete():
    """Phase 1 invariant: the inventory file exists and has 42 rows."""
    inventory = Path("tests/artifacts/PHASE1_SITE_INVENTORY.md")
    assert inventory.exists(), "PHASE1_SITE_INVENTORY.md must exist"
    content = inventory.read_text()
    # Count the table rows (lines starting with | L and a digit)
    import re
    row_count = len(re.findall(r"^\| L\d+", content, re.MULTILINE))
    assert row_count == 42, f"Expected 42 sites in inventory, found {row_count}"


def test_phase_1_invariant_baseline_captured():
    """Phase 1 invariant: the audit JSON captures the baseline counts."""
    data = _load_gui_2_audit()
    gui = [f for f in data["files"] if "gui_2" in f.get("filename", "")][0]
    assert gui["violation_count"] >= 38, f"Expected ≥38 violations, found {gui['violation_count']}"
    assert gui["suspicious_count"] >= 2, f"Expected ≥2 suspicious, found {gui['suspicious_count']}"
    assert gui["unclear_count"] >= 2, f"Expected ≥2 unclear, found {gui['unclear_count']}"
```

- [x] **Step 2: Run the test — it should PASS (the inventory was committed in Task 1.2)**

```bash
uv run python -m pytest tests/test_gui_2_result.py::test_phase_1_invariant_inventory_complete tests/test_gui_2_result.py::test_phase_1_invariant_baseline_captured -v
```

Expected: 2 PASSED

- [x] **Step 3: Update state.toml Phase 1**

```toml
phase_1 = { status = "completed", checkpointsha = "<commit_sha>", name = "Site inventory + classification (42 sites)" }
```

- [x] **Step 4: Commit**

```bash
git add tests/test_gui_2_result.py conductor/tracks/result_migration_gui_2_20260619/state.toml
git commit -m "conductor(plan): mark Phase 1 complete (42-site inventory + invariant test)"
```

---

## Phase 2: Drain Plane Wiring (4 tasks)

**Focus:** Add the 3 new render functions that read from the controller's data plane and render errors as popups / status indicators. This is the drain plane.

### Task 2.1: Add `render_controller_error_modal` [5b139e6]

**Files:**
- Modify: `src/gui_2.py` (add new module-level render function near `render_error_tint` at line ~6700)

- [ ] **Step 1: Add the new render function**

Add to `src/gui_2.py` (find the existing `render_error_tint` function and add this BELOW it):

```python
def render_controller_error_modal(app: 'App') -> None:
 """Drain plane: render accumulated controller errors as a popup modal.

 Reads from 8 controller attributes added by sub-track 3 Phase 6:
   - app._last_request_errors: per-request RAG + symbol resolution errors
   - app._worker_errors + lock: background worker errors
   - app._startup_timeline_errors: first-frame + warmup errors
   - app._signal_handler_error: signal install errors
   - app._inject_preview_error: context preview errors
   - app._mcp_config_parse_error: MCP config parse errors
   - app._save_project_error: project save errors
   - app._model_fetch_errors: per-provider model fetch errors

 For each non-empty attribute, opens an imgui.open_popup(...) modal with
 the errors displayed. Per error_handling.md:396-407 Pattern 2 (GUI error
 display = drain).

 Pattern: this function returns None (drain terminates propagation).
 Thread-safe: app._worker_errors_lock is acquired on read.
 """
 # 1. Per-request errors (most common; surface first)
 if hasattr(app, "_last_request_errors") and app._last_request_errors:
  for op_name, err in app._last_request_errors:
   if imgui.begin_popup(f"Error: last_request[{op_name}]"):
    imgui.text(f"Operation: {op_name}")
    imgui.separator()
    imgui.text_wrapped(err.ui_message() if hasattr(err, "ui_message") else str(err))
    imgui.separator()
    if imgui.button("Dismiss"):
     app._last_request_errors = [(n, e) for n, e in app._last_request_errors if e != err]
     imgui.close_current_popup()
    imgui.end_popup()
    break  # show one at a time

 # 2. Worker errors (thread-safe read)
 worker_errors: list = []
 if hasattr(app, "_worker_errors_lock"):
  with app._worker_errors_lock:
   worker_errors = list(app._worker_errors) if hasattr(app, "_worker_errors") else []
 if worker_errors:
  if imgui.begin_popup("Error: workers"):
   imgui.text(f"{len(worker_errors)} worker error(s):")
   imgui.separator()
   for op_name, err in worker_errors[:10]:  # cap at 10 to avoid spam
    imgui.bullet_text(f"[{op_name}] {err.ui_message() if hasattr(err, 'ui_message') else str(err)}")
   if len(worker_errors) > 10:
    imgui.text(f"... and {len(worker_errors) - 10} more")
   imgui.separator()
   if imgui.button("Clear worker errors"):
    with app._worker_errors_lock:
     app._worker_errors.clear()
    imgui.close_current_popup()
   imgui.end_popup()

 # 3. Single-error attributes
 for attr_name in ("_signal_handler_error", "_inject_preview_error",
                   "_mcp_config_parse_error", "_save_project_error"):
  err = getattr(app, attr_name, None)
  if err is None:
   continue
  popup_name = f"Error: {attr_name.lstrip('_')}"
  if imgui.begin_popup(popup_name):
   imgui.text(f"Source: {attr_name}")
   imgui.separator()
   imgui.text_wrapped(err.ui_message() if hasattr(err, "ui_message") else str(err))
   imgui.separator()
   if imgui.button("Dismiss"):
    setattr(app, attr_name, None)
    imgui.close_current_popup()
   imgui.end_popup()
   break

 # 4. Startup timeline errors (list)
 timeline_errors = getattr(app, "_startup_timeline_errors", None)
 if timeline_errors:
  if imgui.begin_popup("Error: startup_timeline"):
   imgui.text(f"{len(timeline_errors)} startup error(s):")
   imgui.separator()
   for op_name, err in timeline_errors:
    imgui.bullet_text(f"[{op_name}] {err.ui_message() if hasattr(err, 'ui_message') else str(err)}")
   imgui.end_popup()

 # 5. Per-provider model fetch errors (dict)
 model_errors = getattr(app, "_model_fetch_errors", None)
 if model_errors:
  if imgui.begin_popup("Error: model_fetch"):
   imgui.text(f"{len(model_errors)} provider(s) failed:")
   imgui.separator()
   for provider, err in model_errors.items():
    imgui.bullet_text(f"[{provider}] {err.ui_message() if hasattr(err, 'ui_message') else str(err)}")
   imgui.end_popup()
```

**SAFETY:** This function uses `hasattr` guards because the 8 attributes are added by sub-track 3 Phase 6 — if the controller is older (e.g., during a partial migration), some attributes may not exist yet. The `imgui.begin_popup(...)` / `imgui.end_popup()` are balanced.

- [ ] **Step 2: Verify the function compiles**

```bash
uv run python -c "from src import gui_2; assert hasattr(gui_2, 'render_controller_error_modal'); print('OK')"
```

Expected: `OK`

- [ ] **Step 3: Commit**

```bash
git add src/gui_2.py
git commit -m "feat(gui_2): add render_controller_error_modal (drain plane Pattern 2)"
```

### Task 2.2: Add `_render_worker_error_indicator` [5b139e6]

**Files:**
- Modify: `src/gui_2.py`

- [ ] **Step 1: Add the indicator function below `render_controller_error_modal`**

```python
def _render_worker_error_indicator(app: 'App') -> None:
 """Status-bar widget: shows worker error count; click opens the modal.

 Visible only when app._worker_errors is non-empty. The indicator is a
 small red-text label in the status bar; clicking it calls
 imgui.open_popup("Error: workers") which is handled by
 render_controller_error_modal.
 """
 if not hasattr(app, "_worker_errors"):
  return
 worker_count = 0
 if hasattr(app, "_worker_errors_lock"):
  with app._worker_errors_lock:
   worker_count = len(app._worker_errors)
 else:
  worker_count = len(app._worker_errors)
 if worker_count == 0:
  return
 imgui.push_style_color(imgui.Col_.text, 1.0, 0.4, 0.4, 1.0)  # red
 if imgui.small_button(f"[!] {worker_count} worker error(s)"):
  imgui.open_popup("Error: workers")
 imgui.pop_style_color()
 imgui.same_line()
```

**SAFETY:** Thread-safe read via the lock. Uses `imgui.push_style_color` / `imgui.pop_style_color` balanced.

- [ ] **Step 2: Verify it compiles**

```bash
uv run python -c "from src import gui_2; assert hasattr(gui_2, '_render_worker_error_indicator'); print('OK')"
```

Expected: `OK`

- [ ] **Step 3: Commit**

```bash
git add src/gui_2.py
git commit -m "feat(gui_2): add _render_worker_error_indicator (status bar)"
```

### Task 2.3: Add `_render_last_request_errors_modal` [5b139e6]

**Files:**
- Modify: `src/gui_2.py`

- [ ] **Step 1: Add the per-request error modal**

```python
def _render_last_request_errors_modal(app: 'App') -> None:
 """Per-request error modal: shows errors from the most recent AI request.

 Called from _handle_generate_send after each AI request completes.
 Only opens if app._last_request_errors is non-empty. The user can
 dismiss errors via a button; clearing the list resets the data plane.
 """
 if not hasattr(app, "_last_request_errors"):
  return
 if not app._last_request_errors:
  return
 if not imgui.is_popup_open("Error: last_request"):
  imgui.open_popup("Error: last_request")
 if imgui.begin_popup_modal("Error: last_request")[0]:
  imgui.text(f"{len(app._last_request_errors)} error(s) during last request:")
  imgui.separator()
  for op_name, err in app._last_request_errors:
   imgui.bullet_text(f"[{op_name}] {err.ui_message() if hasattr(err, 'ui_message') else str(err)}")
  imgui.separator()
  if imgui.button("Dismiss all"):
   app._last_request_errors.clear()
   imgui.close_current_popup()
  imgui.end_popup()
```

**SAFETY:** Modal is opened only if errors exist; the `imgui.begin_popup_modal(...)[0]` pattern is the standard imgui-bundle modal idiom. The `app._last_request_errors.clear()` is main-thread-only (no lock needed; same thread that appended).

- [ ] **Step 2: Verify it compiles**

```bash
uv run python -c "from src import gui_2; assert hasattr(gui_2, '_render_last_request_errors_modal'); print('OK')"
```

Expected: `OK`

- [ ] **Step 3: Commit**

```bash
git add src/gui_2.py
git commit -m "feat(gui_2): add _render_last_request_errors_modal (per-request errors)"
```

### Task 2.4: Phase 2 invariant test + checkpoint [5b139e6]

**Files:**
- Modify: `tests/test_gui_2_result.py`
- Modify: `conductor/tracks/result_migration_gui_2_20260619/state.toml`

- [x] **Step 1: Add Phase 2 invariant test**

Append to `tests/test_gui_2_result.py`:

```python
def test_phase_2_invariant_drain_plane_render_functions_exist():
 """Phase 2 invariant: the 3 drain plane render functions exist."""
 from src import gui_2
 assert hasattr(gui_2, "render_controller_error_modal"), "render_controller_error_modal missing"
 assert hasattr(gui_2, "_render_worker_error_indicator"), "_render_worker_error_indicator missing"
 assert hasattr(gui_2, "_render_last_request_errors_modal"), "_render_last_request_errors_modal missing"


def test_phase_2_invariant_audit_still_passes():
 """Phase 2 invariant: introducing the drain plane did not create new violations."""
 import subprocess
 r = subprocess.run(
 ["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src/gui_2.py"],
 capture_output=True, text=True,
 )
 # The audit's output for gui_2.py should still show the original counts
 assert "gui_2.py" in r.stdout
 # No new SILENT_SWALLOW or BROAD_CATCH introduced by the new functions
 # (the new functions are clean Result-based; the audit should not flag them)
 gui_line = [l for l in r.stdout.split("\n") if "gui_2.py" in l and "INTERNAL" not in l]
 assert gui_line, f"Expected gui_2.py line in audit output, got: {r.stdout}"
```

- [ ] **Step 2: Run the new tests + Phase 1 tests (regression check)**

```bash
uv run python -m pytest tests/test_gui_2_result.py -v
```

Expected: 4 PASSED (2 Phase 1 + 2 Phase 2)

- [x] **Step 3: Update state.toml Phase 2**

```toml
phase_2 = { status = "completed", checkpointsha = "<commit_sha>", name = "Drain plane wiring (3 new render functions)" }
```

- [x] **Step 4: Commit**

```bash
git add tests/test_gui_2_result.py conductor/tracks/result_migration_gui_2_20260619/state.toml
git commit -m "conductor(plan): mark Phase 2 complete (drain plane: 3 render functions + 2 invariant tests)"
```

---

## Migration Pattern (used by Phases 3-12)

Every migration follows this pattern. The `_result` helper convention:

```python
# BEFORE (in src/gui_2.py)
def _render_x(app, ...):
 try:
  do_something()
 except Exception as e:
  sys.stderr.write(f"Error: {e}\n")  # SLIMING: logging-only, NOT a drain

# AFTER
def _render_x_result(app, ...) -> Result[bool]:
 """Drain-aware: returns Result[bool] so caller can check .ok and route to data plane."""
 try:
  do_something()
  return Result(data=True)
 except Exception as e:
  return Result(data=False, errors=[ErrorInfo(
 kind=ErrorKind.INTERNAL, message=str(e),
 source="gui_2._render_x_result", original=e,
 )])

def _render_x(app, ...):
 """Legacy wrapper. Checks .ok; routes errors to controller's data plane."""
 result = _render_x_result(app, ...)
 if not result.ok:
  app._last_request_errors.append(("_render_x", result.errors[0]))
  return  # drain to data plane; sub-track 4's render_controller_error_modal surfaces it
```

The unit test pattern:

```python
def test_<site>_returns_result_on_success(app_with_x):
 """Migrated helper returns Result.ok=True on success."""
 app, _ = app_with_x
 result = gui_2._render_x_result(app, ...)
 assert result.ok
 assert result.data is True
 assert result.errors == []


def test_<site>_returns_result_with_error_on_failure(app_with_x_failing):
 """Migrated helper returns Result.ok=False with ErrorInfo on failure."""
 app, _ = app_with_x_failing
 result = gui_2._render_x_result(app, ...)
 assert not result.ok
 assert result.errors
 assert result.errors[0].kind == ErrorKind.INTERNAL
 assert result.errors[0].source == "gui_2._render_x_result"


def test_<site>_legacy_wrapper_accumulates_to_data_plane(app_with_x_failing):
 """Legacy wrapper routes errors to app._last_request_errors (data plane)."""
 app, _ = app_with_x_failing
 gui_2._render_x(app, ...)
 assert len(app._last_request_errors) >= 1
 assert app._last_request_errors[-1][0] == "_render_x"
```

**For render-loop sites (Phase 3):** the legacy wrapper returns early on error to avoid breaking the immediate-mode frame.
**For modal sites (Phase 4):** the legacy wrapper triggers `imgui.open_popup("Error: ...")`.
**For event handlers (Phase 5):** the legacy wrapper appends to `app._last_request_errors` (per-event accumulator).
**For signal handlers (Phase 6):** the helper itself is the drain (Pattern 3).
**For worker sites (Phase 7):** the legacy wrapper uses `app._report_worker_error`.
**For setters (Phase 8):** the legacy wrapper uses `app._report_worker_error`.
**For helpers (Phase 9):** the legacy wrapper appends to the appropriate controller attribute.
**For silent-swallow sites (Phase 10):** identical to the pattern above; the only difference is that the source site was logging-only.

---

## Phase 3: INTERNAL_BROAD_CATCH Batch A — Render-Loop Sites (≤10 sites)

**Focus:** The broad-catch sites inside render-loop functions called every frame from `render_main_interface`. Per-site tasks use the migration pattern above.

### Task 3.0: Phase 3 styleguide re-read + site list confirmation

- [ ] **Step 1: Re-read `conductor/code_styleguides/error_handling.md` lines 396-407 (Pattern 2 GUI error display = drain) and 462-476 (what is NOT a drain)**

```bash
sed -n '356,518p' conductor/code_styleguides/error_handling.md
```

- [ ] **Step 2: Confirm the Batch A site list from the Phase 1 inventory**

```bash
grep -E "Phase 3|Batch A|render-loop" tests/artifacts/PHASE1_SITE_INVENTORY.md | head -20
```

- [ ] **Step 3: Styleguide ack commit (empty)**

```bash
git commit --allow-empty -m "chore: TIER-2 READ conductor/code_styleguides/error_handling.md lines 356-518 (Pattern 2 drain) before Phase 3"
```

### Task 3.1: Migrate site L<line1> (representative example)

**Files:**
- Modify: `src/gui_2.py:L<line1>`
- Modify: `tests/test_gui_2_result.py`

- [ ] **Step 1: Write failing test**

```python
def test_l<line1>_returns_result_on_success():
 """<site name> returns Result.ok=True on success."""
 from src import gui_2
 # Build a minimal app mock with the controller attributes this site reads
 app = make_mock_app_with_<attribute>()
 result = gui_2._<site>_result(app, ...)
 assert result.ok
 assert result.data is True
 assert result.errors == []


def test_l<line1>_returns_result_with_error_on_failure():
 """<site name> returns Result.ok=False with ErrorInfo on failure."""
 from src import gui_2
 app = make_mock_app_with_<attribute>_failing()
 result = gui_2._<site>_result(app, ...)
 assert not result.ok
 assert result.errors
 assert result.errors[0].source == "gui_2._<site>_result"
```

- [ ] **Step 2: Run test, verify FAIL**

```bash
uv run python -m pytest tests/test_gui_2_result.py::test_l<line1>_returns_result_on_success -v
```

Expected: FAIL with `AttributeError: module 'src.gui_2' has no attribute '_<site>_result'`

- [ ] **Step 3: Migrate the site**

In `src/gui_2.py`, find the existing `_<site>` function (around L<line1>) and:
1. Extract a new `_<site>_result(app, ...) -> Result[bool]` helper ABOVE the original
2. The original `_<site>` becomes the legacy wrapper that calls the helper and routes errors to `app._last_request_errors`

```python
def _<site>_result(app: 'App', ...) -> Result[bool]:
 """Drain-aware variant of _<site>. Per anti-sliming protocol."""
 try:
  # ... original body ...
  return Result(data=True)
 except Exception as e:
  return Result(data=False, errors=[ErrorInfo(
 kind=ErrorKind.INTERNAL, message=str(e),
 source="gui_2._<site>_result", original=e,
 )])

def _<site>(app: 'App', ...):
 """Legacy wrapper. Routes errors to app._last_request_errors."""
 result = _<site>_result(app, ...)
 if not result.ok:
  app._last_request_errors.append(("_<site>", result.errors[0]))
  return
 # ... original success path ...
```

- [ ] **Step 4: Run test, verify PASS**

```bash
uv run python -m pytest tests/test_gui_2_result.py::test_l<line1>_returns_result_on_success tests/test_gui_2_result.py::test_l<line1>_returns_result_with_error_on_failure -v
```

Expected: 2 PASSED

- [ ] **Step 5: Audit pre/post check**

```bash
# PRE (capture before; should show the site as INTERNAL_BROAD_CATCH)
uv run python scripts/audit_exception_handling.py --src src/gui_2.py --json 2>&1 | python -c "
import json, sys
data = json.loads(sys.stdin.read())
gui = [f for f in data['files'] if 'gui_2' in f.get('filename', '')][0]
site = [f for f in gui['findings'] if f.get('line') == <line1>]
print('PRE:', site[0]['category'] if site else 'NOT FOUND')
"
# POST (after migration; should show the site in INTERNAL_COMPLIANT or moved out of V)
# (run after the migration code is saved)
```

- [ ] **Step 6: Commit**

```bash
git add src/gui_2.py tests/test_gui_2_result.py
git commit -m "refactor(gui_2): migrate L<line1> _<site> to Result[T] (Phase 3)"
```

### Tasks 3.2 through 3.10: Migrate remaining Batch A sites

For each remaining site in Batch A (lines L<line2>, L<line3>, ... L<line10>), repeat Task 3.1's structure:
- Step 1: Write failing test (with the site name + line number)
- Step 2: Run, verify FAIL
- Step 3: Migrate the site (extract `_result` helper + legacy wrapper)
- Step 4: Run test, verify PASS
- Step 5: Audit pre/post check
- Step 6: Commit (one per site)

If the exact line numbers are unknown, run the audit to find them:
```bash
uv run python scripts/audit_exception_handling.py --src src/gui_2.py --json | python -c "
import json, sys
data = json.loads(sys.stdin.read())
gui = [f for f in data['files'] if 'gui_2' in f.get('filename', '')][0]
for f in gui['findings']:
 if f.get('category') == 'INTERNAL_BROAD_CATCH':
  print(f\"L{f['line']}: {f.get('excerpt', '')[:80]}\")
" | head -10
```

### Task 3.11: Phase 3 invariant test + checkpoint

- [ ] **Step 1: Add Phase 3 invariant test**

Append to `tests/test_gui_2_result.py`:

```python
def test_phase_3_invariant_broad_catch_batch_a_count_dropped():
 """Phase 3 invariant: Batch A sites moved out of INTERNAL_BROAD_CATCH."""
 import subprocess, json
 r = subprocess.run(
 ["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src/gui_2.py", "--json"],
 capture_output=True, text=True, check=True,
 )
 data = json.loads(r.stdout)
 gui = [f for f in data["files"] if "gui_2" in f.get("filename", "")][0]
 # Find the Batch A sites from Phase 1 inventory
 batch_a_lines = [int(line) for line in <PHASE1_BATCH_A_LINES>]
 remaining_in_v = [
 f for f in gui["findings"]
 if f.get("line") in batch_a_lines and f.get("category") in ("INTERNAL_BROAD_CATCH", "INTERNAL_SILENT_SWALLOW")
 ]
 assert not remaining_in_v, (
 f"Phase 3 Batch A sites still in V category: {[(f['line'], f['category']) for f in remaining_in_v]}"
 )
```

Replace `<PHASE1_BATCH_A_LINES>` with the actual list (e.g., `[123, 456, 789]`).

- [ ] **Step 2: Run all Phase 3 tests**

```bash
uv run python -m pytest tests/test_gui_2_result.py -v -k "phase_3 or l<line1> or l<line2> or ..."
```

Expected: all PASSED

- [ ] **Step 3: Update state.toml Phase 3**

```toml
phase_3 = { status = "completed", checkpointsha = "<commit_sha>", name = "INTERNAL_BROAD_CATCH Batch A — render-loop sites (≤10)" }
```

- [ ] **Step 4: Commit**

```bash
git add tests/test_gui_2_result.py conductor/tracks/result_migration_gui_2_20260619/state.toml
git commit -m "conductor(plan): mark Phase 3 complete (Batch A: ≤10 render-loop sites + invariant test)"
```

---

## Phase 4: INTERNAL_BROAD_CATCH Batch B — Modal/Dialog Sites (≤10 sites)

**Focus:** The broad-catch sites inside modal functions (`render_approve_script_modal`, `render_patch_modal`, etc.). The migration pattern is identical to Phase 3 EXCEPT the legacy wrapper triggers `imgui.open_popup("Error: ...")` immediately on failure (Pattern 2 drain) instead of appending to `app._last_request_errors`.

### Task 4.0: Phase 4 styleguide re-read

- [ ] **Step 1: Re-read the Pattern 2 section (lines 396-407)**

- [ ] **Step 2: Styleguide ack commit (empty)**

```bash
git commit --allow-empty -m "chore: TIER-2 READ conductor/code_styleguides/error_handling.md Pattern 2 (GUI error display) before Phase 4"
```

### Tasks 4.1 through 4.N: Migrate Batch B sites

For each Batch B site (use the Phase 1 inventory to find the lines):
- Step 1: Write failing test (with site name + line number)
- Step 2: Run, verify FAIL
- Step 3: Migrate — extract `_result` helper + legacy wrapper that triggers `imgui.open_popup`:

```python
def _<modal>_result(app: 'App', ...) -> Result[bool]:
 try:
  # ... original body ...
  return Result(data=True)
 except Exception as e:
  return Result(data=False, errors=[ErrorInfo(
 kind=ErrorKind.INTERNAL, message=str(e),
 source="gui_2._<modal>_result", original=e,
 )])

def _<modal>(app: 'App', ...):
 result = _<modal>_result(app, ...)
 if not result.ok:
  imgui.open_popup(f"Error: _<modal>")  # Pattern 2 drain
  return
 # ... original success path ...
```

- Step 4: Run test, verify PASS
- Step 5: Audit pre/post check
- Step 6: Commit (one per site; message format: `refactor(gui_2): migrate L<line> _<modal> to Result[T] (Phase 4)`)

### Task 4.N+1: Phase 4 invariant test + checkpoint

```python
def test_phase_4_invariant_broad_catch_batch_b_count_dropped():
 """Phase 4 invariant: Batch B sites moved out of INTERNAL_BROAD_CATCH."""
 # Same pattern as Phase 3 invariant, with batch_b_lines list
 batch_b_lines = <PHASE1_BATCH_B_LINES>
 # ... (same as Phase 3 invariant)
```

Update state.toml:
```toml
phase_4 = { status = "completed", checkpointsha = "<commit_sha>", name = "INTERNAL_BROAD_CATCH Batch B — modal/dialog sites (≤10)" }
```

---

## Phase 5: INTERNAL_BROAD_CATCH Batch C — Event Handler Sites (≤10 sites)

**Focus:** The broad-catch sites inside event handlers (`_handle_approve_ask`, `_handle_save_anyway_click`, etc.). The legacy wrapper routes errors to `app._last_request_errors` (the per-event accumulator).

Same structure as Phase 3, but the site categories are event handlers. Use the Phase 1 inventory for line numbers.

### Task 5.0: Phase 5 styleguide re-read + ack commit

### Tasks 5.1 through 5.N: Migrate Batch C sites

For each Batch C site:
- Extract `_handle_<event>_result(app, ...) -> Result[bool]` helper
- Legacy wrapper appends to `app._last_request_errors` with `("_handle_<event>", result.errors[0])`

### Task 5.N+1: Phase 5 invariant test + checkpoint

---

## Phase 6: Signal Handler Sites (≤5 sites)

**Focus:** The INTERNAL_RETHROW sites in signal handlers (`_init_actions` + similar). Per Pattern 3 from `error_handling.md:409-419`, the drain is `sys.stderr.write(...) + sys.exit(1)`.

### Task 6.0: Phase 6 styleguide re-read (Pattern 3 lines 409-419) + ack commit

### Tasks 6.1 through 6.N: Migrate signal handler sites

For each signal handler site, the `_result` helper itself is the drain:

```python
def _install_<signal>_result(app: 'App') -> Result[None]:
 """Signal handler installer. On failure, drain via Pattern 3 (stderr + sys.exit)."""
 try:
  # ... install signal handler ...
  return Result(data=None)
 except Exception as e:
  sys.stderr.write(f"FATAL: failed to install signal handler: {e}\n")
  sys.exit(1)
 # Note: no return after sys.exit; sys.exit raises SystemExit which propagates as drain
```

The unit test captures `sys.exit`:

```python
def test_install_<signal>_result_drains_via_sys_exit_on_failure(monkeypatch):
 """Signal handler install failure triggers Pattern 3 drain (sys.exit)."""
 from src import gui_2
 monkeypatch.setattr(gui_2.sys, "exit", lambda code: (_ for _ in ()).throw(SystemExit(code)))
 # ... trigger the failure path ...
 with pytest.raises(SystemExit):
  gui_2._install_<signal>_result(make_mock_app())
```

### Task 6.N+1: Phase 6 invariant test + checkpoint

---

## Phase 7: Worker / Background Sites (≤5 sites)

**Focus:** The broad-catch sites in worker closures (callbacks invoked from `_io_pool`). Use `app._report_worker_error(op_name, result)` helper (added in sub-track 3 Phase 6 Group 6.5).

### Task 7.0: Phase 7 styleguide re-read + ack commit

### Tasks 7.1 through 7.N: Migrate worker sites

For each worker site:
- Extract `_do_<worker>_result(app, ...) -> Result[None]` helper
- Legacy wrapper calls `app._report_worker_error("_do_<worker>", result)` on failure

The thread-safety invariant test:

```python
def test_worker_<site>_thread_safe_under_concurrent_appends():
 """Worker error accumulator is thread-safe (sub-track 3 Phase 6 lock)."""
 from src import gui_2
 import threading
 app = make_mock_app_with_workers_lock()
 errors = []
 def worker(i):
  for _ in range(100):
  result = gui_2._do_<worker>_result(app, f"payload_{i}")
  if not result.ok:
  app._report_worker_error(f"_do_<worker>_{i}", result)
 threads = [threading.Thread(target=worker, args=(i,)) for i in range(10)]
 for t in threads: t.start()
 for t in threads: t.join()
 assert len(app._worker_errors) == 1000  # all 1000 failures accumulated
```

### Task 7.N+1: Phase 7 invariant test + checkpoint

---

## Phase 8: Property Setter / State Sites (≤5 sites)

**Focus:** The broad-catch sites in property setters / state mutations. Use `_report_worker_error` per sub-track 3 Phase 6 Group 6.3 pattern.

### Task 8.0: Phase 8 styleguide re-read + ack commit

### Tasks 8.1 through 8.N: Migrate setter sites

For each setter:
- Extract `_set_<attr>_result(app, value) -> Result[None]` helper
- Legacy setter calls `app._report_worker_error("_set_<attr>", result)` on failure

### Task 8.N+1: Phase 8 invariant test + checkpoint

---

## Phase 9: Helper / Utility Sites (≤5 sites)

**Focus:** Module-level helpers (`_check_auto_refresh_context_preview`, etc.). Each helper returns `Result[T]`; callers accumulate in the appropriate controller attribute.

### Task 9.0: Phase 9 styleguide re-read + ack commit

### Tasks 9.1 through 9.N: Migrate helper sites

Same pattern as Phase 3 (render-loop Result[T] helpers).

### Task 9.N+1: Phase 9 invariant test + checkpoint

---

## Phase 10: INTERNAL_SILENT_SWALLOW Migrations (≤13 sites)

**Focus:** The 13 logging-only except bodies. **Per the user's principle (2026-06-17), logging is NOT a drain.** Each site MUST be migrated to `Result[T]` propagation. No narrowing + logging; no pass after logging; no "intentional silent recovery."

### Task 10.0: Phase 10 styleguide re-read + ack commit (CRITICAL — this is the sliming-prone phase)

- [ ] **Step 1: Re-read the Broad-Except Distinction table (lines 520-540)**

```bash
sed -n '520,540p' conductor/code_styleguides/error_handling.md
```

- [ ] **Step 2: Re-read the "What is NOT a drain point" section (lines 462-476)**

```bash
sed -n '462,476p' conductor/code_styleguides/error_handling.md
```

- [ ] **Step 3: Styleguide ack commit (empty) — explicitly call out the sliming risk**

```bash
git commit --allow-empty -m "chore: TIER-2 READ conductor/code_styleguides/error_handling.md lines 462-540 (logging NOT a drain) before Phase 10 — NO silent recovery, NO narrowing+logging"
```

### Tasks 10.1 through 10.N: Migrate silent-swallow sites

For each silent-swallow site:
- Extract `_<feature>_result(app, ...) -> Result[T]` helper
- The except body MUST return `Result(data=<zero-value>, errors=[ErrorInfo(original=e)])`
- NO `logging.error(...)` in the except body
- NO `sys.stderr.write(...)` in the except body
- NO `pass` in the except body
- The legacy wrapper appends to `app._last_request_errors` (or another controller attribute)

**Audit post-check is critical:** verify the site is no longer in `INTERNAL_SILENT_SWALLOW` AND no new `INTERNAL_SILENT_SWALLOW` was introduced elsewhere.

### Task 10.N+1: Phase 10 invariant test + checkpoint

```python
def test_phase_10_invariant_silent_swallow_count_zero():
 """Phase 10 invariant: 0 INTERNAL_SILENT_SWALLOW sites in src/gui_2.py."""
 import subprocess, json
 r = subprocess.run(
 ["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src/gui_2.py", "--json"],
 capture_output=True, text=True, check=True,
 )
 data = json.loads(r.stdout)
 gui = [f for f in data["files"] if "gui_2" in f.get("filename", "")][0]
 silent = [f for f in gui["findings"] if f.get("category") == "INTERNAL_SILENT_SWALLOW"]
 assert not silent, f"Expected 0 INTERNAL_SILENT_SWALLOW sites, found {len(silent)}: {[f['line'] for f in silent]}"
```

---

## Phase 11: INTERNAL_RETHROW Classification (≤2 sites)

**Focus:** The 2 INTERNAL_RETHROW sites. Classify per Pattern 1/2/3 from `error_handling.md:625-690`. If a site does not fit any pattern, migrate to `Result[T]` (NOT classified as "suspicious").

### Task 11.0: Phase 11 styleguide re-read (Re-Raise Patterns lines 625-690) + ack commit

### Tasks 11.1 through 11.N: Classify each INTERNAL_RETHROW site

For each site:
- Read the site code
- Determine which of the 3 patterns it fits:
  - Pattern 1: catch + convert + raise as different type (compliant if convert is meaningful)
  - Pattern 2: catch + log + re-raise (compliant if log provides value beyond re-raise)
  - Pattern 3: catch + cleanup + re-raise via try/finally (compliant; canonical cleanup)
- If compliant: add a comment explaining which pattern it follows; no code change needed
- If NOT compliant: migrate to Result[T] using the standard pattern

### Task 11.N+1: Phase 11 invariant test + checkpoint

---

## Phase 12: UNCLEAR Classification (≤2 sites)

**Focus:** The 2 UNCLEAR sites. Read each, classify compliant (with comment) or migrate.

### Task 12.0: Phase 12 styleguide re-read + ack commit

### Tasks 12.1 through 12.N: Classify each UNCLEAR site

For each site:
- Read the site code AND 5 lines of context around it
- Determine if it's truly UNCLEAR or if the Phase 7 heuristic misclassified it
- If the heuristic is wrong: report to user (Tier 1); do NOT change the heuristic in this track
- If the site is genuinely compliant: add a comment explaining why
- If the site needs migration: use the standard migration pattern

### Task 12.N+1: Phase 12 invariant test + checkpoint

```python
def test_phase_12_invariant_unclear_count_zero():
 """Phase 12 invariant: 0 UNCLEAR sites in src/gui_2.py."""
 # (Same pattern as Phase 10 invariant, but for UNCLEAR)
```

---

## Phase 13: Audit Gate + End-of-Track Report (5 tasks)

**Focus:** Verify all gates, run the full batched suite, write the report, mark the track complete.

### Task 13.1: Run the strict audit gate

- [ ] **Step 1: Run `uv run python scripts/audit_exception_handling.py --src src/gui_2.py --strict`**

```bash
uv run python scripts/audit_exception_handling.py --src src/gui_2.py --strict
```

Expected: exit 0; no violations

- [ ] **Step 2: If exit non-zero, identify the failing sites and report to user**

```bash
uv run python scripts/audit_exception_handling.py --src src/gui_2.py --strict 2>&1 | grep -E "VIOLATION|src/gui_2.py"
```

### Task 13.2: Run the unit tests

- [ ] **Step 1: Run `tests/test_gui_2_result.py`**

```bash
uv run python -m pytest tests/test_gui_2_result.py -v
```

Expected: all PASSED (≥55 tests)

### Task 13.3: Run the 11-tier batched suite

- [ ] **Step 1: Run the fixed batched script**

```bash
uv run python scripts/run_tests_batched.py
```

Expected: 11/11 tiers PASS

- [ ] **Step 2: If any tier fails, save the log to `tests/artifacts/PHASE13_TEST_RUN_<timestamp>.log` and report**

### Task 13.4: Write the end-of-track report

**Files:**
- Create: `docs/reports/TRACK_COMPLETION_result_migration_gui_2_20260619.md`

- [ ] **Step 1: Write the report (template below)**

```markdown
# Track Completion: Result Migration — Sub-Track 4 (gui_2.py)

**Track ID:** `result_migration_gui_2_20260619`
**Date:** <YYYY-MM-DD>
**Status:** SHIPPED

## 1. Header / Scope Summary

<1-2 sentence summary>

## 2. Phase-by-Phase Summary

<13 sections, one per phase, with audit count delta>

## 3. Audit Results (Pre vs Post)

| Category | Pre-Phase-0 | Post-Phase-13 |
|---|---|---|
| INTERNAL_BROAD_CATCH | 38 | 0 |
| INTERNAL_SILENT_SWALLOW | 13 | 0 |
| INTERNAL_RETHROW | 2 | 0 |
| UNCLEAR | 2 | 0 |
| INTERNAL_COMPLIANT | 12 | <new count> |

## 4. Last 3 Failures Encountered

<1-2 sentences per failure>

## 5. Files Modified

| Path | Lines | Description |
|---|---|---|

## 6. Git State

<commit count; first/last commit hashes; branch>

## 7. Recommendation

<Sub-track 5 (baseline cleanup) is now unblocked>

## 8. Post-Completion Fixes (if any)

<if any post-completion commits were needed>
```

- [ ] **Step 2: Commit the report**

```bash
git add docs/reports/TRACK_COMPLETION_result_migration_gui_2_20260619.md
git commit -m "docs(reports): TRACK_COMPLETION_result_migration_gui_2_20260619 (13 phases complete)"
```

### Task 13.5: Final checkpoint + tracks.md update + umbrella count

- [ ] **Step 1: Phase 13 checkpoint commit**

```bash
git commit --allow-empty -m "conductor(checkpoint): Phase 13 complete — sub-track 4 SHIPPED"
```

- [ ] **Step 2: Update `conductor/tracks.md` row to "shipped 2026-06-XX"**

- [ ] **Step 3: Update umbrella spec count**

```bash
# Edit conductor/tracks/result_migration_20260616/spec.md
# Update the per-sub-track count: sub-track 4 = 42 migration sites + 6 infra = 48
```

- [ ] **Step 4: Final commit**

```bash
git add conductor/tracks.md conductor/tracks/result_migration_20260616/spec.md conductor/tracks/result_migration_gui_2_20260619/state.toml conductor/tracks/result_migration_gui_2_20260619/metadata.json
git commit -m "conductor(plan): sub-track 4 SHIPPED — tracks.md updated, umbrella count updated"
```

---

## Summary

**13 phases, ~60+ atomic commits, 42 migration sites + 6 infra + 55+ tests + 1 report.**

| Dimension | Count |
|---|---|
| Source files modified | 1 (`src/gui_2.py`) |
| Test files created | 1 (`tests/test_gui_2_result.py`) |
| Migration sites | 42 (38 V + 2 S + 2 UNCLEAR) |
| Infra sites | 6 (3 new render functions + 3 helper integrations) |
| Tests | ≥55 (42 site + 13 invariant) |
| Phases | 13 |
| Atomic commits | ≥60 |

---

## Self-Review

**1. Spec coverage:** All 14 VCs in spec.md §8 are covered by tasks in this plan. VC-1 (audit --strict) is Task 13.1. VC-2 (0 INTERNAL_BROAD_CATCH) is Tasks 3-5 + Phase 3-5 invariants. VC-3 (0 INTERNAL_SILENT_SWALLOW) is Task 10 + Phase 10 invariant. VC-4 (0 UNCLEAR) is Task 12 + Phase 12 invariant. VC-5 (0 INTERNAL_RETHROW) is Task 11 + Phase 11 invariant. VC-6 (3 new render functions) is Tasks 2.1-2.3. VC-7 (≥55 tests) is the per-phase test additions. VC-8 (11-tier batched) is Task 13.3. VC-9 (per-phase audit gates) is the per-phase invariant tests. VC-10 (styleguide ack commits) is the per-phase Task 0 commit. VC-11 (≥60 commits) is the per-site commits. VC-12 (report) is Task 13.4. VC-13 (tracks.md) is Task 13.5. VC-14 (umbrella count) is Task 13.5.

**2. Placeholder scan:** No "TBD", "TODO", "implement later", "fill in details" in this plan. All migration patterns show concrete code. All tasks show concrete commands.

**3. Type consistency:** `Result[bool]` / `Result[None]` / `Result[T]` used consistently across all migration tasks. `app._last_request_errors`, `app._worker_errors`, `app._report_worker_error` referenced consistently per sub-track 3 Phase 6 contract.

**4. Anti-sliming protocol:** Enforced via (a) styleguide re-read at start of every phase, (b) per-site audit pre/post check, (c) per-phase invariant test, (d) per-file atomic commits, (e) explicit instruction in Phase 10 (the sliming-prone phase) that narrowing+logging is forbidden.

---

---

---