Commit Graph
4 Commits
Author SHA1 Message Date
bvandeusen d4c6bb3f2d feat(web): alphabet rail chases pages on click for unloaded letters
test-web / test (push) Successful in 32s
Operator: prior rail disabled empty buckets, so clicking a letter
like 'Z' did nothing if it wasn't loaded yet. AlphabeticalGrid now
accepts a paginated source and walks pages on click until the
target letter surfaces.

- New props: hasMore, onLoadMore. When hasMore=true, every empty
  bucket stays enabled (clicking will chase pages); when hasMore=
  false, only populated buckets are clickable.
- jumpTo(bucket): if populated, scrollIntoView. Otherwise loop
  onLoadMore + tick() until the bucket appears or no more pages.
  Loader2 spinner replaces the letter on the pending button;
  cursor:wait + aria-busy. Other rail buttons disable while one is
  pending so clicks don't stack.
- Library Artists + Albums pass hasMore + onLoadMore through to the
  grid. Existing InfiniteScrollSentinel still handles scroll-driven
  loading.

Test: new case asserts rail enables empty buckets when hasMore=true
(the click would trigger onLoadMore).
2026-06-01 22:29:13 -04:00
bvandeusen ad1a000ad5 feat(web): alphabet rail always shows #/A-Z/& with disabled empty buckets
test-web / test (push) Successful in 33s
Operator: prior rail only emitted buttons for letters with items, so
jumping to a letter required scrolling to that section first — not
useful as a navigation tool. Rail now always renders the full set so
the page reads as a stable A-Z reference regardless of which letters
are present.

- New bucketFor() classifies the first character into '#' (digits),
  'A'-'Z' (letters), or '&' (everything else). Anchor ids switch
  from per-letter to per-bucket: alpha-#, alpha-A, ..., alpha-&.
- ALPHABET + railEntries hardcode the full # / A-Z / & order.
- Buttons for empty buckets render disabled with a low-opacity tint
  + cursor:default so they read as 'no entries here' rather than
  broken jumps. aria-label changes too — 'Jump to A' (enabled) vs
  'C — no entries' (disabled) so screen readers announce state.
- # / & get verbose labels ('numbers'/'symbols') because the bare
  glyph isn't readable.

Tests rewritten — 4 cases: full-rail-with-disabled-buckets,
DOM-order, populated-bucket ids, and a separate fixture confirming
digit-starting items bucket under '#'.
2026-06-01 21:47:10 -04:00
bvandeusen 93aa37c7b6 feat(web): Library — continuous grid + sticky alphabet rail
test-web / test (push) Successful in 33s
Operator UI review: artists/albums layout had a full-row letter
divider per first-character, so a section like '2' (one artist)
left 9 empty cells before '8' (one artist) started a new row. Lots
of dead space across letters with few entries.

Operator chose option 2: drop the dividers, add a side jump-bar.

- AlphabeticalGrid no longer renders per-letter row-spanning
  dividers. Items flow continuously across the grid; first item
  of each letter gets id='alpha-<letter>' so the rail can target
  it via scrollIntoView.
- New sticky vertical alphabet rail on the right. position:sticky
  + top:50% + translateY keeps it vertically centered in the
  viewport while the grid scrolls underneath.
- Each rail entry is a focusable button with aria-label='Jump to
  <letter>'. Hover/focus tinted with accent.
- Only renders when there's more than one distinct letter so
  small libraries (or filter-narrowed views) don't get an empty
  rail.

Test rewritten — letters are buttons on the rail, not dividers in
the grid. Three assertions:
- one jump button per distinct first-letter
- DOM order is items first then rail (was rail-then-items before)
- first item of each letter carries the alpha-<letter> id
2026-06-01 21:23:23 -04:00
bvandeusenandClaude Sonnet 4.6 cf18d51394 feat(web): add AlphabeticalGrid wrapper with letter dividers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 20:21:34 -04:00