Overview
Build the centralized sync framework in src/lib/sync/ — the foundation for all app migrations. No app changes in this PR, just the framework code.
Design doc: docs/plans/2026-06-21-sync-framework-design.md
What to build
Client-side (src/lib/sync/client/)
sync-manager.svelte.ts — SyncManager<TStores> class using Svelte 5 $state in class fields
Reactive state: needsBackup, isBackingUp, isSyncing, lastSyncAt, syncError
Methods: syncOnMount(), handleManualBackup(), handleAutoBackup(), handleSync(), markDirty(), destroy()
Reads/writes through svelte-idb stores (db.store.getAll(), db.store.put(), db.store.clear())
Uses $state.snapshot() before writing reactive data to IndexedDB
merge-strategies.ts — timestampMerge, serverWins, localWins + customMerge() hook
MergeStrategy interface with merge(local, server, storeName) method
triggers.svelte.ts — AutoBackupTrigger, PollingTrigger, ManualTrigger
AutoBackupTrigger: debounced timer, watches needsBackup via $effect, checks isAuthenticated
PollingTrigger: interval + exponential backoff on failure (base * 2^failures, capped at max)
ManualTrigger: no automatic sync
types.ts — SyncConfig, SyncStatus, StoreMap types
Server-side (src/lib/sync/server/)
Root
Key design decisions
Uses svelte-idb (createReactiveDB + liveAll()) for local persistence — replaces PersistedState wrapper
form for manual backup (progressive enhancement), command for auto-backup timer (programmatic)
query for loading data (confirmed supported in SvelteKit 2.27+)
Class-based with $state fields (Svelte 5 class reactivity — confirmed in docs)
Fresh start acceptable — new svelte-idb DB names, no migration from old PersistedState keys
Testing
References
Blocked by
None — this is the first PR in the migration sequence.
Overview
Build the centralized sync framework in
src/lib/sync/— the foundation for all app migrations. No app changes in this PR, just the framework code.Design doc:
docs/plans/2026-06-21-sync-framework-design.mdWhat to build
Client-side (
src/lib/sync/client/)sync-manager.svelte.ts—SyncManager<TStores>class using Svelte 5$statein class fieldsneedsBackup,isBackingUp,isSyncing,lastSyncAt,syncErrorsyncOnMount(),handleManualBackup(),handleAutoBackup(),handleSync(),markDirty(),destroy()db.store.getAll(),db.store.put(),db.store.clear())$state.snapshot()before writing reactive data to IndexedDBmerge-strategies.ts—timestampMerge,serverWins,localWins+customMerge()hookMergeStrategyinterface withmerge(local, server, storeName)methodtriggers.svelte.ts—AutoBackupTrigger,PollingTrigger,ManualTriggerneedsBackupvia$effect, checksisAuthenticatedtypes.ts—SyncConfig,SyncStatus,StoreMaptypesServer-side (
src/lib/sync/server/)create-load-query.ts—createLoadQuery()factory generatingquery()functionsfindManycalls withwhere: { userId },include,orderBycreate-backup-form.ts—createBackupForm()factory generatingform()functionscreate-backup-command.ts—createBackupCommand()factory generatingcommand()functionsprisma-helpers.ts— generic upsert/replace/delete helperstypes.ts—LoadConfig,BackupConfigtypesRoot
index.ts— barrel exports for client + serverREADME.md— framework documentation + migration guideKey design decisions
svelte-idb(createReactiveDB+liveAll()) for local persistence — replacesPersistedStatewrapperformfor manual backup (progressive enhancement),commandfor auto-backup timer (programmatic)queryfor loading data (confirmed supported in SvelteKit 2.27+)$statefields (Svelte 5 class reactivity — confirmed in docs)Testing
bun checkpasses with 0 errorssvelte-autofixerpasses with 0 issuesReferences
docs/plans/2026-06-21-sync-framework-design.md$statein classes: https://svelte.dev/docs/svelte/$state#ClassesBlocked by
None — this is the first PR in the migration sequence.