S10.1 — Playlist/queue persistence spine (schema v3 + DAO + Gate-1 closure)#53
Merged
Conversation
Founder decision: make the S10 chunks individual done-done sprints. Sub-numbered S10.1-S10.5 (not flat S10/S11/...) to avoid renumbering S11-S18 and breaking the R1/R2/R3-after-S10/S14/S17 anchors. - sprint-plan.md: S10 row -> five S10.x rows (S10.1 spine 8sp / S10.2 queue UX 6 / S10.3 playlists+M3U 9 / S10.4 macOS control 5 / S10.5 browse polish 3). Status, critical path, Phase-1 total (~95sp), R1 milestone all updated to S10.1-S10.4. - s10-queue-playlists-macos-plan.md: reframed chunks -> five sprints (+SP, per-sprint DoD). - roadmap.md: S10 row relabeled 'runs as S10.1-S10.5'; R1/critical-path anchors updated.
…l gate) Design phase for S10.1 (first S10 sprint). BA + swift-expert + qa-expert design, then architect + the-fool gate (GO-WITH-CHANGES). Must-fixes folded in: - sweepOrphans symmetric fix (gate's top catch): the per-scan orphan sweep must spare playlist-referenced tracks too, not just removeRoot — else a partial rescan silently CASCADE-drops playlist entries. - durability: eraseDatabaseOnSchemaChange=false UNIFORM (not DEBUG-only) + make reset-db + additive v3 migration + migration-immutability gate check. - defer builtin 'current' seed out of the v3 migration to a lazy bootstrap at S10.2. - position = non-contiguous ordering key (reconciled swift-vs-QA contradiction). - UNIQUE(name) scoped WHERE is_builtin=0. Pending FOUNDER BRAINSTORM before implementation (per the dev process).
Founder decisions locked:
1. Durability = keep it simple, DEFERRED — eraseDatabaseOnSchemaChange stays true;
pre-R1 a schema change drops-and-recreates (playlists included). Real durability
is a new deferred known-issue. (Drops migration-guard/reset-db machinery.)
2. File gone from disk -> removed from playlists (track_id CASCADE; sweep drops).
removeRoot still spares referenced tracks as loose (locked EP-LIBRARY rule).
3. Queue = built-in 'current' playlist (seeded in v3 migration).
4. Duplicate names PREVENTED (UNIQUE(name) WHERE is_builtin=0 + typed reject).
5. Default name Apple-style ('New Playlist N', lowest-unused).
Ready to implement.
…-1 closure) Implements the S10.1 store spine (design s10-1-playlist-queue-store-design.md): - Schema v3: playlists + playlist_entries tables (ordered, own entry id so a track can repeat; UNIQUE(name) WHERE is_builtin=0; single-builtin partial index). migrateV2toV3 seeds the built-in 'current' queue playlist. eraseOnSchemaChange stays true (durability deferred, founder decision §0.1). - LibraryStore+Playlists: create/untitled(Apple-style lowest-unused)/rename/delete (built-in immutable), ordered append/appendEntries/insert-at/remove/removeEntries/ reorder, addLooseFileToPlaylist (folder_id NULL), idempotent builtin bootstrap. Typed PlaylistNameConflict / PlaylistMutationError. Rides GRDB single-writer txns. - Gate-1 (SEQ-1) CLOSED: unreferencedTrackIDs filters against playlist_entries, so removeRoot spares playlist-referenced tracks (kept loose). File-gone -> CASCADE drop. - VerifyLibraryStore: 14 pl-* checks incl. the Gate-1 keep/sweep pair + same-track-twice + reorder + insert-at + remove + loose-add + dup-name-reject + persistence. Updated FTS/migration checks + fullMigrator for v3. make gate 96/96; make strict-gate green. Next: qa-expert + the-fool break-it pass, then founder manual testing + retro.
… R1 gate) - SEQ-1: Gate 1 CLOSED by S10.1 (unreferencedTrackIDs now filters playlist_entries; proven by pl-gate1-* checks). Both SEQ-1 gates now closed. - DUR-1 (new): playlist durability across schema change is deferred (founder §0.1, keep-it-simple pre-release) but flagged as a HARD GATE that MUST be resolved before R1 ships playlists — eraseOnSchemaChange + tracks.id churn would corrupt playlists. Logged so the deferral isn't forgotten (the-fool tripwire).
…pass The green gate had false confidence (checks only hit happy paths). Break-it pass found: - D1 (correctness): reorderPlaylist with a partial list collided positions -> silent scramble. Now renumbers the WHOLE playlist (supplied order + omitted appended; foreign ids ignored) — like insertEntry. - D2 (correctness): addLooseFileToPlaylist of an already-library URL re-upserted it, nulling folder_id/relative_path + resetting metadata_scanned. Now reuses the existing row; only a new URL inserts a loose one. - D3 (data-loss footgun): removeEntry/removeEntries weren't playlist-scoped -> a foreign entry id deleted from another playlist. Added playlistID + AND playlist_id=?. - D4 (robustness): appending to a deleted/absent playlist leaked a raw GRDB FK error -> now typed PlaylistMutationError.notFound (playlist-exists pre-check). - D5/D6 (robustness): a user playlist named 'current' (any case) and empty/whitespace names were accepted -> rejected via validatedUserName + .invalidName. - D7 (sweepOrphans partial-rescan): confirmed, consistent with locked §0.2; deferred (logged as design §5 note + DUR-1). Added 6 fix-covering pl-* checks (partial-reorder, loose-add-existing, remove-foreign, append-notfound, name-validation, remove-entries). VerifyLibraryStore 102/102; make gate + make strict-gate green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First sprint of the S10 series (see s10 plan). Store spine only — no UI (queue/playlist UX is S10.2/S10.3). Design + founder-brainstorm decisions in s10-1-playlist-queue-store-design.md.
What it adds
playlists+playlist_entries(ordered, own entry id so a track can repeat;UNIQUE(name) WHERE is_builtin=0; single-builtin partial index).migrateV2toV3seeds the built-in "current" queue playlist.LibraryStore+Playlists): create / untitled (Apple-style lowest-unused) / rename / delete (built-in immutable); ordered append / appendEntries / insert-at / remove / removeEntries / reorder;addLooseFileToPlaylist; idempotent builtin bootstrap. TypedPlaylistNameConflict/PlaylistMutationError. Rides GRDB single-writer transactions.unreferencedTrackIDsfilters againstplaylist_entries, soremoveRootspares playlist-referenced tracks (kept loose). File-gone → CASCADE drop (founder decision §0.2).Founder-brainstorm decisions (locked)
Durability kept simple / deferred (
eraseOnSchemaChangestays true; logged as DUR-1, a hard gate before R1); file-gone → remove; queue = built-in "current"; duplicate names prevented; Apple-style default names.QA
VerifyLibraryStore102/102 (20pl-*checks incl. Gate-1 keep/sweep pair, same-track-twice, reorder, insert-at, remove, loose-add, persistence).make gate+make strict-gategreen.Not merged blockers, but note: DUR-1 must be resolved before R1 ships playlists.