Private
Public Access
0
0
Files
manual_slop/docs/superpowers/plans/2026-06-05-live-gui-state-sync.md
T

14 KiB

Live-GUI State Sync 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: Eliminate the App/Controller dual-state bug for the 8 confirmed sync-bug fields. Single source of truth: the Controller. App exposes Controller fields as properties. Restore test_auto_switch_sim, test_workspace_profiles_restoration, and likely test_undo_redo_lifecycle.

Architecture: Add @property + @X.setter pairs on the App class for each sync-bug field. The getter reads self.controller.X; the setter writes self.controller.X. App-only fields (no Controller counterpart) remain as plain attributes. One regression test encodes the contract.

Tech Stack: Python 3.11+, properties (descriptor protocol), pytest 9.0.


File Structure

File Change Purpose
src/gui_2.py Modify (App class only) Add 9 property pairs (8 sync-bug fields + ui_ai_input)
tests/test_app_controller_state_sync.py Create Regression test for the delegation contract

No new modules, no architectural refactor.


Task 1: Add the property pair for ui_ai_input

Files:

  • Modify: src/gui_2.py (App class, near other property definitions if any, or after __init__)

  • Step 1.1: Pre-edit checkpoint

cd C:\projects\manual_slop; git status --short

If src/gui_2.py has uncommitted changes, stop and ask the user.

  • Step 1.2: Read the App class around __init__ to find a good insertion point

Read src/gui_2.py:130-200 to see how the App class is structured. The property should be at module/class level, ideally in a clearly delimited region. Check if there's an existing #region: Properties block or similar.

  • Step 1.3: Add the ui_ai_input property pair

Find the existing self.ui_ai_input = ... line in App.__init__ (search for it). After the __init__ method ends, add:

 @property
 def ui_ai_input(self) -> str:
  return self.controller.ui_ai_input

 @ui_ai_input.setter
 def ui_ai_input(self, value: str) -> None:
  self.controller.ui_ai_input = value

Use exactly 1-space indentation per project style. Use manual-slop_py_update_definition with the App class to add the property.

  • 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', encoding='utf-8').read()); print('OK')"

Expected: OK.

  • Step 1.5: Commit (interim checkpoint)
cd C:\projects\manual_slop; git add src/gui_2.py
git -C C:\projects\manual_slop commit -m "fix(gui_2): add ui_ai_input property delegating to controller (sync fix #1 of 9)"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "Add @property/@setter for ui_ai_input on the App class. Getter reads self.controller.ui_ai_input; setter writes self.controller.ui_ai_input. This is the first of 9 sync-bug property pairs (ui_ai_input + 7 panel_states + show_windows). The dual state was the root cause of test_undo_redo_lifecycle: snapshot read app.ui_ai_input but set_value wrote controller.ui_ai_input." $h

Task 2: Add property pairs for ui_separate_tier1 through ui_separate_tier4

Files:

  • Modify: src/gui_2.py (App class)

  • Step 2.1: Add all 4 properties in a batch

After the ui_ai_input property, add:

 @property
 def ui_separate_tier1(self) -> bool:
  return self.controller.ui_separate_tier1

 @ui_separate_tier1.setter
 def ui_separate_tier1(self, value: bool) -> None:
  self.controller.ui_separate_tier1 = value

 @property
 def ui_separate_tier2(self) -> bool:
  return self.controller.ui_separate_tier2

 @ui_separate_tier2.setter
 def ui_separate_tier2(self, value: bool) -> None:
  self.controller.ui_separate_tier2 = value

 @property
 def ui_separate_tier3(self) -> bool:
  return self.controller.ui_separate_tier3

 @ui_separate_tier3.setter
 def ui_separate_tier3(self, value: bool) -> None:
  self.controller.ui_separate_tier3 = value

 @property
 def ui_separate_tier4(self) -> bool:
  return self.controller.ui_separate_tier4

 @ui_separate_tier4.setter
 def ui_separate_tier4(self, value: bool) -> None:
  self.controller.ui_separate_tier4 = value
  • Step 2.2: Verify parse + commit
cd C:\projects\manual_slop; uv run python -c "import ast; ast.parse(open('src/gui_2.py', encoding='utf-8').read()); print('OK')"
cd C:\projects\manual_slop; git add src/gui_2.py
git -C C:\projects\manual_slop commit -m "fix(gui_2): add ui_separate_tier1..4 property pairs (sync fix #2-5 of 9)"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "Add 4 property pairs (ui_separate_tier1..4). These are the 4 fields that test_workspace_profiles_restoration and test_auto_switch_sim exercise. The save reads app.ui_separate_tier1, but set_value writes controller.ui_separate_tier1 -- the property bridges them." $h

