The test_visual_sim_mma_v2 failure in tier-3 batch context was caused
by state pollution from prior live_gui tests sharing the subprocess:
1. track state file missing for leftover track:
`_cb_load_track_result` accessed `state.metadata.id` and
`state.metadata.name`, but `EMPTY_TRACK_STATE` (returned when no
state.toml exists for a track_id) had `metadata={}` (a dict, not a
TrackMetadata object). That raised `'dict' object has no attribute
'id'`. Fixed by normalizing metadata: dict -> TrackMetadata.from_dict,
TrackMetadata stays, anything else -> TrackMetadata(id=track_id,
name=track_id).
2. active_track and active_tickets never reached the App:
`_cb_load_track_result` set `self.active_track` (controller) and
`self.active_tickets = []` (via `_load_active_tickets`) but never
mirrored to `self._app.active_track` / `self._app.active_tickets`.
The /api/gui/mma_status endpoint reads `app.X` first via
`_get_app_attr`, so it returned None / [] and the test's poll
`at_id == track_id and bool(s.get('active_tickets'))` failed. Fixed
by mirroring active_track / active_tickets / active_tier to the App.
3. leftover tracks list in batched run:
Without btn_reset, `app.tracks` (App-side) accumulates tracks from
earlier tests in the session. `_get_app_attr(app, 'tracks', [])`
then returns stale leftovers, and the test's
`target_track = next((... if 'hello_world' in t.get('title') else
tracks_list[0]))` picks a leftover with no on-disk state file.
Fixed in TWO places:
(a) btn_reset now also clears `app.tracks = []` so each test starts
clean if it calls btn_reset.
(b) tests/test_visual_sim_mma_v2.py now calls `client.click('btn_reset')`
at the start. The test was the only one in its tier that did NOT
reset; with the live_gui subprocess shared across batched tests,
that's the source of the state pollution.
Also reverted the `TrackState.metadata` default change (dict -> TrackMetadata)
because it broke TrackState() construction (TrackMetadata requires `id`/`name`).
The metadata normalization in `_cb_load_track_result` is sufficient and
preserves backward compatibility with on-disk state.toml files.
Verified: tests/test_visual_sim_mma_v2.py passes in isolation (58.36s)
and tier-3 batch passes when run standalone. Other tests in tier-3
have pre-existing render-loop contention flakes in batched xdist mode
that are unrelated to this fix.
Per Tier 1 investigation
(docs/reports/INVESTIGATION_rag_phase4_final_verify_20260627.md),
two live_gui tests were leaking temp/relative paths into the shared
subprocess's ui_files_base_dir, which survived across @clean_baseline
tests and caused RAGEngine.index_file to silently no-op on a dead
base_dir.
Three fixes:
1. tests/test_rag_visual_sim.py: stop using tempfile.mkdtemp() (which
defaults to C:\Users\Ed\AppData\Local\Temp\tmpXXXX) and instead use
tempfile.mkdtemp(dir="tests/artifacts", ...). Also restore
files_base_dir and rag_enabled in finally so the next live_gui test
in the session doesn't inherit the dead path.
2. tests/test_visual_sim_mma_v2.py: stop changing files_base_dir to
'tests/artifacts/temp_workspace' and stop clicking btn_project_save
(which persisted the path to manual_slop.toml). The MMA lifecycle
does not depend on a specific files_base_dir.
3. src/app_controller.py _handle_reset_session: defensive fix that
resets ui_files_base_dir from the default project's base_dir. This
makes reset_session() robust to any future polluter (not just the
two known ones). Without this, a test that sets files_base_dir via
set_value leaves a dead path in the session-scoped subprocess even
after reset_session().
Verified: tests/test_rag_visual_sim.py passes 2/2 after the fix.