Open the web app on a library dashboard (ADR-0058 phase 1) - #171
Merged
Conversation
ADR-0058, proposed: the web app is an administration tool, and the first thing an
administrator wants is the state of the thing they administer — not a settings form
with playback controls above library health.
The dashboard itself is an unbuilt front end rather than a new feature. Both endpoints
already existed and both already had a client wrapper nothing called: libraryApi.getStats
over /library/stats, playTrackingApi.getStats over /tracks/stats/plays. The four
pending-analysis queues are shown separately (point 7) because they are distinct backlogs
with distinct version constants, and one number hides which is stuck.
Pointing it at the real 26k library is what turned this into a backend change. Every total
on /library/stats disagreed with the screen it links to:
total_tracks 26,488 vs 26,422 — no status == ACTIVE filter, so 66 missing/deleted
files counted as library size
total_albums 3,873 vs 3,927 — count(distinct Track.album) is a *string* distinct and
merges same-titled albums by different artists; the album list groups by
(album_artist, album), case-insensitively
total_artists 3,664 vs 3,477 — raw tag strings, so "The Beatles" and "Beatles, The"
count twice; the artist list reads canonical Artist (ADR-0052)
Each now counts the way the list endpoint counts. Scoping the totals to active tracks
without also scoping the analysis counts would have rendered a *negative* backlog — -41
pending on the real library — so those join Track too, and pending_analysis is clamped.
albums/compilations/soundtracks cannot be fixed: nothing writes Track.album_type, so every
row keeps the column default. That breakdown reads "26,488 albums · 0 compilations" on a
library ADR-0052 found 297 compilations in. A consumer with no producer — the mirror of the
generated-and-uncalled shape this codebase keeps finding. They stay on the wire because
`library` is a generated tag (ADR-0007) and dropping required fields breaks the Swift
client; they are deprecated in the model and the type, and displayed nowhere. REST-API.md
had been documenting 150 compilations and 45 soundtracks, which no instance ever returned.
tests/test_library_stats.py asserts stats against the list endpoints rather than against
constants — "expect 3 albums" passes under either counting method, which is how this drifted
for as long as it did. All five fail against the previous implementation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI's Backend Lint runs `mypy app`, which wanted an annotation for the intermediate `album_groups` select. Inlining removes the variable rather than annotating it — the subquery is only ever consumed in the next line. No behaviour change; tests/test_library_stats.py unchanged and still passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI's "Check committed OpenAPI schema is current" step caught this. The diff is description-only — the LibraryStats model docstring and the get_library_stats handler docstring, both rewritten to record why the totals changed. No field, type or path moved. Clients generate from this file (ADR-0007 phase 2), so a stale one is a client built against an API that no longer exists — which is why the check exists even for a change that only alters prose. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Supersedes #168, which GitHub auto-closed when its base branch
strip/web-detail-routeswas deleted on merging #167. Same commit, rebased onto main.Proposes ADR-0058: The Web App Is an Administration Tool and ships its phase 1.
The dashboard
The web app opened on Settings — a form, with playback controls above library health, telling the operator its own subject was secondary. It now opens on the state of the library.
Almost none of this is new capability. Both endpoints existed and both already had a client wrapper that nothing called:
libraryApi.getStatsover/library/stats,playTrackingApi.getStatsover/tracks/stats/plays. The four pending-analysis queues are shown separately (point 7) — they are distinct backlogs with distinct version constants, and one number hides which is stuck.No artwork-coverage tile: there is no endpoint that counts albums without art. Point 6 says the count comes first.
Pointing it at the real library turned it into a backend change
Every total on
/library/statsdisagreed with the screen it links to:total_tracksstatus == ACTIVEfilter — 66 missing/deleted files counted as library sizetotal_albumscount(distinct Track.album)is a string distinct, merging same-titled albums by different artiststotal_artistsEach now counts the way the list endpoint counts.
A trap on the way in: scoping the totals to active tracks while leaving the analysis counts unscoped renders a negative backlog — −41 pending on the real library, since 41 analyses belong to tracks that have left it. The analysis counts join
Tracktoo, andpending_analysisis clamped.After deploying,
analyzed_tracksfell 26,463 → 26,422 andpending_analysis25 → 0. The library is fully analysed; that "25 pending" was 66 non-active tracks netted against 41 stale analyses.What could not be fixed
albums/compilations/soundtracksare meaningless: nothing writesTrack.album_type, so every row keeps the column default. The breakdown reads "26,488 albums · 0 compilations" on a library ADR-0052 found 297 compilations in — a consumer with no producer, the mirror of the generated-and-uncalled shape this codebase keeps finding.They stay on the wire (
libraryis a generated tag under ADR-0007, so dropping required fields breaks the Swift client), deprecated in the model and intypes/index.ts, displayed nowhere.docs/REST-API.mdhad been documenting150compilations and45soundtracks — values no instance has ever returned. Recorded as an ADR follow-up: either the scanner setsalbum_type(MusicBrainz release types are already fetched) or the column and the fields go.Tests
backend/tests/test_library_stats.pyasserts stats against the list endpoints, not against constants — "expect 3 albums" passes under either counting method, which is how this drifted unnoticed. All five fail against the previous implementation; verified by stashing the fix.Verification
pytest tests/test_library_stats.py tests/test_api_library.py tests/test_library_artists_canonical.py— 42 passedtsc --noEmit— 14 errors (unchanged baseline)pnpm test— 63 files / 952 tests passedpnpm run build— cleanDashboardchunk contains nocompilationsreferenceADR-0058 is
proposed— points 1–7 need your say-so before phase 2 (navigation).🤖 Generated with Claude Code