Commit Graph
6 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 b7f50c42a9 feat(web): one track-list row shape, and drag reorder that snaps (M494, #5393)
release / govulncheck (push) Successful in 34s
release / go (push) Successful in 1m37s
release / web (push) Failing after 1m20s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 4m38s
release / android (push) Canceled after 4m39s
release / Build signed APK (releases and dev) (push) Canceled after 3m49s
Every track list (album, Liked, Search, Search → Tracks, playlist,
History) now draws its rows with TrackListRow inside a TrackList box,
in place of four row components that had drifted apart:

- TrackListRow owns the look: lead column, title over "artist · album",
  like, radio, add to queue, menu, duration, then a trailing slot. It
  also owns the stripe, hover, the now-playing band and the
  unavailable state. The stripe is dropped on now-playing and selected
  rows, where Tailwind would otherwise let `odd:` win.
- TrackRow, PlaylistTrackRow and HistoryRow are wrappers that keep only
  their behaviour: track number and multi-select; grip, remove and
  dead-entry states; cover and timestamp. Playlist and History rows
  gain radio, add and the menu, so every list offers the same actions.
- The radio control is Lucide's Radio icon, not the 📻 emoji.
- Enter on a control inside a row now belongs to that control. The old
  TrackRow played the track when Enter was pressed on "add to queue".
- TrackList has no fill, so the stripe shows on playlists too.

Drag reorder (#5393): neodrag kept the drop offset, so after the list
re-rendered the dragged row sat between two slots. reorderDrag, shared
by the playlist and queue rows:
- snaps the row to whole rows while dragging;
- resets it to its slot on drop;
- on playlists, gives the touch gesture to the grip only, so the list
  still scrolls by touch.
The playlist page reorders optimistically and rolls back on error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:14:06 -04:00
bvandeusenandClaude Opus 5.5 94a9c8cbe3 chore(deps): SvelteKit 3 with adapter-static 4 and TypeScript 6, full-tree npm audit (#5021)
Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps,
plus the migration they need. The mechanical part is `sv migrate
sveltekit-3`, run one task at a time and reviewed:

- svelte.config.js is gone. Its options move into sveltekit() in
  vite.config.ts, exported as kitOptions so vitest.config.ts runs the
  same kit setup, including the $test-utils alias the tests import.
- $lib becomes #lib through package.json "imports". There is no
  src/lib/index, so only the "#lib/*" entry is kept.
- tsconfig extends $app/tsconfig.
- Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite
  ^8.0.12, svelte-check ^4.7.5.

By hand, from the codemod's list of non-automated tasks:

- goto's replaceState option is now replace; keepFocus becomes
  reset: false. For the search typeahead, reset: false also stops the
  scroll-to-top, which is wanted while typing.
- The test setup mocks drop pushState/replaceState and $app/paths
  base/assets, which kit 3 removed, and mock refreshAll in place of
  invalidateAll.
- The other flagged files only read page.url or goto internal routes,
  so they needed no change.

TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares
typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to
7 once both accept it.

With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0,
so the web lane now audits every dependency rather than only what
ships to browsers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:50:39 -04:00
bvandeusen 859aff8d30 feat(web): smart empty-states with action affordances (Tier B6)
test-web / test (push) Successful in 32s
Replace dead-end empty copy with EmptyState cards that include a
clear next step.

- EmptyState.svelte: reusable card with `block` (whole-tab) and
  `inline` (sub-section) variants, an actions snippet for buttons.
- Artists / Albums: when the whole library is empty, link to
  /settings (the operator can scan a folder there).
- Liked: when all three sub-sections are empty, show a single
  whole-tab card ("No likes yet" with Explore Home + Browse albums).
  When only one sub-section is empty, an inline hint with a link
  to the corresponding library tab.
- History: "Listen to something" button → Home.
- Playlists: "Create a playlist" button calls the existing create
  flow; renamed from "New playlist" to avoid colliding with the
  header's button-by-name lookup in tests.

Liked tab tests updated to match: the previous "three 'no liked X
yet'" assertion is now a single onboarding card test + a sibling
test for the mixed populated/empty case.
2026-06-01 13:53:38 -04:00
bvandeusen be7be6f617 feat(web): quick-filter on Library tabs (Tier B5)
test-web / test (push) Successful in 33s
Add a debounced QuickFilter input at the top of each Library section
to narrow the loaded list without leaving the tab. Server-side
/search remains the route for full-library searches.

- QuickFilter.svelte: 120ms debounced two-way bound input with a
  clear button and Escape-to-clear.
- Artists: filter by name; empty-match copy includes a "Search the
  full library" link to /search/artists.
- Albums: filter by title + artist_name; same fallback link.
- Liked: filters all three sub-sections (artists/albums/tracks);
  sub-section header hides when its filtered length is zero.
- History: filters flatEvents before groupByDay, so day-grouping
  reflects the filter.
- Playlists: filters owned + public by name; owners CTA stays.

Covered by QuickFilter unit tests (debounce + clear + Escape).
2026-06-01 13:48:05 -04:00
bvandeusen cd0b9c97e1 fix(web/m7-365): align /library/history with albums-page conventions
Code-quality review flagged convention drift from sibling library
pages. Aligning so the history page matches the same shape:

- queryStore + $derived($queryStore) pattern (single subscription
  point) instead of $query.X everywhere.
- h1 uses font-display text-2xl font-medium per FabledSword design
  system (weights 400/500 only — font-semibold drifted to 600).
- h2 uses font-medium + z-10 for sticky-header layering.
- InfiniteScrollSentinel uses its `enabled` prop (which exists; the
  earlier spec note claiming otherwise was wrong) so the observer
  isn't recreated on every fetch cycle.
- Loading more… / End of history footers added to match albums.
- onRetry passes query.refetch by reference, not wrapped.
- Dropped redundant `as HistoryEvent[]` cast.

Also fixed test case 4 which trivially passed regardless of the
conditional gate's correctness — now queries the sentinel's actual
DOM root (div[aria-hidden="true"].h-px) and asserts presence/absence.
Added a positive twin test for hasNextPage=true.
2026-05-04 06:57:24 -04:00
bvandeusenandClaude Sonnet 4.6 ecf4ed86f5 feat(web/m7-365): /library/history page with day grouping + infinite scroll
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 06:53:37 -04:00