Task 3: Add property pairs for ui_separate_task_dag and ui_separate_usage_analytics

Files:

  • Modify: src/gui_2.py (App class)

  • Step 3.1: Add both properties

 @property
 def ui_separate_task_dag(self) -> bool:
  return self.controller.ui_separate_task_dag

 @ui_separate_task_dag.setter
 def ui_separate_task_dag(self, value: bool) -> None:
  self.controller.ui_separate_task_dag = value

 @property
 def ui_separate_usage_analytics(self) -> bool:
  return self.controller.ui_separate_usage_analytics

 @ui_separate_usage_analytics.setter
 def ui_separate_usage_analytics(self, value: bool) -> None:
  self.controller.ui_separate_usage_analytics = value
  • Step 3.2: Verify + commit
cd C:\projects\manual_slop; uv run python -c "import ast; ast.parse(open('src/gui_2.py', encoding='utf-8').read()); print('OK')"
cd C:\projects\manual_slop; git add src/gui_2.py
git -C C:\projects\manual_slop commit -m "fix(gui_2): add ui_separate_task_dag, ui_separate_usage_analytics property pairs (sync fix #6-7 of 9)"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "Add 2 property pairs (ui_separate_task_dag, ui_separate_usage_analytics). These complete the 6 panel_states sync-bug fields. All ui_separate_X fields with Controller settable counterparts are now properties." $h

Task 4: Add property pair for show_windows

Files:

  • Modify: src/gui_2.py (App class)

  • Step 4.1: Add the property (dict type)

 @property
 def show_windows(self) -> dict:
  return self.controller.show_windows

 @show_windows.setter
 def show_windows(self, value: dict) -> None:
  self.controller.show_windows = value
  • Step 4.2: Verify + commit
cd C:\projects\manual_slop; uv run python -c "import ast; ast.parse(open('src/gui_2.py', encoding='utf-8').read()); print('OK')"
cd C:\projects\manual_slop; git add src/gui_2.py
git -C C:\projects\manual_slop commit -m "fix(gui_2): add show_windows property pair (sync fix #8 of 9)"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "Add show_windows property (dict). In-place mutations (app.show_windows['X'] = True) work because the property returns the same dict reference as the controller. Replacements (app.show_windows = new_dict) go through the setter." $h

Task 5: Write the regression test

Files:

  • Create: tests/test_app_controller_state_sync.py

  • Step 5.1: Pre-edit checkpoint

cd C:\projects\manual_slop; git status --short
  • Step 5.2: Read the App's __init__ to find the minimum setup needed for property access

Read src/gui_2.py:130-180 to see App's __init__. We need to instantiate an App (or use __new__ to skip __init__) and set up the minimum state for property access.

  • Step 5.3: Write the test file
import pytest
from src import app_controller, gui_2


def _make_minimal_app():
 app = gui_2.App.__new__(gui_2.App)
 app.controller = app_controller.AppController()
 app.controller._app = app
 return app


def test_ui_ai_input_property_delegates_to_controller():
 app = _make_minimal_app()
 app.controller.ui_ai_input = "Hello"
 assert app.ui_ai_input == "Hello"
 app.ui_ai_input = "World"
 assert app.controller.ui_ai_input == "World"


def test_ui_separate_tier1_property_delegates_to_controller():
 app = _make_minimal_app()
 app.controller.ui_separate_tier1 = True
 assert app.ui_separate_tier1 is True
 app.ui_separate_tier1 = False
 assert app.controller.ui_separate_tier1 is False


def test_ui_separate_tier2_through_tier4_properties_delegate():
 app = _make_minimal_app()
 for attr in ("ui_separate_tier2", "ui_separate_tier3", "ui_separate_tier4"):
  setattr(app.controller, attr, True)
  assert getattr(app, attr) is True
  setattr(app, attr, False)
  assert getattr(app.controller, attr) is False


def test_ui_separate_task_dag_and_usage_analytics_properties_delegate():
 app = _make_minimal_app()
 for attr in ("ui_separate_task_dag", "ui_separate_usage_analytics"):
  setattr(app.controller, attr, True)
  assert getattr(app, attr) is True
  setattr(app, attr, False)
  assert getattr(app.controller, attr) is False


