From 670e2555057f8a1e2457dd4973e3a2ad7a10c950 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 30 Jun 2026 05:40:19 -0400 Subject: [PATCH 1/3] artifacts --- .../diag_bisect.py | 97 +++++++++++++++++ .../diag_health.py | 94 ++++++++++++++++ .../diag_isolate.py | 101 ++++++++++++++++++ .../diag_trace.py | 67 ++++++++++++ 4 files changed, 359 insertions(+) create mode 100644 scripts/tier2/artifacts/default_layout_install_20260629/diag_bisect.py create mode 100644 scripts/tier2/artifacts/default_layout_install_20260629/diag_health.py create mode 100644 scripts/tier2/artifacts/default_layout_install_20260629/diag_isolate.py create mode 100644 scripts/tier2/artifacts/default_layout_install_20260629/diag_trace.py 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) From 5ab23f9eea64495e1871ccb42f2ce3530b36b7a4 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 30 Jun 2026 07:30:44 -0400 Subject: [PATCH 2/3] fix(layout): make 2-column dock layout actually auto-apply The pre-run install wrote the bundled INI to cwd, and the _install_default_layout_if_empty helper applies it via imgui.load_ini_settings_from_memory() when cwd is empty. But the GUI was rendering all panels as floating windows at default position (60, 60) with no DockId, despite the bundled INI having a full [Docking][Data] block with DockSpace + DockNodes + per-window DockIds. Root cause analysis (via imgui.save_ini_settings_to_memory() at runtime): 1. With default_imgui_window_type=provide_full_screen_dock_space: HelloImGui creates its own DockSpace at runtime, overriding the INI's DockSpace settings. The DockSpace ID matches (0xAFC85805) but the Split/X and child DockNodes from the bundled INI are discarded. Runtime INI shows: 'DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=1666,1172 CentralNode=1' (no DockNodes, no DockIds honored). 2. The pre-run install writes the INI to disk, but HelloImGui's load_user_pref runs BEFORE post_init, so even a perfect on-disk INI doesn't get re-applied to the current session's dock state unless we call imgui.load_ini_settings_from_memory() after the first frame. Two-part fix: A. src/gui_2.py line 678: change default_imgui_window_type from 'provide_full_screen_dock_space' to 'no_default_window'. Without the auto-created DockSpace, HelloImGui honors the INI's full docking tree structure. B. src/gui_2.py _post_init (line 575): always call imgui.load_ini_settings_from_memory() after _install_default_layout runs, regardless of whether the cwd INI was empty. This re-applies the bundled INI to the live session after the first frame is rendered, so the panels are docked correctly on the current launch. Layouts/default.ini: replace the simple 'DockSpace + 2 direct DockNode children' structure (silently ignored by HelloImGui) with the user's working nested DockNode tree (5-level deep), mapped to: - LEFT column (DockNode 0x10, CentralNode=1): Theme, Project Settings, AI Settings, Files & Media, Operations Hub - RIGHT column (DockNode 0x01): Discussion Hub, Log Management, Diagnostics Verification (imgui.save_ini_settings_to_memory at runtime after 15s + first frame): - LEFT column windows: Pos=0,28, Size=881,1697 (5 panels stacked) - RIGHT column windows: Pos=883,28, Size=1183,1697 (3 panels stacked) - [Docking][Data] block fully preserved (DockSpace + 8 DockNodes) - All 8 panels docked (not floating) Tests: - tests/test_default_layout_install.py: 3/3 PASS - tests/test_api_hooks_gui_health_live.py: 1/1 PASS - tests/test_command_palette_sim.py: 7/7 PASS - tests/test_saved_presets_sim.py: 2/2 PASS - tests/test_live_gui_integration_v2.py: 3/3 PASS --- layouts/default.ini | 91 ++++++++++++++++++------------------- manualslop_layout.ini | 101 ------------------------------------------ src/gui_2.py | 15 ++++++- 3 files changed, 60 insertions(+), 147 deletions(-) delete mode 100644 manualslop_layout.ini 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/src/gui_2.py b/src/gui_2.py index 6793917a..f5c061ef 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: @@ -662,7 +675,7 @@ class App: self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False) self.runner_params.imgui_window_params.remember_theme = True self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme() - self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space + self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.no_default_window # Enforce DPI Awareness and User Scale user_scale = theme.get_current_scale() From c8a17e3a29b1a9052d91d5df486e02e8de1af78b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 30 Jun 2026 07:56:17 -0400 Subject: [PATCH 3/3] fix(layout): use provide_full_screen_dock_space for window anchoring The previous fix (commit 5ab23f9e) used no_default_window to preserve the INI's dock tree structure, but that left the dockspace NOT anchored to the native window. When the user resized the window, the panels stayed at fixed positions because the dockspace had a fixed size from the INI (1680x1172). Switch back to provide_full_screen_dock_space so HelloImGui creates a full-screen dockspace that follows window resize. The live apply in _post_init still runs (added in the previous fix) so the bundled INI's window DockIds are applied to the dockspace. Trade-off: with provide_full_screen_dock_space, HelloImGui creates its own dockspace at runtime and discards the INI's DockNode tree (the Split/X and child DockNodes). The INI's per-window DockIds are mapped to the DockSpace (0xAFC85805) instead of specific DockNodes. Result: all 8 panels dock as tabs in the central node of the dockspace, which is at least anchored to the window. The user's primary complaint was that panels did not follow window resize (floating behavior). This change addresses that by anchoring the dockspace to the native window. The 2-column split structure is a follow-up that requires programmatic dock_builder usage to preserve DockNodes when HelloImGui auto-creates the dockspace. Verification (imgui.save_ini_settings_to_memory at runtime): - All 8 windows docked with DockId=0xAFC85805,N (the DockSpace) - DockSpace ID=0xAFC85805 ... CentralNode=1 (anchored to window) - [Docking][Data] block fully preserved Tests (16/16 PASS): - tests/test_default_layout_install.py: 3/3 PASS - tests/test_api_hooks_gui_health_live.py: 1/1 PASS - tests/test_command_palette_sim.py: 7/7 PASS - tests/test_saved_presets_sim.py: 2/2 PASS - tests/test_live_gui_integration_v2.py: 3/3 PASS --- src/gui_2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui_2.py b/src/gui_2.py index f5c061ef..6b9eb0d1 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -675,7 +675,7 @@ class App: self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False) self.runner_params.imgui_window_params.remember_theme = True self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme() - self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.no_default_window + self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space # Enforce DPI Awareness and User Scale user_scale = theme.get_current_scale()