perf(explore): ask the disk once, prefetch once, and menu the releases
Three things on the Explore surfaces, all about not asking twice. A portrait already on disk costs no network call. explore-view seeded only from the library store — owned artists, which on a catalog search is nearly none of the results — and sent everything else to GetArtistImageURL, the resolving entry point, one await at a time. GetArtistImagesCachedPaths asks the disk about every unresolved artist in one call, and only what it does not answer reaches the resolver, in parallel. The artist page's two sections both wanted PrefetchReleases and each called it, so the most expensive call the app makes was issued twice for an overlapping set on a 1 req/s limiter. They are collected and sent once on a microtask, and prefetchRequested stops the cold-artist refetch re-asking for what it already asked for. The release cards — most of the artist page — had no context menu at all. They have one now on both release shapes, normalised to a ReleaseMenuTarget when the menu opens so the union does not reach the action handlers. It is a discriminated union rather than one nullable field per kind because the panel is shared with the track menu: that is what keeps aria-label moving with the target, which is the fault cover-grid shipped. Which items appear is three different questions — playback is gated on a local album id, not on "owned", and the request needs a catalog MBID, so it is absent for a library-only release. Note on the docs: the CLAUDE.md and NOTES.md prose here was reconstructed after a mishandled `git stash --keep-index` destroyed the uncommitted originals. One NOTES.md section is marked as incomplete where its text could not be recovered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDCbcCZQepnpSQYJ6SxxZm
This commit is contained in:
@@ -157,6 +157,34 @@ would be two copies free to drift.
|
||||
Bulk loads must suspend them: measured on a real import, assembly runs at
|
||||
~31 rows/s with the triggers attached and ~4,700 rows/s without.
|
||||
|
||||
They are now **dropped and recreated** on every open rather than created
|
||||
with "already exists" tolerated, because a trigger is a definition: an
|
||||
existing install would otherwise keep the first one it ever got, and
|
||||
these definitions are where this table's write cost is decided.
|
||||
`explore_index_au` is scoped to `UPDATE OF title, artist_name, aliases`
|
||||
with a `WHEN` guard on them having changed, so the common write — an
|
||||
upsert whose merge rules keep every existing value — re-indexes nothing.
|
||||
|
||||
## The writer stall that broke playback is only half explained (2026-08-14)
|
||||
|
||||
The user's report — play an album, the track changes, the transport
|
||||
stays paused, nothing appears in the queue, the play button does
|
||||
nothing — was diagnosed on the running app: 91% of its CPU was
|
||||
`BackfillLibraryDiscographies` → `upsertBatch`, with four of its six
|
||||
workers parked in `sql.(*DB).conn` waiting for the single write
|
||||
connection, while the play path did its writes inline under `q.mu` and
|
||||
`p.mu`. The locking half is fixed and tested (see CLAUDE.md, "Playing a
|
||||
track does not wait for the database to hear about it").
|
||||
|
||||
**What is not explained is why those upserts were so expensive.**
|
||||
|
||||
> **Recovered note, 2026-08-14.** The rest of this section was lost to a
|
||||
> mishandled `git stash --keep-index` before it was ever committed; what
|
||||
> survives above is verbatim, and the two arguments that followed
|
||||
> "Two things argue against the obvious answer" are gone. Re-derive them
|
||||
> from a profile before acting on this — do not treat the question as
|
||||
> answered.
|
||||
|
||||
## Explore "library only" toggle was removed (2026-08-06)
|
||||
|
||||
The Explore UI used to have a "library only" mode toggle
|
||||
|
||||
Reference in New Issue
Block a user