diff --git a/layouts/default.ini b/layouts/default.ini index ec02a784..eeb5a4dd 100644 --- a/layouts/default.ini +++ b/layouts/default.ini @@ -4,22 +4,19 @@ ;;; missing/empty/small). ;;; ;;; Mechanism: HelloImGui reads this INI at app startup via the -;;; ini_folder_type/ini_filename on the RunnerParams. The DockSpace ID, -;;; DockNode IDs, and per-window DockId lines below tell HelloImgui where -;;; to place each panel. The literal IDs (0xAFC85805, 0x00000001, -;;; 0x00000002) match the runtime-generated MainDockSpace ID (decimal -;;; 2949142533) that HelloImGui computes deterministically per session. +;;; ini_folder_type/ini_filename on the RunnerParams. The DockSpace ID +;;; 0xAFC85805 (= decimal 2949142533) is the runtime-generated MainDockSpace +;;; ID. The nested DockNode tree creates a 2-column layout: +;;; - LEFT column (DockNode 0x10, CentralNode=1): tabs for Theme, Project +;;; Settings, AI Settings, Files & Media, Operations Hub +;;; - RIGHT column (DockNode 0x01): tabs for Discussion Hub, Log +;;; Management, Diagnostics ;;; -;;; Window list matches the post-config-merge effective visibility set -;;; (8 default-true windows excluding Response (stale) and the four -;;; Tier panels (disabled in config.toml)): -;;; Project Settings, Files & Media, AI Settings, AI operations, -;;; Discussion Hub, Operations Hub, Theme, Log Management, Diagnostics. -;;; Per-window DockId: -;;; 0x00000001,0..4 = left column tabs (Theme, Project Settings, -;;; AI Settings, Files & Media, Operations Hub) -;;; 0x00000002,0..2 = right column tabs (Discussion Hub, Log Management, -;;; Diagnostics) +;;; The nested structure (DockSpace -> DockNode 0x03 -> DockNode 0x0B -> +;;; DockNode 0x05 -> DockNode 0x10) is REQUIRED for HelloImGui to honor +;;; the docking. A simpler "DockSpace + 2 direct children" structure is +;;; silently ignored by HelloImGui (verified empirically — runtime INI +;;; shows DockSpace with no DockNodes, all windows float). ;;; ;;; All Collapsed=0 so the windows expand immediately on first render. ;;; @@ -30,65 +27,70 @@ ;;; ;;; To iterate on this layout: open sloppy.py, arrange windows as ;;; desired, quit (HelloImGui auto-saves), then copy the resulting -;;; cwd/manualslop_layout.ini over this one. (HelloImGui adds SplitsIds, -;;; Tables, and other internal sections on save; the bundled default -;;; version is the minimal scaffold needed for first-run visibility.) +;;; cwd/manualslop_layout.ini over this one. ;;; [Window][Project Settings] Pos=0,28 -Size=481,1172 +Size=881,1697 Collapsed=0 -DockId=0x00000001,1 +DockId=0x00000010,1 [Window][Files & Media] Pos=0,28 -Size=481,1172 +Size=881,1697 Collapsed=0 -DockId=0x00000001,3 +DockId=0x00000010,2 [Window][AI Settings] Pos=0,28 -Size=481,1172 +Size=881,1697 Collapsed=0 -DockId=0x00000001,2 +DockId=0x00000010,3 [Window][Operations Hub] Pos=0,28 -Size=481,1172 +Size=881,1697 Collapsed=0 -DockId=0x00000001,4 +DockId=0x00000010,4 [Window][Theme] Pos=0,28 -Size=481,1172 +Size=881,1697 +Collapsed=0 +DockId=0x00000010,0 + +[Window][Discussion Hub] +Pos=883,28 +Size=1183,1697 Collapsed=0 DockId=0x00000001,0 -[Window][Discussion Hub] -Pos=483,28 -Size=1197,1172 -Collapsed=0 -DockId=0x00000002,0 - [Window][Log Management] -Pos=483,28 -Size=1197,1172 +Pos=883,28 +Size=1183,1697 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000001,1 [Window][Diagnostics] -Pos=483,28 -Size=1197,1172 +Pos=883,28 +Size=1183,1697 Collapsed=0 -DockId=0x00000002,2 +DockId=0x00000001,2 [Docking][Data] -DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=1680,1172 Split=X - DockNode ID=0x00000001 Parent=0xAFC85805 SizeRef=481,1172 CentralNode=1 Selected=0x3F1379AF - DockNode ID=0x00000002 Parent=0xAFC85805 SizeRef=1197,1172 Selected=0xB4CBF21A +DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=1680,1172 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=820,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=1754,1681 Split=X Selected=0x6F2B5B04 + DockNode ID=0x00000001 Parent=0x00000006 SizeRef=1183,1924 Selected=0xB4CBF21A + DockNode ID=0x00000002 Parent=0x00000006 SizeRef=569,1924 Selected=0x0D5A5273 + DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6 + DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=488,1183 Selected=0x3AEC3498 -;;;<<>>;;; [Layout] Name=Default [StatusBar] @@ -97,5 +99,4 @@ ShowFps=true [Theme] Name=ImGuiColorsDark -;;;<<>>;;; -{"gImGuiSplitIDs":{"MainDockSpace":2949142533}} +{"gImGuiSplitIDs":{"MainDockSpace":2949142533}} \ No newline at end of file diff --git a/manualslop_layout.ini b/manualslop_layout.ini deleted file mode 100644 index ec02a784..00000000 --- a/manualslop_layout.ini +++ /dev/null @@ -1,101 +0,0 @@ -;;; -;;; Manual Slop default docking layout for live_gui test sessions AND -;;; first-run production launches (when cwd/manualslop_layout.ini is -;;; missing/empty/small). -;;; -;;; Mechanism: HelloImGui reads this INI at app startup via the -;;; ini_folder_type/ini_filename on the RunnerParams. The DockSpace ID, -;;; DockNode IDs, and per-window DockId lines below tell HelloImgui where -;;; to place each panel. The literal IDs (0xAFC85805, 0x00000001, -;;; 0x00000002) match the runtime-generated MainDockSpace ID (decimal -;;; 2949142533) that HelloImGui computes deterministically per session. -;;; -;;; Window list matches the post-config-merge effective visibility set -;;; (8 default-true windows excluding Response (stale) and the four -;;; Tier panels (disabled in config.toml)): -;;; Project Settings, Files & Media, AI Settings, AI operations, -;;; Discussion Hub, Operations Hub, Theme, Log Management, Diagnostics. -;;; Per-window DockId: -;;; 0x00000001,0..4 = left column tabs (Theme, Project Settings, -;;; AI Settings, Files & Media, Operations Hub) -;;; 0x00000002,0..2 = right column tabs (Discussion Hub, Log Management, -;;; Diagnostics) -;;; -;;; All Collapsed=0 so the windows expand immediately on first render. -;;; -;;; This INI does NOT include any of the _STALE_WINDOW_NAMES from -;;; src/gui_2.py:603-607 (Projects, Files, Screenshots, Discussion History, -;;; Provider, Message, Response, Tool Calls, Comms History, System Prompts). -;;; _diag_layout_state will not emit a "stale window name" warning. -;;; -;;; To iterate on this layout: open sloppy.py, arrange windows as -;;; desired, quit (HelloImGui auto-saves), then copy the resulting -;;; cwd/manualslop_layout.ini over this one. (HelloImGui adds SplitsIds, -;;; Tables, and other internal sections on save; the bundled default -;;; version is the minimal scaffold needed for first-run visibility.) -;;; - -[Window][Project Settings] -Pos=0,28 -Size=481,1172 -Collapsed=0 -DockId=0x00000001,1 - -[Window][Files & Media] -Pos=0,28 -Size=481,1172 -Collapsed=0 -DockId=0x00000001,3 - -[Window][AI Settings] -Pos=0,28 -Size=481,1172 -Collapsed=0 -DockId=0x00000001,2 - -[Window][Operations Hub] -Pos=0,28 -Size=481,1172 -Collapsed=0 -DockId=0x00000001,4 - -[Window][Theme] -Pos=0,28 -Size=481,1172 -Collapsed=0 -DockId=0x00000001,0 - -[Window][Discussion Hub] -Pos=483,28 -Size=1197,1172 -Collapsed=0 -DockId=0x00000002,0 - -[Window][Log Management] -Pos=483,28 -Size=1197,1172 -Collapsed=0 -DockId=0x00000002,1 - -[Window][Diagnostics] -Pos=483,28 -Size=1197,1172 -Collapsed=0 -DockId=0x00000002,2 - -[Docking][Data] -DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=1680,1172 Split=X - DockNode ID=0x00000001 Parent=0xAFC85805 SizeRef=481,1172 CentralNode=1 Selected=0x3F1379AF - DockNode ID=0x00000002 Parent=0xAFC85805 SizeRef=1197,1172 Selected=0xB4CBF21A - -;;;<<>>;;; -[Layout] -Name=Default -[StatusBar] -Show=false -ShowFps=true -[Theme] -Name=ImGuiColorsDark - -;;;<<>>;;; -{"gImGuiSplitIDs":{"MainDockSpace":2949142533}} diff --git a/scripts/tier2/artifacts/default_layout_install_20260629/diag_bisect.py b/scripts/tier2/artifacts/default_layout_install_20260629/diag_bisect.py new file mode 100644 index 00000000..626ca56b --- /dev/null +++ b/scripts/tier2/artifacts/default_layout_install_20260629/diag_bisect.py @@ -0,0 +1,97 @@ +"""Find the orphan by disabling each default-visible window in turn via API hook.""" +import sys, time, subprocess, json, urllib.request, shutil +from pathlib import Path + +PROJ = Path("C:/projects/manual_slop_tier2") + +VISIBLE = [ + "AI Settings", + "Diagnostics", + "Discussion Hub", + "Files & Media", + "Log Management", + "Operations Hub", + "Project Settings", + "Response", + "Theme", +] + +def make_wrapper(visible): + return ''' +import sys +sys.path.insert(0, "C:/projects/manual_slop_tier2") +sys.argv = ["sloppy.py", "--enable-test-hooks"] +from src.gui_2 import App as _App +_orig_init = _App.__init__ +def _patched_init(self, *a, **kw): + _orig_init(self, *a, **kw) + for k in list(self.show_windows.keys()): + self.show_windows[k] = False + for k in %r: + if k in self.show_windows: + self.show_windows[k] = True +_App.__init__ = _patched_init + +import argparse +from pathlib import Path +from src.paths import initialize_paths +initialize_paths(None) +from src.gui_2 import main as _sloppy_main +_sloppy_main() +''' % visible + +def run_test(visible_subset, label, wait_after_health=6): + WORK = PROJ / "tests" / "artifacts" / f"diag4_{label}" + LOG = PROJ / "tests" / "artifacts" / f"diag4_{label}.log" + if WORK.exists(): + shutil.rmtree(WORK) + WORK.mkdir(parents=True, exist_ok=True) + if LOG.exists(): + LOG.unlink() + WRAP = PROJ / "tests" / "artifacts" / f"diag4_wrap_{label}.py" + WRAP.write_text(make_wrapper(visible_subset), encoding='utf-8') + + proc = subprocess.Popen( + ["uv", "run", "python", "-u", str(WRAP.absolute())], + stdout=open(LOG, "w"), + stderr=subprocess.STDOUT, + cwd=str(WORK), + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == "win32" else 0, + ) + + ready = False + for i in range(30): + time.sleep(0.5) + try: + urllib.request.urlopen("http://127.0.0.1:8999/status", timeout=1) + ready = True + break + except Exception: + pass + + if not ready: + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + time.sleep(1) + return "NO_HOOK" + + # Wait for first frame to render + time.sleep(wait_after_health) + + try: + r = urllib.request.urlopen("http://127.0.0.1:8999/api/gui_health", timeout=2) + body = json.loads(r.read().decode()) + except Exception: + body = {"healthy": None} + + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + time.sleep(1) + return "HEALTHY" if body.get("healthy") else f"DEGRADED: {body.get('degraded_reason', '')[:80]}" + + +print("BASELINE (all 9 visible):", flush=True) +print(f" -> {run_test(VISIBLE, 'all')}", flush=True) + +for skip in VISIBLE: + visible_subset = [w for w in VISIBLE if w != skip] + print(f"DISABLE {skip}:", flush=True) + print(f" -> {run_test(visible_subset, f'skip_{skip.replace(chr(32), chr(95)).replace(chr(58), chr(95))}')}", flush=True) diff --git a/scripts/tier2/artifacts/default_layout_install_20260629/diag_health.py b/scripts/tier2/artifacts/default_layout_install_20260629/diag_health.py new file mode 100644 index 00000000..5e339919 --- /dev/null +++ b/scripts/tier2/artifacts/default_layout_install_20260629/diag_health.py @@ -0,0 +1,94 @@ +"""Quick diagnostic: start sloppy.py as subprocess, hit /api/gui_health, print everything.""" +import subprocess +import time +import json +import urllib.request +from pathlib import Path + +PROJ = Path("C:/projects/manual_slop_tier2") +WORK = PROJ / "tests" / "artifacts" / "diag_workspace" +LOG = PROJ / "tests" / "artifacts" / "diag_sloppy.log" + +# Clean workspace +import shutil +if WORK.exists(): + shutil.rmtree(WORK) +WORK.mkdir(parents=True, exist_ok=True) +if LOG.exists(): + LOG.unlink() + +# Spawn +print(f"Starting sloppy.py in {WORK}...", flush=True) +proc = subprocess.Popen( + ["uv", "run", "python", "-u", str(PROJ / "sloppy.py"), "--enable-test-hooks"], + stdout=open(LOG, "w"), + stderr=subprocess.STDOUT, + cwd=str(WORK), +) +print(f"PID: {proc.pid}", flush=True) + +# Wait for hook server +ready = False +for i in range(30): + time.sleep(1.0) + try: + r = urllib.request.urlopen("http://127.0.0.1:8999/status", timeout=2) + ready = True + print(f"Hook server ready after {i+1}s", flush=True) + print(f" /status: {r.read().decode()}", flush=True) + break + except Exception as e: + if i % 5 == 0: + print(f" waiting ({i+1}s)... {e.__class__.__name__}", flush=True) + +if not ready: + print("Hook server never came up!", flush=True) + +# Get health +try: + r = urllib.request.urlopen("http://127.0.0.1:8999/api/gui_health", timeout=2) + body = r.read().decode() + print(f"\n/api/gui_health: {body}", flush=True) + data = json.loads(body) + print(f" healthy: {data.get('healthy')}", flush=True) + print(f" degraded_reason: {data.get('degraded_reason')!r}", flush=True) + print(f" last_assert: {data.get('last_assert')!r}", flush=True) +except Exception as e: + print(f"health check failed: {e}", flush=True) + +# Get startup timeline +try: + r = urllib.request.urlopen("http://127.0.0.1:8999/api/startup_timeline", timeout=2) + print(f"\n/api/startup_timeline: {r.read().decode()}", flush=True) +except Exception as e: + print(f"startup_timeline failed: {e}", flush=True) + +# Get warmup status +try: + r = urllib.request.urlopen("http://127.0.0.1:8999/api/warmup_status", timeout=2) + print(f"\n/api/warmup_status: {r.read().decode()}", flush=True) +except Exception as e: + print(f"warmup_status failed: {e}", flush=True) + +# Give it another 5 seconds to render +print("\nWaiting 5s for first frame...", flush=True) +time.sleep(5) + +# Re-check health +try: + r = urllib.request.urlopen("http://127.0.0.1:8999/api/gui_health", timeout=2) + body = r.read().decode() + print(f"\n/api/gui_health (after 5s): {body}", flush=True) +except Exception as e: + print(f"re-check health failed: {e}", flush=True) + +# Kill +print(f"\nKilling PID {proc.pid}...", flush=True) +try: + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], check=False) +except Exception as e: + print(f"taskkill failed: {e}", flush=True) + +# Print log +print("\n=== STDOUT/STDERR LOG ===", flush=True) +print(LOG.read_text(encoding="utf-8", errors="replace")) diff --git a/scripts/tier2/artifacts/default_layout_install_20260629/diag_isolate.py b/scripts/tier2/artifacts/default_layout_install_20260629/diag_isolate.py new file mode 100644 index 00000000..83de81b6 --- /dev/null +++ b/scripts/tier2/artifacts/default_layout_install_20260629/diag_isolate.py @@ -0,0 +1,101 @@ +"""Diagnostic: disable each default-visible window one at a time, find which fixes the error.""" +import sys, time, subprocess, json, urllib.request, shutil +from pathlib import Path + +PROJ = Path("C:/projects/manual_slop_tier2") + +# Default-visible windows per the diag log +VISIBLE = [ + "AI Settings", + "Diagnostics", + "Discussion Hub", + "Files & Media", + "Log Management", + "Operations Hub", + "Project Settings", + "Response", + "Theme", +] + +def make_wrapper(visible): + """Build a wrapper that monkey-patches show_windows before App() is constructed.""" + visible_set = set(visible) + return ''' +import sys +sys.path.insert(0, "C:/projects/manual_slop_tier2") +sys.argv = ["sloppy.py", "--enable-test-hooks"] +from src.gui_2 import App as _App +_orig_init = _App.__init__ +def _patched_init(self, *a, **kw): + _orig_init(self, *a, **kw) + for k in list(self.show_windows.keys()): + self.show_windows[k] = False + for k in %r: + if k in self.show_windows: + self.show_windows[k] = True +_App.__init__ = _patched_init + +# Replicate sloppy.py main path +import argparse +from pathlib import Path +from src.paths import initialize_paths +initialize_paths(None) +from src.gui_2 import main as _sloppy_main +_sloppy_main() +''' % visible + +def run_diag(visible_subset, label): + WORK = PROJ / "tests" / "artifacts" / f"diag3_{label}" + LOG = PROJ / "tests" / "artifacts" / f"diag3_{label}.log" + if WORK.exists(): + shutil.rmtree(WORK) + WORK.mkdir(parents=True, exist_ok=True) + if LOG.exists(): + LOG.unlink() + WRAP = PROJ / "tests" / "artifacts" / f"diag3_wrap_{label}.py" + WRAP.write_text(make_wrapper(visible_subset), encoding='utf-8') + + proc = subprocess.Popen( + ["uv", "run", "python", "-u", str(WRAP.absolute())], + stdout=open(LOG, "w"), + stderr=subprocess.STDOUT, + cwd=str(WORK), + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == "win32" else 0, + ) + + ready = False + for i in range(15): + time.sleep(0.5) + try: + urllib.request.urlopen("http://127.0.0.1:8999/status", timeout=1) + ready = True + break + except Exception: + pass + + if not ready: + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + time.sleep(1) + return "NO_HOOK" + + try: + r = urllib.request.urlopen("http://127.0.0.1:8999/api/gui_health", timeout=2) + body = json.loads(r.read().decode()) + except Exception: + body = {"healthy": None} + + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + time.sleep(1) + return "HEALTHY" if body.get("healthy") else f"DEGRADED: {body.get('degraded_reason', '')[:60]}" + +# Baseline: all 9 visible +print("BASELINE (all 9 visible):") +result = run_diag(VISIBLE, "all") +print(f" -> {result}") + +# Disable each one +for skip in VISIBLE: + visible_subset = [w for w in VISIBLE if w != skip] + print(f"DISABLE {skip}:") + result = run_diag(visible_subset, f"skip_{skip.replace(' ', '_').replace(':', '')}") + print(f" -> {result}") diff --git a/scripts/tier2/artifacts/default_layout_install_20260629/diag_trace.py b/scripts/tier2/artifacts/default_layout_install_20260629/diag_trace.py new file mode 100644 index 00000000..750b920a --- /dev/null +++ b/scripts/tier2/artifacts/default_layout_install_20260629/diag_trace.py @@ -0,0 +1,67 @@ +"""Find the exact unbalanced begin/end by monkey-patching imgui.""" +import sys, json, time, subprocess, urllib.request, traceback +from pathlib import Path + +PROJ = Path("C:/projects/manual_slop_tier2") +WORK = PROJ / "tests" / "artifacts" / "diag2_workspace" +LOG = PROJ / "tests" / "artifacts" / "diag2_sloppy.log" + +import shutil +if WORK.exists(): + shutil.rmtree(WORK) +WORK.mkdir(parents=True, exist_ok=True) +if LOG.exists(): + LOG.unlink() + +WRAPPER = PROJ / "tests" / "artifacts" / "diag2_wrapper.py" +# Wrapper content is pre-written; this script just runs it. + +# Spawn the wrapper +print(f"Starting wrapper in {WORK}...", flush=True) +proc = subprocess.Popen( + ["uv", "run", "python", "-u", str(WRAPPER.absolute())], + stdout=open(LOG, "w"), + stderr=subprocess.STDOUT, + cwd=str(WORK), +) +print(f"PID: {proc.pid}", flush=True) + +# Wait for hook server +ready = False +for i in range(30): + time.sleep(1.0) + try: + r = urllib.request.urlopen("http://127.0.0.1:8999/status", timeout=2) + ready = True + print(f"Hook server ready after {i+1}s", flush=True) + break + except Exception: + pass + +if not ready: + print("Hook server never came up!", flush=True) +else: + # Get health + try: + r = urllib.request.urlopen("http://127.0.0.1:8999/api/gui_health", timeout=2) + body = r.read().decode() + print(f"health: {body}", flush=True) + except Exception as e: + print(f"health failed: {e}", flush=True) + time.sleep(3) + +# Kill +print(f"\nKilling PID {proc.pid}...", flush=True) +subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], check=False) + +# Print log (only the IMGUI lines) +print("\n=== IMGUI TRACE ===", flush=True) +content = LOG.read_text(encoding="utf-8", errors="replace") +for line in content.splitlines(): + if "imgui-error" in line or "Missing End" in line or "MAIN_CALL" in line or "first frame" in line or "first _gui_func" in line: + print(line, flush=True) + +# Also dump the FULL log +print("\n=== FULL LOG (last 50 lines) ===", flush=True) +for line in content.splitlines()[-50:]: + print(line, flush=True) diff --git a/src/gui_2.py b/src/gui_2.py index 6793917a..6b9eb0d1 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -580,6 +580,19 @@ class App: if not install_result.ok: if not hasattr(self, "_startup_timeline_errors"): self._startup_timeline_errors = [] self._startup_timeline_errors.append(("_install_default_layout", install_result.errors[0])) + # Always re-apply the bundled layout to the live imgui session after + # the first frame is rendered. HelloImGui reads ini_filename from disk + # BEFORE post_init fires (during immapp.run's load_user_pref phase), + # so even a perfect on-disk INI may not be honored unless we also + # apply it via imgui.load_ini_settings_from_memory here. The pre-run + # install writes the INI for the NEXT launch; this live apply + # makes the CURRENT launch render the bundled layout correctly. + try: + src_text = src_layout_path.read_text(encoding="utf-8", errors="replace") + imgui.load_ini_settings_from_memory(src_text) + except OSError as e: + if not hasattr(self, "_startup_timeline_errors"): self._startup_timeline_errors = [] + self._startup_timeline_errors.append(("_post_init.live_apply", str(e))) if hasattr(self.controller, "on_warmup_complete"): cb_result = _post_init_callback_result(self) if not cb_result.ok: