18 KiB
Live-GUI Fragility Fixes Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Fix 3 failing live_gui tests discovered in the 2026-06-05 batched test run (269/272 → 272/272) by repairing a regression in the defer-not-catch fix for _capture_workspace_profile, fixing a test mock for imscope.window, and adding a regression unit test.
Architecture: Surgical 1-line fix on the production code path (the str/bytes sentinel that violated the WorkspaceProfile.ini_content: str contract), a 2-line fix on the prior session test mock (add missing tuple-return for imscope.window), and a new unit test that encodes the str/bytes contract so future regressions are caught at unit-test speed.
Tech Stack: Python 3.11+, pytest 9.0, imgui-bundle (imgui.save_ini_settings_to_memory()), tomli_w, tomllib.
File Structure
| File | Change | Purpose |
|---|---|---|
src/gui_2.py |
Modify lines 601-609 | Fix ini = b"" → ini = "" in defer branch + except handler. Add str() defensive wrap. |
tests/test_prior_session_no_pop_imbalance.py |
Modify (add 2 lines) | Add (True, True) tuple-return mock for imscope.window. |
tests/test_workspace_profile_serialization.py |
Create | New unit test for the ini_content: str round-trip contract. |
conductor/tracks.md |
Modify (1 line, plan updates) | Register new track. |
docs/superpowers/specs/2026-06-05-live-gui-fragility-fixes-design.md |
(already written) | Spec for this work. |
No new files needed in src/. No production-code refactoring. No changes to the workspace profile save/load architecture.
Task 1: Fix _capture_workspace_profile str/bytes sentinel
Files:
-
Modify:
src/gui_2.py:601-609 -
Test: deferred to Task 3 (regression unit test)
-
Step 1.1: Pre-edit checkpoint
cd C:\projects\manual_slop; git add .
- Step 1.2: Read the current state of
_capture_workspace_profile
Read src/gui_2.py:601-609 to confirm the current code.
- Step 1.3: Apply the fix
Replace the current _capture_workspace_profile defer-and-except block (lines 601-609) with:
def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile:
if not getattr(self, "_ini_capture_ready", False):
self._ini_capture_ready = True
ini = ""
else:
try:
ini = str(imgui.save_ini_settings_to_memory() or "")
except Exception:
ini = ""
panel_states = {
Use manual-slop_py_update_definition with the existing function name _capture_workspace_profile to do the surgical replacement. The body change is:
- Line 604:
ini = b""→ini = "" - Line 609:
ini = b""→ini = "" - Line 607:
ini = imgui.save_ini_settings_to_memory()→ini = str(imgui.save_ini_settings_to_memory() or "")
Use exactly 1-space indentation.
- Step 1.4: Verify the file still parses
cd C:\projects\manual_slop; uv run python -c "import ast; ast.parse(open('src/gui_2.py').read())"
Expected: no error.
- Step 1.5: Run the workspace-profile-related tests to verify the fix
cd C:\projects\manual_slop; uv run pytest tests/test_workspace_manager.py tests/test_workspace_profiles_sim.py tests/test_auto_switch_sim.py -v --timeout=60
Expected:
test_workspace_manager.pypasses (it tests the manager's save/load semantics with mocked profiles).test_workspace_profiles_sim.pypasses (it useslive_gui).test_auto_switch_sim.pypasses (it useslive_gui).
If test_workspace_profiles_sim.py or test_auto_switch_sim.py fails, it should be ONLY because of session-state pollution from a prior run. The fix targets the underlying bug; the test infrastructure (live_gui fixture) is what makes these flake. Re-run individually if needed:
cd C:\projects\manual_slop; uv run pytest tests/test_workspace_profiles_sim.py::test_workspace_profiles_restoration -v --timeout=60
- Step 1.6: Commit
cd C:\projects\manual_slop; git add src/gui_2.py
git -C C:\projects\manual_slop commit -m "fix(gui_2): use str sentinel not bytes in _capture_workspace_profile"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "WorkspaceProfile.ini_content is str (src/models.py:799) and tomli_w rejects bytes. The d7487af4 defer fix used ini=b'' which crashed TOML serialization, so save_workspace_profile raised TypeError, profile was never saved, and load_workspace_profile became a no-op. Changed both ini=b'' to ini='' and added str() defensive wrap on the non-defer path. Fixes test_auto_switch_sim and test_workspace_profiles_restoration." $h
Task 2: Fix prior session test mock for imscope.window
Files:
-
Modify:
tests/test_prior_session_no_pop_imbalance.py(the mock setup loop ~line 75-78, where the test sets up imscope context managers) -
Step 2.1: Pre-edit checkpoint
cd C:\projects\manual_slop; git add .
- Step 2.2: Read the current mock setup
Read tests/test_prior_session_no_pop_imbalance.py:60-95 to see the mock_imscope setup.
- Step 2.3: Apply the fix
Find the loop that sets __enter__ and __exit__ for all imscope context managers (it looks like this around line 70-80):
for sc in [mock_imscope.style_color, mock_imscope.style_var, mock_imscope.child, mock_imscope.tab_bar, mock_imscope.tab_item, mock_imscope.tree_node_ex, mock_imscope.group, mock_imscope.indent, mock_imscope.id, mock_imscope.text_wrap, mock_imscope.tooltip, mock_imscope.menu, mock_imscope.menu_bar, mock_imscope.popup, mock_imscope.popup_modal, mock_imscope.window, mock_imscope.table]:
sc.return_value.__enter__ = MagicMock(side_effect=_scope_enter)
sc.return_value.__exit__ = MagicMock(side_effect=_scope_exit)
Note: mock_imscope.window is in this list. The default MagicMock(side_effect=_scope_enter) returns a bare MagicMock (non-iterable), but production code at src/gui_2.py:2333 does with imscope.window(...) as (opened, visible): which expects a 2-tuple.
After the loop (around line 91, after the mock_imscope.popup_modal.return_value.__enter__ = MagicMock(return_value=(True, None)) line), add:
mock_imscope.window.return_value.__enter__ = MagicMock(return_value=(True, True))
This matches the pattern already used for popup_modal (which returns (True, None)). The __exit__ from the loop above is preserved (returns False, indicating no exception).
Use manual-slop_edit_file with the exact old_string from the popup_modal mock line and the new_string with the additional window mock line right after it.
Use exactly 1-space indentation.
- Step 2.4: Run the prior session test
cd C:\projects\manual_slop; uv run pytest tests/test_prior_session_no_pop_imbalance.py -v --timeout=30
Expected: PASS.
- Step 2.5: Commit
cd C:\projects\manual_slop; git add tests/test_prior_session_no_pop_imbalance.py
git -C C:\projects\manual_slop commit -m "test(prior_session): mock imscope.window with tuple-return matching popup_modal"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "The test's mock setup loop for imscope context managers set __enter__ to a bare MagicMock (non-iterable), but render_preset_manager_window at src/gui_2.py:2333 does 'with imscope.window(...) as (opened, visible):' which expects a 2-tuple. popup_modal already had the right setup; window was missing it. Added the tuple-return for window, matching popup_modal's pattern." $h
Task 3: Add regression unit test for WorkspaceProfile str/bytes contract
Files:
-
Create:
tests/test_workspace_profile_serialization.py -
Step 3.1: Pre-edit checkpoint
cd C:\projects\manual_slop; git add .
- Step 3.2: Write the test file
Create tests/test_workspace_profile_serialization.py:
import io
import tomllib
import pytest
import tomli_w
from src.models import WorkspaceProfile
def test_workspace_profile_empty_ini_content_roundtrips():
"""WorkspaceProfile with ini_content='' (empty str) must round-trip through TOML.
This is the str/bytes type contract that the defer-not-catch fix in d7487af4 violated
(it used ini=b'' which tomli_w rejects with TypeError).
"""
profile = WorkspaceProfile(
name="t",
ini_content="",
show_windows={"A": True, "B": False},
panel_states={"x": 1, "y": 2.0, "z": True},
)
d = profile.to_dict()
buf = io.BytesIO()
tomli_w.dump({"t": d}, buf)
buf.seek(0)
back = tomllib.load(buf)
loaded = WorkspaceProfile.from_dict("t", back["t"])
assert loaded.ini_content == ""
assert loaded.show_windows == {"A": True, "B": False}
assert loaded.panel_states == {"x": 1, "y": 2.0, "z": True}
def test_workspace_profile_with_actual_ini_content_roundtrips():
"""WorkspaceProfile with real ini content (str) must round-trip through TOML.
This mirrors how save_ini_settings_to_memory() returns a str at runtime.
"""
profile = WorkspaceProfile(
name="real",
ini_content="[Window][Debug]\nPos=10,20\n",
show_windows={},
panel_states={},
)
d = profile.to_dict()
buf = io.BytesIO()
tomli_w.dump({"real": d}, buf)
buf.seek(0)
back = tomllib.load(buf)
loaded = WorkspaceProfile.from_dict("real", back["real"])
assert loaded.ini_content == "[Window][Debug]\nPos=10,20\n"
assert loaded.name == "real"
assert loaded.show_windows == {}
assert loaded.panel_states == {}
def test_workspace_profile_bytes_ini_content_rejected_by_toml():
"""Regression guard: a bytes ini_content must raise TypeError from tomli_w.
This documents the type contract; if tomli_w ever gains bytes support, the
contract should be revisited (e.g. by switching WorkspaceProfile.ini_content
to bytes and updating the imgui.load_ini_settings_from_memory call site).
"""
profile = WorkspaceProfile(
name="bad",
ini_content=b"", # type: ignore[arg-type]
show_windows={},
panel_states={},
)
d = profile.to_dict()
buf = io.BytesIO()
with pytest.raises(TypeError, match="bytes"):
tomli_w.dump({"bad": d}, buf)
Use exactly 1-space indentation. No comments per project style.
- Step 3.3: Run the test to verify it passes (Change 1 should already be applied)
cd C:\projects\manual_slop; uv run pytest tests/test_workspace_profile_serialization.py -v --timeout=15
Expected: 3 passed (one per test).
- Step 3.4: Verify the test would catch the regression
Temporarily revert the fix in src/gui_2.py:604 from ini = "" back to ini = b"" and re-run:
cd C:\projects\manual_slop; uv run pytest tests/test_workspace_profile_serialization.py -v --timeout=15
Expected: the first two tests should still pass (they test the dataclass round-trip directly, not the defer fix), and the third test confirms the type contract. The test that catches the regression is the integration test in Task 1 (which goes through the live_gui save flow).
Restore the fix:
cd C:\projects\manual_slop; git diff src/gui_2.py # confirm only the in-scope fix is there
If you reverted the fix, re-apply it via manual-slop_edit_file and verify the tests still pass.
- Step 3.5: Commit
cd C:\projects\manual_slop; git add tests/test_workspace_profile_serialization.py
git -C C:\projects\manual_slop commit -m "test(workspace_profile): add str/bytes TOML serialization contract test"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "Encodes the WorkspaceProfile.ini_content: str contract. The d7487af4 defer fix used ini=b'' which tomli_w rejects with TypeError. This test would have caught the regression at unit-test speed (no live_gui needed). 3 tests: empty str round-trips, real ini content round-trips, bytes ini_content is rejected (documents the contract)." $h
Task 4: Verify all 3 originally-failing tests now pass
Files: (no file changes; verification only)
- Step 4.1: Run the 3 originally-failing tests
cd C:\projects\manual_slop; uv run pytest tests/test_auto_switch_sim.py tests/test_workspace_profiles_sim.py tests/test_prior_session_no_pop_imbalance.py -v --timeout=60
Expected: 3 passed (one file each).
- Step 4.2: Run the regression unit test
cd C:\projects\manual_slop; uv run pytest tests/test_workspace_profile_serialization.py -v --timeout=15
Expected: 3 passed.
- Step 4.3: Run the full batched test suite
cd C:\projects\manual_slop; uv run python scripts/run_tests_batched.py
Expected: 273 files (272 + 1 new), all batches pass (273/273 = 100%).
- Step 4.4: Commit plan update
cd C:\projects\manual_slop; git add conductor/tracks.md docs/superpowers/plans/2026-06-05-live-gui-fragility-fixes.md
Then append the following entry to conductor/tracks.md (under the existing regression_fixes_20260605 entry or as a new entry):
- [x] **Track: Live-GUI Fragility Fixes (post regression_fixes_20260605)** `[checkpoint: <sha>]`
*Link: [./tracks/live_gui_fragility_fixes_20260605/](./tracks/live_gui_fragility_fixes_20260605/), Spec: [./../../docs/superpowers/specs/2026-06-05-live-gui-fragility-fixes-design.md](./../../docs/superpowers/specs/2026-06-05-live-gui-fragility-fixes-design.md), Plan: [./../../docs/superpowers/plans/2026-06-05-live-gui-fragility-fixes.md](./../../docs/superpowers/plans/2026-06-05-live-gui-fragility-fixes.md)*
*Goal: Fix 3 remaining live_gui test failures (269/272 → 272/272). 1-line src fix in `_capture_workspace_profile` (str/bytes sentinel that broke TOML serialization), 2-line test mock fix for `imscope.window` tuple-return, 1 new regression unit test for the str/bytes contract. All atomic per-file commits. The d7487af4 defer fix had introduced a TypeError via `ini=b""`; the regression was traced to `WorkspaceProfile.ini_content: str` and tomli_w's bytes rejection.*
(Replace <sha> with the actual checkpoint SHA from the last commit.)
cd C:\projects\manual_slop; git -c core.autocrlf=false commit -m "conductor(plan): mark live_gui_fragility_fixes track complete"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "Track complete. 273/273 tests pass (was 269/272 pre-track, 272/273 mid-track). 3 atomic per-file commits: src/gui_2.py, test_prior_session_no_pop_imbalance.py, new test_workspace_profile_serialization.py." $h
Task 5 (OPTIONAL): Doc hardening of defer-not-catch sections
Skip this task if time is short. Per user review 2026-06-05, this is deferred to the end. If you've reached the end of the track with time to spare, do it; otherwise, leave for a follow-up.
Files:
-
Modify:
docs/guide_gui_2.md"Workspace Profile Defer-Not-Catch" section -
Modify:
docs/guide_testing.md"Early-Render C-Level Crashes" section -
Modify:
conductor/workflow.md"Defer-Not-Catch Pattern for Native Crashes" section -
Step 5.1: Add a one-paragraph note to each of the three docs
Add this note (paraphrased to fit each doc's voice) to each of the three defer-not-catch sections:
"Sentinel type contract. When implementing a defer-not-catch guard, the early-return sentinel value must match the type contract of the downstream consumer. For
WorkspaceProfile.ini_content: str(in this codebase), the sentinel must be""(str), notb""(bytes) —tomli_wrejects bytes, andimgui.load_ini_settings_from_memory(ini_data: str, ...)also expects str. A previous version of this fix usedb""and silently broke the save flow via aTypeErrorraised bytomli_w.dump; tests passed unit-test-wise but failed in the live_gui save+load round-trip."
- Step 5.2: Commit
cd C:\projects\manual_slop; git add docs/guide_gui_2.md docs/guide_testing.md conductor/workflow.md
git -C C:\projects\manual_slop commit -m "docs: add sentinel-type-contract note to defer-not-catch sections"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "Three doc updates: guide_gui_2.md, guide_testing.md, workflow.md. Added a 'Sentinel type contract' note to each defer-not-catch section warning that the early-return sentinel must match the downstream consumer's type contract (str not bytes for WorkspaceProfile.ini_content). Prevents future regressions of the kind introduced by d7487af4." $h
Self-Review
After writing the complete plan, check against the spec:
1. Spec coverage:
- Change 1 (
b""→""fix): Task 1, Step 1.3. ✓ - Change 2 (test mock fix): Task 2, Step 2.3. ✓
- Change 3 (regression unit test): Task 3, Step 3.2. ✓
- Change 4 (doc hardening, deferred): Task 5, marked OPTIONAL. ✓
- Goals: 100% pass rate (Task 4, Step 4.3). ✓
- Non-goals respected: no workspace profile refactor, no wait-for-ready framework, no sloppy.py startup changes. ✓
2. Placeholder scan: No "TBD"/"TODO"/"implement later" patterns. All code blocks are complete. ✓
3. Type consistency: WorkspaceProfile.ini_content: str referenced consistently. b"" → "" change is the single source of the fix. _ini_capture_ready flag is preserved. str(...) or "" wrap is documented. ✓
Execution Handoff
This plan is sized for inline execution (single agent, no subagents, per the user's stated preference). Execute Tasks 1-4 in order; skip Task 5 if time is short.
After each task's commit, attach the git note (the $h line in each task). After all tasks, run Task 4's full suite to confirm 100% pass.
If any task fails, stop and run /conductor:implement --debug or escalate to a Tier 4 QA analysis (per conductor/workflow.md).