Private
Public Access
0
0

Compare commits

...

4 Commits

Author SHA1 Message Date
ed 7e3ce307e1 Merge remote-tracking branch 'tier2-clone/tier2/default_layout_install_20260629' into tier2/default_layout_install_20260629 2026-06-30 08:10:08 -04:00
ed c8a17e3a29 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
2026-06-30 07:56:17 -04:00
ed 5ab23f9eea 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
2026-06-30 07:30:44 -04:00
ed 670e255505 artifacts 2026-06-30 05:40:19 -04:00
7 changed files with 418 additions and 146 deletions
+46 -45
View File
@@ -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
;;;<<<HelloImGui_Misc>>>;;;
[Layout]
Name=Default
[StatusBar]
@@ -97,5 +99,4 @@ ShowFps=true
[Theme]
Name=ImGuiColorsDark
;;;<<<SplitIds>>>;;;
{"gImGuiSplitIDs":{"MainDockSpace":2949142533}}
{"gImGuiSplitIDs":{"MainDockSpace":2949142533}}
-101
View File
@@ -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
;;;<<<HelloImGui_Misc>>>;;;
[Layout]
Name=Default
[StatusBar]
Show=false
ShowFps=true
[Theme]
Name=ImGuiColorsDark
;;;<<<SplitIds>>>;;;
{"gImGuiSplitIDs":{"MainDockSpace":2949142533}}
@@ -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)
@@ -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"))
@@ -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}")
@@ -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)
+13
View File
@@ -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: