get_latest_record is a single fast ORDER BY LIMIT 1 query --
no reason to async-load it. Removes the <.async_result> wrapper
and the now-unnecessary render_async() from its test.
Move 7 non-critical queries out of synchronous mount into assign_async
and start_async tasks with skeleton loading placeholders.
Only scalar badge counters (collection count, wishlist count, scrobble
count) remain synchronous. All async sections use <.async_result> with
loading and failed slots. Scrobble activity preserves LiveView streams
via handle_async. On-this-day date changes use AsyncResult.ok/2.
Remove 12 unit tests that accessed socket internals via :sys.get_state
and called handle_async directly — these leak implementation details and
error/exit paths can't be reached through the DOM.
Keep the 3 integration delete tests that use pure DOM assertions: visit,
assert_has, click, refute_has, render_async, DB verification.
Add MusicLibrary.Collection.Enrichment module with batch scrobble, artist
country, and selected release enrichment from existing tables.
Wire enrichment into all four collection API endpoints (index, latest,
random, on_this_day). New JSON fields: scrobble_count, last_listened_at,
artist_country, selected_release.
Uses 3 fixed-count batch queries regardless of page size — no N+1 risk.
No schema changes or migrations required.
Cover current delete behavior across LiveView index and show paths, and strengthen existing delete tests with persistence assertions.\n\nValidation: mise run dev:precommit
Add live_action guard to handle_info({:update, record}) in both
CollectionLive.Show and WishlistLive.Show. When the user is editing,
background worker updates are skipped and a warning toast is shown
instead of overwriting the socket. Normal show-mode updates and
mismatched-ID no-ops are unchanged.
6 tests added covering all three code paths.
Replace direct LiveView PID and isolated component test patterns with PhoenixTest-backed page flows while keeping narrow unwrap bridges where PhoenixTest cannot express the component interaction.
Precommit: mise run dev:precommit
Delete ArchiveController, its routes, the maintenance UI backup
button, and the test file. Management scripts and Litestream
supersede this functionality.
Waves 1-2: scrobble_live/index, scrobbled_tracks_live/index, release_group_show, release_show. Dropped unwrap/2 bridges and page_title/1 usage. Search form tests use visit with query params. page_title tests use assert_has("title", ...).
Import workers now broadcast :records_index_changed on
"records:index_changed" after successful import via
Records.broadcast_index_changed/0.
CollectionLive.Index and WishlistLive.Index subscribe to the
topic in mount/3 and reload their record streams on receipt,
with a live_action guard to skip reloads when the grid is
hidden behind a modal (:import, :barcode_scan).
IndexActions.handle_index_changed/1 refreshes total_entries
before reloading to keep the pagination bar accurate.
Replace phx-click server event with Fluxon.open_dialog for zero-latency
modal opening. The modal DOM is now always present (no :if conditional) so
it can be shown instantly; the server syncs state in the background.
Fix test selectors that now match the always-present modal form by scoping
to forms without phx-target (which the modal form always has).
- show/2: use case instead of bare Integer.parse match so
/api/v1/errors/not-an-id returns 404 instead of crashing
- index/2: clamp limit to >= 1 and offset to >= 0 to prevent
SQLite from treating negative values as unlimited
- Add 3 tests: non-integer id 404, negative limit/offset clamping