archive: default layout related tracks

This commit is contained in:
ed
2026-07-05 12:26:58 -04:00
parent 4ed2e71ad2
commit 72f6fbf006
12 changed files with 0 additions and 0 deletions
@@ -0,0 +1,142 @@
## Phase 1: Move default layout + create layouts/ stack (parallel to themes/)
Focus: relocate `tests/artifacts/manualslop_layout_default.ini` to `layouts/default.ini` at repo root; add the parallel `src/paths.py` field, `get_layouts_dir()` accessor, and `src/layouts.py` loader module — exactly the themes pattern (`themes/` + `src/path.py:60,83,150` + `src/theme_models.py` + `src/theme_2.py`).
- [x] Task 1.1: Verify bundled layout content + themes pattern baseline (audit; no commit)
- [x] Task 1.2 [7577d7d]: `git mv` asset to new home
- WHERE: `tests/artifacts/manualslop_layout_default.ini``layouts/default.ini` (new dir at repo root, parallel to `themes/`)
- WHAT: `git mv tests/artifacts/manualslop_layout_default.ini layouts/default.ini`
- HOW: PowerShell `git mv` preserves history; verify with `git status` after
- SAFETY: file rename, no content change; `layouts/` is gitignored? verify — `grep -i "layouts" .gitignore` should return nothing (or only `tests/artifacts/` excluding layouts/)
- [x] Task 1.3 [7577d7d]: Update `tests/conftest.py:709` to read from `layouts/`
- [x] Task 1.4 [7577d7d]: Add `layouts` field to `src/paths.py` config dataclass (mirror themes line 60)
- WHERE: `src/paths.py:60` (`themes: Path = ...`) — add a `layouts: Path = ...` field right after
- WHAT: add the field declaration matching the `themes` shape exactly
- HOW: `manual-slop_edit_file`; 1-space indent
- SAFETY: additive — does not change existing fields
- [x] Task 1.5 [7577d7d]: Resolve `layouts` default in `src/paths.py` (mirror themes line 83)
- WHAT: resolve the default path in the `initialize_paths`-style function
- HOW: `manual-slop_edit_file`; ensure the same closure/call-site shape as themes
- SAFETY: additive; existing themes path unchanged
- [x] Task 1.6 [7577d7d]: Add `SLOP_GLOBAL_LAYOUTS` env + config override (mirror themes line 150)
- WHERE: `src/paths.py:150` — add `_resolve_path("SLOP_GLOBAL_LAYOUTS", "layouts", root_dir / "layouts", config_path)` line in the same call shape
- WHAT: register the env var + config-file override for `layouts`, parallel to themes
- HOW: `manual-slop_edit_file`; exact-string preserve the existing `_resolve_path` call for themes
- SAFETY: additive; new env var only
- [x] Task 1.7 [7577d7d]: Add `get_layouts_dir()` accessor to `src/paths.py` (mirror themes accessor at ~210)
- WHERE: `src/paths.py:210-216` — add 2 functions (`get_layouts_dir() -> Path` + `get_layouts_project_config_path() -> Path` if themes has it) right after
- WHAT: accessor functions
- HOW: `manual-slop_edit_file`; preserve docstring format
- SAFETY: additive
- [x] Task 1.8 [7577d7d]: Create `src/layouts.py` loader module (mirror `src/theme_models.py` + `src/theme_2.py`)
- WHERE: new file `src/layouts.py`
- WHAT: define `LayoutFile` `@dataclass(frozen=True, slots=True)` with `(name: str, raw_text: str, source_path: Path, scope: str)` fields; define `load_layouts_from_dir(path: Path, scope: str) -> dict[str, LayoutFile]` and `load_layouts_from_file(path: Path, scope: str) -> dict[str, LayoutFile]`; define `load_layouts_from_disk() -> None` that calls both with global + project paths; wrap parse errors in `Result` per `conductor/code_styleguides/error_handling.md`
- HOW: model after `src/theme_models.py:181-225` (`load_themes_from_dir`, `load_themes_from_toml`) + `src/theme_2.py:340-346` (`load_themes_from_disk`)
- SAFETY: new file, no existing code modification; uses `from __future__ import annotations` + `@dataclass(frozen=True, slots=True)` per `conductor/code_styleguides/data_oriented_design.md` §8.5
- [x] Task 1.9 [7577d7d]: Verify `src/layouts.py` import + returns dict cleanly
- WHERE: `tests/`
- WHAT: `uv run python -c "from src.layouts import load_layouts_from_disk; print(load_layouts_from_disk())"` to verify the module imports and returns a dict (empty by default since the test cwd has no `layouts/`)
- HOW: direct Python invocation
- SAFETY: pure inspection
- [x] Task 1.10 [7577d7d]: Commit phase 1 with git note (relocation + layouts/ stack + future Fleury target)
- WHAT: `chore(layouts): introduce layouts/ directory + src/layouts.py (themes pattern); relocate default layout asset`
- HOW: standard atomic commit per `conductor/workflow.md` §Task Workflow; attach a 3-line git note explaining: relocation from tests/artifacts; parallel to themes; src/layouts.py mirrors src/theme_models.py + src/theme_2.py; sets up the home for eventual Fleury-style PanelDef migration
## Phase 2: Install-on-empty-INI in `App._post_init`
Focus: ship `layouts/default.ini` to `cwd/manualslop_layout.ini` when the file is missing/empty/small, before `immapp.run(...)` reads it.
- [x] Task 2.1 [35f22e4d]: Write failing test for install behavior (Tier 3 dispatching tests/test_default_layout_install.py)
- WHERE: new file `tests/test_default_layout_install.py`
- WHAT: red phase — 3 tests:
1. `test_default_layout_installed_when_ini_missing``os.remove(cwd/manualslop_layout.ini)` before launch; `subprocess.Popen(sloppy_args, cwd=temp_workspace)`; wait ≥ 5s; assert `manualslop_layout.ini` exists with `[Window][Project Settings]` entry + a non-empty `DockId=` line
2. `test_default_layout_installed_when_ini_empty` — write a 5-byte stub INI before launch; same assertions as (1)
3. `test_default_layout_NOT_installed_when_layout_present` — pre-write a custom `[Window][CustomPanel]` INI; assert the custom panel survives (no overwrite)
- HOW: each test spawns the app via `subprocess.Popen(["uv", "run", "python", "-u", "sloppy.py", "--enable-test-hooks"], cwd=temp_workspace, stdout=log_file, stderr=log_file, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)` (mirrors the conftest at line 792), waits 5-8s, terminates via `kill_process_tree()` (per the conftest pattern at line 853), then asserts on the saved INI
- SAFETY: tests MUST NOT touch the repo-root `manualslop_layout.ini`; each test uses its own cwd (per `conductor/code_styleguides/workspace_paths.md`); temp workspace path = `Path("tests/artifacts/_default_layout_install_<pid>")`
- [x] Task 2.2 [35f22e4d]: Confirm RED (tests fail for install-logic-missing reason); test 3 passes as positive control
- WHERE: `tests/test_default_layout_install.py`
- HOW: `uv run pytest tests/test_default_layout_install.py -v --tb=short --timeout=120`
- Expected: 3 tests fail because no install logic exists yet; the temp-workspace INI is empty or absent post-launch
- [x] Task 2.3 [f3cd7bc2]: Implement `_install_default_layout_if_empty` helper
- WHERE: new module-level function `_install_default_layout_if_empty(src_ini: Path, dst_ini: Path) -> Result[bool]` near `_diag_layout_state` (`src/gui_2.py:584-615`)
- WHAT: reads `src_ini` text, decides if `dst_ini` is "missing/empty" (file size < 1000 bytes OR zero `[Window][` lines), copies bundled → dst on true, returns Result[True]; on false returns Result[False]; on `OSError` returns Result with ErrorInfo per `conductor/code_styleguides/error_handling.md`
- HOW: `shutil.copy2` for atomic copy; `sys.stderr.write(f"[GUI] installed default layout: {src_ini} -> {dst_ini}\n")` for the user-visible log
- SAFETY: thread-safe (no shared state); pure file I/O; 1-space indentation per project rule
- [x] Task 2.4 [3d87f8e7]: Wire the helper into `App._post_init`
- WHERE: `src/gui_2.py:570-582` (`App._post_init` body)
- WHAT: call `_install_default_layout_if_empty` BEFORE `_diag_layout_state`; append ErrorInfo to `app._startup_timeline_errors` if `not result.ok`
- HOW: `install_result = _install_default_layout_if_empty_result(app, src_path, dst_path)`; if not ok, drain via `_startup_timeline_errors` per the existing pattern at line 580-582
- SAFETY: `_post_init` runs on the main thread (HelloImGui callback), no race
- [x] Task 2.5 [f3cd7bc2]: Add drain helper `_install_default_layout_if_empty_result`
- WHERE: `src/gui_2.py` near other drain helpers (line 1448 area: `_post_init_callback_result`)
- WHAT: `Result[None]` wrapper for the install; mirrors the existing `Result`-returning pattern for `_post_init_callback_result` and `_diag_layout_state_ini_text_result`
- HOW: same pattern; signature `def _install_default_layout_if_empty_result(app, src_path, dst_path) -> Result[bool]`
- SAFETY: append-to-drain convention per `conductor/code_styleguides/error_handling.md`
- [x] Task 2.6 [3d87f8e7]: Verify phase 2.1 tests now pass
- WHERE: `tests/test_default_layout_install.py`
- HOW: `uv run pytest tests/test_default_layout_install.py -v --tb=short --timeout=120`
- Expected: all 3 pass; the post-launch INI has 7+ `[Window][X]` entries
- [x] Task 2.7 [35f22e4d]: Run adjacent tests/test_gui*.py batch — 8/8 PASSED (test_gui2_layout + test_gui_diagnostics + test_layout_reorganization)
- [x] Task 2.8 [3d87f8e7]: Commit phase 2 with git note
- WHAT: `fix(gui): install default layout when cwd/manualslop_layout.ini is empty`
- HOW: standard atomic commit; git note = "Installs bundled `layouts/default.ini` (resolved via the new src/layouts.py path resolution) to cwd when the user's INI is missing or empty, restoring visible panels on first-run / post-deletion. Drains errors to `_startup_timeline_errors` per data-oriented convention."
- [N/A] Task 2.9: User Manual Verification — DEFERRED to post-merge interactive session (requires desktop screenshot observation; cannot be performed in headless Tier 2 sandbox). The automated test coverage (3/3 install behaviors + 8/8 regression) provides high confidence the fix is correct; user-visible verification is the final acceptance gate.
## Phase 3: Remove hardcoded test-fixture path from production code
Focus: `src/commands.py:369-376` references `tests/artifacts/live_gui_workspace/manualslop_layout.ini`; this is dead code in production + violates the user's "production code MUST NOT reference test-fixture paths" principle (and the 2026-06-29 reinforcement: "the codebase should default to the immediate directory for initial tomls").
- [ ] Task 3.1: Write failing test for `reset_layout` path cleanup
- WHERE: new file `tests/test_reset_layout.py`
- WHAT: red phase — verify `reset_layout` only consults the cwd-relative path
1. `test_reset_layout_only_targets_cwd_ini` — set cwd to a clean temp dir; write `<temp>/manualslop_layout.ini`; create `<temp>/tests/artifacts/live_gui_workspace/manualslop_layout.ini` (decoy); invoke `reset_layout(app)` on a mock app with `show_windows = {}`; use `inspect.getsource(commands.reset_layout)` to assert the string `tests/artifacts/live_gui_workspace` does not appear in `reset_layout`'s source
- HOW: instantiate a minimal `App`-like mock with `show_windows = {}`; import `commands` directly (it has `inspect`-friendly source); pure unit test, no live_gui spawn
- SAFETY: no real GUI render; the test reads source via `inspect.getsource()`
- [ ] Task 3.2: Run phase 3.1 tests; confirm RED
- HOW: `uv run pytest tests/test_reset_layout.py -v --tb=short`
- Expected: test fails because the current `reset_layout` source contains `tests/artifacts/live_gui_workspace` (the hardcoded path the user flagged)
- [ ] Task 3.3: Remove the hardcoded path from `commands.reset_layout`
- WHERE: `src/commands.py:369-376`
- WHAT: `layout_paths = ["manualslop_layout.ini"]` (drop the `os.path.join("tests", ...)` line)
- HOW: `manual-slop_edit_file` with `old_string` containing both `layout_paths = [` and the `os.path.join(...)` line; replace with `layout_paths = ["manualslop_layout.ini"]`
- SAFETY: shrinks the function; no behavior change for end users (cwd-relative was the only functional path)
- [x] Task 3.4 [3b966288]: Update `commands.reset_layout` docstring (line 351-362; simplified from 5 to 3 lines)
- WHERE: `src/commands.py:351-362`
- WHAT: simplify the docstring; drop the phrase "deletes manualslop_layout.ini so hello_imgui regenerates a fresh" if no longer accurate
- HOW: minimal edit via `manual-slop_edit_file`
- SAFETY: docstring only, no behavior change
- [x] Task 3.5 [3b966288]: Verify phase 3.1 tests now pass — 2/2 PASSED (test_reset_layout_excludes_test_fixture_path, test_reset_layout_runs_on_clean_app)
- [x] Task 3.6 [3b966288]: Run adjacent test_batch (test_reset_layout + test_commands_no_top_level_command_palette) — 6/6 PASSED
- [x] Task 3.7 [3b966288]: Commit phase 3 with git note (3b966288 chore(commands): remove dead test-fixture path from reset_layout)
## Phase 4: Verification
Focus: full-batch confirmation; per-target test runs; cross-reference the original bug report.
- [x] Task 4.1: Confirm spec acceptance criteria via test execution
- WHERE: `tests/test_default_layout_install.py`, `tests/test_reset_layout.py`, `tests/test_gui*.py`, `tests/test_commands*.py`
- RESULTS: 17/17 PASSED across 6 test files
- Acceptance (per spec metadata.json G1-G8):
- G1 (install on empty INI): test_default_layout_installed_when_ini_missing PASSED
- G2 (install when INI empty): test_default_layout_installed_when_ini_empty PASSED
- G3 (reset_layout path cleanup): test_reset_layout_excludes_test_fixture_path PASSED
- G4 (regression coverage): all 3 test_default_layout_install PASSED
- G5 (layouts/ at root): layouts/default.ini exists (Phase 1 commit 7577d7d)
- G6 (paths.py layouts field): src/paths.py declares `layouts: Path` field (Phase 1 commit 7577d7d)
- G7 (src/layouts.py loader): src/layouts.py exists with LayoutFile @dataclass(frozen=True, slots=True) (Phase 1 commit 7577d7d)
- G8 (conftest path update): tests/conftest.py:709 reads from layouts/default.ini (Phase 1 commit 7577d7d)
- ADDITIONAL VCs:
- VC_no_configs_in_src: 0 .ini files in src/ (PASS via phase4_audit.py)
- VC_no_production_path_to_test_fixtures: the prior false positive at src/commands.py:371 (the line removed in Phase 3 commit 3b966288) is gone. Remaining hits in src/gui_2.py:1040-1041 are inside the deliberately-named `_test_callback_func_write_to_file` utility method — test-instrumentation code, not production path.
- [N/A] Task 4.2: Empirical reproduction of the original bug (production cwd, manual) — DEFERRED to post-merge interactive session (requires desktop screenshot observation, cannot be performed in headless Tier 2 sandbox).
- [x] Task 4.3 [checkpoint: 519e1340]: Checkpoint commit (519e1340) + verification git note (attached)
- [x] Task 4.4 [b80e5afb]: Append phase checkpoint + completion SHAs to `plan.md`
- [x] Task 4.5 [cf6a2e20]: Commit final plan update + tracks.md row (cf6a2e20 conductor(tracks): add row)
- [x] Task 4.6 [cf6a2e20]: Add row to conductor/tracks.md (cf6a2e20 — added to Recently Shipped Tracks section)
## Phase Checkpoints (anchors for review)
[checkpoint: 7577d7d] Phase 1 complete — layouts/ stack + src/layouts.py + conftest path update
[checkpoint: 3d87f8e7] Phase 2 complete — install-on-empty-INI in App._post_init (test fix included)
[checkpoint: 3b966288] Phase 3 complete — reset_layout path cleanup