Bug: when cwd/manualslop_layout.ini is missing/empty after first-run,
post-deletion, or post-corrupt-INI, the GUI panels are not visible
despite show_windows[name] = True. Root cause is structural: imgui.begin
without [Window][name] + DockId in the INI produces a floating window
that gets clipped by the full-screen dockspace. Empirically confirmed:
8s of running produces a 585-byte INI containing only [Window][Debug##Default].
Fix shape (4 phases):
Phase 1: relocate tests/artifacts/manualslop_layout_default.ini ->
layouts/default.ini (at repo root, parallel to themes/ per
user directive 'no configs in src/'); add src/paths.py
'layouts' field + SLOP_GLOBAL_LAYOUTS env override (mirror
themes pattern at line 60/83/150/210-216); add src/layouts.py
loader module (mirror src/theme_models.py + src/theme_2.py
contract; LayoutFile = @dataclass(frozen=True, slots=True)
per the C11/Odin/Jai-in-Python value-type mandate).
Phase 2: install-on-empty-INI in App._post_init. _install_default_layout_if_empty
helper + drain helper, called BEFORE _diag_layout_state and
BEFORE immapp.run. logs '[GUI] installed default layout: <src> -> <dst>'.
Phase 3: drop hardcoded 'tests/artifacts/live_gui_workspace/...' path
from src/commands.py:reset_layout line 369-376 (dead code in
production; violates 'production code defaults to immediate
directory' directive 2026-06-29).
Phase 4: 3-test regression suite in tests/test_default_layout_install.py
+ 1 unit test in tests/test_reset_layout.py; user manual verify
(delete INI, run sloppy.py standalone, see panels).
TDD red-first per task. Atomic per-task commits with git notes (per
conductor/workflow.md §Task Workflow step 9-10). No day estimates per
conductor/workflow.md §Tier 1 Track Initialization Rules.
Out of scope (deferred): panel_defs_fleury_migration - migrate the ~40
render_x functions to declarative PanelDef records per Ryan Fleury's
raddbg 'type view' / 'lens' pattern. Spec §Eventual Normalization Target
documents the design sketch + the transcripts at docs/transcripts/.
This track sets up layouts/ at repo root + src/layouts.py as the typed
loader so the future migration has somewhere to land.
Tracks.md row will be added in Phase 4 (Task 4.6) when the track ships.
21 KiB
Track Specification: Default Layout Install + Hardcoded Path Cleanup
Overview
Manual Slop's GUI panels become invisible at startup whenever manualslop_layout.ini is missing, empty, or refers to window names that don't exist in the current build. The root cause is structural: imgui.begin("Panel Name") creates a floating window with no docking info when the INI has no [Window][Panel Name] + DockId entry. Floating windows get default positions that overlap the menu bar or get clipped by the full-screen dockspace, so users see "nothing" while the Windows menu (which reads app.show_windows) still shows the panels as "checked."
The pre-existing workaround in tests/conftest.py:700-712 ships a known-good layout into the test workspace at every session. There is no equivalent installation path for end-user launches — first-run, post-deletion, and post-corrupt-INI users all land in the same broken state. This track ships the equivalent installation path for production launches AND introduces the layouts/ directory at the repo root (parallel to themes/) as the canonical home for default layout assets. It also removes a hardcoded tests/artifacts/... path that escaped into src/commands.py.
Two patterns established by this track:
-
layouts/directory pattern (the immediate deliverable): Same shape asthemes/— bundled assets at repo root, path resolution viasrc/paths.py, loaders in a parallelsrc/module. Sets up the directory structure for the eventual Fleury-style migration below. -
Fleury "type view" / "lens" pattern (the eventual normalization target, NOT in this track): The user's stated long-term direction is to define GUI panels as declarative "constructs" — data tables of
(panel_name, render_callable, dock_target)tuples that the renderer iterates per-frame, similar to how Ryan Fleury defines type views ("lenses in the code, but views to the user") in the rad debugger to say "if you have this type, just do that automatically for me" (verified from the rad debugger talk transcripts stored atdocs/transcripts/rcJwvx2CTZY_ryan_fleury_raddbg_codebase_intro.jsonv1@2241s anddocs/transcripts/_9_bK_WjuYY_ryan_fleury_raddbg_walkthrough.jsonv2@7697s; see "Eventual Normalization Target" below). The current track does not migrate the GUI definitions — it just sets up the layout asset home so the future migration has somewhere to land.
Current State Audit (as of master 1bea0d23, branch tier2/post_module_taxonomy_de_cruft_20260627)
Already Implemented (DO NOT re-implement)
-
themes/directory + path/loader stack (the PARALLEL pattern this track mirrors):themes/at repo root contains 8 built-in themes (nord_dark.toml,monokai.toml, etc.). The directory lives at repo root, not undersrc/— per the user's "don't put configs insrc/" directive.src/paths.py:60declaresthemes: Path;src/paths.py:83resolves it toroot_dir / "themes";src/paths.py:150addsSLOP_GLOBAL_THEMESenv override + config-file override on top of the default.src/theme_models.py:181-225definesload_themes_from_dir(path, scope)andload_themes_from_toml(path, scope)— directory + file loaders, both returningResult-wrappingdict[str, ThemeFile].src/theme_2.py:340-346callsload_themes_from_disk()which iteratescfg.themesand mergesload_themes_from_dir(...)per scope.- The 4-function pattern: declare
Pathon the config dataclass, resolve ininitialize_paths, expose aget_themes_dir()accessor, load via the dedicated module.
-
tests/artifacts/manualslop_layout_default.ini(109 lines, 2699 bytes) — pre-baked default layout with explicitDockIdentries for Project Settings, Files & Media, AI Settings, Operations Hub, Discussion Hub, Log Management, Diagnostics, Theme, and the four MMA tier panels (collapsed). Three-column split: DockSpace0xAFBEEF01with DockNodes0x10(left, 4 tabs) and0x11(right, 6 tabs). Docstring lists the iter-step procedure: "open sloppy.py, arrange, quit (HelloImGui auto-saves), copy resulting INI over this one." -
live_guifixture ships the default layout (tests/conftest.py:700-712): copiestests/artifacts/manualslop_layout_default.initotemp_workspace / "manualslop_layout.ini"before spawningsloppy.py --enable-test-hooks. Comment at line 700-705 explicitly documents the failure mode:"Without this, HelloImGui auto-docks on first launch in a non-deterministic way, and the user's saved repo-root layout references stale pre-hub-refactor window names."
-
App._diag_layout_state()(src/gui_2.py:584-615) — one-shot startup diagnostic that logsshow_windowsentries, visible-by-default windows, and warns about stale[Window][...]entries in the INI that reference post-refactor-renamed windows (e.g. "Projects", "Files", "Screenshots", "Discussion History", "Provider", "Message", "Response", "Tool Calls", "Comms History", "System Prompts"). Already wired into_post_initat line 580. -
commands.reset_layout(src/commands.py:342-378) — sets everyshow_windows[*]to True and deletes the layout INI. Docstring (line 351-362) acknowledges: "User will need to restart sloppy.py for the dock layout to fully take effect." -
HelloImGui save on shutdown (
src/gui_2.py:1494-1515via_shutdown_save_ini_result, called fromApp.shutdownline 972-973):imgui.save_ini_settings_to_disk(app.runner_params.ini_filename)writes whatever ImGui has in its settings registry. Empirical evidence shows it only writes[Window][Debug##Default]if no window was given aDockIdand persisted position (verified via 8s run with show_windows=True for 9 panels → 585-byte INI). -
ini_filenameresolution (src/gui_2.py:681):self.runner_params.ini_filename = "manualslop_layout.ini"— relative to cwd.ini_folder_type = IniFolderType.current_folderon line 680. HelloImGui resolves this to<cwd>/manualslop_layout.ini. -
Test workspace isolation (
tests/conftest.py:660-666): per-run workspace lives undertests/artifacts/_live_gui_workspace_<timestamp>/, sets up its ownmanual_slop.toml+conductor/tracks/+config.toml.
Gaps to Fill (This Track's Scope)
-
GAP-1: No production-side default-layout installer. When
manualslop_layout.iniis missing or empty AND the user launchessloppy.pyoutside the test harness, the app does not install a sane default. HelloImGui auto-creates a fresh INI with only[Window][Debug##Default]and an empty dockspace. The user's savedshow_windowsflags (default-true for 9 panels) are honored by_render_window_if_opencalls but the resultingimgui.begin(...)calls produce invisible floating windows. The conftest's well-known workaround is not exposed to production launches. -
GAP-2: Hardcoded test-fixture path in production code.
src/commands.py:371containsos.path.join("tests", "artifacts", "live_gui_workspace", "manualslop_layout.ini")inside thereset_layoutcommand. This path only exists inside the test runner's per-session workspace. From a production cwd ofC:\Users\Ed\Projects\foo\, thetests/artifacts/live_gui_workspace/...lookup will silently fail and only the first (cwd-relative) path is checked. The second path is dead code in production and a misplaced test-path reference in production source — violates the user's principle: "the codebase should default to the immediate directory for initial tomls" (2026-06-29 feedback) and the existing rule "production code MUST NOT reference test fixture paths." -
GAP-3: No
layouts/directory + path/loader stack. Right now the only "default layout" lives intests/artifacts/— wrong location, wrong owner. The themes system has the full pattern (themes/+src/paths.pydeclaration +src/theme_models.py/src/theme_2.pyloaders); the layouts system has nothing. This track ships the analogouslayouts/+src/layouts.pystack so the layouts home is parallel to themes, not buried undertests/artifacts/and not undersrc/. -
GAP-4: No regression test for the visibility-after-empty-INI scenario. The existing
test_workspace_profiles_sim.py::test_workspace_profiles_restorationandtest_gui_text_viewer.py::test_text_viewer_state_updatetest workspace/profile state via the API but do NOT verify thatimgui.begin(...)actually registers a docked window (i.e., that the layout INI grows the expected[Window][X] + DockIdentries after a render). Without an INI-content regression test, GAP-1 can regress silently.
Goals
-
G1. When
sloppy.py(production) launches andcwd/manualslop_layout.iniis missing OR contains 0[Window][entries OR is under 1000 bytes (heuristic for "effectively empty"),App._post_initSHALL installlayouts/default.ini(the bundled asset) tocwd/manualslop_layout.iniBEFORE HelloImGui loads it. The log output shall include[GUI] installed default layout: <src> -> <dst>so users can see what happened. -
G2.
App._post_initSHALL respect the user'sshow_windowsoverrides fromconfig.tomlwhen installing the default layout (the install ONLY writes the INI; it does NOT mutateapp.show_windows). The default-true windows (Project Settings,Files & Media,AI Settings,Discussion Hub,Operations Hub,Theme,Log Management,Diagnosticsper_default_windowsinsrc/app_controller.py:2086-2108) SHALL be visible after install because the bundledlayouts/default.inireferences exactly those names withDockIdentries. -
G3.
commands.reset_layout(src/commands.py:342-378) SHALL remove the hardcodedtests/artifacts/...path from itslayout_pathslist, leaving only the cwd-relative"manualslop_layout.ini". Thelive_guiworkspace path is owned by the test fixture, not the app. -
G4. A new
layouts/directory at repo root SHALL exist parallel tothemes/. The new assetlayouts/default.iniSHALL be agit mvoftests/artifacts/manualslop_layout_default.ini(preserving git history). Thesrc/paths.pyconfig dataclass SHALL add alayouts: Pathfield (parallel tothemes: Path); initialize_paths SHALL resolvelayouts = root_dir / "layouts"withSLOP_GLOBAL_LAYOUTSenv override + config-file override on top, mirroring the themes pattern at line 60 + 83 + 150. -
G5. A new
src/layouts.pymodule SHALL be added (parallel tosrc/theme_2.py/src/theme_models.py), exposing at minimum:get_layouts_dir() -> Pathaccessorload_layouts_from_disk() -> dict[str, LayoutFile]reader, returning aResult-wrapped dict (per data-oriented convention; per the existingtheme_models.load_themes_from_dirshape)- The
LayoutFiledataclass as a@dataclass(frozen=True, slots=True)per the project's C11/Odin/Jai-in-Python value-type mandate (nodict[str, Any]) - No new
.pyfile beyond thissrc/layouts.py; the loader reuses the existingResult[T]plumbing insrc/result_types.pyand follows thetheme_models.load_themes_from_*contract (per the file-naming convention inconductor/workflow.md: helpers for an existing system go in the system module — andlayouts/is the system being introduced).
-
G6. Add
tests/test_default_layout_install.pythat:- Removes
cwd/manualslop_layout.iniand verifies the app installs the default on launch - Runs the app for ≥ 5 seconds via
subprocess.Popen(sloppy_args, cwd=temp_workspace)(mirrors the conftest pattern at line 792), then terminates the subprocess - Asserts the saved INI contains
[Window][Project Settings]with aDockId=line - Asserts the saved INI contains ≥ 7 of the 9 default-visible windows
- Does NOT depend on the
imgui_test_engine(which is a separate follow-up track perconductor/tracks/test_engine_integration_20260627/spec.md)
- Removes
-
G7. Add
tests/test_reset_layout.pythat assertscommands.reset_layout's source has notests/artifacts/...string and only consults the cwd-relative"manualslop_layout.ini". Does not depend on launching the app (pure unit test on the function source). -
G8. Update
tests/conftest.py:709to read the bundled layout fromlayouts/default.ini(new path) instead oftests/artifacts/manualslop_layout_default.ini(old path). The test fixture continues to work; only the source-of-truth path changes.
Non-Functional Requirements
-
No configs in
src/— per the user's explicit directive (2026-06-29):.iniconfig files live at repo root (themes/,layouts/,config.toml, etc.), not undersrc/. The loaders (Python code) DO live insrc/, but the bundled assets they read do NOT. -
No day estimates in track artifacts (per
conductor/workflow.md§"Tier 1 Track Initialization Rules" — HARD BAN). -
No opaque types in new code (per
conductor/code_styleguides/data_oriented_design.md§8.5 — Python Type Promotion Mandate). The newLayoutFiledataclass uses@dataclass(frozen=True, slots=True)with explicit fields. Thedict[str, Any]BANNED pattern fromconductor/code_styleguides/python.md§17 is explicitly avoided; loaders returndict[str, LayoutFile](typed instances, not opaque dicts). -
Mirror the
themes/pattern faithfully — the newsrc/layouts.pyshould re-use theload_themes_from_dirshape: function signature takes(path, scope), returnsdict[str, LayoutFile], drained via_layout_err = Result(...). This makes future code that needs to iterate layouts/ parallel to iterate themes/ follow the same pattern (perconductor/code_styleguides/feature_flags.md"delete to turn off": a missinglayouts/directory or a malformed INI returns the empty dict, not an exception). -
Atomic per-task commits with git notes (per
conductor/workflow.md§"Task Workflow" step 9-10).
Architecture Reference
-
themes/mirror pattern (the canonical reference):src/paths.py:60—themes: Path = ...field on the config dataclasssrc/paths.py:83—root_dir / "themes"default in the resolve functionsrc/paths.py:150—SLOP_GLOBAL_THEMESenv override + config overridesrc/paths.py:210-216—get_themes_dir()accessor functionssrc/theme_models.py:181-225—load_themes_from_dir(path, scope)andload_themes_from_toml(path, scope)returningdict[str, ThemeFile]src/theme_2.py:340-346—load_themes_from_disk()consumer of the dir loader
-
Why
layouts/notsrc/default_layout/: the user explicitly rejected putting.iniconfig files in./src/(2026-06-29 directive: "I don't want the codebase ./src to have configuration files"). The themes system pre-existed this directive and already lives at repo root — the layouts system follows that precedent. -
HelloImGui IniFolderType / save_ini_settings_to_disk:
src/gui_2.py:680-681,src/gui_2.py:1494-1515. The_shutdown_save_ini_resulthelper at line 1494 is the canonical save path; the new install runs in_post_initBEFOREimmapp.run(...)(which happens after_post_initatsrc/gui_2.py:1486). -
_diag_layout_state(src/gui_2.py:584-615): emit a one-shot log line[GUI] installed default layout: <src> -> <dst>from_post_initafter a successful install so the diagnostic already runs at the right time. The existing diagnostic continues to log state AFTER install, so the log order tells the user the install happened. -
_render_window_if_open(src/gui_2.py:1115-1120): the_post_initinstall runs beforeimmapp.run(...), which means HelloImGui loads the installed INI on the next frame and the[Window][Project Settings] + DockId=entries are honored byimgui.begin(...). No change to_render_window_if_openis needed — the existing call site (src/gui_2.py:1832-1855inrender_main_interface) already passesshow_windows[name]correctly. -
conductor/code_styleguides/error_handling.md: the install is best-effort. OnOSError/FileNotFoundError(asset missing in the wheel), append toapp._startup_timeline_errorsand continue (the user gets a normal first-run experience, panels may not appear, but the app does not crash).
Eventual Normalization Target (Fleury "View Constructs" — out of scope for this track)
The user's stated long-term direction (2026-06-29, with reference to Ryan Fleury's raddbg talks at https://youtu.be/rcJwvx2CTZY and https://youtu.be/_9_bK_WjuYY, transcripts at docs/transcripts/rcJwvx2CTZY_ryan_fleury_raddbg_codebase_intro.json and docs/transcripts/_9_bK_WjuYY_ryan_fleury_raddbg_walkthrough.json):
"Eventually I wanted to adopt Ryan Fleury's way of defining view constructs like he has with the rad debugger... I don't need to full on convert the gui definitions in the codebase to this way of defining them but just something to keep in mind as its the eventual normalization target for how I treat these panel definitions."
The pattern, extracted from the transcripts:
- v1@2237s: Ryan calls
imgui.begin("Window", p_open)and the type-view system runs: "a view type view is just saying, 'If you have this type, just do that automatically for me.'" - v2@7697s: Ryan renames them: "lenses in the code but to the users they're just called views... the type view is just saying... if you have this type, just do that automatically for me."
- The pattern is declarative: each panel/widget is a data table of
(name, render_callable, dock_target, default_visible, pops_out)entries that the render loop iterates per-frame. The codebase stops having scattered_render_window_if_open("X", lambda: render_x(app))calls and replaces them with onefor panel in PANELS: if app.show_windows.get(panel.name): panel.render(app).
Why this track sets up that future:
layouts/at repo root = the home for the declarative asset (eventually a.pymodule alongside, or a TOML/INI with panel-by-panel config).src/layouts.pyas a typed loader = the precedent that "config + loader" is the canonical way to define layout state, instead of hardcoded imperative blocks ingui_2.py.layouts/default.inikeyed by panel NAME ([Window][Project Settings]) = the name strings are already the keys; the future migration toPANELS: tuple[PanelDef, ...]will keep those names but addrender_callableanddock_targetfields.
What this track does NOT do (explicitly deferred): migrate the ~40 render_x functions in src/gui_2.py into declarative PanelDef records. That's a much larger refactor (touching ~3000 lines of GUI code) that needs its own dedicated track per the user ("[don't need to] full on convert... just something to keep in mind"). Logged in metadata.json:deferred_to_followup_tracks for the next planner.
Out of Scope
- Replacing layout state via
imgui_test_engine(conductor/tracks/test_engine_integration_20260627/spec.md) — this is a separate follow-up track. G6's regression test uses INI content as a proxy for "imgui.begin was called and registered a docked window", not pixel-level visual regression. - Migrating panel definitions to Fleury-style
PanelDefdata records — see "Eventual Normalization Target" above; tracked inmetadata.json:deferred_to_followup_tracks[].panel_defs_fleury_migration. - Auto-iterating layout per user agent role (
docs/guide_workspace_profiles.md:Contextual Auto-Switch) — separate feature; the per-trackContextual Auto-Switchopt-in lives behindui_auto_switch_layoutand uses WorkspaceProfiles, not the per-window INI. - Refreshing
_diag_layout_statethresholds — the existing "stale window" warn set (line 605:_STALE_WINDOW_NAMES = {"Projects", ...}) is unchanged by this track. - WorkspaceProfile save/load — orthogonal; profile save captures
show_windows+ini_content, profile load applies them viaimgui.load_ini_settings_from_memory(src/gui_2.py:927). The install on first run does not interact with profiles. - Layout editing UI (
src/gui_2.py:render_operations_hub"Workspace Layouts" tab) — unchanged. - Adding more than one bundled layout to
layouts/—default.iniis enough for this track; users can hand-authormy-layout.iniand switch via WorkspaceProfile. Future track may addcompact.ini,wide.ini, etc.
See Also
docs/guide_workspace_profiles.md— Workspace profiles (orthogonal but conceptually adjacent)conductor/tracks/test_engine_integration_20260627/spec.md— ImGui Test Engine integration (deferred follow-up for visual regression coverage)conductor/code_styleguides/feature_flags.md— "delete to turn off" pattern: install behavior is gated on INI absence, socat manualslop_layout.inito leave a no-op stub (≥ 1000 bytes / ≥ 1[Window][entry) suppresses the installconductor/code_styleguides/error_handling.md— boundary handling for the install pathconductor/tech-stack.md§"src/paths.py" — the existing themes pattern is the canonical reference for the new layouts path resolution- Video transcripts (Fleury talks):
docs/transcripts/rcJwvx2CTZY_ryan_fleury_raddbg_codebase_intro.json,docs/transcripts/_9_bK_WjuYY_ryan_fleury_raddbg_walkthrough.json— recorded byscripts/video_analysis/extract_transcript.py