feat(engine): Phase M — sync transparency + local data backup
Closes out the 13-phase menu redesign. Three parts:
1. Post-sync merge summary: the pull poller now keeps the
`ConflictReport`s the merge produces (previously discarded) in a new
`SyncConflictLog` resource and toasts "Synced — N conflicts, kept
newer values" when a merge wasn't clean. No wire changes — the
server-side `SyncResponse.conflicts` field already existed.
2. Account tab detail: a per-field conflict list under the sync row
("win_streak_current — this device: 3 / server: 5") plus a
clean-merge caption, so the last merge is always inspectable.
3. Local data export/import: new `solitaire_data::transfer` module —
one versioned JSON bundle (settings, stats, achievements, progress)
written atomically next to the other saves, with a version-gated
loader that surfaces every failure instead of defaulting. Account
tab grows an Export/Import button pair (desktop+Android only); the
handler runs in `Last`, off the annotated Update spine. Import
rewrites the live resources, persists via the existing save fns,
and fires SettingsChangedEvent so appliers react normally.
Tests: bundle round-trip/version-gate/missing-file in solitaire_data;
ECS-level export→import round trip and failed-import-warns in
settings_plugin. Gate: workspace tests green, clippy -D warnings clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ pub(super) fn sync_settings_panel_visibility(
|
||||
theme_registry: Option<Res<crate::theme::ThemeRegistry>>,
|
||||
theme_thumbs: Option<Res<ThemeThumbnailCache>>,
|
||||
card_images: Option<Res<crate::card_plugin::CardImageSet>>,
|
||||
conflict_log: Option<Res<crate::resources::SyncConflictLog>>,
|
||||
) {
|
||||
if !screen.is_changed() {
|
||||
return;
|
||||
@@ -47,6 +48,7 @@ pub(super) fn sync_settings_panel_visibility(
|
||||
theme_registry.as_deref(),
|
||||
theme_thumbs.as_deref(),
|
||||
card_images.as_deref(),
|
||||
conflict_log.as_deref(),
|
||||
scroll_pos.0,
|
||||
active_tab.0,
|
||||
);
|
||||
@@ -76,6 +78,7 @@ fn build_panel(
|
||||
theme_registry: Option<&crate::theme::ThemeRegistry>,
|
||||
theme_thumbs: Option<&ThemeThumbnailCache>,
|
||||
card_images: Option<&crate::card_plugin::CardImageSet>,
|
||||
conflict_log: Option<&crate::resources::SyncConflictLog>,
|
||||
scroll_offset: f32,
|
||||
active_tab: SettingsTab,
|
||||
) {
|
||||
@@ -125,6 +128,7 @@ fn build_panel(
|
||||
scroll_offset,
|
||||
font_res,
|
||||
theme_overrides_back,
|
||||
conflict_log,
|
||||
active_tab,
|
||||
);
|
||||
}
|
||||
@@ -145,6 +149,7 @@ pub(super) fn rebuild_panel_on_tab_change(
|
||||
theme_registry: Option<Res<crate::theme::ThemeRegistry>>,
|
||||
theme_thumbs: Option<Res<ThemeThumbnailCache>>,
|
||||
card_images: Option<Res<crate::card_plugin::CardImageSet>>,
|
||||
conflict_log: Option<Res<crate::resources::SyncConflictLog>>,
|
||||
) {
|
||||
if !active_tab.is_changed() || active_tab.is_added() {
|
||||
return;
|
||||
@@ -167,6 +172,7 @@ pub(super) fn rebuild_panel_on_tab_change(
|
||||
theme_registry.as_deref(),
|
||||
theme_thumbs.as_deref(),
|
||||
card_images.as_deref(),
|
||||
conflict_log.as_deref(),
|
||||
0.0,
|
||||
active_tab.0,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user