@behindthemusictree/app-kit-ui 40.1.0 → 41.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -55,6 +55,7 @@ var import_lucide_react14 = require("lucide-react");
55
55
  var import_ui21 = require("@behindthemusictree/ui");
56
56
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
57
57
  var import_genre_tree30 = require("@behindthemusictree/app-kit/genre-tree");
58
+ var import_genre_tree31 = require("@behindthemusictree/app-kit/genre-tree");
58
59
  var import_popup4 = require("@behindthemusictree/app-kit/popup");
59
60
 
60
61
  // src/genre-tree/playlist-tree/size-metric.ts
@@ -1402,6 +1403,29 @@ function ClampedText({ children, className: className2 = "" }) {
1402
1403
  ] });
1403
1404
  }
1404
1405
 
1406
+ // src/genre-tree/CommonsPhoto.tsx
1407
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1408
+ function ExternalLink({ href, children }) {
1409
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1410
+ }
1411
+ function CommonsPhoto({
1412
+ image,
1413
+ alt,
1414
+ creditLabel = "Photo"
1415
+ }) {
1416
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("figure", { className: "flex flex-col gap-1.5", children: [
1417
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("img", { src: image.url, alt, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1418
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("figcaption", { className: "text-caption text-[var(--color-text-muted)]", children: [
1419
+ creditLabel,
1420
+ ": ",
1421
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1422
+ ",",
1423
+ " ",
1424
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1425
+ ] })
1426
+ ] });
1427
+ }
1428
+
1405
1429
  // src/genre-tree/GenrePlaylistTracks.tsx
1406
1430
  var import_react13 = require("react");
1407
1431
  var import_react_query = require("@tanstack/react-query");
@@ -1424,15 +1448,15 @@ var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
1424
1448
 
1425
1449
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1426
1450
  var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1427
- var import_jsx_runtime26 = require("react/jsx-runtime");
1451
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1428
1452
  function MusicBrainzRecordingLink({
1429
1453
  song,
1430
1454
  children,
1431
1455
  className: className2 = ""
1432
1456
  }) {
1433
1457
  const url = (0, import_genre_tree11.musicBrainzRecordingUrl)(song);
1434
- if (!url) return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_jsx_runtime26.Fragment, { children });
1435
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1458
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_jsx_runtime27.Fragment, { children });
1459
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1436
1460
  "a",
1437
1461
  {
1438
1462
  href: url,
@@ -1449,7 +1473,7 @@ function MusicBrainzRecordingLink({
1449
1473
  // src/genre-tree/GenrePlaylistTracks.tsx
1450
1474
  var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
1451
1475
  var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
1452
- var import_jsx_runtime27 = require("react/jsx-runtime");
1476
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1453
1477
  var CATALOGUE_PAGE_SIZE = 100;
1454
1478
  var SongPageSchema = (0, import_transport3.PaginatedResponseSchema)(import_genre_tree15.SongDetailedSchema).extend({
1455
1479
  overallTotal: import_zod.z.number().nullable(),
@@ -1536,35 +1560,35 @@ function GenrePlaylistTracks({
1536
1560
  started.catch((error) => {
1537
1561
  console.error("Failed to play genre playlist:", error);
1538
1562
  if (error instanceof import_genre_tree16.TracksFailureAlreadyReportedError) return;
1539
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(InternalErrorPopup, { errorCode: error instanceof import_transport5.AppError ? error.code : import_transport5.ErrorCode.CLIENT_UNKNOWN }));
1563
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime28.jsx)(InternalErrorPopup, { errorCode: error instanceof import_transport5.AppError ? error.code : import_transport5.ErrorCode.CLIENT_UNKNOWN }));
1540
1564
  });
1541
1565
  };
1542
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui8.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { children: [
1566
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { children: [
1543
1567
  renderTracksToolbar?.(),
1544
- scope === "me" && !genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { role: "alert", children: [
1568
+ scope === "me" && !genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { role: "alert", children: [
1545
1569
  "Couldn't load ",
1546
1570
  trackNoun.plural,
1547
1571
  ".",
1548
1572
  " ",
1549
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1550
- ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-body [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1573
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1574
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-body [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1551
1575
  tracks.map((track, index) => {
1552
1576
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1553
1577
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1554
1578
  const isPlaying = isCurrent && playState === import_player6.PlayStates.PLAYING;
1555
- const text = /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: TEXT_CELL, children: [
1556
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree15.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1557
- track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1558
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_genre_tree17.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1559
- track.album && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1579
+ const text = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: TEXT_CELL, children: [
1580
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree15.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1581
+ track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1582
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree17.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1583
+ track.album && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1560
1584
  ] });
1561
1585
  const leading = renderSongLeading?.(track, index);
1562
1586
  const actions = renderSongActions?.(track);
1563
1587
  const isUnplayable = (0, import_genre_tree15.isSong)(track) && (0, import_genre_tree15.songUnplayableReason)(track) !== null;
1564
1588
  if (isUnplayable) {
1565
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1589
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1566
1590
  leading,
1567
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1591
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1568
1592
  "button",
1569
1593
  {
1570
1594
  type: "button",
@@ -1572,27 +1596,27 @@ function GenrePlaylistTracks({
1572
1596
  "aria-disabled": "true",
1573
1597
  "aria-label": label,
1574
1598
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1575
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa2.FaPlay, { size: 10 })
1599
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa2.FaPlay, { size: 10 })
1576
1600
  }
1577
1601
  ),
1578
1602
  text,
1579
1603
  actions
1580
1604
  ] }, track.uuid);
1581
1605
  }
1582
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1606
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1583
1607
  "li",
1584
1608
  {
1585
1609
  className: `group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? "font-medium text-[var(--color-primary)]" : ""}`,
1586
1610
  children: [
1587
1611
  leading,
1588
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1612
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1589
1613
  "button",
1590
1614
  {
1591
1615
  type: "button",
1592
1616
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1593
1617
  className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== import_player6.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
1594
1618
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1595
- children: isCurrent && playState === import_player6.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui8.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa2.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa2.FaPlay, { size: 10 })
1619
+ children: isCurrent && playState === import_player6.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa2.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa2.FaPlay, { size: 10 })
1596
1620
  }
1597
1621
  ),
1598
1622
  text,
@@ -1602,7 +1626,7 @@ function GenrePlaylistTracks({
1602
1626
  track.uuid
1603
1627
  );
1604
1628
  }),
1605
- hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1629
+ hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1606
1630
  ] })
1607
1631
  ] }) });
1608
1632
  }
@@ -1610,7 +1634,7 @@ function GenrePlaylistTracks({
1610
1634
  // src/genre-tree/GenreDetailExtras.tsx
1611
1635
  var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1612
1636
  var import_si2 = require("react-icons/si");
1613
- var import_jsx_runtime28 = require("react/jsx-runtime");
1637
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1614
1638
  function GenreDetailExtras({
1615
1639
  genreUuid,
1616
1640
  genrePlaylist,
@@ -1636,17 +1660,18 @@ function GenreDetailExtras({
1636
1660
  criteriaOverviewSchema
1637
1661
  );
1638
1662
  if (isPending) {
1639
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1640
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui9.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui9.Skeleton, { className: "h-4 w-3/4" }) }) }),
1663
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1664
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.Skeleton, { className: "h-4 w-3/4" }) }) }),
1641
1665
  family
1642
1666
  ] });
1643
1667
  }
1644
- if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_jsx_runtime28.Fragment, { children: family });
1645
- const { summary, summarySourceUrl, essentialSongs } = overview;
1668
+ if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_jsx_runtime29.Fragment, { children: family });
1669
+ const { summary, summarySourceUrl, essentialSongs, image } = overview;
1646
1670
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1647
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
1648
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui9.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-caption text-[var(--color-text)]", children: [
1649
- summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1671
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1672
+ image && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(CommonsPhoto, { image, alt: overview.name, creditLabel: "Image" }),
1673
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-caption text-[var(--color-text)]", children: [
1674
+ summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1650
1675
  "a",
