loadLibrary() replaces app state with the server's library and consults the local buffer only for activeId (src/storage.js:320-336). Any edit sitting in the buffer that never reached the server is discarded on the next load, without a word.
That was survivable while the pagehide/visibilitychange flush essentially always landed. With conditional writes (#4) it no longer does: a flush onto a song another machine has edited since is now correctly refused with 409, and there's no tab left to ask.
Repro
- Open the same song on two machines.
- On machine B, edit and let it save.
- On machine A — which hasn't synced since — type an edit and close the tab within the 10s debounce.
- The flush PUTs, gets a 409, and the edit stays in the buffer.
- Reopen on machine A. B's version loads. A's edit is gone.
Not a regression in the sense that matters — before conditional writes A's edit would have landed and silently destroyed B's, which is worse — and the hide-then-return path is fine, because the visibility sync flags it as a conflict. But closing the tab at that exact moment loses work quietly, and "changes kept locally" is a promise the buffer isn't currently keeping.
The same gap already applied to any flush that failed for other reasons: offline at close, session expired, server down.
Shape of a fix
loadLibrary() would reconcile rather than replace: compare buffered songs against what the server returns, and where a buffered song differs from the server copy and from the snapshot it was last saved against, treat it as the same kind of conflict a live sync produces — surface it through the existing SyncNotice rather than picking a side.
The awkward part is that the pre-close snapshot doesn't survive the reload; serverSnapshot is module state, reset on load. Persisting the per-song tokens alongside the buffer would give the next session enough to tell "this buffered song is a pending edit" from "this buffered song is just stale". Worth doing — it also covers browser crashes and forced restarts, which today lose the same way.
loadLibrary()replaces app state with the server's library and consults the local buffer only foractiveId(src/storage.js:320-336). Any edit sitting in the buffer that never reached the server is discarded on the next load, without a word.That was survivable while the
pagehide/visibilitychangeflush essentially always landed. With conditional writes (#4) it no longer does: a flush onto a song another machine has edited since is now correctly refused with 409, and there's no tab left to ask.Repro
Not a regression in the sense that matters — before conditional writes A's edit would have landed and silently destroyed B's, which is worse — and the hide-then-return path is fine, because the visibility sync flags it as a conflict. But closing the tab at that exact moment loses work quietly, and "changes kept locally" is a promise the buffer isn't currently keeping.
The same gap already applied to any flush that failed for other reasons: offline at close, session expired, server down.
Shape of a fix
loadLibrary()would reconcile rather than replace: compare buffered songs against what the server returns, and where a buffered song differs from the server copy and from the snapshot it was last saved against, treat it as the same kind of conflict a live sync produces — surface it through the existingSyncNoticerather than picking a side.The awkward part is that the pre-close snapshot doesn't survive the reload;
serverSnapshotis module state, reset on load. Persisting the per-song tokens alongside the buffer would give the next session enough to tell "this buffered song is a pending edit" from "this buffered song is just stale". Worth doing — it also covers browser crashes and forced restarts, which today lose the same way.