# Follow-up Plan Distilled from REVIEW_STEP_1..4. Ordered by impact, not by review number. ## Correctness / risk ### 1. Recovery vs. in-flight downloads race `install::recover_on_startup` (`transaction.rs:118-156`) sweeps `.version.ini.tmp` / `.version.ini.discarded` for every game root without consulting `active_operations`. If `load_local_library` runs while a download is in-flight (e.g. `SetGameDir` mid-download), the sweep can delete files the download is renaming. **Fix:** in `recover_download_transients` (and the per-root recovery), skip game IDs present in `Ctx::active_operations`. ### 2. Recovery re-runs on every state change `load_local_library` (and therefore `recover_on_startup`) runs at startup, on `SetGameDir`, and after every install/update/uninstall (`handlers.rs:487-497`). On a 100+ game library this is O(N) fs reads per operation for no gain — the just-completed transaction already cleared its own state. **Fix:** either run `recover_on_startup` only at startup + `SetGameDir`, or scope post-operation recovery to the single affected game ID. ## Dead / misleading code ### 3. `PeerCommand::UpdateGame` is unreachable Variant exists in `lib.rs:163` but has no sender. Tauri's `update_game` sends `GetGame`; auto-install uses `RequestedInstallOperation::Auto`. **Fix:** remove the variant, or wire `update_game` to send it. ### 4. `InstallIntent::manifest_hash` never populated Field defined in `intent.rs` but always `None`. **Fix:** remove it, or add a one-line comment explaining it's reserved. ### 5. `Availability::Downloading` never emitted `build_game_summary` only produces `Ready`/`LocalOnly`. Operation-table gating already covers in-progress state. **Fix:** add a comment in the proto enum that `Downloading` is intentionally unreachable from `build_game_summary`, so the next implementor doesn't add a third gate. ### 6. `RequestedInstallOperation::Auto` and `Install` collapse to the same thing `handlers.rs:355-363`: both pick `Updating` if `local/` exists, `Installing` otherwise. Three variants, two distinct behaviors. **Fix:** merge `Auto`+`Install` into one variant (e.g. `Inferred`) or a bool flag. ### 7. `partition_download_descriptions` doesn't actually partition `download.rs:110-137` keeps `version.ini` inside `transfer_descs` and only additionally extracts a `version_desc`. The runtime routes sentinel chunks via `version_buffer.matches(...)`, not by list separation. **Fix:** rename to reflect what it does (e.g. `extract_version_descriptor`), or actually split into two lists. ## UI / event naming ### 8. Rename `game-unpack-finished` → `game-install-finished` There is no longer an "unpack" stage distinct from "install". The event is internal; no external consumer. Update both `src-tauri/src/lib.rs:880-894` and `src/App.tsx:293-320`. ### 9. UI `LocalOnly` is reverse-engineered from `installed && !downloaded` `build_game_summary` computes `Availability` then throws it away in `game_from_summary`. UI recomputes (`App.tsx:742-744`). Works today, but silently misses any future `Availability` variant. **Fix:** add `availability: string` to the TS `Game` and serialize it end-to-end. ### 10. Tauri-side `active_operations` has no reconciliation If a `PeerEvent::InstallGameFinished` / `…Failed` is dropped, the UI is stuck "installing" until app restart. **Fix (defer):** include an in-progress snapshot in `PeerEvent::LocalGamesUpdated` so the UI can recompute from authoritative state instead of accumulating from event history. ## Test breadth (largest gap) ### 11. Install-recovery matrix — only 1 of ~11 rows tested Only `recovery_restores_backup_for_interrupted_update` (`transaction.rs:591`) exercises an intent-driven row. **Fix:** convert to a table-driven test over `(intent_state, local, .local.installing, .local.backup)` combinations and assert resulting FS + intent for each. ### 12. Missing transaction test cases - Successful **update** path (only failure path is tested). - Update rollback on **commit-rename failure** (not extract failure). - Uninstall **delete-failure restore** — `restore_backup` rollback in `transaction.rs:107-114`. - `begin_version_ini_transaction` initial-rename behavior (`download.rs:193-206`). - `rollback_version_ini_transaction` (`download.rs:208-221`). - Multi-`.eti` download. - Intent JSON: parse error / corrupt body / mismatched `id` field (only schema-mismatch is tested). - Installed-only → Ready transition when user drops in a `version.ini`. ### 13. Scanner gating: zero dispatch-level tests `handle_watch_event` (`local_monitor.rs:208-236`) and `RescanGate` / `run_gated_rescan` (`local_monitor.rs:261-287`) have no tests. Add: - 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`. ### 14. Serve gating: only predicates tested, not dispatch `local_download_available` is tested; `handle_get_game_command`, `handle_get_game_file_data`, `handle_get_game_file_chunk` dispatch paths are not. Add small tests against an in-memory `Ctx` covering: non-catalog ID, mid-operation, missing sentinel. ### 15. Windows-path coverage for `is_version_ini` `db.rs:181-184` normalizes `\\`→`/`. Add one test with a backslash path. ## Code hygiene ### 16. Consolidate `TempDir` test helper Reimplemented in 5 files (`install/intent.rs:128`, `install/transaction.rs:498`, `local_games.rs:615`, `download.rs:926`, +variants). Move to a single `test_support` module or use the `tempfile` crate. ### 17. Rename `partition_requires_exactly_one_root_version_ini` The test's "duplicate" case is actually a nested-decoy case under the new tight predicate. Split into two named tests: one for nested-decoy ignored, one for true duplicates rejected. ### 18. Split `download.rs` (1162 lines) Mixes chunk planning, retry orchestration, version-sentinel transaction, in-memory buffer, and the main loop. Future split into `download/{transaction,chunks,orchestrator}.rs`. ### 19. `save_library_index` is not atomic `local_games.rs:141-148` writes without temp+rename. Blast radius is small (corrupt index → next scan rebuilds), but matching the intent-log atomic pattern would be cheap. ## Documentation ### 20. Add undocumented decisions to IMPL_DECISIONS.md Observed but not recorded: - `partition_download_descriptions` keeps version.ini in `transfer_descs` (see #7). - `recover_on_startup` re-runs on every `load_local_library` (see #2). - `PeerCommand::UpdateGame` left as dead variant (see #3). - `Auto`/`Install` collapse in `run_install_operation` (see #6).