1651
1676
  {
1652
1677
  href: summarySourceUrl,
@@ -1654,12 +1679,12 @@ function GenreDetailExtras({
1654
1679
  rel: "noopener noreferrer",
1655
1680
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1656
1681
  children: [
1657
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1682
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1658
1683
  "Wikipedia"
1659
1684
  ]
1660
1685
  }
1661
1686
  ),
1662
- wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1687
+ wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1663
1688
  "a",
1664
1689
  {
1665
1690
  href: wikidataUrl,
@@ -1667,17 +1692,17 @@ function GenreDetailExtras({
1667
1692
  rel: "noopener noreferrer",
1668
1693
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1669
1694
  children: [
1670
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1695
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1671
1696
  "Wikidata"
1672
1697
  ]
1673
1698
  }
1674
1699
  )
1675
1700
  ] }) }),
1676
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui9.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ClampedText, { className: "text-body text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1701
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ClampedText, { className: "text-body text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1677
1702
  family,
1678
1703
  renderGenreDetailExtras?.(overview),
1679
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui9.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "text-body text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-body text-[var(--color-text)]", children: "\u2014" }) }),
1680
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1704
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "text-body text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-body text-[var(--color-text)]", children: "\u2014" }) }),
1705
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1681
1706
  GenrePlaylistTracks,
1682
1707
  {
1683
1708
  genreUuid,
@@ -1695,10 +1720,10 @@ function GenreDetailExtras({
1695
1720
  }
1696
1721
 
1697
1722
  // src/genre-tree/GenreInfoPanelBody.tsx
1698
- var import_jsx_runtime29 = require("react/jsx-runtime");
1723
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1699
1724
  var import_react14 = require("react");
1700
1725
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1701
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(GenreActionBand, { ...bandProps, playActions: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(GenrePlayActions, { genrePlaylist, playback }) });
1726
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GenreActionBand, { ...bandProps, playActions: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GenrePlayActions, { genrePlaylist, playback }) });
1702
1727
  }
1703
1728
  function GenreInfoPanelBody({
1704
1729
  toolbar,
@@ -1715,9 +1740,9 @@ function GenreInfoPanelBody({
1715
1740
  }) {
1716
1741
  const summary = scope === "reference" ? import_genre_tree19.GenreCatalogueCountsSchema.parse(genrePlaylist ?? catalogue) : void 0;
1717
1742
  const selection = summary ? (0, import_genre_tree19.defaultGenreMusicSelection)(summary) : void 0;
1718
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1743
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1719
1744
  toolbar,
1720
- genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(GenreActionBand, { ...band }),
1745
+ genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GenreActionBand, { ...band }),
1721
1746
  /* @__PURE__ */ (0, import_react14.createElement)(
1722
1747
  GenreDetailExtras,
1723
1748
  {
@@ -1737,7 +1762,7 @@ function GenreInfoPanelBody({
1737
1762
  }
1738
1763
 
1739
1764
  // src/genre-tree/GenreDetailPanel.tsx
1740
- var import_jsx_runtime30 = require("react/jsx-runtime");
1765
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1741
1766
  var RELATIVE_ITEM_COUNT = 1;
1742
1767
  function toNode(genre, parentId) {
1743
1768
  return {
@@ -1762,7 +1787,8 @@ function useGenreDetailContent({
1762
1787
  expanded,
1763
1788
  treeStyles
1764
1789
  }) {
1765
- const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
1790
+ const result = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
1791
+ const detail = result.status === "success" ? result.genre : void 0;
1766
1792
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: [], artist: extras?.artist });
1767
1793
  const playback = hostPlayback ?? defaultPlayback;
1768
1794
  const view = (0, import_react15.useMemo)(() => {
@@ -1791,22 +1817,27 @@ function useGenreDetailContent({
1791
1817
  )
1792
1818
  };
1793
1819
  }, [detail, scope, treeStyles]);
1794
- if (isError) return {
1820
+ if (result.status === "not-found") return {
1821
+ name: null,
1822
+ title: "Genre not found",
1823
+ content: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This genre does not exist." })
1824
+ };
1825
+ if (result.status === "error") return {
1795
1826
  name: null,
1796
1827
  title: "Genre unavailable",
1797
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { role: "alert", className: "text-body text-[var(--color-text)]", children: "This genre could not be loaded." })
1828
+ content: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { role: "alert", className: "text-body text-[var(--color-text)]", children: "This genre could not be loaded." })
1798
1829
  };
1799
- if (isPending || !detail || !view) return {
1830
+ if (!detail || !view) return {
1800
1831
  name: null,
1801
1832
  title: "Loading genre\u2026",
1802
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col gap-3", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
1803
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.Skeleton, { className: "h-6 w-2/3" }),
1804
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.Skeleton, { className: "h-4 w-full" }),
1805
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.Skeleton, { className: "h-4 w-3/4" })
1833
+ content: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col gap-3", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
1834
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui10.Skeleton, { className: "h-6 w-2/3" }),
1835
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui10.Skeleton, { className: "h-4 w-full" }),
1836
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui10.Skeleton, { className: "h-4 w-3/4" })
1806
1837
  ] })
1807
1838
  };
1808
1839
  const selectGenre = (nodeId) => onSelectGenre(import_genre_tree20.GenreUuidSchema.parse(nodeId));
1809
- const family = /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1840
+ const family = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1810
1841
  import_genre_tree_view4.GenreNodeFamily,
1811
1842
  {
1812
1843
  node: view.node,
@@ -1817,17 +1848,17 @@ function useGenreDetailContent({
1817
1848
  onSelectNode: selectGenre
1818
1849
  }
1819
1850
  );
1820
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
1851
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
1821
1852
  return {
1822
1853
  name: detail.name,
1823
1854
  title: detail.name,
1824
1855
  headerAccent: { background: view.fill, color: view.textColor },
1825
1856
  aside: expanded ? ancestors : void 0,
1826
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1827
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1857
+ content: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
1858
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1828
1859
  GenreInfoPanelBody,
1829
1860
  {
1830
- family: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1861
+ family: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
1831
1862
  family,
1832
1863
  !expanded && ancestors
1833
1864
  ] }),
@@ -1871,7 +1902,7 @@ function GenreDetailPanel({
1871
1902
  expanded,
1872
1903
  band: { onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 }
1873
1904
  });
1874
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1905
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1875
1906
  import_ui10.DetailPanel,
