Skip to content

feat(saves): save version history and rollback UI#225

Merged
danielcopper merged 2 commits intomainfrom
feature/213-save-version-history
Apr 12, 2026
Merged

feat(saves): save version history and rollback UI#225
danielcopper merged 2 commits intomainfrom
feature/213-save-version-history

Conversation

@danielcopper
Copy link
Copy Markdown
Owner

@danielcopper danielcopper commented Apr 12, 2026

Closes #213.

Summary

  • Lets users browse older server-side versions of a save file within a slot and roll back to any of them via an expandable "Previous Versions" panel per file row
  • Versions are grouped by file_name_no_tags so that POST uploads (which RomM tags with a timestamp in the filename) from our own plugin or from other devices surface as versions of the same logical save
  • Rollback is an explicit user action; it is not blocked by the presence of a newer save in the slot. A tracked_missing safety check remains (bypassable via a force-confirm modal)
  • Newer-in-slot warnings are now surfaced during get_save_status as well, so the banner appears on tab open — not only after a manual sync
  • Saves tab's tracked file row was redesigned with labeled info rows, the server filename, the server save id, the emulator, and a wrap-friendly local path
  • Restore and slot switching are disabled when RomM is offline; the version history expander shows an "offline" message instead of an empty list
  • Tracked row refreshes live after a restore via the existing romm_data_changed event

Test plan

  • RomM 4.7+ ROM with a tracked save and at least one other server-side version of the same save file: "Previous Versions (N)" is shown, expanding loads the versions, restoring downloads the target and updates the tracked row without leaving the page
  • RomM 4.6 ROM: "Previous Versions" expander is hidden (feature gated on supports_version_history)
  • Locally modified save that hasn't been synced yet: clicking Restore shows "Unsynced Local Changes" modal, Continue force-overwrites
  • Tracked save deleted from the RomM web UI: clicking Restore shows "Current save missing on server" modal, Continue force-bypasses and proceeds
  • Disconnect RomM server: Restore button disabled, expander shows "Offline — versions unavailable", Activate Slot disabled with hint
  • Newer save uploaded by another device: warning banner appears on the saves tab on open (not only after a full sync)
  • Full test suite (python -m pytest tests/ -q) + ruff check + basedpyright + pnpm build + lint-imports

Follow-ups

Browse older server-side versions of a save file within a slot and roll
back to a previous version. The list uses file_name_no_tags matching so
saves that share the same base name (multiple POST uploads from our own
plugin or from other devices) are grouped together as versions.

Backend:
- SaveService.list_file_versions: fetches slot-scoped saves with device_id,
  filters by file_name_no_tags, excludes the tracked save, returns sorted
  by updated_at desc
- SaveService.rollback_to_version: async guard for unsynced local changes
  (Gate D, force-bypassable) and for a missing tracked save on the server
  (Gate F, force-bypassable), downloads the target via the existing
  _do_download_save path, state update reuses _update_file_sync_state
- _find_file_state: robust lookup that tolerates legacy state keys with
  RomM timestamp tags by falling back to a file_name_no_tags match against
  the server response
- Newer-in-slot detection is now surfaced from _get_save_status_io as well
  (not only during a full sync), so the warning appears when the saves tab
  opens rather than after a manual sync
- _build_file_status now includes server_file_name and server_emulator
- device_id is passed to list_saves in list_file_versions and
  rollback_to_version so device_syncs is populated and _is_save_from_our_device
  can distinguish foreign uploads

Frontend:
- Saves tab tracked-save row redesigned with labeled info rows
  (Last synced / Last updated / Server save / Local path), server filename
  shown verbatim, local path wraps instead of truncating
- Previous Versions expander per file row with three-line entries
  (#id / emulator / size, Last updated, server filename) and Restore button
- VersionHistoryPanel refreshes the parent saveStatus via romm_data_changed
  after a successful restore so the tracked row updates live
- Restore + Activate Slot are disabled while offline; the expander shows an
  offline message instead of an empty list
- ConfirmModal guards unsynced local changes and the new tracked_missing
  case, both override via a force retry

Tests:
- New service tests for list_file_versions (v4.6 gating, base-name match,
  sort order, exclude tracked, empty cases, shape) and rollback_to_version
  (unsupported, rom-not-installed, save-id missing, newer foreign save
  does not block, tracked_missing with force bypass, unsynced_changes with
  force bypass, happy path, missing local file, no last_sync_hash)
- Plugin-level callable smoke tests for saves_supports_version_history,
  saves_list_file_versions, saves_rollback_to_version

README:
- Downloads badge via shields.io (GitHub release asset total)

Closes #213
…ed ternaries

Splits the expanded-body render and per-version row rendering out of the
JSX tree to satisfy SonarCloud's typescript:S3358 (no nested conditional
expressions).  Pure refactor — no behavioural change.
@sonarqubecloud
Copy link
Copy Markdown

@danielcopper danielcopper merged commit 4bc8ff1 into main Apr 12, 2026
9 checks passed
@danielcopper danielcopper deleted the feature/213-save-version-history branch April 12, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Save version history and rollback UI

1 participant