Private
Public Access
0
0
This commit is contained in:
2026-06-07 02:14:46 -04:00
8 changed files with 232 additions and 88 deletions
+2 -1
View File
@@ -12,7 +12,8 @@
"mcp__manual-slop__get_file_summary",
"mcp__manual-slop__get_tree",
"mcp__manual-slop__list_directory",
"mcp__manual-slop__py_get_skeleton"
"mcp__manual-slop__py_get_skeleton",
"Bash(uv run *)"
]
},
"enableAllProjectMcpServers": true,
+11 -13
View File
@@ -12,11 +12,9 @@ use_default_base_prompt = true
[projects]
paths = [
"C:/projects/gencpp/.ai/gencpp_sloppy.toml",
"C:/projects/manual_slop/manual_slop.toml",
"C:/projects/Pikuma/ps1-ai/pikuma_ps1.toml",
"project.toml",
]
active = "C:/projects/Pikuma/ps1-ai/pikuma_ps1.toml"
active = "project.toml"
[gui]
separate_message_panel = true
@@ -62,23 +60,23 @@ Diagnostics = false
"Undo/Redo History" = false
[theme]
palette = "solarized_dark"
font_path = "C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf"
palette = "Monokai"
font_path = "fonts/MapleMono-Regular.ttf"
font_size = 20.0
scale = 1.0199999809265137
transparency = 1.0
child_transparency = 1.0
[theme.tone_mapping.solarized_light]
brightness = 0.6899999976158142
contrast = 0.8600000143051147
gamma = 0.7699999809265137
[theme.tone_mapping.moss]
brightness = 1.059999942779541
contrast = 0.5799999833106995
gamma = 1.059999942779541
[theme.tone_mapping.solarized_light]
brightness = 0.6899999976158142
contrast = 0.8600000143051147
gamma = 0.7699999809265137
[theme.tone_mapping.Binks]
brightness = 0.5600000023841858
contrast = 0.7900000214576721
@@ -97,8 +95,8 @@ api_key = "test-secret-key"
[paths]
conductor_dir = "C:\\projects\\gencpp\\.ai\\conductor"
logs_dir = "C:\\projects\\manual_slop\\logs"
scripts_dir = "C:\\projects\\manual_slop\\scripts"
logs_dir = "C:\\projects\\sloppy\\logs"
scripts_dir = "C:\\projects\\sloppy\\scripts"
[rag]
enabled = false
+19 -19
View File
@@ -44,20 +44,20 @@ Collapsed=0
DockId=0x00000010,0
[Window][Message]
Pos=1448,29
Size=1465,1840
Pos=561,29
Size=1138,1195
Collapsed=0
DockId=0x00000006,1
[Window][Response]
Pos=0,29
Size=1446,1840
Size=559,1195
Collapsed=0
DockId=0x00000010,5
[Window][Tool Calls]
Pos=1448,29
Size=1465,1840
Pos=561,29
Size=1138,1195
Collapsed=0
DockId=0x00000006,3
@@ -77,7 +77,7 @@ DockId=0xAFC85805,2
[Window][Theme]
Pos=0,29
Size=1446,1840
Size=559,1195
Collapsed=0
DockId=0x00000010,1
@@ -105,26 +105,26 @@ Collapsed=0
DockId=0x0000000D,0
[Window][Discussion Hub]
Pos=1448,29
Size=1465,1840
Pos=561,29
Size=1138,1195
Collapsed=0
DockId=0x00000006,0
[Window][Operations Hub]
Pos=0,29
Size=1446,1840
Size=559,1195
Collapsed=0
DockId=0x00000010,4
[Window][Files & Media]
Pos=0,29
Size=1446,1840
Size=559,1195
Collapsed=0
DockId=0x00000010,2
[Window][AI Settings]
Pos=0,29
Size=1446,1840
Size=559,1195
Collapsed=0
DockId=0x00000010,3
@@ -140,8 +140,8 @@ Collapsed=0
DockId=0x00000006,2
[Window][Log Management]
Pos=1448,29
Size=1465,1840
Pos=561,29
Size=1138,1195
Collapsed=0
DockId=0x00000006,2
@@ -410,7 +410,7 @@ DockId=0x00000006,1
[Window][Project Settings]
Pos=0,29
Size=1446,1840
Size=559,1195
Collapsed=0
DockId=0x00000010,0
@@ -510,7 +510,7 @@ Pos=60,60
Size=900,700
Collapsed=0
[Window][###Text_Viewer]
[Window][Text_Viewer]
Pos=58,169
Size=1801,1532
Collapsed=0
@@ -520,7 +520,7 @@ Pos=156,171
Size=2176,1441
Collapsed=0
[Window][###Text_Viewer_Unified]
[Window][Text_Viewer_Unified]
Pos=182,742
Size=1163,908
Collapsed=0
@@ -829,13 +829,13 @@ Column 4 Weight=1.0000
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,29 Size=2913,1840 Split=X
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,29 Size=1699,1195 Split=X
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2357,1183 Split=X
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
DockNode ID=0x00000005 Parent=0x0000000B SizeRef=1446,1681 Split=Y Selected=0x3F1379AF
DockNode ID=0x00000005 Parent=0x0000000B SizeRef=573,1681 Split=Y Selected=0x3F1379AF
DockNode ID=0x00000010 Parent=0x00000005 SizeRef=983,1140 CentralNode=1 Selected=0x3F1379AF
DockNode ID=0x00000011 Parent=0x00000005 SizeRef=983,184 Selected=0x432BAE4E
DockNode ID=0x00000006 Parent=0x0000000B SizeRef=1465,1681 Selected=0x2C0206CE
DockNode ID=0x00000006 Parent=0x0000000B SizeRef=1138,1681 Selected=0x2C0206CE
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=488,1183 Selected=0x3AEC3498
+1 -1
View File
@@ -9,5 +9,5 @@ active = "main"
[discussions.main]
git_commit = ""
last_updated = "2026-06-05T18:41:59"
last_updated = "2026-06-06T13:21:40"
history = []
+10 -1
View File
@@ -421,13 +421,22 @@ def _classify_minimax_error(exc: Exception) -> ProviderError:
if "400" in body_l or "bad request" in body_l: return ProviderError("unknown", "minimax", Exception(f"MiniMax Bad Request: {body}"))
return ProviderError("unknown", "minimax", Exception(body))
def set_provider(provider: str, model: str) -> None:
def set_provider(provider: str, model: str, validate: bool = True) -> None:
"""
Updates the active LLM provider and model name.
When validate is True (default), the model is checked against the provider's
LIVE model list, which for gemini_cli/minimax means a blocking subprocess /
network call (and importing the provider SDK). Pass validate=False during
startup so the GUI's first frame is not blocked — AppController._fetch_models
corrects the model against the live list shortly after, off the main thread.
[C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController._init_ai_and_hooks, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.do_fetch, src/multi_agent_conductor.py:run_worker_lifecycle, src/orchestrator_pm.py:generate_tracks, tests/conftest.py:reset_ai_client, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_completion_logic, tests/test_deepseek_provider.py:test_deepseek_model_selection, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoning_logic, tests/test_deepseek_provider.py:test_deepseek_streaming, tests/test_deepseek_provider.py:test_deepseek_tool_calling, tests/test_gemini_cli_edge_cases.py:test_gemini_cli_loop_termination, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_cli_parity_regression.py:test_send_invokes_adapter_send, tests/test_gui2_mcp.py:test_mcp_tool_call_is_dispatched, tests/test_minimax_provider.py:test_minimax_default_model, tests/test_minimax_provider.py:test_minimax_model_selection, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_rag_integration.py:test_rag_integration, tests/test_tier4_interceptor.py:test_ai_client_passes_qa_callback, tests/test_tier4_interceptor.py:test_gemini_provider_passes_qa_callback_to_run_script, tests/test_token_usage.py:test_token_usage_tracking]
"""
global _provider, _model
_provider = provider
if not validate:
_model = model
return
if provider == "gemini_cli":
valid_models = _list_gemini_cli_models()
if model != "mock" and (model not in valid_models or model.startswith("deepseek")):
+43 -4
View File
@@ -801,7 +801,7 @@ class AppController:
Owns the application state and manages background services.
"""
def __init__(self, log_to_stderr: bool = True):
def __init__(self, defer_warmup: bool = False, log_to_stderr: Optional[bool] = None):
"""
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
"""
@@ -830,10 +830,22 @@ class AppController:
# --- Shared background pool + proactive warmup (startup_speedup_20260606) ---
self._io_pool = make_io_pool()
# Warmup progress is a diagnostic; keep stderr quiet unless explicitly asked.
# Explicit log_to_stderr arg wins; otherwise default to the SLOP_WARMUP_DEBUG env flag.
if log_to_stderr is None:
log_to_stderr = bool(os.environ.get("SLOP_WARMUP_DEBUG"))
self._warmup = WarmupManager(self._io_pool, log_to_stderr=log_to_stderr)
# Hook warmup completion to stamp warmup_done_ts for startup_timeline().
self._warmup.on_complete(self._on_warmup_complete_for_timeline)
self._warmup.submit(self._compute_warmup_list())
self._warmup_started: bool = False
self._defer_warmup: bool = defer_warmup
self._pending_fetch_provider: Optional[str] = None
# The desktop GUI defers warmup until the first frame is painted (see
# App._gui_func) so the ~2s of heavy SDK C-extension imports don't hold the
# GIL while the window and font atlas are being created — that contention is
# what made the window slow to appear. Headless/web/tests warm immediately.
if not defer_warmup:
self.start_warmup()
# --- Internal State ---
self._ai_status: str = "idle"
@@ -2193,6 +2205,13 @@ class AppController:
"""
[C: src/gui_2.py:App.run]
"""
# In the desktop GUI, model listing imports the provider SDKs (the same
# ~2s C-extension load warmup pays for). Defer it until the first frame is
# painted so it doesn't contend for the GIL during window creation; the
# deferred fetch is fired from start_warmup().
if self._defer_warmup and not self._warmup_started:
self._pending_fetch_provider = provider
return
self.ai_status = "fetching models..."
def do_fetch() -> None:
@@ -2274,9 +2293,26 @@ class AppController:
"""
return self._warmup.is_done()
def start_warmup(self) -> None:
"""
Submit the heavy-module warmup jobs to the io_pool (idempotent).
Separated from __init__ so the desktop GUI can call it AFTER the first
frame is painted, keeping the ~2s of SDK C-extension imports off the GIL
while the window is being created. Safe to call multiple times.
"""
if self._warmup_started:
return
self._warmup_started = True
self._warmup.submit(self._compute_warmup_list())
# Run any model fetch that was deferred while the window was being created.
if self._pending_fetch_provider is not None:
provider, self._pending_fetch_provider = self._pending_fetch_provider, None
self._fetch_models(provider)
def wait_for_warmup(self, timeout: Optional[float] = None) -> bool:
"""
Block until warmup completes. Returns True on done, False on timeout.
[SDM: src/app_controller.py:wait_for_warmup]
"""
@@ -2329,7 +2365,10 @@ class AppController:
def _init_ai_and_hooks(self, app: Any = None) -> None:
from src import api_hooks
ai_client.set_provider(self._current_provider, self._current_model)
# validate=False: skip the live model-list lookup (network/subprocess +
# provider-SDK import) on the main thread during startup. _fetch_models
# corrects the model against the live list after the first frame, off-thread.
ai_client.set_provider(self._current_provider, self._current_model, validate=False)
if self._current_provider == "gemini_cli":
if not ai_client._gemini_cli_adapter:
ai_client._gemini_cli_adapter = ai_client.GeminiCliAdapter(binary_path=self.ui_gemini_cli_path)
-1
View File
@@ -1,7 +1,6 @@
# src/bg_shader.py
import time
import math
import numpy as np
from typing import Optional
from imgui_bundle import imgui, nanovg as nvg, hello_imgui
+146 -48
View File
@@ -161,6 +161,75 @@ def truncate_entries(entries: list[dict[str, Any]], max_pairs: int) -> list[dict
if count == target: return entries[i:]
return entries
def _detect_refresh_rate_win32() -> float:
"""Return the primary display's current refresh rate in Hz, or 0.0 on failure.
Uses user32.EnumDisplaySettingsW (ENUM_CURRENT_SETTINGS) which reads the value
directly from the display driver in microseconds. The previous implementation
shelled out to PowerShell + WMI (Get-CimInstance Win32_VideoController), which
cost ~350ms on every startup and blocked the first frame.
"""
try:
import ctypes
from ctypes import wintypes
class _DEVMODE(ctypes.Structure):
_fields_ = [
("dmDeviceName", wintypes.WCHAR * 32), ("dmSpecVersion", wintypes.WORD),
("dmDriverVersion", wintypes.WORD), ("dmSize", wintypes.WORD),
("dmDriverExtra", wintypes.WORD), ("dmFields", wintypes.DWORD),
("dmStuff", ctypes.c_byte * 16), ("dmColor", wintypes.SHORT),
("dmDuplex", wintypes.SHORT), ("dmYResolution", wintypes.SHORT),
("dmTTOption", wintypes.SHORT), ("dmCollate", wintypes.SHORT),
("dmFormName", wintypes.WCHAR * 32), ("dmLogPixels", wintypes.WORD),
("dmBitsPerPel", wintypes.DWORD), ("dmPelsWidth", wintypes.DWORD),
("dmPelsHeight", wintypes.DWORD), ("dmDisplayFlags", wintypes.DWORD),
("dmDisplayFrequency", wintypes.DWORD), ("dmICMMethod", wintypes.DWORD),
("dmICMIntent", wintypes.DWORD), ("dmMediaType", wintypes.DWORD),
("dmDitherType", wintypes.DWORD), ("dmReserved1", wintypes.DWORD),
("dmReserved2", wintypes.DWORD), ("dmPanningWidth", wintypes.DWORD),
("dmPanningHeight", wintypes.DWORD),
]
dm = _DEVMODE()
dm.dmSize = ctypes.sizeof(_DEVMODE)
if ctypes.windll.user32.EnumDisplaySettingsW(None, -1, ctypes.byref(dm)):
# dmDisplayFrequency is 0 or 1 for "default/hardware" on some drivers.
if dm.dmDisplayFrequency > 1:
return float(dm.dmDisplayFrequency)
except Exception:
pass
return 0.0
def _resolve_font_path(font_path: str, assets_dir: Path) -> str:
"""Normalize a configured font path to something hello_imgui can load.
hello_imgui resolves relative paths against the assets folder. A config may
carry a stale ABSOLUTE path from a different project checkout (e.g.
C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf after the repo
moved to C:/projects/sloppy). In that case the absolute file does not exist
and the load fails. This recovers by:
1. If the absolute path lives under the current assets folder -> relativize.
2. If the absolute path exists on disk as-is -> keep it.
3. Otherwise recover the basename under assets/fonts or assets.
4. Final fallback: the bundled default Inter font.
"""
p = Path(font_path)
if not p.is_absolute():
return font_path # already relative; hello_imgui searches the assets folder
try:
if p.is_relative_to(assets_dir):
return str(p.relative_to(assets_dir)).replace("\\", "/")
except (ValueError, AttributeError):
pass
if p.exists():
return font_path # absolute path to a real file elsewhere — load directly
# Stale absolute path: recover by basename relative to the assets folder.
name = p.name
for rel in (f"fonts/{name}", name):
if (assets_dir / rel).exists():
return rel
return "fonts/Inter-Regular.ttf"
class App:
"""The main ImGui interface orchestrator for Manual Slop."""
@@ -172,7 +241,7 @@ class App:
# --- Core Dependencies & State ---
from src.startup_profiler import startup_profiler
with startup_profiler.phase("app_init_AppController"):
self.controller = app_controller.AppController()
self.controller = app_controller.AppController(defer_warmup=True)
self.controller._app = self
with startup_profiler.phase("app_init_history_perfmon"):
from src import history, performance_monitor
@@ -187,7 +256,8 @@ class App:
# --- Command Palette ---
self.show_command_palette: bool = False
# --- Initialization ---
self.controller.init_state()
with startup_profiler.phase("app_init_state"):
self.controller.init_state()
from src.hot_reloader import HotReloader, HotModule
if 'src.gui_2' not in HotReloader.HOT_MODULES:
HotReloader.register(HotModule(
@@ -196,11 +266,13 @@ class App:
state_keys=['active_discussion', 'show_windows', 'ui_file_paths', 'ui_screenshot_paths', 'disc_entries', 'disc_roles'],
delegation_targets=['_render_main_interface', '_render_discussion_hub', '_render_files_and_media', '_render_ai_settings_hub', '_render_operations_hub', '_render_mma_dashboard']
))
self.workspace_manager = workspace_manager.WorkspaceManager(project_root=self.controller.active_project_root)
self.disc_entries = self.controller.disc_entries
self.disc_roles = self.controller.disc_roles
self.workspace_profiles = self.workspace_manager.load_all_profiles()
self.controller.start_services(self)
with startup_profiler.phase("app_init_workspace"):
self.workspace_manager = workspace_manager.WorkspaceManager(project_root=self.controller.active_project_root)
self.disc_entries = self.controller.disc_entries
self.disc_roles = self.controller.disc_roles
self.workspace_profiles = self.workspace_manager.load_all_profiles()
with startup_profiler.phase("app_init_start_services"):
self.controller.start_services(self)
# --- Controller Callbacks & Actions ---
self.controller._predefined_callbacks['save_context_preset'] = self.save_context_preset
self.controller._predefined_callbacks['load_context_preset'] = self.load_context_preset
@@ -457,16 +529,13 @@ class App:
user_scale = theme.get_current_scale()
self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale
# Detect Monitor Refresh Rate for capping (Win32 only)
# Detect Monitor Refresh Rate for capping (Win32 only).
# Uses the native EnumDisplaySettings call (~0.3ms) instead of spawning a
# PowerShell/WMI subprocess (~350ms) so the first frame is not blocked.
fps_cap = 60.0
if sys.platform == "win32":
try:
# Use PowerShell to get max refresh rate across all controllers
cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\""
out = subprocess.check_output(cmd, shell=True).decode().splitlines()
rates = [float(r.strip()) for r in out if r.strip().isdigit()]
if rates: fps_cap = max(rates)
except Exception: pass
rate = _detect_refresh_rate_win32()
if rate: fps_cap = rate
# Enable idling with monitor refresh rate to effectively cap FPS
self.runner_params.fps_idling.enable_idling = True
@@ -476,11 +545,17 @@ class App:
self.runner_params.imgui_window_params.show_menu_view_themes = True
self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder
self.runner_params.ini_filename = "manualslop_layout.ini"
def _profiled_setup_style() -> None:
with startup_profiler.phase("setup_imgui_style"):
theme.apply_current()
def _profiled_post_init() -> None:
with startup_profiler.phase("post_init"):
self._post_init()
self.runner_params.callbacks.show_gui = self._gui_func
self.runner_params.callbacks.show_menus = self._show_menus
self.runner_params.callbacks.load_additional_fonts = self._load_fonts
self.runner_params.callbacks.setup_imgui_style = theme.apply_current
self.runner_params.callbacks.post_init = self._post_init
self.runner_params.callbacks.setup_imgui_style = _profiled_setup_style
self.runner_params.callbacks.post_init = _profiled_post_init
self._fetch_models(self.current_provider)
md_options = markdown_helper.get_renderer().options
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
@@ -489,42 +564,39 @@ class App:
session_logger.close_session()
def _load_fonts(self) -> None:
# Set hello_imgui assets folder to the actual absolute path
assets_dir = Path(__file__).parent.parent / "assets"
if assets_dir.exists():
hello_imgui.set_assets_folder(str(assets_dir.absolute()))
from src.startup_profiler import startup_profiler
with startup_profiler.phase("load_fonts"):
# Set hello_imgui assets folder to the actual absolute path
assets_dir = Path(__file__).parent.parent / "assets"
if assets_dir.exists():
hello_imgui.set_assets_folder(str(assets_dir.absolute()))
# Improved font rendering with oversampling
config = imgui.ImFontConfig()
config.oversample_h = 3
config.oversample_v = 3
# Improved font rendering with oversampling
config = imgui.ImFontConfig()
config.oversample_h = 3
config.oversample_v = 3
font_path, font_size = theme.get_font_loading_params()
if font_path:
p = Path(font_path)
if p.is_absolute():
font_path, font_size = theme.get_font_loading_params()
if font_path:
font_path = _resolve_font_path(font_path, assets_dir)
# Just try loading it directly; hello_imgui will look in the assets folder
try:
if p.is_relative_to(assets_dir):
font_path = str(p.relative_to(assets_dir)).replace("\\", "/")
except (ValueError, AttributeError):
pass # Fallback to original font_path if relative_to fails or on old Python
# Just try loading it directly; hello_imgui will look in the assets folder
try:
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}")
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
else:
self.main_font = None
else:
self.main_font = None
try:
params = hello_imgui.FontLoadingParams(font_config=config)
self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params)
except Exception as e:
print(f"Failed to load mono font: {e}")
self.mono_font = None
try:
with startup_profiler.phase("load_fonts.mono"):
params = hello_imgui.FontLoadingParams(font_config=config)
self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params)
except Exception as e:
print(f"Failed to load mono font: {e}")
self.mono_font = None
def _handle_approve_mma_step(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending MMA step."""
@@ -817,6 +889,32 @@ class App:
# ---------------------------------------------------------------- gui
def _gui_func(self) -> None:
# One-shot: log when immapp first hands control to our render callback. The
# span init -> here is window/GL/context creation + the font/style/post_init
# callbacks (all opaque C++); the span here -> mark_first_frame_rendered is
# the cost of the first full-UI render. Splitting them attributes the gap.
if not getattr(self, "_gui_func_entered", False):
self._gui_func_entered = True
try:
init_ts = getattr(self.controller, "_init_start_ts", None)
if init_ts is not None:
sys.stderr.write(f"[startup] first _gui_func entry at {(time.time() - init_ts) * 1000:.1f}ms after init (window/GL + font/style/post_init callbacks done)\n")
sys.stderr.flush()
except Exception: pass
# One-shot: kick off the controller's heavy-module warmup on the shared
# io_pool once the FIRST frame has actually been painted. Waiting one frame
# keeps the ~2s of SDK C-extension imports from holding the GIL during
# window creation and font-atlas building, so the window appears at full
# speed; the SDKs are then warmed by the time the user sends their first
# message. start_warmup() is idempotent.
if not getattr(self, "_preload_started", False):
if getattr(self, "_first_frame_painted", False):
self.controller.start_warmup()
self._preload_started = True
else:
self._first_frame_painted = True
io = imgui.get_io()
if io.key_ctrl and io.key_alt and imgui.is_key_down(imgui.Key.r):
self._trigger_hot_reload()