1876
1907
  {
1877
1908
  side: "left",
@@ -1884,7 +1915,7 @@ function GenreDetailPanel({
1884
1915
  edgeTab,
1885
1916
  aside: panel.aside,
1886
1917
  titleInSearch,
1887
- headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_lucide_react9.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_lucide_react9.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
1918
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui10.IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react9.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react9.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
1888
1919
  children: panel.content
1889
1920
  }
1890
1921
  );
@@ -1893,7 +1924,7 @@ function GenreDetailPanel({
1893
1924
  // src/genre-tree/ChangeParentPopup.tsx
1894
1925
  var import_react16 = require("react");
1895
1926
  var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
1896
- var import_jsx_runtime31 = require("react/jsx-runtime");
1927
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1897
1928
  function ChangeParentPopup({
1898
1929
  genrePlaylist,
1899
1930
  genrePlaylists,
@@ -1928,12 +1959,12 @@ function ChangeParentPopup({
1928
1959
  window.addEventListener("keydown", handleKeyDown);
1929
1960
  return () => window.removeEventListener("keydown", handleKeyDown);
1930
1961
  }, [onClose]);
1931
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1932
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("p", { className: "mb-3 text-body text-[var(--color-text-muted)]", children: [
1962
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1963
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("p", { className: "mb-3 text-body text-[var(--color-text-muted)]", children: [
1933
1964
  "Current parent: ",
1934
1965
  genrePlaylist.parent?.name ?? "none"
1935
1966
  ] }),
1936
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1967
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1937
1968
  GenreSearch,
1938
1969
  {
1939
1970
  genrePlaylists: candidates,
@@ -1941,7 +1972,7 @@ function ChangeParentPopup({
1941
1972
  placeholder: "Search a new parent\u2026"
1942
1973
  }
1943
1974
  ) }),
1944
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { role: "alert", className: "mt-3 text-body text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1975
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { role: "alert", className: "mt-3 text-body text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1945
1976
  ] });
1946
1977
  }
1947
1978
 
@@ -1954,7 +1985,7 @@ var import_lucide_react10 = require("lucide-react");
1954
1985
  var import_ui11 = require("@behindthemusictree/ui");
1955
1986
  var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
1956
1987
  var import_player7 = require("@behindthemusictree/app-kit/player");
1957
- var import_jsx_runtime32 = require("react/jsx-runtime");
1988
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1958
1989
  function ArtistActionBand({ artist, share }) {
1959
1990
  const { trackList, playNewTrackListFromArtist } = (0, import_genre_tree22.useTrackList)();
1960
1991
  const { isPlaying, setIsPlaying } = (0, import_player7.usePlayer)();
@@ -1970,11 +2001,11 @@ function ArtistActionBand({ artist, share }) {
1970
2001
  console.error("Failed to load artist tracks:", error);
1971
2002
  });
1972
2003
  };
1973
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui11.PanelBleed, { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_ui11.ActionBand, { children: [
1974
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2004
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.PanelBleed, { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_ui11.ActionBand, { children: [
2005
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1975
2006
  import_ui11.ActionButton,
1976
2007
  {
1977
- icon: playing ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
2008
+ icon: playing ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1978
2009
  label: playing ? "Pause" : "Play",
1979
2010
  caption: String(artist.playableTracksCount),
1980
2011
  disabled: artist.playableTracksCount === 0,
@@ -1993,7 +2024,7 @@ var import_ui13 = require("@behindthemusictree/ui");
1993
2024
  var import_lucide_react11 = require("lucide-react");
1994
2025
  var import_si3 = require("react-icons/si");
1995
2026
  var import_ui12 = require("@behindthemusictree/ui");
1996
- var import_jsx_runtime33 = require("react/jsx-runtime");
2027
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1997
2028
  var LINK_KINDS = {
1998
2029
  official: { label: "Official website", Icon: import_lucide_react11.Globe },
1999
2030
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
@@ -2030,7 +2061,7 @@ function artistLinks(artist) {
2030
2061
  return links;
2031
2062
  }
2032
2063
  function ArtistLinks({ links }) {
2033
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui12.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
2064
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
2034
2065
  }
2035
2066
 
2036
2067
  // src/genre-tree/artist/formatYears.ts
@@ -2041,7 +2072,7 @@ function formatYears(beginYear, endYear, ended) {
2041
2072
  }
2042
2073
 
2043
2074
  // src/genre-tree/artist/ArtistHeader.tsx
2044
- var import_jsx_runtime34 = require("react/jsx-runtime");
2075
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2045
2076
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
2046
2077
  function ArtistHeader({ artist }) {
2047
2078
  const fields = [];
@@ -2051,35 +2082,23 @@ function ArtistHeader({ artist }) {
2051
2082
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
2052
2083
  if (years) fields.push({ label: "Active", value: years });
2053
2084
  const links = artistLinks(artist);
2054
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistLinks, { links }) });
2085
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ArtistLinks, { links }) });
2055
2086
  if (fields.length === 0) return null;
2056
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui13.FieldList, { items: fields });
2087
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.FieldList, { items: fields });
2057
2088
  }
2058
2089
 
2059
2090
  // src/genre-tree/artist/ArtistPhoto.tsx
2060
- var import_jsx_runtime35 = require("react/jsx-runtime");
2061
- function ExternalLink({ href, children }) {
2062
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
2063
- }
2091
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2064
2092
  function ArtistPhoto({ image, name }) {
2065
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("figure", { className: "flex flex-col gap-1.5", children: [
2066
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
2067
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("figcaption", { className: "text-caption text-[var(--color-text-muted)]", children: [
2068
- "Photo: ",
2069
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
2070
- ",",
2071
- " ",
2072
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
2073
- ] })
2074
- ] });
2093
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(CommonsPhoto, { image, alt: name });
2075
2094
  }
2076
2095
 
2077
2096
  // src/genre-tree/artist/ArtistMembers.tsx
2078
2097
  var import_ui14 = require("@behindthemusictree/ui");
2079
- var import_jsx_runtime36 = require("react/jsx-runtime");
2098
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2080
2099
  function MembershipList({ title, memberships, renderArtistLink }) {
2081
2100
  if (memberships.length === 0) return null;
2082
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { children: memberships.map((membership, index) => {
2101
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: memberships.map((membership, index) => {
2083
2102
  const details = [
2084
2103
  formatYears(membership.beginYear, membership.endYear, membership.ended),
2085
2104
  ...membership.roles
@@ -2087,9 +2106,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2087
2106
  const detailsText = details.filter(Boolean).join(" \xB7 ");
2088
2107
  return (
2089
2108
  // A member can have several periods in the same group.
2090
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2091
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2092
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2109
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2110
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2111
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2093
2112
  " \u2014 ",
2094
2113
  detailsText
2095
2114
  ] })
@@ -2100,9 +2119,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2100
2119
  function ArtistMembers({ title, memberships, renderArtistLink }) {
2101
2120
  if (memberships.length === 0) return null;
2102
2121
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
2103
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_ui14.PanelSection, { title, children: [
2104
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2105
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2122
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_ui14.PanelSection, { title, children: [
2123
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2124
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2106
2125
  MembershipList,
2107
2126
  {
2108
2127
  title: "Past",
@@ -2115,7 +2134,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
2115
2134
 
2116
2135
  // src/genre-tree/artist/ArtistDiscography.tsx
2117
2136
  var import_ui15 = require("@behindthemusictree/ui");
2118
- var import_jsx_runtime37 = require("react/jsx-runtime");
2137
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2119
2138
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
2120
2139
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
2121
2140
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -2135,12 +2154,12 @@ function byYear(a, b) {
2135
2154
  function ArtistDiscography({ albums }) {
2136
2155
  if (albums.length === 0) return null;
2137
2156
  const sorted = [...albums].sort(byYear);
2138
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2157
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2139
2158
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
2140
2159
  if (groupAlbums.length === 0) return null;
2141
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2142
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2143
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2160
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2161
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2162
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2144
2163
  ] }, album.uuid)) }) }, group);
2145
2164
  }) });
2146
2165
  }
@@ -2161,7 +2180,7 @@ var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
2161
2180
  var import_fa3 = require("react-icons/fa");
2162
2181
  var import_player8 = require("@behindthemusictree/app-kit/player");
2163
2182
  var import_player9 = require("@behindthemusictree/app-kit/player");
2164
- var import_jsx_runtime38 = require("react/jsx-runtime");
2183
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2165
2184
  function TrackPositionPlayPause({
2166
2185
  position,
2167
2186
  uuid,
@@ -2175,14 +2194,14 @@ function TrackPositionPlayPause({
2175
2194
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player9.PlayStates.PLAYING;
2176
2195
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player9.PlayStates.PAUSED;
2177
2196
  const isCurrentTrackActive = isPlaying || isPaused;
2178
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2197
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
2179
2198
  "div",
2180
2199
  {
2181
2200
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
2182
2201
  style: { minWidth: "4rem", maxWidth: "4rem" },
2183
2202
  onClick: handlePlayPauseClick,
2184
2203
  children: [
2185
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2204
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
2186
2205
  "span",
2187
2206
  {
2188
2207
  className: "track-position-play-pause__bars",
@@ -2190,18 +2209,18 @@ function TrackPositionPlayPause({
2190
2209
  role: "img",
2191
2210
  "aria-label": isPlaying ? "Playing" : "Paused",
2192
2211
  children: [
2193
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__bar" }),
2194
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__bar" }),
2195
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__bar" })
2212
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__bar" }),
2213
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__bar" }),
2214
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__bar" })
2196
2215
  ]
2197
2216
  }
2198
2217
  ) : position }),
2199
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2218
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2200
2219
  "div",
2201
2220
  {
2202
2221
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
2203
2222
  "aria-hidden": "true",
2204
- children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fa3.FaPlay, {})
2223
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_fa3.FaPlay, {})
2205
2224
  }
2206
2225
  )
2207
2226
  ]
@@ -2212,7 +2231,7 @@ function TrackPositionPlayPause({
2212
2231
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
2213
2232
  var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
2214
2233
  var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
2215
- var import_jsx_runtime39 = require("react/jsx-runtime");
2234
+ var import_jsx_runtime40 = require("react/jsx-runtime");
2216
2235
  function TrackItem({
2217
2236
  track,
2218
2237
  position,
@@ -2236,12 +2255,12 @@ function TrackItem({
2236
2255
  loadTrackForPlayer(track.uuid);
2237
2256
  }
2238
2257
  };
2239
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
2258
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
2240
2259
  "div",
2241
2260
  {
2242
2261
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
2243
2262
  children: [
2244
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2263
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2245
2264
  "div",
2246
2265
  {
2247
2266
  className: "flex items-center justify-center text-lg w-16",
@@ -2249,7 +2268,7 @@ function TrackItem({
2249
2268
  "aria-disabled": "true",
2250
2269
  children: position
2251
2270
  }
2252
- ) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2271
+ ) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2253
2272
  TrackPositionPlayPause,
2254
2273
  {
2255
2274
  position,
@@ -2258,12 +2277,12 @@ function TrackItem({
2258
2277
  isSelected: selectedTrack?.uuid === track.uuid
2259
2278
  }
2260
2279
  ),
2261
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2262
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree24.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2263
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree25.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2280
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2281
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree24.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2282
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree25.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2264
2283
  ] }),
2265
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-caption font-bold", children: track.genre.name }) : "" }),
2266
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2284
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-caption font-bold", children: track.genre.name }) : "" }),
2285
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2267
2286
  "div",
2268
2287
  {
2269
2288
  className: "duration flex text-base w-16 items-center justify-center",
@@ -2271,14 +2290,14 @@ function TrackItem({
2271
2290
  children: renderDuration(track)
2272
2291
  }
2273
2292
  ),
2274
- renderActions && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2293
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2275
2294
  ]
2276
2295
  }
2277
2296
  );
2278
2297
  }
2279
2298
 
2280
2299
  // src/genre-tree/artist/ArtistSongs.tsx
2281
- var import_jsx_runtime40 = require("react/jsx-runtime");
2300
+ var import_jsx_runtime41 = require("react/jsx-runtime");
2282
2301
  var SongPageSchema2 = (0, import_transport6.PaginatedResponseSchema)(import_genre_tree26.SongDetailedSchema);
2283
2302
  var SORTS = [
2284
2303
  { ordering: "title", label: "Title" },
@@ -2311,8 +2330,8 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2311
2330
  event.stopPropagation();
2312
2331
  void play(song.uuid);
2313
2332
  };
2314
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui16.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui16.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2315
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex flex-wrap items-center justify-end gap-2 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-caption", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2333
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
2334
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "flex flex-wrap items-center justify-end gap-2 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-caption", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2316
2335
  "button",
2317
2336
  {
2318
2337
  type: "button",
@@ -2326,7 +2345,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2326
2345
  },
2327
2346
  sort.ordering
2328
2347
  )) }) }),
2329
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2348
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2330
2349
  TrackItem,
2331
2350
  {
2332
2351
  track: song,
@@ -2335,12 +2354,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2335
2354
  renderSongLink
2336
2355
  }
2337
2356
  ) }, song.uuid)) }),
