# undo_redo_lifecycle_fix_20260605 — Design **Date:** 2026-06-05 **Status:** Draft **Track:** undo_redo_lifecycle_fix_20260605 (sub-project of v2) ## Problem Statement `tests/test_undo_redo_sim.py::test_undo_redo_lifecycle` failed in the 2026-06-05 second batched test run (after the first run had it passing). The test: 1. Sets `temperature=0.5` and `ai_input="Initial Input"`. 2. Modifies to `temperature=1.5` and `ai_input="Modified Input"`. 3. Asserts current state — passes. 4. Clicks `btn_undo`. 5. Asserts `ai_input == "Initial Input"` and `temperature == 0.5`. 6. **Fails on the `ai_input` assertion**: gets `''` (empty string). The undo restores `temperature` correctly but not `ai_input`. The other 2 tests in the same file (`test_undo_redo_discussion_mutation`, `test_undo_redo_context_mutation`) pass — they don't exercise `ai_input`. ### Possible causes 1. **App/Controller state sync bug for `ai_input`.** The snapshot at `src/gui_2.py:551` reads `self.ui_ai_input` (App), but `set_value` writes to `controller.ui_ai_input`. The snapshot captures the App's (stale) value. **This should be fixed by the `live_gui_state_sync_20260605` track** (which adds an `ui_ai_input` property on the App that delegates to the Controller). 2. **Snapshot doesn't include `ai_input` field at all.** Check `src/history.py:UISnapshot` — if `ai_input` isn't a field, the snapshot stores nothing, and the apply can't restore. 3. **Test flake.** The test was passing in the first run, failing in the second. The `live_gui` fixture is session-scoped, and different test orders can produce different state. The test's `time.sleep(2.0)` after `btn_undo` may not be enough if the GUI is under load. 4. **Recent user commit `873edf42` regression.** The user's cleanup commit touched 53 files including `src/gui_2.py:261` lines. If the cleanup accidentally changed the snapshot mechanism, this could break the test. ## Design ### Approach: Two-phase investigation **Phase 1: Re-run the test after the `live_gui_state_sync_20260605` track lands.** If the state-sync property fix for `ui_ai_input` unblocks the test, the issue is resolved. No further work needed. **Phase 2: If the test still fails, deep-dive into the snapshot mechanism.** Investigate in this order: 1. Check `src/history.py:UISnapshot` to see if `ai_input` is a field. If not, add it. 2. Check `src/gui_2.py:_apply_snapshot` to see if it restores `ai_input`. If not, add the restore line. 3. Check if there's a per-tick snapshot filter that excludes certain fields. 4. Add a regression test that explicitly verifies the snapshot/undo round-trip for `ai_input`. **Phase 3: If still failing, test-ordering / flake investigation.** The test uses `time.sleep(2.0)` after `btn_undo`. Convert to polling (`wait_for_load_completion` from the `wait_for_ready_test_pattern_20260605` track). If the test passes with polling, it was a flake. ### Why this approach - **Sequential investigation**: cheapest fixes first. State-sync is the most likely cause (it just landed as a property fix). Snapshot mechanism is the second most likely. Flake is the third. - **No speculative changes**: don't add `ai_input` to the snapshot if it's already there. Don't change the undo mechanism if the state-sync fix is sufficient. ## File Changes ### Phase 1: None (state-sync fix is in a different track) ### Phase 2 (if needed): - Modify: `src/history.py` (add `ai_input` field to UISnapshot if missing) - Modify: `src/gui_2.py:_apply_snapshot` (add `ai_input` restore line if missing) - Add: `tests/test_undo_redo_ai_input_snapshot.py` (regression test for the round-trip) ### Phase 3 (if needed): - Modify: `tests/test_undo_redo_sim.py` (replace `time.sleep(2.0)` with `wait_for_load_completion`) ## Risk Assessment | Risk | Likelihood | Impact | Mitigation | |---|---|---|---| | Phase 1 fixes the issue | High | None | Done | | Phase 2 needed: snapshot already has ai_input but apply doesn't restore | Medium | Low | Check first, then add the restore line | | Phase 2 needed: snapshot doesn't have ai_input | Low | Low | Add the field + apply line | | Phase 3 needed: it's a flake | Low | None | Replace sleeps with polling | ## Out of Scope - **State sync fix** — separate track (`live_gui_state_sync_20260605`). - **prior_session test** — separate track (`prior_session_test_harden_20260605`). - **wait_for_ready pattern** — separate track (`wait_for_ready_test_pattern_20260605`). - **General undo/redo system improvements** — out of scope.