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

4.5 KiB

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.