2338
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui16.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2357
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2339
2358
  ] }) });
2340
2359
  }
2341
2360
 
2342
2361
  // src/genre-tree/artist/useArtistPanel.tsx
2343
- var import_jsx_runtime41 = require("react/jsx-runtime");
2362
+ var import_jsx_runtime42 = require("react/jsx-runtime");
2344
2363
  function useArtistPanel({
2345
2364
  uuid,
2346
2365
  scope,
@@ -2361,26 +2380,26 @@ function useArtistPanel({
2361
2380
  return {
2362
2381
  ...panel,
2363
2382
  title: "Artist",
2364
- content: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2365
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2366
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui17.Skeleton, { className: "h-4 w-32" }),
2367
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2368
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui17.Skeleton, { className: "h-4 w-24" }),
2369
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui17.Skeleton, { className: "h-16 mt-2.5" }),
2370
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui17.Skeleton, { className: "h-16" })
2383
+ content: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2384
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2385
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-4 w-32" }),
2386
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2387
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-4 w-24" }),
2388
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-16 mt-2.5" }),
2389
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-16" })
2371
2390
  ] })
2372
2391
  };
2373
2392
  case "not-found":
2374
2393
  return {
2375
2394
  ...panel,
2376
2395
  title: "Artist not found",
2377
- content: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This artist does not exist." })
2396
+ content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This artist does not exist." })
2378
2397
  };
2379
2398
  case "error":
2380
2399
  return {
2381
2400
  ...panel,
2382
2401
  title: "Artist",
2383
- content: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-red-600", children: "Could not load this artist." })
2402
+ content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-red-600", children: "Could not load this artist." })
2384
2403
  };
2385
2404
  case "success": {
2386
2405
  const { artist } = result;
@@ -2389,19 +2408,19 @@ function useArtistPanel({
2389
2408
  return {
2390
2409
  ...panel,
2391
2410
  title: artist.name,
2392
- content: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
2393
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2411
+ content: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
2412
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2394
2413
  ArtistActionBand,
2395
2414
  {
2396
2415
  artist,
2397
- share: shareTarget && shareUrl ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) : void 0
2416
+ share: shareTarget && shareUrl ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) : void 0
2398
2417
  }
2399
2418
  ),
2400
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2401
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistHeader, { artist }),
2402
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2403
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2404
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2419
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2420
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistHeader, { artist }),
2421
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2422
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2423
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2405
2424
  ArtistSongs,
2406
2425
  {
2407
2426
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2411,7 +2430,7 @@ function useArtistPanel({
2411
2430
  }
2412
2431
  )
2413
2432
  ] }),
2414
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2433
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2415
2434
  };
2416
2435
  }
2417
2436
  }
@@ -2427,7 +2446,7 @@ var import_lucide_react12 = require("lucide-react");
2427
2446
  var import_ui18 = require("@behindthemusictree/ui");
2428
2447
  var import_player12 = require("@behindthemusictree/app-kit/player");
2429
2448
  var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
2430
- var import_jsx_runtime42 = require("react/jsx-runtime");
2449
+ var import_jsx_runtime43 = require("react/jsx-runtime");
2431
2450
  var numberFormat = new Intl.NumberFormat("en");
2432
2451
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2433
2452
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
@@ -2435,27 +2454,27 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
2435
2454
  const [open, setOpen] = (0, import_react18.useState)(false);
2436
2455
  const history = (0, import_genre_tree28.useSongHistory)(uuid, getBackendBaseUrl, open);
2437
2456
  let body = null;
2438
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui18.Skeleton, { className: "h-10", "aria-busy": "true" });
2439
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2457
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui18.Skeleton, { className: "h-10", "aria-busy": "true" });
2458
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2440
2459
  else if (history.status === "success")
2441
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { className: "py-1.5 text-body", children: [
2442
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "font-medium", children: entry.action }),
2443
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2460
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("li", { className: "py-1.5 text-body", children: [
2461
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "font-medium", children: entry.action }),
2462
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2444
2463
  " ",
2445
2464
  "\u2014 ",
2446
2465
  entry.oldValue ?? "none",
2447
2466
  " \u2192 ",
2448
2467
  entry.newValue ?? "none"
2449
2468
  ] }) : null,
2450
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "block text-caption text-[var(--color-text-muted)]", children: [
2469
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "block text-caption text-[var(--color-text-muted)]", children: [
2451
2470
  entry.actorPseudo ?? "Anonymous",
2452
2471
  " \xB7 ",
2453
2472
  dateFormat.format(new Date(entry.createdOn))
2454
2473
  ] })
2455
2474
  ] }, entry.uuid)) });
2456
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui18.PanelBleed, { padded: true, className: "border-t border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("details", { onToggle: (event) => setOpen(event.currentTarget.open), className: "pb-2.5", children: [
2457
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("summary", { className: `mt-2.5 cursor-pointer ${import_ui18.PANEL_LABEL_CLASS}`, children: "History" }),
2458
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "mt-1.5", children: body })
2475
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui18.PanelBleed, { padded: true, className: "border-t border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("details", { onToggle: (event) => setOpen(event.currentTarget.open), className: "pb-2.5", children: [
2476
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("summary", { className: `mt-2.5 cursor-pointer ${import_ui18.PANEL_LABEL_CLASS}`, children: "History" }),
2477
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "mt-1.5", children: body })
2459
2478
  ] }) });
2460
2479
  }
