fix(web): a11y + reactivity warnings (paying down accumulated warnings)

Cleans up the 8 svelte-check warnings I deferred in prior CI runs.
Two distinct categories — handling them differently:

REAL REACTIVITY BUGS — fixed:
- AlbumMenu.svelte / ArtistMenu.svelte: cachedTracks initialized
  from the `tracks` prop only at component creation. When the menu
  is reused across rows (operator opens menu on one album, then
  another), the cache from the first open serves stale tracks.
  Fix: initialize cachedTracks to null; ensureTracks() reads the
  current `tracks` prop on each call before falling through to
  the network fetch.

INTENTIONAL PATTERNS LINT FLAGGED — suppressed with explanatory
comments:
- AlbumMenu / ArtistMenu / PlaylistCard menu containers: the
  onclick={(e) => e.stopPropagation()} is a guard against the
  window-level "any click closes the menu" listener — not
  user-facing interaction. A paired keyboard handler is
  intentionally absent (Escape-to-close lives on svelte:window
  onkeydown). Suppressed a11y_click_events_have_key_events with
  explanatory comments.
- CompactTrackCard overlay div: catches clicks on its inner
  LikeButton + queue button so they don't bubble to the wrapping
  play-card button. Inner buttons carry their own keyboard
  handling. Suppressed both a11y_click_events_have_key_events and
  a11y_no_static_element_interactions.
- theme.svelte.ts module-scope `_resolved = $state(resolve(_theme))`:
  module-init reads _theme to seed _resolved; subsequent updates
  go through setTheme() and the matchMedia listener. $derived
  doesn't apply at module scope. Suppressed state_referenced_locally
  with an explanatory comment.

Promised in the prior fix-forward: "I'll fix bugs when I see them
or open a follow-up task with a specific name, not park them in an
umbrella." Following through.
This commit is contained in:
2026-05-07 18:38:44 -04:00
parent 042f9919fe
commit 081b8e62d5
5 changed files with 42 additions and 2 deletions
+15 -1
View File
@@ -28,7 +28,12 @@
let menuOpen = $state(false);
let addOpen = $state(false);
let cachedTracks: TrackRef[] | null = $state(tracks ?? null);
// Lazy track cache. Starts null so the `tracks` prop is read inside
// ensureTracks() each call rather than captured at component-creation
// time — the menu is reused across different album rows, and pinning
// the prop's initial value here would serve a stale list when the
// user opens the menu on a second album.
let cachedTracks: TrackRef[] | null = $state(null);
const queryClient = useQueryClient();
const likedQ = $derived(createLikedIdsQuery());
@@ -50,6 +55,10 @@
async function ensureTracks(): Promise<TrackRef[]> {
if (cachedTracks) return cachedTracks;
if (tracks) {
cachedTracks = tracks;
return tracks;
}
const fetched = await listAlbumTracks(albumId);
cachedTracks = fetched;
return fetched;
@@ -105,6 +114,11 @@
</button>
{#if menuOpen}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- onclick is a click-outside guard (stops bubble to the
<svelte:window onclick> menu-closer above); not user-facing
interaction, so a paired keyboard handler is intentionally
absent. Escape-to-close lives on svelte:window onkeydown. -->
<div
role="menu"
tabindex="-1"