S10.3 tail: dead-file handling (F) + playlist scan/scale checks (G); folders cut#60
Merged
Conversation
…cate + remove-missing A playlist entry whose track resolves but whose FILE is gone from disk (a loose file deleted, or a move not yet reconciled — a deleted library track is already FK-cascade-dropped) is now handled instead of silently failing to play. - PlaylistDetailEntry.isAvailable = track resolved AND file exists on disk, checked OFF-MAIN (Task.detached .utility stat loop, Sendable capture) in loadDetail under the existing detailEpoch guard. resolvedEntries/playableFiles filter isAvailable → play SKIPS missing entries (never halts); keyboard nav skips them too. - Unavailable row: dimmed metadata + a trailing warning-badge MENU (Locate / Remove) — visible, click- AND keyboard-reachable (not right-click-only) + a combined VoiceOver element with the same actions. Count line shows "· N unavailable". - Locate: .fileImporter → relocateEntry → reuses the id-preserving moveTrack (every playlist referencing the track is fixed at once); a URL-conflict is a per-row alert, never the pane-wide load-error state. - Remove-missing (bulk): confirmationDialog (irreversible, no undo) + disabled while the library is populating (availability flickers mid-scan). swiftui-pro review folded in (all of the above); it confirmed the epoch placement, skip-on-play index mapping, and Locate id-preservation were already correct. Sandbox note: app is non-sandboxed Developer-ID, so a Locate'd out-of-root file plays; a security-scoped bookmark would be needed IF App Sandbox is later adopted. Also: folders (Chunk D) CUT per founder — data layer left dormant; docs (§Status + design §6) updated. Model dead-file helpers split to PlaylistsModel+DeadFiles; detail actions/helpers to PlaylistDetailView+Actions (type-body length). strict-gate PASSED.
…isolation + explain + write-during-scan Chunk G — the headless VerifyLibraryStore invariants that close the S10.3 tail: - pl-move-membership-survives (US-PLIST-08): a track's file moves on disk, the REAL LibraryScanner move-matches it (keeps tracks.id), and the playlist entry (by id) is still intact after reconcile — membership survives a Finder move. - pl-reorder-isolation: reordering one playlist leaves every other playlist's order untouched (per-playlist renumber, scoped by playlist_id). - pl-explain-plan: the playlist list + entries reads are index-driven — playlist_entries is reached via idx_playlist_entries_playlist, never a full scan (scale tripwire; new explainPlaylistReadsPlan hook reusing collectQueryPlan). - pl-write-during-scan: a playlist write concurrent with a real scan completes without deadlock/corruption — the single serialized DatabaseWriter serializes them. VerifyLibraryStore 125/125. Also fixed two latent Periphery findings (unused SongsTable.playlists env var + unused LibraryStore import in ChecksMigrationConvergence — both dormant on main). strict-gate PASSED.
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.
S10.3 tail — dead-file handling (F) + scan/scale checks (G)
Follow-up to #59 (S10.3 core). Completes the S10.3 scope now that playlist folders are CUT (founder, 2026-07-16 — the folder data layer from #59 is left dormant; no folders UI).
Chunk F — dead / missing-file handling
A playlist entry whose track resolves but whose file is gone from disk (a loose file deleted, or a move not yet reconciled — a deleted library track is already FK-cascade-dropped) is now handled instead of silently failing to play:
PlaylistDetailEntry.isAvailable= track resolved AND file exists, checked off-main (aTask.detached(.utility)fileExistsloop under the existingdetailEpochguard). Play + keyboard nav skip unavailable entries (never halt).moveTrack(fixes every playlist referencing it at once); a URL-conflict is a per-row alert, never the pane-wide error state.swiftui-pro reviewed; it confirmed the epoch placement, skip-on-play index mapping, and Locate id-preservation were correct, and the three MAJORs it found (bulk-remove guardrail, Locate error UX, keyboard-reachable row actions) are all fixed. Sandbox note: the app is non-sandboxed Developer-ID, so a Locate'd out-of-root file plays; a security-scoped bookmark would be needed only IF App Sandbox is later adopted.
Chunk G — playlist ⇄ scanner / scale invariants (headless)
Four new VerifyLibraryStore checks (125/125):
pl-move-membership-survives(US-PLIST-08): a track's file moves, the realLibraryScannermove-matches it (keepstracks.id), and the playlist entry is still intact after reconcile.pl-reorder-isolation: reordering one playlist leaves every other playlist's order untouched.pl-explain-plan: playlist list + entries reads are index-driven (idx_playlist_entries_playlist), never a fullplaylist_entriesscan (scale tripwire).pl-write-during-scan: a playlist write concurrent with a real scan completes without deadlock/corruption.Also fixes two latent Periphery findings dormant on main (an unused
SongsTable.playlistsenv var + an unusedLibraryStoreimport).Docs: sprint-plan §Status + design §6 updated (S10.3 core shipped #59; folders cut; F/G).
Test plan
make strict-gategreen (VerifyLibraryStore 125/125 + unit tests + Periphery + the migrator/drop-path/erase guards). Pending founder by-ear: add a song to a playlist → move/delete its file → reopen: the row badges unavailable, Play skips it, the badge menu offers Locate/Remove, and "Remove N Missing" confirms.