feat(saves): save version history and rollback UI#225
Merged
danielcopper merged 2 commits intomainfrom Apr 12, 2026
Merged
Conversation
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.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #213.
Summary
file_name_no_tagsso 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 savetracked_missingsafety check remains (bypassable via a force-confirm modal)get_save_statusas well, so the banner appears on tab open — not only after a manual syncromm_data_changedeventTest plan
supports_version_history)python -m pytest tests/ -q) +ruff check+basedpyright+pnpm build+lint-importsFollow-ups
saves.pyI/O paths (pre-existingos.path.join(saves_dir, filename)pattern; not specific to this feature)file_name_no_tagsmatching + rollback state handling are already compatible once it lands