You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## What this is
PR 4 of #1570. A version can disappear after the picker loads but before
the user selects it. The backend now performs a fresh exact-id
availability check immediately before moving a shortcut binding onto an
already-local target.
A definitive target 404 refuses the switch. Temporary uncertainty still
fails open so the offline **Switch anyway** path remains usable and
bounded by one short attempt.
## What changed
- Local targets receive exactly one executor-backed
`get_rom_once(target_rom_id)` after all non-writing guards pass and
immediately before the binding-write path.
- Only `RommNotFoundError` returns:
`{success: false, reason: "version_vanished", message: "This version is
no longer available on RomM."}`
- Timeout, transport, authentication, 5xx, malformed, empty, and
wrong-id probe responses log uncertainty and fail open.
- `allow_stranded` bypasses only the save-stranding guard; both **Sync
now & switch** and **Switch anyway** retries still receive the final
liveness check.
- Server-only targets keep their mandatory full-detail fetch. Its typed
404 maps to the same `version_vanished` refusal; other failures remain
classified normally.
- A refusal leaves the binding, appId, launch options, artwork, cache,
collections, playtime, rows, installs, saves, sync state, and completion
stamps untouched.
- All three frontend switch paths use the same failure handling. Only
explicit `server_unreachable` updates global connection state.
- Switch success no longer claims that RomM is connected when the
optional probe may have failed open.
- A vanished-target refusal reloads the picker directly without emitting
`version_switched`.
- All list loads share latest-request-wins publication, so a late
refusal reload cannot overwrite a newer successful switch, another app's
state, or a newer connection verdict.
The active-target no-op is not probed, and no network request is held
inside a SQLite unit of work.
## Safety
The check narrows the stale-list window but does not pretend RomM and
SQLite share a transaction. RomM can still delete an id after a
successful response; retained local identity and the PR-3 recovery path
keep that state recoverable.
No library sync or fetch-stamp invalidation is introduced.
## Docs
Updated backend architecture, Steam shortcut architecture, and the
managing-games user guide. Frozen ADR history remains unchanged.
## Verification
The full `mise run gate` passed on the implementation state:
- 6395 backend tests
- 2267 frontend tests
- Ruff and basedpyright clean
- TypeScript, ESLint, Prettier, Deno, build, size limit, import
contracts, and repository invariant checks passed
Review fixes then passed 100 focused backend/contract tests, 57 frontend
tests, changed-file static/type/format checks, and an independent review
with no remaining findings.
## On-device verification
Passed in Game Mode with a safely staged local binding: a retained id
that RomM answers with 404 was temporarily bound to the existing Steam
appId, remained visible and disabled, and returned an honest `not_found`
response on Download. Switching back to its live local sibling succeeded
through the new pre-write target probe; the appId stayed stable, RomM
was not reported offline, the picker guard released, and the database
binding returned to the live id. No RomM data was deleted for the test.
Closes part of #1570.
0 commit comments