2461
2480
  function SongPanelContent(props) {
@@ -2474,13 +2493,13 @@ function SongPanelContent(props) {
2474
2493
  const genre = song.genre;
2475
2494
  fields.push({
2476
2495
  label: "Genre",
2477
- value: onSelectGenre ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", onClick: () => onSelectGenre(genre), className: "hover:underline", children: genre.name }) : path ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { children: [
2478
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2479
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", onClick: () => props.onSelectGenrePlaylist?.(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2496
+ value: onSelectGenre ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", onClick: () => onSelectGenre(genre), className: "hover:underline", children: genre.name }) : path ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
2497
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2498
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", onClick: () => props.onSelectGenrePlaylist?.(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2480
2499
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2481
2500
  });
2482
2501
  } else {
2483
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2502
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2484
2503
  }
2485
2504
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2486
2505
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2490,11 +2509,11 @@ function SongPanelContent(props) {
2490
2509
  const links = [];
2491
2510
  if (musicBrainzUrl)
2492
2511
  links.push(
2493
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2512
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2494
2513
  );
2495
2514
  if (song.youtubeVideo)
2496
2515
  links.push(
2497
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2516
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2498
2517
  "a",
2499
2518
  {
2500
2519
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2509,31 +2528,31 @@ function SongPanelContent(props) {
2509
2528
  if (links.length > 0)
2510
2529
  fields.push({
2511
2530
  label: "Links",
2512
- value: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "flex gap-3", children: links })
2531
+ value: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex gap-3", children: links })
2513
2532
  });
2514
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
2515
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui18.PanelBleed, { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_ui18.ActionBand, { children: [
2516
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2533
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
2534
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui18.PanelBleed, { children: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_ui18.ActionBand, { children: [
2535
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2517
2536
  import_ui18.ActionButton,
2518
2537
  {
2519
- icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react12.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react12.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
2538
+ icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react12.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react12.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
2520
2539
  label: isPlaying ? "Pause" : "Play",
2521
2540
  disabled: !!unplayableReason,
2522
2541
  title: unplayableReason ?? void 0,
2523
2542
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope)
2524
2543
  }
2525
2544
  ),
2526
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2545
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2527
2546
  ] }) }),
2528
- song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_genre_tree28.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2529
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui18.FieldList, { items: fields }),
2530
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui18.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2531
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2547
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_genre_tree28.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2548
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui18.FieldList, { items: fields }),
2549
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui18.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2550
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2532
2551
  ] });
2533
2552
  }
2534
2553
 
2535
2554
  // src/genre-tree/song/useSongPanel.tsx
2536
- var import_jsx_runtime43 = require("react/jsx-runtime");
2555
+ var import_jsx_runtime44 = require("react/jsx-runtime");
2537
2556
  function useSongPanel({
2538
2557
  uuid,
2539
2558
  scope,
@@ -2554,24 +2573,24 @@ function useSongPanel({
2554
2573
  return {
2555
2574
  ...panel,
2556
2575
  title: "Song",
2557
- content: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2558
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui19.Skeleton, { className: "h-3 w-16" }),
2559
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui19.Skeleton, { className: "h-4 w-32" }),
2560
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui19.Skeleton, { className: "h-3 w-16" }),
2561
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui19.Skeleton, { className: "h-4 w-24" })
2576
+ content: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2577
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui19.Skeleton, { className: "h-3 w-16" }),
2578
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui19.Skeleton, { className: "h-4 w-32" }),
2579
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui19.Skeleton, { className: "h-3 w-16" }),
2580
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui19.Skeleton, { className: "h-4 w-24" })
2562
2581
  ] })
2563
2582
  };
2564
2583
  case "not-found":
2565
2584
  return {
2566
2585
  ...panel,
2567
2586
  title: "Song not found",
2568
- content: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This song does not exist." })
2587
+ content: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This song does not exist." })
2569
2588
  };
2570
2589
  case "error":
2571
2590
  return {
2572
2591
  ...panel,
2573
2592
  title: "Song",
2574
- content: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "text-red-600", children: "Could not load this song." })
2593
+ content: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "text-red-600", children: "Could not load this song." })
2575
2594
  };
