Three small things found reviewing #4, none urgent, all cheap.
1. The placeholder song propagates to everyone. ensureNonEmpty() (src/App.jsx:44-49) mints a blank "New Song" when a reconcile would leave the library empty, so the app always has something to render. It carries no wpId, so the next save — triggered by any unrelated edit — POSTs it to the server, where it reaches every other machine.
Reachable when the whole library is trashed from another machine while untouched here (a bandmate clearing out, or re-importing from scratch), and via "Delete here too" on the last remaining song. The comment says it's "left un-dirty so it's never pushed to everyone's server on its own", which holds only until the next save of anything else.
Options: mark the placeholder so the save diff skips it until edited, or let the library legitimately hold zero songs and give the editor an empty state.
2. The buffer can briefly hold a zero-song library. doSync() calls writeBuffer(reconciled) (src/storage.js:496) before the caller applies ensureNonEmpty, so between those two the offline copy has no songs. Only observable if the tab dies in that window. Applying the floor inside doSync — or writing the buffer after — closes it.
3. setPullFlash timeout isn't cleaned up. src/App.jsx:367 schedules a 6s reset with no clearTimeout on unmount. Harmless today; it's a stray state update on a dead component.
Not a bug, worth knowing: GET /library/state does the same work as GET /library — full get_posts() plus a json_decode per song — and only the payload is small. The decode isn't optional (skipping it would let an unparseable post appear in state but not in the library, which reads as permanent drift and re-pulls the whole library on every probe). Fine at band-library scale, and the probe now runs on every save and every tab focus, so worth remembering if a library ever gets large.
Three small things found reviewing #4, none urgent, all cheap.
1. The placeholder song propagates to everyone.
ensureNonEmpty()(src/App.jsx:44-49) mints a blank "New Song" when a reconcile would leave the library empty, so the app always has something to render. It carries nowpId, so the next save — triggered by any unrelated edit — POSTs it to the server, where it reaches every other machine.Reachable when the whole library is trashed from another machine while untouched here (a bandmate clearing out, or re-importing from scratch), and via "Delete here too" on the last remaining song. The comment says it's "left un-dirty so it's never pushed to everyone's server on its own", which holds only until the next save of anything else.
Options: mark the placeholder so the save diff skips it until edited, or let the library legitimately hold zero songs and give the editor an empty state.
2. The buffer can briefly hold a zero-song library.
doSync()callswriteBuffer(reconciled)(src/storage.js:496) before the caller appliesensureNonEmpty, so between those two the offline copy has no songs. Only observable if the tab dies in that window. Applying the floor insidedoSync— or writing the buffer after — closes it.3.
setPullFlashtimeout isn't cleaned up.src/App.jsx:367schedules a 6s reset with noclearTimeouton unmount. Harmless today; it's a stray state update on a dead component.Not a bug, worth knowing:
GET /library/statedoes the same work asGET /library— fullget_posts()plus ajson_decodeper song — and only the payload is small. The decode isn't optional (skipping it would let an unparseable post appear in state but not in the library, which reads as permanent drift and re-pulls the whole library on every probe). Fine at band-library scale, and the probe now runs on every save and every tab focus, so worth remembering if a library ever gets large.