From 7de1b4edc1f82d0bab22a1af7fc08e5daff63be4 Mon Sep 17 00:00:00 2001 From: Logan Date: Tue, 11 Aug 2026 01:15:39 -0400 Subject: [PATCH] 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 with the catalogPending/catalogLoaded distinction behind it. --- .../plans/completed/006-orientation-fixes.md | 89 +++++++++++++++++++ CLAUDE.md | 27 ++++++ 2 files changed, 116 insertions(+) create mode 100644 .planning/plans/completed/006-orientation-fixes.md diff --git a/.planning/plans/completed/006-orientation-fixes.md b/.planning/plans/completed/006-orientation-fixes.md new file mode 100644 index 0000000..084ef11 --- /dev/null +++ b/.planning/plans/completed/006-orientation-fixes.md @@ -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. +- `` — 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. +- ``: 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`. diff --git a/CLAUDE.md b/CLAUDE.md index 125597e..91494a6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -192,6 +192,13 @@ See `.planning/plans/active/005-agent-development-harness.md`. - `mediacontrols` — MPRIS integration on Linux via D-Bus. - `system` — OS-specific paths (XDG on Linux, `%LOCALAPPDATA%` on Windows). - `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/`). **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. +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. +- **``** 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/`. Emit through **`events.Emit(ctx, name, data...)`**, never