Let the album page be asked for the whole tracklist #113

Merged
logan merged 1 commits from feat/7-full-tracklist-toggle into main 2026-08-19 01:10:34 +00:00
Collaborator

Answers #7 — the next item of the roadmap's (#73) Phase 1, unblocked by #16.

What was wrong

An album the user holds part of showed only the tracks on disk, with nothing to say the rest existed. The page could already draw the full release with the missing rows dimmed — it just could not be asked. The automatic rule fires on completeness.known, which needs either the files' per-disc total or the catalog's total_tracks.

Neither reaches most albums, and the second is worse than it looks.

Measured, while confirming Direction part 2

The shipped artifact does not carry total_tracks:

$ curl -sSI .../generic/yellowjacket-core-index/latest/core-index.db.zst
last-modified: Mon, 10 Aug 2026 04:38:16 GMT

$ sqlite3 core-index.db \
    "SELECT COUNT(*) FROM pragma_table_info('explore_index') WHERE name='total_tracks';"
0
$ sqlite3 core-index.db "SELECT COUNT(*) FROM explore_index;"
1079667

The column landed on 2026-08-16; the artifact is from 08-10, and index-artifact.yml is a weekly cron rather than a push trigger. So completenessAnswer()'s catalog fallback answers 0 for every user today. artifactHasTotals() is doing precisely its job — which is why nothing is failing — but a probe that makes a column optional also makes its absence silent. Noted on #88, which is the publish that carries it, and in .planning/NOTES.md.

That is the argument for Direction part 1 being the real ask.

Commits

Commit Issue
feat(explore): let the album page be asked for the whole tracklist #7

What it does

A "Show the whole album" switch above the tracklist, flipping the synthetic "Your Library" entry between the local files and the release — the same rendering the automatic rule produces, reached deliberately.

Three things that are load-bearing

  • showFullTracklist is a tri-state, null meaning "follow the automatic rule". The rule is right when it fires and the switch must agree with the page it sits on rather than starting out contradicting it; a plain boolean would need its default recomputed every time the completeness answer moved underneath it. The user outranks it in both directions.
  • fullReleaseCluster() falls back to the highest-scoring cluster. findLibraryCluster is a guess over the inLibrary flags and returns nothing when none are set — exactly the untagged library this exists for, so without the fallback the control would be absent precisely where it is needed.
  • It appears only where it can change what is on screen: against the library entry, with a release to switch to, and only when the two tracklists differ. A complete album's release has the same rows as its files, so the switch would redraw the same list and read as broken — the same test the version dropdown one section up already answers.

Verification actually run

  • make ui-test — 844 passed (74 files), up from 843.
  • frontend/test/components/album-full-tracklist.test.ts — 8 specs: the report's own case (2 local, 12 on the release, no declared total), the round trip back, the automatic rule still firing and still being overridable, and three absence cases.
  • tsc --noEmit clean; css-literals and frontend-typecheck via pre-commit.

The accessible name is asserted, not assumed, through the browser's own computation (page.getByRole('switch', {name})). wa-switch happens to get it right, and for a third reason again: its <input role="switch"> sits inside a native <label> that also holds the <slot>, so the name is computed across the flattened tree from light-DOM text. This app has shipped the opposite twice — wa-slider and config-field.

Not done here

No e2e spec: the fixture library has no partly-owned MBID-matched album to drive one against, and staging one is a larger piece of work than the feature. The component tier exercises the real element with the real store code.

Closes #7

