docs: record the orientation fixes and the two new frontend fixtures
CLAUDE.md gains backend/home and the two cross-cutting frontend pieces a list or detail view now has to know about: explore-link's always-navigate rule with its double-click grace, and <catalog-scope-notice> with the catalogPending/catalogLoaded distinction behind it.
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
# 006 — Orientation fixes: knowing where you are and what you're looking at
|
||||||
|
|
||||||
|
**Status:** implemented
|
||||||
|
**Branch:** main
|
||||||
|
**Created:** 2026-08-11
|
||||||
|
**Follows:** 005-agent-development-harness
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Six reports from using the app, which turned out to be one theme with
|
||||||
|
six faces: **the UI knew things it did not say.**
|
||||||
|
|
||||||
|
1. Some track names in the track list were links, most were not. The
|
||||||
|
rule (has both a release-group *and* a recording MBID) was invisible,
|
||||||
|
so the list looked randomly broken.
|
||||||
|
2. Opening an album sometimes showed the full catalog tracklist and
|
||||||
|
sometimes only the tracks the user owned, with nothing on screen
|
||||||
|
distinguishing the two — or distinguishing either from "still
|
||||||
|
fetching".
|
||||||
|
3. The same on artist pages: a discography that was the artist's, or a
|
||||||
|
discography that was the user's shelf, rendered identically.
|
||||||
|
4. "Check now" on the requests list appeared to do nothing, because it
|
||||||
|
honoured each request's retry backoff — a request searched an hour
|
||||||
|
ago was not due, so a deliberate button press produced silence.
|
||||||
|
5. Pressing **M** muted playback and left the volume indicator
|
||||||
|
unchanged, because mute does not change the volume *number* and
|
||||||
|
`VolumeChanged` carried nothing else.
|
||||||
|
6. The home page did not exist. The sidebar had a Home item; it fell
|
||||||
|
through to "Coming soon: home".
|
||||||
|
|
||||||
|
## What shipped
|
||||||
|
|
||||||
|
**Backend**
|
||||||
|
|
||||||
|
- `events.MuteChanged` (bool), emitted alongside `VolumeChanged` so the
|
||||||
|
UI has something to react to when silence is the only thing that
|
||||||
|
changed. `Player.Muted()` for symmetry; `MuteToggle` now takes the
|
||||||
|
speaker lock and refuses politely when no streamer exists.
|
||||||
|
- `download.Reconciler.RunNow` — a forced pass that ignores backoff,
|
||||||
|
backed by a new `ListWantedDownloadRequests` query. `RunOnce` (the
|
||||||
|
loop) still honours it: the backoff is a promise to the providers,
|
||||||
|
not to the user, and a person pressing a button *is* the schedule.
|
||||||
|
`Summary` gained `Waiting` and `NoProviders` so "nothing happened"
|
||||||
|
can be reported with a reason.
|
||||||
|
- `backend/home` — the shelf builder, with queries in
|
||||||
|
`sql/queries/home.sql` that return album ids only, joined back to
|
||||||
|
`GetAllAlbumsWithDetails` in Go rather than restating the album
|
||||||
|
projection six times. A shelf with nothing behind it is omitted.
|
||||||
|
|
||||||
|
**Frontend**
|
||||||
|
|
||||||
|
- `explore-link.ts` rewritten: a name always goes somewhere. No MBID
|
||||||
|
means the *library* page for the same album/artist (both detail views
|
||||||
|
already accept a local id), resolved through the library store, with
|
||||||
|
an untagged track highlighted by title instead of by recording MBID.
|
||||||
|
Links now fire on a genuine single click only — see below.
|
||||||
|
- `<catalog-scope-notice>` — one banner, four states (`catalog`,
|
||||||
|
`loading`, `library`, `unavailable`), used by both detail pages. The
|
||||||
|
album and artist pages grew an explicit `catalogPending` /
|
||||||
|
`catalogLoaded` pair, because `loadingReleases` already meant
|
||||||
|
"something is renderable" and a library stand-in satisfies that.
|
||||||
|
- Artist page: an empty `BrowseReleaseGroups` no longer wipes the
|
||||||
|
library-hydrated discography — an empty catalog answer means "not
|
||||||
|
indexed yet", not "released nothing".
|
||||||
|
- Downloads: a no-client banner, per-request "next check in …", honest
|
||||||
|
idle summaries, and copy that says the retry schedule exists.
|
||||||
|
- `<home-view>`: shelves as horizontal rows; a cover opens the album, a
|
||||||
|
play button plays it.
|
||||||
|
|
||||||
|
## The one thing worth remembering
|
||||||
|
|
||||||
|
**Making every track name a link broke double-click-to-play**, and the
|
||||||
|
e2e playback suite caught it: the title is the widest thing in a row,
|
||||||
|
so the first click of the double-click landed on the link and navigated
|
||||||
|
away. Fixed in one place — `singleClick()` in `explore-link.ts` holds
|
||||||
|
the navigation for one double-click interval (250 ms) and drops it if a
|
||||||
|
`dblclick` arrives, while leaving the dblclick itself to bubble to the
|
||||||
|
row. Rows do not need to know links exist.
|
||||||
|
|
||||||
|
This is exactly the failure mode plan 005's e2e tier was built for; it
|
||||||
|
was invisible before the change because the seeded fixture library has
|
||||||
|
no MBIDs, so no track name was a link.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`make lint` (3 configs), `make test` (3 passes), `make ui-test`
|
||||||
|
(329 passing, up from 313), `make e2e` (23 passing, up from 19 — four
|
||||||
|
new home-page specs), `tsc --noEmit`, and manual verification of all
|
||||||
|
six items in the running app via `make dev-headless` + `playwright-cli`.
|
||||||
@@ -192,6 +192,13 @@ See `.planning/plans/active/005-agent-development-harness.md`.
|
|||||||
- `mediacontrols` — MPRIS integration on Linux via D-Bus.
|
- `mediacontrols` — MPRIS integration on Linux via D-Bus.
|
||||||
- `system` — OS-specific paths (XDG on Linux, `%LOCALAPPDATA%` on Windows).
|
- `system` — OS-specific paths (XDG on Linux, `%LOCALAPPDATA%` on Windows).
|
||||||
- `explore` — Catalog search and browse over `explore_index`. See below.
|
- `explore` — Catalog search and browse over `explore_index`. See below.
|
||||||
|
- `home` — The home page's "start listening" shelves. Each shelf is a
|
||||||
|
*reason* (what you played last, what you never played, a genre you
|
||||||
|
have depth in) rather than a filter, and carries the sentence that
|
||||||
|
says so. Its queries (`sql/queries/home.sql`) return album ids only
|
||||||
|
and are joined back to `GetAllAlbumsWithDetails` in Go, so the album
|
||||||
|
projection has one definition. A shelf with nothing behind it is
|
||||||
|
omitted, never rendered empty.
|
||||||
- `profiling` — pprof server on `:6060`, compiled out in non-dev builds via build tags (`internal/dev/`).
|
- `profiling` — pprof server on `:6060`, compiled out in non-dev builds via build tags (`internal/dev/`).
|
||||||
|
|
||||||
**Explore catalog** (`backend/explore/`): the searchable MusicBrainz/
|
**Explore catalog** (`backend/explore/`): the searchable MusicBrainz/
|
||||||
@@ -217,6 +224,26 @@ work happens **once, centrally**, and users download the result:
|
|||||||
|
|
||||||
**Frontend** (`frontend/`): Lit 3.2 web components + Web Awesome UI library + HTMX. State management via singleton reactive stores in `src/store/`. Wails bindings auto-generated in `frontend/wailsjs/` — don't edit by hand.
|
**Frontend** (`frontend/`): Lit 3.2 web components + Web Awesome UI library + HTMX. State management via singleton reactive stores in `src/store/`. Wails bindings auto-generated in `frontend/wailsjs/` — don't edit by hand.
|
||||||
|
|
||||||
|
Two cross-cutting pieces of that UI are worth knowing before touching
|
||||||
|
a list or a detail view:
|
||||||
|
|
||||||
|
- **`utils/explore-link.ts`** renders every track/album/artist name in
|
||||||
|
the app. A name always navigates: to the MusicBrainz page when the
|
||||||
|
entity is tagged, and to the *library* page for the same thing when
|
||||||
|
it is not (`explore-album-details` and `explore-artist-details` both
|
||||||
|
accept a local id instead of an MBID). It fires on a genuine single
|
||||||
|
click only — the navigation is held for one double-click interval
|
||||||
|
and dropped if a second click arrives, because the title is the
|
||||||
|
widest thing in a row and double-clicking a row plays it. Rows do
|
||||||
|
not need to know links exist.
|
||||||
|
- **`<catalog-scope-notice>`** is how a detail page admits what it is
|
||||||
|
showing: catalog data (silent), a library stand-in while a fetch is
|
||||||
|
in flight, library-only because the entity has no MBID, or a failed/
|
||||||
|
empty catalog answer with a retry. Both detail views track
|
||||||
|
`catalogPending`/`catalogLoaded` separately from their loading flags,
|
||||||
|
since "something is renderable" and "this is the catalog's answer"
|
||||||
|
are different questions.
|
||||||
|
|
||||||
**Event-driven communication**: Backend emits events via Wails runtime; frontend stores subscribe to them. Event names are constants in `backend/events/`.
|
**Event-driven communication**: Backend emits events via Wails runtime; frontend stores subscribe to them. Event names are constants in `backend/events/`.
|
||||||
|
|
||||||
Emit through **`events.Emit(ctx, name, data...)`**, never
|
Emit through **`events.Emit(ctx, name, data...)`**, never
|
||||||
|
|||||||
Reference in New Issue
Block a user