"""Shared module loader for heavy imports. Per startup_speedup_20260606 spec §2.2 Layer 1, the main thread (the one that enters `immapp.run()`) must NEVER import a module heavier than `imgui_bundle` and the lean `gui_2` skeleton. Heavy modules are removed from main-thread-reachable files entirely and accessed via `_require_warmed` at use sites. The warmup mechanism in `src/warmup.py` (driven by `AppController.__init__`) pre-loads them on the `_io_pool` so the first user-triggered call is instant — the import cost is paid during startup on a background thread, not on the user's first click. This module is the single home of the `_require_warmed` helper so that multiple files (src/ai_client.py, src/app_controller.py, future Phase 5 feature-gated modules) share one canonical implementation instead of duplicating the lookup logic. The earlier practice of defining the helper locally in src/ai_client.py created a cross-module import smell (app_controller -> ai_client) when other modules needed the same primitive. Public API: _require_warmed(name) -> module O(1) sys.modules lookup if warmup ran; importlib fallback otherwise. Why a function instead of a `from X import Y` inside each call site? Per the spec, lazy-loading on first use causes user-perceptible lag when a UI action propagates to a controller method that triggers the first import. Proactive warmup on bg threads means the cost is paid during the visible startup window, not on the user's click. """ import importlib import sys from typing import Any def _require_warmed(name: str) -> Any: """Return a heavy module that the AppController's warmup should have loaded. Heavy SDKs (anthropic, google.genai, openai, google.genai.types, requests, fastapi, fastapi.security.api_key, src.command_palette, src.theme_nerv, src.theme_nerv_fx, src.markdown_table, numpy) are warmed on AppController's _io_pool at startup. This function expects them to already be in sys.modules and just returns the cached module object. If the module is NOT in sys.modules (e.g. in tests where warmup didn't run), falls back to importlib so the call still works. In production: this is an O(1) sys.modules lookup. The 1+ second import cost is paid during startup on a bg thread, NOT on the first user-triggered AI call. """ mod = sys.modules.get(name) if mod is not None: return mod return importlib.import_module(name)