# 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 `/manualslop_layout.ini`. - **Test workspace isolation** (`tests/conftest.py:660-666`): per-run workspace lives under `tests/artifacts/_live_gui_workspace_/`, 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: -> ` 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:60` — `themes: Path = ...` field on the config dataclass - `src/paths.py:83` — `root_dir / "themes"` default in the resolve function - `src/paths.py:150` — `SLOP_GLOBAL_THEMES` env override + config override - `src/paths.py:210-216` — `get_themes_dir()` accessor functions - `src/theme_models.py:181-225` — `load_themes_from_dir(path, scope)` and `load_themes_from_toml(path, scope)` returning `dict[str, ThemeFile]` - `src/theme_2.py:340-346` — `load_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: -> ` 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`