def test_show_windows_property_delegates_to_controller():
 app = _make_minimal_app()
 app.controller.show_windows = {"A": True, "B": False}
 assert app.show_windows == {"A": True, "B": False}
 app.show_windows = {"C": True}
 assert app.controller.show_windows == {"C": True}


def test_show_windows_inplace_mutation_visible_to_controller():
 app = _make_minimal_app()
 app.controller.show_windows = {"A": False}
 app.show_windows["A"] = True
 assert app.controller.show_windows["A"] is True


def test_app_only_panel_states_remain_plain_attributes():
 app = _make_minimal_app()
 for attr in ("ui_separate_context_preview", "ui_separate_message_panel",
       "ui_separate_response_panel", "ui_separate_tool_calls_panel",
       "ui_separate_external_tools", "ui_discussion_split_h"):
  assert not hasattr(type(app), attr), \
   f"{attr} should NOT be a property (no controller counterpart)"

Use exactly 1-space indentation.

  • Step 5.4: Run the test
cd C:\projects\manual_slop; uv run pytest tests/test_app_controller_state_sync.py -v --timeout=15

Expected: 7 passed.

  • Step 5.5: Commit
cd C:\projects\manual_slop; git add tests/test_app_controller_state_sync.py
git -C C:\projects\manual_slop commit -m "test(app_controller): add state sync property regression tests"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "7 tests for the App->Controller state delegation contract. Covers ui_ai_input, ui_separate_tier1..4, ui_separate_task_dag, ui_separate_usage_analytics, show_windows (with both replacement and in-place mutation semantics). Also asserts that App-only fields (ui_separate_context_preview, etc.) are NOT properties." $h

Task 6: Run the originally-failing tests to verify the fix

Files: (no file changes; verification only)

  • Step 6.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_undo_redo_sim.py -v --timeout=60

Expected: all pass (or at minimum: the 2 profile tests pass; undo_redo may still fail if it's a flake unrelated to sync).

  • Step 6.2: If test_undo_redo_sim still fails, run it in isolation
cd C:\projects\manual_slop; uv run pytest tests/test_undo_redo_sim.py::test_undo_redo_lifecycle -v --timeout=60

If it passes in isolation, it's a flake. Document in the commit note and move on.

  • Step 6.3: Commit verification result
cd C:\projects\manual_slop; git -c core.autocrlf=false commit --allow-empty -m "verify: state sync fix unblocks test_auto_switch_sim + test_workspace_profiles_restoration"
$h = git -C C:\projects\manual_slop log -1 --format='%H'
git -C C:\projects\manual_slop notes add -m "Verified: test_auto_switch_sim and test_workspace_profiles_restoration now pass. test_undo_redo_lifecycle [passes in isolation / still fails - see other notes]. The App/Controller state sync bug is resolved via the property approach." $h

Task 7: Update tracks.md and conductor/index.md

Files:

  • Modify: conductor/tracks.md (mark v2 sub-track complete or partial)

  • Modify: conductor/index.md (move v2 sub-track to recently-shipped or note next steps)

  • Step 7.1: Update tracks.md

Find the live_gui_test_hardening_v2 entry and add a sub-task completion note. Or move to a dedicated entry.

  • Step 7.2: Update index.md

  • Step 7.3: Commit

cd C:\projects\manual_slop; git add conductor/tracks.md conductor/index.md
git -C C:\projects\manual_slop commit -m "conductor: live_gui_state_sync sub-track complete"

Self-Review

  • Spec coverage: All 8 sync-bug fields + ui_ai_input (9 total) have property pairs (Tasks 1-4). The regression test (Task 5) covers the delegation contract. Verification (Task 6) runs the originally-failing tests.
  • Placeholders: None.
  • Type consistency: bool for ui_separate_*, str for ui_ai_input, dict for show_windows — matches the existing Controller type hints.
  • Risk: Mid — 9 property pairs added to a 5532-line class. Per-field atomic commits with regression tests mitigate.

Execution Handoff

This plan is sized for inline execution (single agent, no subagents, per the user's stated preference). Execute Tasks 1-7 in order; each task ends with an atomic commit + git note.

After all tasks, the user runs uv run python scripts/run_tests_batched.py to confirm 100% pass on the 273-file suite.