Files
lanspread/FOLLOW_UP_2.md
T
ddidderr 95e70ef520 fix(ui): reconcile active operations from local scans
Local operation spinners were driven by begin, finish, and failure event
history. If one of those lifecycle events was missed, the Tauri bridge could
keep a stale active operation and the React state would keep showing an
in-progress spinner until restart.

Peer local scan updates now carry an authoritative active-operation snapshot.
The peer still suppresses active game roots from peer-facing library deltas,
but it emits LocalGamesUpdated to the UI even when no library delta changed so
the snapshot can clear stale state after rollback or completion. The Tauri
bridge replaces its active-operation map from that snapshot, emits it with the
games-list payload, and the React merge uses it to restore download, install,
update, and uninstall spinners from current peer state rather than event
history alone.

This also enables the Tauri lib unit-test target so the reconciliation helper
can stay covered by the workspace test recipe.

Test Plan:
- git diff --check
- just fmt
- just clippy
- just test

Follow-up-Plan: FOLLOW_UP_2.md
2026-05-16 09:01:17 +02:00

73 lines
4.5 KiB
Markdown

# Follow-up Plan #2
State of FOLLOW_UP_PLAN.md after two implementation rounds. Items here are
what's still open, plus notes for follow-up items completed after this file was
created.
## Context for next time
Branch `p2p-codex-muenchhausen` has uncommitted work that completes the bulk of FOLLOW_UP_PLAN.md (52 tests pass via `just test`). Specifically these items in the original plan are **done** and shouldn't be re-opened: #1, #2, #3, #4, #5, #6, #7, #8, #9, #15, #17, #20, plus parts of #12 (update success, version-ini begin/rollback, multi-eti sorted order, corrupt + mismatched-id intent JSON).
The OperationGuard ordering fix in `handlers.rs` is the structurally most important change: install/update/uninstall now drop the guard _before_ the finish event and `refresh_local_game`, so peers see settled state in the next announcement instead of waiting for a scan tick. Tests `*_refreshes_settled_state_after_guard_release` pin this.
## Completed after this file was created
- Tauri-side `active_operations` reconciliation: `PeerEvent::LocalGamesUpdated`
now carries an authoritative active-operation snapshot, the Tauri bridge
replaces its UI operation map from that snapshot, and the React game-list
merge uses the snapshot to clear stale download/install/update/uninstall
spinners even when a begin/finish/fail event was missed.
## Still open
### Tests still missing
#### 2. Install-recovery matrix (~10 rows)
Only two intent-driven rows are covered: `Updating | no local | .local.installing | .local.backup` (`recovery_restores_backup_for_interrupted_update`) and the active-skip case (`startup_recovery_skips_active_game_roots`). The other ~9 combinations of `(intent_state ∈ {Installing, Updating, Uninstalling}, local present?, .local.installing present?, .local.backup present?)` are unverified.
**Approach:** convert `recovery_restores_backup_for_interrupted_update` into a table-driven test. Iterate over the named matrix rows in PLAN.md, set up FS + intent, run `recover_game_root`, assert resulting FS + intent.
#### 3. Update rollback on commit-rename failure
Only extract-failure is tested (`update_failure_restores_previous_local`). The branch where extract succeeds but the staging→local rename fails is unexercised. Force it by making `local/` un-renamable (e.g. swap in a directory under that name post-extract) or by injecting a rename hook.
#### 4. Uninstall delete-failure restore
`restore_backup` rollback in `transaction.rs:107-114` is unreached by tests.
#### 5. Installed-only → Ready rescan
PLAN listed this. No test exercises the transition where a user drops `version.ini` into a `local/`-only game root and the next rescan flips it to Ready.
#### 6. Scanner gating dispatch
`handle_watch_event` and `RescanGate` / `run_gated_rescan` (`local_monitor.rs:208-287`) have zero tests. Cover:
- Event for ID under active operation is dropped.
- Burst of events collapses to ≤2 rescans for the same ID.
- Sideload picked up by fallback scan.
- Non-catalog game produces no library index entry and no `LibraryDelta`.
#### 7. Serve gating dispatch
`local_download_available` predicate is tested; `handle_get_game_command`, `handle_get_game_file_data`, `handle_get_game_file_chunk` dispatch paths aren't. Small tests against an in-memory `Ctx` covering: non-catalog ID, mid-operation, missing sentinel.
### Code hygiene
#### 8. Consolidate `TempDir` test helper (getting worse)
Reimplemented in **five** test modules now: `install/intent.rs`, `install/transaction.rs`, `local_games.rs`, `download.rs`, `handlers.rs`. Each round of new tests adds another copy. Move to a single `test_support` module (or use the `tempfile` crate, already stable). The longer this waits, the bigger the eventual consolidation diff.
#### 9. `availability` field is `String`, not a typed enum
`lanspread-db::Game::availability: String` with `AVAILABILITY_*` consts works but allows invalid values. `remote_peer::summary_from_game` already has a defensive fallback for unknown strings. A typed enum + serde would be tighter, but is a wire-format change — coordinate if/when other peers are upgraded.
#### 10. `save_library_index` non-atomic
`local_games.rs:141-148` writes without temp+rename. Corrupt index → next scan rebuilds, so blast radius is small. Match the intent-log atomic pattern if touching that module.
#### 11. Split `download.rs` (1162 lines, growing)
Mixes chunk planning, retry orchestration, version-sentinel transaction, in-memory buffer, and the main loop. Future split into `download/{transaction,chunks,orchestrator}.rs`. Pure cosmetic.