Private
Public Access
0
0
Files
manual_slop/conductor/tracks/default_layout_install_20260629/spec.md
T
ed 5ad062b13a conductor(track): init default_layout_install_20260629 (empty INI -> install default; layouts/ at root + src/layouts.py; reset_layout path cleanup)
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.
2026-06-29 14:02:41 -04:00

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:

  1. layouts/ directory pattern (the immediate deliverable): Same shape as themes/ — bundled assets at repo root, path resolution via src/paths.py, loaders in a parallel src/ module. Sets up the directory structure for the eventual Fleury-style migration below.

  2. 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 at docs/transcripts/rcJwvx2CTZY_ryan_fleury_raddbg_codebase_intro.json v1@2241s and docs/transcripts/_9_bK_WjuYY_ryan_fleury_raddbg_walkthrough.json v2@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 under src/ — per the user's "don't put configs in src/" directive.
    • src/paths.py:60 declares themes: Path; src/paths.py:83 resolves it to root_dir / "themes"; src/paths.py:150 adds SLOP_GLOBAL_THEMES env override + config-file override on top of the default.
    • src/theme_models.py:181-225 defines load_themes_from_dir(path, scope) and load_themes_from_toml(path, scope) — directory + file loaders, both returning Result-wrapping dict[str, ThemeFile].
    • src/theme_2.py:340-346 calls load_themes_from_disk() which iterates cfg.themes and merges load_themes_from_dir(...) per scope.
    • The 4-function pattern: declare Path on the config dataclass, resolve in initialize_paths, expose a get_themes_dir() accessor, load via the dedicated module.
  • tests/artifacts/manualslop_layout_default.ini (109 lines, 2699 bytes) — pre-baked default layout with explicit DockId entries 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: DockSpace 0xAFBEEF01 with DockNodes 0x10 (left, 4 tabs) and 0x11 (right, 6 tabs). Docstring lists the iter-step procedure: "open sloppy.py, arrange, quit (HelloImGui auto-saves), copy resulting INI over this one."

  • live_gui fixture ships the default layout (tests/conftest.py:700-712): copies tests/artifacts/manualslop_layout_default.ini to temp_workspace / "manualslop_layout.ini" before spawning sloppy.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 logs show_windows entries, 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_init at line 580.

  • commands.reset_layout (src/commands.py:342-378) — sets every show_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-1515 via _shutdown_save_ini_result, called from App.shutdown line 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 a DockId and persisted position (verified via 8s run with show_windows=True for 9 panels → 585-byte INI).

  • ini_filename resolution (src/gui_2.py:681): self.runner_params.ini_filename = "manualslop_layout.ini" — relative to cwd. ini_folder_type = IniFolderType.current_folder on line 680. HelloImGui resolves this to <cwd>/manualslop_layout.ini.

  • Test workspace isolation (tests/conftest.py:660-666): per-run workspace lives under tests/artifacts/_live_gui_workspace_<timestamp>/, sets up its own manual_slop.toml + conductor/tracks/ + config.toml.

