The app opened on Tracks — an alphabetical list of everything, which is
the one entry point that is identical every time and therefore gives the
user nothing to start from. Home is listed first in the nav and is the
page built to answer 'what should I play' (H-8).
Two things had to be true before that was an improvement.
An album with no cover rendered as a small dim icon on a surface the
same colour as the page, so a shelf read as having holes in it, while
the Albums and Artists grids both drew a letter tile (H-9). It draws the
same tile now.
And a shelf that repeats the one above it is suppressed, the way an
empty one already is — 'On repeat' was 'Pick up where you left off'
reordered. The rule fires only when the shelf is not showing the whole
library: a repeat is a fault only if a different row was possible, and
measured against a fixed shelf size instead this let an 11-album library
keep three identical shelves while a 13-album one lost them.
The first two versions of that rule were wrong and the *existing* Go
tests caught both — it collapsed a four-album library to a single shelf.
Nine e2e specs assumed the app starts on Tracks and now navigate there,
and one new spec freezes the landing itself. Home's page-header action
is 'Shuffle suggestions': 'Shuffle' alone was two different controls
with one accessible name, which only became reachable together once a
cached Home was always in the tree.
Four views had a heading and four did not, two had a sort control and
none showed a count, so the app changed shape as you moved through it
and "how many albums have I got" could only be answered by counting.
The reason they disagreed is that each had written its own arrangement:
the sort toolbar existed three times, in track-list, cover-grid and
playlist-view, as the same twenty lines with different bugs.
<page-header> is that arrangement once - title, count, sort, actions -
and nine views adopt it. Artists and Genres gain the sort control they
never had; Artists sorts by name only, because library.Artist carries
nothing countable, so the header renders a label and a direction button
rather than a select with one option in it. The header keeps its place
while a view loads: a heading that appears only once the data does is
the shifting layout this is meant to stop. The count is omitted, not
zero, until the view has an answer.
The header search box keeps its slot on every view instead of vanishing
on the ones it cannot serve - which is what moved the library filter
and the job indicator on every navigation. It is view-scoped by
decision and now says so: "Search albums" in the placeholder, the scope
named in the header ("Showing artists matching 'tide'"), and disabled
with a reason where there is nothing to search or the page has a search
of its own.
Also fixes an e2e trap this uncovered: the view-lifecycle spec toggled
shuffle and never toggled it back, so a second run against the same app
failed playback.spec's shuffle assertion - a failure that reads exactly
like a regression in whatever you are holding.
Each was written first and watched fail first:
- `view-lifecycle` — pressing `s` on Settings must not skip an album
out of the Autotag queue, and on Autotag the same key must not also
toggle shuffle.
- `player-truth` — the elapsed clock tracks the backend through steady
playback and four keyboard seeks (the measurement that failed by
30 s), a finished queue keeps the track on the bar at 0:00, and
auto-advance skips a missing file and reaches the next track.
- `offline-icons` — blocks every non-local request and asserts on the
<svg> *inside* each icon's shadow root, since asserting the element
exists would have passed before the fix too. Verified red: 24 empty
icons.
- `failure-voice` — induces a real binding failure through /__test/sql
and asserts a sentence appears. Its first version renamed the decoy
library to its own name, which the backend accepts: it failed at the
right assertion while never inducing the failure, so it now picks the
row by the seeded library's name from /__test/health.
- `play-count` — a finished track does not refetch the library.
The queue spec now opens the panel before asserting on its rows and
waits for it to close again: the panel's width is animated and the
transport slides with it, so a click issued during the close lands on
whichever button moved under the pointer.