Skip to content

fix(persistence): never overwrite an unreadable data.json or write during window unload - #376

Merged
chhoumann merged 2 commits into
masterfrom
fix/unreadable-data-json
Oct 11, 2026
Merged

chhoumann merged 2 commits into
masterfrom
fix/unreadable-data-json

Conversation

@chhoumann

@chhoumann chhoumann commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

Reloading Obsidian while an episode plays no longer resets PodNotes to defaults, and PodNotes never saves over a data.json it could not read.

Fixes #370

The cause had two parts, both traced in real Obsidian:

  • The empty file. During "Reload app without saving", pagehide fires and then the player's visibilitychange handler saves the position. That write truncates data.json, and the page unloads before the write finishes, so the file is left at 0 bytes.
  • The overwrite. On the next start, Obsidian's loadData returns nothing for a file it cannot parse. PodNotes treated that like a new install and saved defaults over the file. It also deleted every private feed URL from SecretStorage, because the defaults reference none.

What changed:

  • If data.json exists but cannot be read, PodNotes leaves it unchanged. It shows a sticky Notice with the file path, runs on default settings, and saves nothing for the session. Strict saves (import, API key selection) reject.
  • From pagehide or Obsidian's quit on, every data.json write is refused. One write boundary covers the save loop and the startup migration writes.
  • Quit waits for a write already in flight. PodNotes adds a quit task only then, so a normal close is not delayed.

Scope: Obsidian's file adapter has no atomic replace (adapter.rename refuses an existing destination). So a reload that lands during an in-flight periodic save can still truncate the file. Here a 1 MB write takes about 19 ms (median), with one save about every 4 s of playback. Atomic writes would need Node fs on desktop, so they are left for a follow-up.

Notice for an unreadable data.json

Verification

Runtime: Obsidian 1.13.7 (installer 1.13.7), Linux, headless isolated worktree instance. Feed: the verify-skill fixture PodNotes Fixture (http://127.0.0.1.nip.io:32735/feed.xml), Fixture Episode 1 (60 s WAV), added through Settings and played from the episode list.

  • Reload during playback, before (master). A write trace showed pagehide, then saveData from setEpisodeTime via runSaveLoop, then a writeFile start with no finish before unload. data.json went from 3799 to 1960 bytes, with no feeds and no history.
  • Reload during playback, after. In 6 of 6 trials, no write started after pagehide. data.json kept its feeds and its byte count.
  • Truncated file, before (master). I cut data.json to half its size while Obsidian was stopped and then started it. Within 5 s PodNotes replaced the file with defaults, with no Notice.
  • Truncated file, after. The file keeps its sha256, and the Notice above appears. The session has no feeds, a strict save rejects, and a volume change writes nothing.
  • Close during playback, after. quit fired with no write in flight and no task was added. The window closed in 387 ms, no write started afterwards, and data.json kept its feeds.
  • E2E. keeps data.json when Obsidian reloads while an episode plays and leaves a data.json it cannot read unchanged and saves nothing both fail on master code, 2 of 2 runs. Both pass in all 6 full runs of npm run test:e2e (21 tests) across the review rounds, including the head commit.
  • Unit. New tests cover an unreadable file (no write, defaults, private feed secrets kept), a migration write during unload, a queued strict save at unload, the pagehide wiring, and the quit wiring (no task when idle, a task that waits for the write in flight and never rejects). Each fails on master main.ts.

Checks on the head commit (Node 22.23.2):

  • npm run lint: pass
  • npm run format:check: pass
  • npm run typecheck: pass
  • npm run build: pass
  • npm run test: pass (Vitest 80 files, 1216 tests)
  • npm run docs:build: pass, with mkdocs from docs/requirements.txt in a scratch venv

Release impact

Storage: no schema or format change. An unreadable data.json is now preserved, and that session runs read-only on defaults. Saves requested after the window starts unloading or Obsidian starts quitting are dropped. So the last position update before a reload or quit, the write that caused the loss, is not saved; the periodic save keeps the position within about 5 s. No API or URI changes.

Note

Never overwrite unreadable data.json or write during window unload in PodNotes

  • loadSettings in src/main.ts now checks whether data.json exists when loadData returns null. If the file exists but cannot be read or parsed, the plugin keeps the file unchanged, resets settings to defaults, shows a persistent notice, and refuses all later writes.
  • onload registers a window pagehide handler and an Obsidian quit handler that refuse new writes. The quit handler also registers the active write as a quit task so Obsidian waits for an in-flight save to settle.
  • All saves, including legacy-schema and private-feed migration writes, now go through the new writeDataFile guard, which rejects refused writes and tracks the in-flight promise.
  • Adds unit tests for wiring and persistence plus end-to-end tests covering reload during playback and unreadable data.json.
  • Behavioral Change: strict save requests now reject with an error after writes are refused (e.g. after pagehide or an unreadable data.json) instead of silently succeeding; callers that don't catch these rejections will see rejected promises (a catch handler is attached internally to avoid unhandled rejections).

Macroscope summarized 4f0b1f7.

…ring window unload

PodNotes treated a data.json it could not read like a missing one and saved
defaults over it. A reload during playback produced such a file: the player's
hide save started a write after pagehide, and the page unloaded after the
write truncated data.json.

An unreadable data.json is now left unchanged. PodNotes shows a Notice, runs
on defaults and saves nothing for the session. From pagehide or Obsidian's
quit on, every data.json write is refused, and strict saves report the
refusal. Quit waits for a write already in flight.

Fixes #370
@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: 4f0b1f7
Status: ✅  Deploy successful!
Preview URL: https://7b1c1cf1.podnotes.pages.dev
Branch Preview URL: https://fix-unreadable-data-json.podnotes.pages.dev

View logs

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.

Reloading Obsidian while playing can reset PodNotes data to defaults

1 participant