2576
2595
  case "success": {
2577
2596
  const { song } = result;
@@ -2579,7 +2598,7 @@ function useSongPanel({
2579
2598
  return {
2580
2599
  ...panel,
2581
2600
  title: song.title,
2582
- content: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2601
+ content: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2583
2602
  SongPanelContent,
2584
2603
  {
2585
2604
  song,
@@ -2632,12 +2651,12 @@ function useEntityPanel({
2632
2651
  // src/genre-tree/SizeMetricToggle.tsx
2633
2652
  var import_md = require("react-icons/md");
2634
2653
  var import_ui20 = require("@behindthemusictree/ui");
2635
- var import_jsx_runtime44 = require("react/jsx-runtime");
2654
+ var import_jsx_runtime45 = require("react/jsx-runtime");
2636
2655
  function SizeMetricToggle({
2637
2656
  value,
2638
2657
  onChange
2639
2658
  }) {
2640
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2659
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2641
2660
  import_ui20.SegmentedControl,
2642
2661
  {
2643
2662
  variant: "floating",
@@ -2645,15 +2664,15 @@ function SizeMetricToggle({
2645
2664
  value,
2646
2665
  onChange,
2647
2666
  options: [
2648
- { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2649
- { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2667
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2668
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2650
2669
  ]
2651
2670
  }
2652
2671
  );
2653
2672
  }
2654
2673
 
2655
2674
  // src/genre-tree/GenreTreeView.tsx
2656
- var import_jsx_runtime45 = require("react/jsx-runtime");
2675
+ var import_jsx_runtime46 = require("react/jsx-runtime");
2657
2676
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2658
2677
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2659
2678
  var noop = () => {
@@ -2761,7 +2780,7 @@ function GenreTreeView({
2761
2780
  allowWheelRotation: internalAllowWheelRotation,
2762
2781
  toggleWheelRotation,
2763
2782
  onNodeHover: handleNodeHover
2764
- } = (0, import_genre_tree30.useGenreTreeViewState)({
2783
+ } = (0, import_genre_tree31.useGenreTreeViewState)({
2765
2784
  scope,
2766
2785
  getBackendBaseUrl,
2767
2786
  viewMode: controlledViewMode,
@@ -2780,12 +2799,12 @@ function GenreTreeView({
2780
2799
  restoreRootUuid = selectedGenrePlaylist.root.uuid;
2781
2800
  setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
2782
2801
  }
2783
- const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree30.genreUpdateMutationKey }) > 0;
2784
- const { mutate: updateGenre } = (0, import_genre_tree30.useUpdateGenre)(scope, getBackendBaseUrl);
2802
+ const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree31.genreUpdateMutationKey }) > 0;
2803
+ const { mutate: updateGenre } = (0, import_genre_tree31.useUpdateGenre)(scope, getBackendBaseUrl);
2785
2804
  const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
2786
2805
  const isArtistFiltered = artistUuid !== null;
2787
2806
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2788
- const artistResult = (0, import_genre_tree30.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2807
+ const artistResult = (0, import_genre_tree31.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2789
2808
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2790
2809
  const filterArtist = (0, import_react19.useMemo)(
2791
2810
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
@@ -2805,14 +2824,14 @@ function GenreTreeView({
2805
2824
  const openSongRef = (0, import_react19.useRef)(openSong);
2806
2825
  openSongRef.current = openSong;
2807
2826
  const resolvedRenderSongLink = (0, import_react19.useCallback)(
2808
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2827
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2809
2828
  [renderSongLink]
2810
2829
  );
2811
2830
  const [expandedSongUuid, setExpandedSongUuid] = (0, import_react19.useState)(null);
2812
2831
  const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react19.useState)(null);
2813
2832
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
2814
2833
  const songBaseUrl = getBackendBaseUrl;
2815
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2834
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2816
2835
  const getGenrePath = (genre) => {
2817
2836
  const path = [];
2818
2837
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2889,9 +2908,10 @@ function GenreTreeView({
2889
2908
  resolveStyle
2890
2909
  );
2891
2910
  const treeStyles = (0, import_react19.useMemo)(() => toGenreNodeStyles(relations, visibleGenrePlaylists), [relations, visibleGenrePlaylists]);
2892
- const [selectionHistory, setSelectionHistory] = (0, import_react19.useState)({ current: selectedGenreUuid, previous: null });
2893
- if (selectionHistory.current !== selectedGenreUuid) {
2894
- setSelectionHistory({ current: selectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
2911
+ const validSelectedGenreUuid = (0, import_genre_tree30.toGenreUuid)(selectedGenreUuid);
2912
+ const [selectionHistory, setSelectionHistory] = (0, import_react19.useState)({ current: validSelectedGenreUuid, previous: null });
2913
+ if (selectionHistory.current !== validSelectedGenreUuid) {
2914
+ setSelectionHistory({ current: validSelectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
2895
2915
  }
2896
2916
  const handlePrevious = selectionHistory.previous ? () => selectGenre(selectionHistory.previous) : void 0;
2897
2917
  const { showPopup, hidePopup } = (0, import_popup4.usePopup)();
@@ -2923,7 +2943,7 @@ function GenreTreeView({
2923
2943
  const canToggleSide = !readOnly && criteria !== void 0 && criteria !== null && parentPlaylist !== void 0 && parentPlaylist.parent === null;
2924
2944
  const side = criteria?.side ?? "core";
2925
2945
  const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find((sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist?.uuid && sibling.criteria?.side === "pop") : void 0;
2926
- const shareTarget = selectedGenreUuid ? { kind: "genre", genreUuid: selectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
2946
+ const shareTarget = validSelectedGenreUuid ? { kind: "genre", genreUuid: validSelectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
2927
2947
  const shareUrl = shareTarget ? getShareUrlRef.current?.(shareTarget) ?? void 0 : void 0;
2928
2948
  const genreContent = useGenreDetailContent({
2929
2949
  genreUuid: selectedGenreUuid,
@@ -2945,7 +2965,7 @@ function GenreTreeView({
2945
2965
  onAddChild: readOnly || !criteria ? void 0 : () => handleGenreCreationAction(criteria),
2946
2966
  onRename: readOnly || !criteria ? void 0 : () => handleGenreRenameAction(criteria),
2947
2967
  onChangeParent: readOnly || !genrePlaylist || !criteria ? void 0 : () => showPopup(
2948
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2968
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2949
2969
  ChangeParentPopup,
2950
2970
  {
2951
2971
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2989,7 +3009,7 @@ function GenreTreeView({
2989
3009
  onClose: otherPanel.onClose,
2990
3010
  searchName: otherPanel.title
2991
3011
  };
2992
- } else if (selectedGenreUuid) {
3012
+ } else if (selectedGenreUuid !== null) {
2993
3013
  activePanel = {
2994
3014
  title: genreContent.title,
2995
3015
  content: genreContent.content,
@@ -3006,19 +3026,19 @@ function GenreTreeView({
3006
3026
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
3007
3027
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
3008
3028
  } : void 0;
3009
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3029
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3010
3030
  import_ui21.IconButton,
3011
3031
  {
3012
3032
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
3013
3033
  style: activePanel.headerAccent && { color: "inherit" },
3014
3034
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
3015
- children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react14.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react14.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3035
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react14.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react14.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3016
3036
  }
3017
3037
  ) : null;
3018
3038
  const renderArtistFilter = () => {
3019
3039
  if (isLoading || !filterArtist) return null;
3020
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-body", children: [
3021
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3040
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-body", children: [
3041
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3022
3042
  "button",
3023
3043
  {
3024
3044
  type: "button",
@@ -3030,7 +3050,7 @@ function GenreTreeView({
3030
3050
  children: filterArtist.name
3031
3051
  }
3032
3052
  ),
3033
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3053
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3034
3054
  "button",
3035
3055
  {
3036
3056
  type: "button",
@@ -3042,8 +3062,8 @@ function GenreTreeView({
3042
3062
  )
3043
3063
  ] });
3044
3064
  };
3045
- const actions = /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
3046
- !isLoading && !isError && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3065
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
3066
+ !isLoading && !isError && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3047
3067
  GenreTreeSearch,
3048
3068
  {
3049
3069
  artistBaseUrl,
@@ -3076,9 +3096,9 @@ function GenreTreeView({
3076
3096
  extraGroups: extraSearchGroups
3077
3097
  }
3078
3098
  ),
3079
- !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3080
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
3081
- !isLoading && !isError && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3099
+ !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3100
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
3101
+ !isLoading && !isError && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3082
3102
  import_ui21.SegmentedControl,
3083
3103
  {
3084
3104
  "aria-label": "Tree view mode",
@@ -3092,13 +3112,13 @@ function GenreTreeView({
3092
3112
  ]
3093
3113
  }
3094
3114
  ),
3095
- !isLoading && !isError && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3115
+ !isLoading && !isError && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3096
3116
  "div",
3097
3117
  {
3098
3118
  className: "flex items-center gap-1",
3099
3119
  role: "group",
3100
3120
  "aria-label": "Tree display options",
3101
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3121
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3102
3122
  import_ui21.Button,
3103
3123
  {
3104
3124
  variant: allowWheelRotation ? "default" : "outline",
@@ -3109,7 +3129,7 @@ function GenreTreeView({
3109
3129
  )
3110
3130
  }
3111
3131
  ),
3112
- !isLoading && !isError && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3132
+ !isLoading && !isError && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3113
3133
  import_ui21.IconTextButton,
3114
3134
  {
3115
3135
  icon: import_lucide_react13.Plus,
@@ -3119,38 +3139,38 @@ function GenreTreeView({
3119
3139
  )
3120
3140
  ] })
3121
3141
  ] });
3122
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3142
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
3123
3143
  "div",
3124
3144
  {
3125
3145
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
3126
3146
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
3127
3147
  children: [
3128
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3148
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3129
3149
  "div",
3130
3150
  {
3131
3151
  className: `actions-container absolute z-30 flex items-center gap-2 ${placement === "regions" ? "left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] md:left-[var(--layout-panel-inset)] md:top-[var(--layout-panel-inset)] max-md:right-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:top-[var(--layout-band-top)] max-md:flex-col max-md:items-stretch" : "left-5 top-5"}`,
3132
3152
  children: actions
3133
3153
  }
3134
3154
  ),
3135
- !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3155
+ !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3136
3156
  "div",
3137
3157
  {
3138
3158
  className: `absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${placement === "regions" ? "top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] md:top-[var(--layout-panel-inset)]" : "top-5"}`,
3139
3159
  children: renderArtistFilter()
3140
3160
  }
3141
3161
  ),
3142
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3143
- !isLoading && !isError && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3144
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view5.GenreTreeViewSkeleton, { viewMode }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("p", { role: "alert", className: "m-auto text-center", children: [
3162
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3163
+ !isLoading && !isError && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3164
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_genre_tree_view5.GenreTreeViewSkeleton, { viewMode }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { role: "alert", className: "m-auto text-center", children: [
3145
3165
  "Couldn't load the genre tree.",
3146
3166
  " ",
3147
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("button", { type: "button", className: "underline", onClick: refetch, children: "Retry" })
3167
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", className: "underline", onClick: refetch, children: "Retry" })
3148
3168
  ] }) : viewMode === "wheel" ? (
3149
3169
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
3150
3170
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
3151
3171
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
3152
3172
  // The panel's own Zoom in button is hidden: the search bar carries it.
3153
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3173
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3154
3174
  GenrePlaylistTreeWheel,
3155
3175
  {
3156
3176
  scope,
@@ -3168,9 +3188,9 @@ function GenreTreeView({
3168
3188
  allowWheelRotation,
3169
3189
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3170
3190
  busy,
3171
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex items-center gap-2", children: [
3191
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex items-center gap-2", children: [
3172
3192
  floatingControlsLeading,
3173
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3193
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3174
3194
  ] }) : void 0,
3175
3195
  defaultViewport,
3176
3196
  viewportRestore,
@@ -3179,7 +3199,7 @@ function GenreTreeView({
3179
3199
  ) }) })
3180
3200
  ) : viewMode === "pop-core" ? (
3181
3201
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
3182
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3202
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3183
3203
  GenrePlaylistTreeWheelRadialPopCore,
3184
3204
  {
3185
3205
  scope,
@@ -3197,9 +3217,9 @@ function GenreTreeView({
3197
3217
  allowWheelRotation,
3198
3218
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3199
3219
  busy,
3200
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex items-center gap-2", children: [
3220
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex items-center gap-2", children: [
3201
3221
  floatingControlsLeading,
3202
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3222
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3203
3223
  ] }) : void 0,
3204
3224
  defaultViewport,
3205
3225
  viewportRestore,
@@ -3207,7 +3227,7 @@ function GenreTreeView({
3207
3227
  ...focus
3208
3228
  }
3209
3229
  ) }) })
3210
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3230
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3211
3231
  GenrePlaylistTreeWheelRadialPopCore,
3212
3232
  {
3213
3233
  outline: true,
@@ -3224,13 +3244,13 @@ function GenreTreeView({
3224
3244
  selectedNodeId,
3225
3245
  readOnly
3226
3246
  }
3227
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
3247
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
3228
3248
  ([uuid, genrePlaylistTreePerRoot]) => {
3229
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3249
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3230
3250
  "div",
3231
3251
  {
3232
3252
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
3233
- children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3253
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3234
3254
  GenrePlaylistTreePerRoot,
3235
3255
  {
3236
3256
  scope,
@@ -3257,7 +3277,7 @@ function GenreTreeView({
3257
3277
  );
3258
3278
  }
3259
3279
  ) }),
3260
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3280
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3261
3281
  import_ui21.DetailPanel,
3262
3282
  {
3263
3283
  ref: panelRef,
@@ -3272,7 +3292,7 @@ function GenreTreeView({
3272
3292
  onExpandedChange: activePanel?.onExpandedChange,
3273
3293
  aside: activePanel?.aside,
3274
3294
  edgeTab: detailTab,
3275
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
3295
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
3276
3296
  activePanel.content,
3277
3297
  activePanel.aside
3278
3298
  ] }) : activePanel?.content
@@ -3290,10 +3310,10 @@ var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3290
3310
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3291
3311
  var import_react20 = require("react");
3292
3312
  var import_lucide_react15 = require("lucide-react");
3293
- var import_genre_tree31 = require("@behindthemusictree/app-kit/genre-tree");
3294
3313
  var import_genre_tree32 = require("@behindthemusictree/app-kit/genre-tree");
3314
+ var import_genre_tree33 = require("@behindthemusictree/app-kit/genre-tree");
3295
3315
  var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
3296
- var import_jsx_runtime46 = require("react/jsx-runtime");
3316
+ var import_jsx_runtime47 = require("react/jsx-runtime");
3297
3317
  var useBrowserLayoutEffect = typeof window === "undefined" ? import_react20.useEffect : import_react20.useLayoutEffect;
3298
3318
  function TrackListSidebar({
3299
3319
  className: className2 = "",
@@ -3306,7 +3326,7 @@ function TrackListSidebar({
3306
3326
  renderArtistLink,
3307
3327
  renderSongLink
3308
3328
  }) {
3309
- const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree31.useTrackList)();
3329
+ const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree32.useTrackList)();
3310
3330
  const trackNoun = (0, import_track_noun3.useTrackNoun)();
3311
3331
  const trackListRef = (0, import_react20.useRef)(null);
3312
3332
  const previousOriginRef = (0, import_react20.useRef)(trackList?.origin ?? null);
@@ -3395,25 +3415,25 @@ function TrackListSidebar({
3395
3415
  }
3396
3416
  };
3397
3417
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3398
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
3418
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
3399
3419
  "div",
3400
3420
  {
3401
3421
  "data-theme": "dark",
3402
3422
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3403
3423
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3404
3424
  children: [
3405
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3406
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3407
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "origin flex text-xl", children: [
3408
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3409
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3425
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3426
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3427
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "origin flex text-xl", children: [
3428
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3429
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3410
3430
  ] }),
3411
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "info flex items-center text-body text-[var(--color-text-muted)]", children: [
3412
- trackList.origin.type === import_genre_tree32.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree32.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3431
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "info flex items-center text-body text-[var(--color-text-muted)]", children: [
3432
+ trackList.origin.type === import_genre_tree33.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree33.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3413
3433
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3414
3434
  ] })
3415
3435
  ] }),
3416
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3436
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3417
3437
  "button",
3418
3438
  {
3419
3439
  type: "button",
@@ -3422,11 +3442,11 @@ function TrackListSidebar({
3422
3442
  "aria-expanded": !isCollapsed,
3423
3443
  className: `ml-2 flex h-8 w-8 shrink-0 items-center justify-center rounded-md hover:bg-[var(--color-surface-muted)] ${isCollapsed ? "text-[var(--color-text-muted)]" : "text-white"}`,
3424
3444
  onClick: isCollapsed ? onExpand : onMinimize,
3425
- children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react15.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3445
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react15.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3426
3446
  }
3427
3447
  )
3428
3448
  ] }),
3429
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
3449
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
3430
3450
  "ul",
3431
3451
  {
3432
3452
  ref: trackListRef,
@@ -3436,7 +3456,7 @@ function TrackListSidebar({
3436
3456
  onPointerDown: markUserScrollIntent,
3437
3457
  className: layout === "inline" ? "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0" : "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0",
3438
3458
  children: [
3439
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3459
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3440
3460
  TrackItem,
3441
3461
  {
3442
3462
  track,
@@ -3447,7 +3467,7 @@ function TrackListSidebar({
3447
3467
  renderSongLink
3448
3468
  }
3449
3469
  ) }, track.uuid)),
3450
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3470
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3451
3471
  ]
3452
3472
  }
3453
3473
  )
@@ -3460,14 +3480,14 @@ function TrackListSidebar({
3460
3480
  var import_react21 = require("react");
3461
3481
  var import_tailwind_merge = require("tailwind-merge");
3462
3482
  var import_player14 = require("@behindthemusictree/app-kit/player");
3463
- var import_genre_tree34 = require("@behindthemusictree/app-kit/genre-tree");
3483
+ var import_genre_tree35 = require("@behindthemusictree/app-kit/genre-tree");
3464
3484
  var import_ui22 = require("@behindthemusictree/ui");
3465
3485
 
3466
3486
  // src/genre-tree/PlayerControls.tsx
3467
3487
  var import_lucide_react16 = require("lucide-react");
3468
- var import_genre_tree33 = require("@behindthemusictree/app-kit/genre-tree");
3488
+ var import_genre_tree34 = require("@behindthemusictree/app-kit/genre-tree");
3469
3489
  var import_player13 = require("@behindthemusictree/app-kit/player");
3470
- var import_jsx_runtime47 = require("react/jsx-runtime");
3490
+ var import_jsx_runtime48 = require("react/jsx-runtime");
3471
3491
  function PlayerControls() {
3472
3492
  const {
3473
3493
  duration,
@@ -3479,22 +3499,22 @@ function PlayerControls() {
3479
3499
  setVolume,
3480
3500
  volume
3481
3501
  } = (0, import_player13.usePlayer)();
3482
- const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree33.useTrackList)();
3502
+ const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree34.useTrackList)();
3483
3503
  const currentTime = (0, import_player13.useCurrentTime)();
3484
3504
  const isBusy = isLoading || playState === import_player13.PlayStates.LOADING;
3485
3505
  const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
3486
3506
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3487
3507
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3488
3508
  const isPlaying = playState === import_player13.PlayStates.PLAYING;
3489
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
3509
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
3490
3510
  "section",
3491
3511
  {
3492
3512
  "aria-label": "Player controls",
3493
3513
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3494
3514
  children: [
3495
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3496
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree33.formatTime)(safeCurrentTime) }),
3497
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3515
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3516
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree34.formatTime)(safeCurrentTime) }),
3517
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3498
3518
  "input",
3499
3519
  {
3500
3520
  "aria-label": "Seek",
@@ -3508,11 +3528,11 @@ function PlayerControls() {
3508
3528
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3509
3529
  }
3510
3530
  ),
3511
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree33.formatTime)(safeDuration) })
3531
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree34.formatTime)(safeDuration) })
3512
3532
  ] }),
3513
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3514
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3515
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3533
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3534
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3535
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3516
3536
  "button",
3517
3537
  {
3518
3538
  type: "button",
@@ -3521,10 +3541,10 @@ function PlayerControls() {
3521
3541
  disabled: !isReady || !hasPreviousTrack,
3522
3542
  onClick: playPreviousTrack,
3523
3543
  className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
3524
- children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react16.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3544
+ children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react16.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3525
3545
  }
3526
3546
  ),
3527
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3547
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3528
3548
  "button",
3529
3549
  {
3530
3550
  type: "button",
@@ -3533,10 +3553,10 @@ function PlayerControls() {
3533
3553
  disabled: !isReady,
3534
3554
  onClick: handlePlayPauseAction,
3535
3555
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white text-zinc-950 hover:bg-zinc-200 disabled:cursor-not-allowed disabled:opacity-40",
3536
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react16.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react16.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3556
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react16.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react16.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3537
3557
  }
3538
3558
  ),
3539
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3559
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3540
3560
  "button",
3541
3561
  {
3542
3562
  type: "button",
@@ -3545,14 +3565,14 @@ function PlayerControls() {
3545
3565
  disabled: !isReady || !hasNextTrack,
3546
3566
  onClick: playNextTrack,
3547
3567
  className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
3548
- children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react16.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3568
+ children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react16.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3549
3569
  }
3550
3570
  )
3551
3571
  ] }),
3552
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
3553
- volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react16.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react16.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3554
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "sr-only", children: "Volume" }),
3555
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3572
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
3573
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react16.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react16.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3574
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "sr-only", children: "Volume" }),
3575
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3556
3576
  "input",
3557
3577
  {
3558
3578
  "aria-label": "Volume",
@@ -3574,7 +3594,7 @@ function PlayerControls() {
3574
3594
  }
3575
3595
 
3576
3596
  // src/genre-tree/PlayerPanel.tsx
3577
- var import_jsx_runtime48 = require("react/jsx-runtime");
3597
+ var import_jsx_runtime49 = require("react/jsx-runtime");
3578
3598
  var PANEL_ID = "player-panel";
3579
3599
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3580
3600
  function PlayerPanel({
@@ -3587,7 +3607,7 @@ function PlayerPanel({
3587
3607
  renderSongLink
3588
3608
  }) {
3589
3609
  const { playerTrackObject, retryPlayback } = (0, import_player14.usePlayer)();
3590
- const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree34.useTrackListSidebarVisibility)();
3610
+ const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree35.useTrackListSidebarVisibility)();
3591
3611
  const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react21.useState)("");
3592
3612
  const loadedTrackTitle = playerTrackObject?.track.title;
3593
3613
  (0, import_react21.useEffect)(() => {
@@ -3614,7 +3634,7 @@ function PlayerPanel({
3614
3634
  onClick: () => setIsExtendRequested(true),
3615
3635
  expanded: false
3616
3636
  } : null;
3617
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
3637
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
3618
3638
  "div",
3619
3639
  {
3620
3640
  id: PANEL_ID,
@@ -3627,7 +3647,7 @@ function PlayerPanel({
3627
3647
  ),
3628
3648
  style: isExtended ? { width: extendedWidth } : void 0,
3629
3649
  children: [
3630
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3650
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3631
3651
  import_ui22.PanelEdgeTab,
3632
3652
  {
3633
3653
  side: "right",
@@ -3637,7 +3657,7 @@ function PlayerPanel({
3637
3657
  className: "absolute right-full top-1/2 -translate-y-1/2"
3638
3658
  }
3639
3659
  ),
3640
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
3660
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
3641
3661
  "div",
3642
3662
  {
3643
3663
  className: (0, import_tailwind_merge.twMerge)(
@@ -3645,20 +3665,20 @@ function PlayerPanel({
3645
3665
  isExtended ? "flex-row" : "flex-col"
3646
3666
  ),
3647
3667
  children: [
3648
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3649
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3650
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "relative w-full shrink-0", children: [
3651
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_player14.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3652
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3668
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3669
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3670
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "relative w-full shrink-0", children: [
3671
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_player14.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3672
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3653
3673
  playerTrackObject.loadError,
3654
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3674
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3655
3675
  void retryPlayback();
3656
3676
  }, children: "Retry" })
3657
3677
  ] })
3658
3678
  ] }),
3659
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(PlayerControls, {})
3679
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(PlayerControls, {})
3660
3680
  ] }),
3661
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3681
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3662
3682
  TrackListSidebar,
3663
3683
  {
3664
3684
  layout: "inline",
@@ -3684,33 +3704,33 @@ function PlayerPanel({
3684
3704
  // src/genre-tree/Rating.tsx
3685
3705
  var import_uuid = require("uuid");
3686
3706
  var import_ai = require("react-icons/ai");
3687
- var import_genre_tree35 = require("@behindthemusictree/app-kit/genre-tree");
3688
- var import_jsx_runtime49 = require("react/jsx-runtime");
3707
+ var import_genre_tree36 = require("@behindthemusictree/app-kit/genre-tree");
3708
+ var import_jsx_runtime50 = require("react/jsx-runtime");
3689
3709
  function Rating({ rating, handleChange }) {
3690
3710
  const instanceId = (0, import_uuid.v4)();
3691
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3692
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3711
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3712
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3693
3713
  "input",
3694
3714
  {
3695
3715
  id: `star-${instanceId}-${i}`,
3696
3716
  className: "w-6 h-6 opacity-0",
3697
3717
  type: "radio",
3698
3718
  name: "rating",
3699
- value: i === 0 ? import_genre_tree35.FORM_RATING_NULL_VALUE : i,
3719
+ value: i === 0 ? import_genre_tree36.FORM_RATING_NULL_VALUE : i,
3700
3720
  checked: rating === (i === 0 ? null : i),
3701
3721
  onChange: handleChange
3702
3722
  }
3703
3723
  ),
3704
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex", children: [
3705
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3724
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex", children: [
3725
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3706
3726
  import_ai.AiOutlineStar,
3707
3727
  {
3708
- className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree35.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3728
+ className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree36.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3709
3729
  }
3710
3730
  ),
3711
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "flex-grow w-8" }),
3731
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "flex-grow w-8" }),
3712
3732
  " "
3713
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3733
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3714
3734
  import_ai.AiFillStar,
3715
3735
  {
3716
3736
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3722,7 +3742,7 @@ function Rating({ rating, handleChange }) {
3722
3742
  // src/genre-tree/EntityDetailPanel.tsx
3723
3743
  var import_react22 = require("react");
3724
3744
  var import_ui23 = require("@behindthemusictree/ui");
3725
- var import_jsx_runtime50 = require("react/jsx-runtime");
3745
+ var import_jsx_runtime51 = require("react/jsx-runtime");
3726
3746
  function EntityDetailPanel({
3727
3747
  scope,
3728
3748
  getBackendBaseUrl,
@@ -3745,8 +3765,8 @@ function EntityDetailPanel({
3745
3765
  getBackendBaseUrl,
3746
3766
  getArtistBackendBaseUrl,
3747
3767
  getShareUrl,
3748
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => onArtistUuidChange(artist.uuid), className: "hover:underline", children: artist.name })),
3749
- renderSongLink: renderSongLink ?? ((song) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => onSongUuidChange(song.uuid), className: "hover:underline", children: song.title })),
3768
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", onClick: () => onArtistUuidChange(artist.uuid), className: "hover:underline", children: artist.name })),
3769
+ renderSongLink: renderSongLink ?? ((song) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", onClick: () => onSongUuidChange(song.uuid), className: "hover:underline", children: song.title })),
3750
3770
  song: {
3751
3771
  uuid: songUuid,
3752
3772
  expanded: songUuid !== null && expandedSongUuid === songUuid,
@@ -3762,7 +3782,7 @@ function EntityDetailPanel({
3762
3782
  ...navigation
3763
3783
  });
3764
3784
  if (!panel) return null;
3765
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3785
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3766
3786
  import_ui23.DetailPanel,
3767
3787
  {
3768
3788
  side: "left",
@@ -3774,7 +3794,7 @@ function EntityDetailPanel({
3774
3794
  onExpandedChange: panel.onExpandedChange,
3775
3795
  aside: panel.extraContent,
3776
3796
  edgeTab,
3777
- children: panel.extraContent && !panel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
3797
+ children: panel.extraContent && !panel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
3778
3798
  panel.content,
3779
3799
  panel.extraContent
3780
3800
  ] }) : panel.content