Answers #7 — the next item of the roadmap's (#73) Phase 1, unblocked by #16. ## What was wrong An album the user holds part of showed only the tracks on disk, with nothing to say the rest existed. The page could already draw the full release with the missing rows dimmed — it just could not be **asked**. The automatic rule fires on `completeness.known`, which needs either the files' per-disc total or the catalog's `total_tracks`. Neither reaches most albums, and the second is worse than it looks. ## Measured, while confirming Direction part 2 The shipped artifact does **not** carry `total_tracks`: ``` $ curl -sSI .../generic/yellowjacket-core-index/latest/core-index.db.zst last-modified: Mon, 10 Aug 2026 04:38:16 GMT $ sqlite3 core-index.db \ "SELECT COUNT(*) FROM pragma_table_info('explore_index') WHERE name='total_tracks';" 0 $ sqlite3 core-index.db "SELECT COUNT(*) FROM explore_index;" 1079667 ``` The column landed on 2026-08-16; the artifact is from 08-10, and `index-artifact.yml` is a weekly cron rather than a push trigger. So `completenessAnswer()`'s catalog fallback answers 0 for **every** user today. `artifactHasTotals()` is doing precisely its job — which is why nothing is failing — but a probe that makes a column optional also makes its absence silent. Noted on #88, which is the publish that carries it, and in `.planning/NOTES.md`. That is the argument for Direction part 1 being the real ask. ## Commits | Commit | Issue | | --- | --- | | `feat(explore): let the album page be asked for the whole tracklist` | #7 | ## What it does A "Show the whole album" switch above the tracklist, flipping the synthetic "Your Library" entry between the local files and the release — the same rendering the automatic rule produces, reached deliberately. ## Three things that are load-bearing - **`showFullTracklist` is a tri-state**, `null` meaning "follow the automatic rule". The rule is right when it fires and the switch must agree with the page it sits on rather than starting out contradicting it; a plain boolean would need its default recomputed every time the completeness answer moved underneath it. The user outranks it in both directions. - **`fullReleaseCluster()` falls back to the highest-scoring cluster.** `findLibraryCluster` is a guess over the `inLibrary` flags and returns *nothing* when none are set — exactly the untagged library this exists for, so without the fallback the control would be absent precisely where it is needed. - **It appears only where it can change what is on screen**: against the library entry, with a release to switch to, and only when the two tracklists differ. A complete album's release has the same rows as its files, so the switch would redraw the same list and read as broken — the same test the version dropdown one section up already answers. ## Verification actually run - `make ui-test` — 844 passed (74 files), up from 843. - `frontend/test/components/album-full-tracklist.test.ts` — 8 specs: the report's own case (2 local, 12 on the release, no declared total), the round trip back, the automatic rule still firing and still being overridable, and three absence cases. - `tsc --noEmit` clean; `css-literals` and `frontend-typecheck` via pre-commit. **The accessible name is asserted, not assumed**, through the browser's own computation (`page.getByRole('switch', {name})`). `wa-switch` happens to get it right, and for a third reason again: its `<input role="switch">` sits inside a native `<label>` that also holds the `<slot>`, so the name is computed across the flattened tree from light-DOM text. This app has shipped the opposite twice — `wa-slider` and `config-field`. ## Not done here No e2e spec: the fixture library has no partly-owned MBID-matched album to drive one against, and staging one is a larger piece of work than the feature. The component tier exercises the real element with the real store code. Closes #7
logan added 1 commit 2026-08-19 00:49:46 +00:00
feat(explore): let the album page be asked for the whole tracklist
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m25s
CI / e2e (pull_request) Successful in 6m12s
aa59773d22
An album the user holds part of showed only the tracks on disk, with
nothing to say the rest existed. The page could already draw the full
release with the missing rows dimmed -- it just could not be asked: the
automatic rule fires on `completeness.known`, which depends on the files
declaring a per-disc total, or failing that on the catalog's own
`total_tracks`.

Neither reaches most albums. #16 fixed the first input for anything
tagged from now on, and the second is worse than it looks: the published
artifact is from 2026-08-10 and the column landed on 08-16, so
`completenessAnswer()`'s catalog fallback answers 0 for every user until
the index job republishes. Measured, and noted on #88, which is the
publish that carries it.

So the control is explicit. A "Show the whole album" switch flips the
synthetic "Your Library" entry between the local files and the release,
which is the same rendering, reached deliberately rather than inferred.

Three things about it are load-bearing:

- `showFullTracklist` is a tri-state, `null` meaning "follow the
  automatic rule". The rule is right when it fires, and the switch has
  to agree with the page it is sitting on rather than starting out
  contradicting it -- a plain boolean would need its default recomputed
  every time the completeness answer moved underneath it. The user
  outranks the rule in both directions.
- `fullReleaseCluster()` falls back to the highest-scoring cluster.
  `findLibraryCluster` is a guess over the `inLibrary` flags and returns
  nothing at all when none are set, which is exactly the untagged
  library this exists for -- without the fallback the control would be
  absent precisely where it is needed. The sublabel names the release
  either way rather than leaving the user to wonder whose tracklist they
  are reading.
- It appears only where it can change what is on screen: against the
  library entry, with a release to switch to, and only when the two
  tracklists differ. A complete album's release has the same rows as its
  files, so the switch would redraw the same list and read as broken --
  the same test the version dropdown one section up already answers.

The accessible name is asserted rather than assumed, through the
browser's own computation. `wa-switch` happens to get it right, and for
a third reason again: its `<input role="switch">` sits inside a native
`<label>` that also holds the `<slot>`, so the name is computed across
the flattened tree from light-DOM text. This app has shipped the
opposite twice.

Closes #7
logan merged commit 18a08daa91 into main 2026-08-19 01:10:34 +00:00
Sign in to join this conversation.