Skip to content

docs(sync): explain merged persistence and save recovery - #397

Closed
chhoumann wants to merge 6 commits into
masterfrom
docs/sync-merge-behavior
Closed

chhoumann wants to merge 6 commits into
masterfrom
docs/sync-merge-behavior

Conversation

@chhoumann

@chhoumann chhoumann commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

Explain the merged sync behavior from #388 and #398, including how a new device's library combines with synced data. Both pages describe conflicts and recovery after a save notice.

  • Sync and devices explains same-library merges, separate-library combining, whole-entry conflicts, save timing, verification, and file recovery.
  • FAQ answers whether progress syncs and links to the recovery steps. Existing URLs and headings remain.

Refs #388 and #398.

These pages describe merged master behavior. Installed PodNotes 2.21.1 predates #388; coordinate public docs timing with that difference.

Verification

  • Checks ran on e3ce5c4 after npm ci with Node 22.23.2. npm run lint, npm run format:check, npm run typecheck, and npm run build all passed.
  • npm run test passed Svelte checks and 1,453 tests across 90 files. npm run docs:build passed internal links and all 73 legacy fragments. A separate sidebar check covered all 14 content pages.
  • Local static preview of /sync/ and /faq/ through preview_* at 1440 x 1000 and 390 x 844. No horizontal overflow. The FAQ recovery link opens its target section. Screenshots follow.
  • Independent source review by claudeAgent/claude-opus-5-5 approved e3ce5c441d5ec2d89d886c7ab026eab812a67f99. Recovery steps explain that starting a new library locally can bring the old library back when another device syncs it. Obsidian runtime verification was not run for this documentation-only change. No feed or media fixtures changed.

Claim sources

Checked against the merged source on origin/master at 3e8db3a, plus PR #388 and PR #398. Each source below is pinned to that commit.

Claim in the edited pages Merged source
Every actual save reads disk and merges the last copy, local edits, and disk. Separate feed and progress changes survive. writer.ts, persist and merge.ts, threeWayMergeSettings.
Shared-setting conflicts favor local edits. Saved feeds with secret references, progress, note mappings, current episodes, and list or download episodes resolve whole. Download metadata stays together. merge.ts, atomic paths and whole-entry merge.
Episode identity uses podcast name and title. episodeKey.ts, getEpisodeKey.
External changes update library stores without a restart. A loaded player keeps its episode current and saves its own position even after synced progress arrives. writer.ts, applyExternalChange, main.ts, publishDocument, and EpisodePlayer.svelte, position persistence.
Read/write races and in-place writes can lose an arriving change or expose a partial file. Older writers can overwrite the file. writer.ts, read/write limit, main.ts, saveData and older-file refusal, and PR #388 limits. Updating every device and waiting for sync are advice based on these limits.
User edits request saves immediately and queued edits share a save. Only periodic position checkpoints wait at least 10 seconds between writes. Pause, hide, and player close save immediately, including popouts. writer.ts, request and checkpoint, request, checkpoint scheduling, and EpisodePlayer.svelte, position events.
No new writes on window unload or quit. Quit waits for a write already in flight. Every write is read back and a mismatch fails the save. main.ts, lifecycle guards and writeDataFile.
A non-empty unreadable file stays unchanged with a persistent notice. Startup uses defaults. Adoption after unreadable startup refuses files that need repairs. Dropped entries in an already loaded library retain their previous values. main.ts, startup refusal, runtime read, and merge.ts, restoreDropped.
An older-format synced file stops saving and requests restart. Startup migration stores legacy API keys before writing. main.ts, older-file notice and startup migration.
Empty files count as missing. Startup starts a new library. A later save can recover an empty failed write with merged changes. Non-empty partial files are not repaired. main.ts, empty-file check, startup defaults, and writer.ts, failedWrite recovery.
Saving resumes after reading a usable replacement. Restart retries loading and migrates older data. Startup adoption replaces the unsaved library; with a loaded library, recovery merges edits. Deleting and restarting starts a new library locally; another device with the old library loaded combines the two when the new file arrives. main.ts, liftUnreadableRefusal, dataFileAccess, startup loading, and writer.ts, adoption. merge.ts, library combining and main.persistence.test.ts, new-file recovery cases confirm that another device can retain the old library. Space, permissions, backups, and a restart fallback are recovery advice.
data.json records library identity. New libraries receive an ID ordered by their start time. podNotesData.ts, newLibraryId and encoding and libraryId encoding.
Files that identify different libraries combine podcasts, progress, and playlists, including a device that started before sync delivered its data. Settings changed from defaults on only one side keep that value. The combined file keeps the earlier library identity. Same-library files still merge three ways. merge.ts, mergeDataFile and startedFirst, merge.ts, default-setting merge, writer.ts, save and external change paths, and PR #398 reproduction.
A podcast named differently in the two libraries appears under both names. merge.ts, object-key recursion, episodeKey.ts, and PR #398 rename verification.

Screenshots

Page or section Desktop Phone
Library identity Library identity at desktop width Library identity at phone width
Sync merge Sync merge at desktop width Sync merge at phone width
Save timing Save timing at desktop width Save timing at phone width
Refusals and recovery Refusals and recovery at desktop width Refusals and recovery at phone width
FAQ progress answer FAQ progress answer at desktop width FAQ progress answer at phone width

FAQ phone continuation.

Compatibility

None. Documentation only. No storage, settings, API, URI, or migration changes.

@chhoumann
chhoumann marked this pull request as draft October 11, 2026 09:06
@chhoumann

Copy link
Copy Markdown
Owner Author

Held as a draft until the release that ships #388. Today's published PodNotes (2.21.1) does not merge data.json, so publishing this page now would describe behavior users do not have yet. The docs site deploys from master.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Deploying podnotes with  Cloudflare Pages  Cloudflare Pages

Latest commit: e3ce5c4
Status: ✅  Deploy successful!
Preview URL: https://f5900ecb.podnotes.pages.dev
Branch Preview URL: https://docs-sync-merge-behavior.podnotes.pages.dev

View logs

@chhoumann

Copy link
Copy Markdown
Owner Author

Closing in favor of #458, the held 2.22.0 docs PR. It ports this PR's sync and FAQ text into the restructured pages: "Changes on two devices" and "When PodNotes saves" on /sync/, the FAQ progress answer, and the recovery steps, which now live on a new /save-problems/ page. The port is updated for #398 (two libraries combine), #401 (no save on view close; the episode in the player keeps its position), and #425, #426, and #433 (the save-failure notice and automatic retries). #458 stays a draft until the owner releases 2.22.0.

@chhoumann chhoumann closed this Oct 13, 2026
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