Gaps to Fill (This Track's Scope)

  • GAP-1: No production-side default-layout installer. When manualslop_layout.ini is missing or empty AND the user launches sloppy.py outside 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 saved show_windows flags (default-true for 9 panels) are honored by _render_window_if_open calls but the resulting imgui.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:371 contains os.path.join("tests", "artifacts", "live_gui_workspace", "manualslop_layout.ini") inside the reset_layout command. This path only exists inside the test runner's per-session workspace. From a production cwd of C:\Users\Ed\Projects\foo\, the tests/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 in tests/artifacts/ — wrong location, wrong owner. The themes system has the full pattern (themes/ + src/paths.py declaration + src/theme_models.py/src/theme_2.py loaders); the layouts system has nothing. This track ships the analogous layouts/ + src/layouts.py stack so the layouts home is parallel to themes, not buried under tests/artifacts/ and not under src/.

  • GAP-4: No regression test for the visibility-after-empty-INI scenario. The existing test_workspace_profiles_sim.py::test_workspace_profiles_restoration and test_gui_text_viewer.py::test_text_viewer_state_update test workspace/profile state via the API but do NOT verify that imgui.begin(...) actually registers a docked window (i.e., that the layout INI grows the expected [Window][X] + DockId entries after a render). Without an INI-content regression test, GAP-1 can regress silently.

Goals

  • G1. When sloppy.py (production) launches and cwd/manualslop_layout.ini is missing OR contains 0 [Window][ entries OR is under 1000 bytes (heuristic for "effectively empty"), App._post_init SHALL install layouts/default.ini (the bundled asset) to cwd/manualslop_layout.ini BEFORE HelloImGui loads it. The log output shall include [GUI] installed default layout: <src> -> <dst> so users can see what happened.

  • G2. App._post_init SHALL respect the user's show_windows overrides from config.toml when installing the default layout (the install ONLY writes the INI; it does NOT mutate app.show_windows). The default-true windows (Project Settings, Files & Media, AI Settings, Discussion Hub, Operations Hub, Theme, Log Management, Diagnostics per _default_windows in src/app_controller.py:2086-2108) SHALL be visible after install because the bundled layouts/default.ini references exactly those names with DockId entries.

  • G3. commands.reset_layout (src/commands.py:342-378) SHALL remove the hardcoded tests/artifacts/... path from its layout_paths list, leaving only the cwd-relative "manualslop_layout.ini". The live_gui workspace path is owned by the test fixture, not the app.

  • G4. A new layouts/ directory at repo root SHALL exist parallel to themes/. The new asset layouts/default.ini SHALL be a git mv of tests/artifacts/manualslop_layout_default.ini (preserving git history). The src/paths.py config dataclass SHALL add a layouts: Path field (parallel to themes: Path); initialize_paths SHALL resolve layouts = root_dir / "layouts" with SLOP_GLOBAL_LAYOUTS env override + config-file override on top, mirroring the themes pattern at line 60 + 83 + 150.

  • G5. A new src/layouts.py module SHALL be added (parallel to src/theme_2.py/src/theme_models.py), exposing at minimum:

    • get_layouts_dir() -> Path accessor
    • load_layouts_from_disk() -> dict[str, LayoutFile] reader, returning a Result-wrapped dict (per data-oriented convention; per the existing theme_models.load_themes_from_dir shape)
    • The LayoutFile dataclass as a @dataclass(frozen=True, slots=True) per the project's C11/Odin/Jai-in-Python value-type mandate (no dict[str, Any])
    • No new .py file beyond this src/layouts.py; the loader reuses the existing Result[T] plumbing in src/result_types.py and follows the theme_models.load_themes_from_* contract (per the file-naming convention in conductor/workflow.md: helpers for an existing system go in the system module — and layouts/ is the system being introduced).
  • G6. Add tests/test_default_layout_install.py that:

    • Removes cwd/manualslop_layout.ini and 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 a DockId= 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 per conductor/tracks/test_engine_integration_20260627/spec.md)
  • G7. Add tests/test_reset_layout.py that asserts commands.reset_layout's source has no tests/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:709 to read the bundled layout from layouts/default.ini (new path) instead of tests/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): .ini config files live at repo root (themes/, layouts/, config.toml, etc.), not under src/. The loaders (Python code) DO live in src/, 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 new LayoutFile dataclass uses @dataclass(frozen=True, slots=True) with explicit fields. The dict[str, Any] BANNED pattern from conductor/code_styleguides/python.md §17 is explicitly avoided; loaders return dict[str, LayoutFile] (typed instances, not opaque dicts).

  • Mirror the themes/ pattern faithfully — the new src/layouts.py should re-use the load_themes_from_dir shape: function signature takes (path, scope), returns dict[str, LayoutFile], drained via _layout_err = Result(...). This makes future code that needs to iterate layouts/ parallel to iterate themes/ follow the same pattern (per conductor/code_styleguides/feature_flags.md "delete to turn off": a missing layouts/ 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:60themes: Path = ... field on the config dataclass
    • src/paths.py:83root_dir / "themes" default in the resolve function
    • src/paths.py:150SLOP_GLOBAL_THEMES env override + config override
    • src/paths.py:210-216get_themes_dir() accessor functions
    • src/theme_models.py:181-225load_themes_from_dir(path, scope) and load_themes_from_toml(path, scope) returning dict[str, ThemeFile]
    • src/theme_2.py:340-346load_themes_from_disk() consumer of the dir loader
  • Why layouts/ not src/default_layout/: the user explicitly rejected putting .ini config 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_result helper at line 1494 is the canonical save path; the new install runs in _post_init BEFORE immapp.run(...) (which happens after _post_init at src/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_init after 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_init install runs before immapp.run(...), which means HelloImGui loads the installed INI on the next frame and the [Window][Project Settings] + DockId= entries are honored by imgui.begin(...). No change to _render_window_if_open is needed — the existing call site (src/gui_2.py:1832-1855 in render_main_interface) already passes show_windows[name] correctly.

  • conductor/code_styleguides/error_handling.md: the install is best-effort. On OSError / FileNotFoundError (asset missing in the wheel), append to app._startup_timeline_errors and 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 one for panel in PANELS: if app.show_windows.get(panel.name): panel.render(app).

Why this track sets up that future:

  1. layouts/ at repo root = the home for the declarative asset (eventually a .py module alongside, or a TOML/INI with panel-by-panel config).
  2. src/layouts.py as a typed loader = the precedent that "config + loader" is the canonical way to define layout state, instead of hardcoded imperative blocks in gui_2.py.
  3. layouts/default.ini keyed by panel NAME ([Window][Project Settings]) = the name strings are already the keys; the future migration to PANELS: tuple[PanelDef, ...] will keep those names but add render_callable and dock_target fields.

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 PanelDef data records — see "Eventual Normalization Target" above; tracked in metadata.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-track Contextual Auto-Switch opt-in lives behind ui_auto_switch_layout and uses WorkspaceProfiles, not the per-window INI.
  • Refreshing _diag_layout_state thresholds — 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 via imgui.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.ini is enough for this track; users can hand-author my-layout.ini and switch via WorkspaceProfile. Future track may add compact.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, so cat manualslop_layout.ini to leave a no-op stub (≥ 1000 bytes / ≥ 1 [Window][ entry) suppresses the install
  • conductor/code_styleguides/error_handling.md — boundary handling for the install path
  • conductor/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 by scripts/video_analysis/extract_transcript.py