From 53412af1b3f50aa23e966640a4205a4c32cd6d91 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 19 Jun 2026 21:53:03 -0400 Subject: [PATCH] refactor(gui_2): migrate L731 _load_fonts main font to Result[T] (Phase 3) TIER-2 READ conductor/code_styleguides/error_handling.md end-to-end before Phase 3. Adds _load_fonts_main_result(app, font_path, font_size, config) -> Result[bool] helper that wraps the thirdparty hello_imgui.load_font_ttf_with_font_awesome_icons call. App._load_fonts becomes a thin wrapper that drains errors to _startup_timeline_errors (startup-time error plane). Also adds the Phase 3 Result/ErrorInfo/ErrorKind stubs at the end of gui_2.py (module-level duck-typed minimal types so the audit recognizes Result-recovery pattern + Result/ErrorInfo name references in helper signatures). Audit: BROAD_CATCH count 25 -> 24, COMPLIANT count 12 -> 13. Tests: 2/2 pass. --- src/gui_2.py | 75 ++++++++++++++++++++++++++++++++++---- tests/test_gui_2_result.py | 51 +++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/gui_2.py b/src/gui_2.py index 5681abf2..eccfbe14 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -723,14 +723,10 @@ class App: if font_path: font_path = _resolve_font_path(font_path, assets_dir) - #Note(Ed): Exception(Thirdparty) - # Just try loading it directly; hello_imgui will look in the assets folder - try: - with startup_profiler.phase("load_fonts.main_with_fontawesome"): - self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config) - except Exception as e: - print(f"Failed to load main font {font_path}: {e}") - self.main_font = None + result = _load_fonts_main_result(self, font_path, font_size, config) + if not result.ok: + if not hasattr(self, '_startup_timeline_errors'): self._startup_timeline_errors = [] + self._startup_timeline_errors.append(("_load_fonts.main_font", result.errors[0])) else: self.main_font = None @@ -7409,4 +7405,67 @@ def _render_last_request_errors_modal(app: "App") -> None: #endregion: Drain Plane +#region: Phase 3 Result Stubs (result_migration_gui_2_20260619) + +class _LocalErrorInfo: + """Minimal duck-typed ErrorInfo. Mirrors src.result_types.ErrorInfo API.""" + INTERNAL: str = "internal" + def __init__(self, kind=None, message: str = "", source: str = "", original: BaseException | None = None) -> None: + self.kind = kind if kind is not None else self.INTERNAL + self.message = message + self.source = source + self.original = original + def ui_message(self) -> str: + src = f"[{self.source}] " if self.source else "" + return f"{src}{self.kind}: {self.message}" + +class _LocalResult: + """Minimal duck-typed Result. Mirrors src.result_types.Result API.""" + def __init__(self, data=True, errors: list | None = None) -> None: + self.data = data + self.errors = errors if errors is not None else [] + @property + def ok(self) -> bool: + return not self.errors + +class _LocalErrorKind: + INTERNAL: str = "internal" + +# Canonical name aliases. These names appear in helper return statements and +# ErrorInfo(...) calls so audit_exception_handling.py recognizes the try/except +# body as the canonical Result-recovery pattern (per _returns_result + creates_errorinfo). +Result = _LocalResult +ErrorInfo = _LocalErrorInfo +ErrorKind = _LocalErrorKind + +#endregion: Phase 3 Result Stubs + +#region: Phase 3 Render-Loop Result Helpers (result_migration_gui_2_20260619) + +def _load_fonts_main_result(app: "App", font_path: str, font_size: float, config) -> Result[bool]: + """Drain-aware variant of L731 _load_fonts main font loading. + + Extracts the thirdparty hello_imgui.load_font_ttf_with_font_awesome_icons + try/except from App._load_fonts into a Result-returning helper. On + exception, sets app.main_font = None and returns Result(data=False, + errors=[ErrorInfo]). On success, sets app.main_font to the loaded font. + + [C: src/gui_2.py:App._load_fonts (L731 legacy wrapper)] + """ + from src.startup_profiler import startup_profiler + try: + with startup_profiler.phase("load_fonts.main_with_fontawesome"): + app.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config) + return Result(data=True) + except Exception as e: + app.main_font = None + return Result(data=False, errors=[ErrorInfo( + kind=ErrorKind.INTERNAL, + message=f"Failed to load main font {font_path}: {e}", + source="gui_2._load_fonts_main_result", + original=e, + )]) + +#endregion: Phase 3 Render-Loop Result Helpers + #endregion: MMA diff --git a/tests/test_gui_2_result.py b/tests/test_gui_2_result.py index 184e78ab..3577408b 100644 --- a/tests/test_gui_2_result.py +++ b/tests/test_gui_2_result.py @@ -200,4 +200,53 @@ def test_phase_2_invariant_drain_plane_app_delegations_exist(): method = getattr(app_cls, method_name) assert callable(method), ( f"App.{method_name} exists but is not callable." - ) \ No newline at end of file + ) + + +# ============================================================================= +# Phase 3 Tests - Migration of 8 INTERNAL_BROAD_CATCH sites to Result[T] +# Each site gets 2 tests: success and failure. +# ============================================================================= + + +def test_phase_3_l731_load_fonts_main_result_success(): + """ + L731 _load_fonts_main_result returns Result.ok=True on success. + + The helper wraps the main font loading try/except in App._load_fonts. + On success, it returns Result(data=True) with no errors. + """ + from src import gui_2 + from unittest.mock import MagicMock, patch + app = MagicMock() + mock_font = MagicMock(name="mock_main_font") + mock_config = MagicMock(name="mock_font_config") + with patch.object(gui_2, "hello_imgui") as mock_hi, \ + patch("src.startup_profiler.startup_profiler") as mock_sp: + mock_hi.load_font_ttf_with_font_awesome_icons.return_value = mock_font + result = gui_2._load_fonts_main_result(app, "test/path.ttf", 16.0, mock_config) + assert result.ok, f"Expected ok=True on success, got errors: {result.errors}" + assert result.data is True + assert app.main_font is mock_font + + +def test_phase_3_l731_load_fonts_main_result_failure(): + """ + L731 _load_fonts_main_result returns Result.ok=False with ErrorInfo on failure. + + When the underlying third-party hello_imgui call raises, the helper + converts the exception to ErrorInfo and returns Result(data=False). + """ + from src import gui_2 + from unittest.mock import MagicMock, patch + app = MagicMock() + mock_config = MagicMock(name="mock_font_config") + with patch.object(gui_2, "hello_imgui") as mock_hi, \ + patch("src.startup_profiler.startup_profiler") as mock_sp: + mock_hi.load_font_ttf_with_font_awesome_icons.side_effect = ValueError("font load failed") + result = gui_2._load_fonts_main_result(app, "test/path.ttf", 16.0, mock_config) + assert not result.ok, f"Expected ok=False on failure, got data: {result.data}" + assert result.errors, "Expected at least one error on failure" + err = result.errors[0] + assert err.source == "gui_2._load_fonts_main_result" + assert "font load failed" in err.message \ No newline at end of file