Skip to content

[feature] cloud sync: cross-device freshness, dirty re-push, streamed download - #50

Merged
YJack0000 merged 2 commits into
mainfrom
feat/cloud-sync-multidevice
Jun 25, 2026
Merged

YJack0000 merged 2 commits into
mainfrom
feat/cloud-sync-multidevice

Conversation

@YJack0000

Copy link
Copy Markdown
Contributor

Closes the three deferred multi-device/perf gaps from the earlier sync review (reviewed adversarially; fixes folded in).

What

  1. Cross-device stalenesssrc/lib/cloud/syncState.ts (localStorage) records the cloud updatedAt each local copy matches. listMergedHistory marks an entry stale (amber ↻) when the cloud is strictly newer (another device re-analyzed it); opening it re-pulls first. Existing entries backfill as synced so nothing flips spuriously.
  2. Stale cloud repair — save/re-analyze/rename mark the entry dirty (cleared only on a confirmed push); the sweep re-pushes missing or dirty entries, skips the pass if the list fails, and aborts on the first auth error.
  3. Download perfsave_remote_history_entry is async and fetches the audio itself (reqwest, bearer) — off the JS↔Rust IPC.

Review fixes folded in

  • Audio is fetched before anything is written → a failed download leaves nothing partial / doesn't corrupt a good local copy (was HIGH).
  • Rename goes through the dirty→push→clear lifecycle.
  • The first-sight backfill never clears a live dirty flag.
  • Pushes are serialized per id (no out-of-order clobber).

Single-device behavior is unchanged (push→synced immediately; downloads only for cloud-only/stale cards, which don't arise on one device). tsc + cargo check + 79 tests green.

… audio download

Closes the three deferred multi-device/perf gaps from the sync review.

1. Cross-device staleness (#1): a new localStorage side-channel (src/lib/cloud/
   syncState.ts) records, per entry, the cloud updatedAt the local copy matches.
   listMergedHistory now marks an entry "stale" when the cloud is strictly newer
   (another device re-analyzed it) and not locally dirty; opening a stale card
   re-pulls it first. Existing synced entries backfill as synced on first sight,
   so nothing flips stale spuriously. New amber RefreshCw badge + i18n.

2. Stale-cloud repair (#2): save/re-analyze marks the entry dirty (cleared on a
   confirmed push), so a failed inline push is retried by the background sweep —
   which now pushes missing OR dirty entries, skips the pass if the cloud list
   fails, and aborts on the first auth failure instead of hammering every entry.

3. Download perf (#3): save_remote_history_entry is now async and fetches the
   audio itself (reqwest, bearer auth), streaming it to disk — the multi-MB blob
   no longer crosses the JS<->Rust IPC as a JSON number[].

Single-device behavior is unchanged: entries are pushed-then-synced immediately
(never stale, never dirty-after-push), and downloads only happen for cloud-only/
stale cards, which don't arise on one device.
…-safe backfill, serialized pushes

From the adversarial review of the multi-device sync:
- save_remote_history_entry fetches the audio BEFORE writing anything, so a
  failed download leaves nothing partial (new cloud-only entry) and doesn't
  disturb the existing files (stale re-pull) — kills the phantom-audio /
  mutated-good-copy corruption (was HIGH).
- renameHistoryEntry now pushes via pushToCloud (marks dirty), so a failed
  rename push is retried by the sweep instead of leaving the cloud title stale.
- listMergedHistory's first-sight backfill no longer clears a live dirty flag
  (would have dropped a pending re-push and shown the entry falsely synced).
- pushLocalEntry is serialized per id, so two concurrent same-id pushes can't
  complete out of order and record stale content as synced.

Deferred nits: download records the list-snapshot updatedAt (one redundant
re-pull on a concurrent remote bump); localStorage eviction can mask staleness;
res.bytes() buffers the blob (still off the IPC). All cosmetic/best-effort.

tsc + cargo check + 79 tests green.
@YJack0000
YJack0000 merged commit 8e330c5 into main Jun 25, 2026
@sonarqubecloud

Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

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.

1 participant