@behindthemusictree/app-kit-ui 33.3.0 → 34.1.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.
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-DKQQLLMY.js";
6
6
 
7
7
  // src/genre-tree/GenreTreeView.tsx
8
- import { useCallback as useCallback6, useDeferredValue, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef3, useState as useState8 } from "react";
8
+ import { useCallback as useCallback6, useDeferredValue as useDeferredValue2, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef3, useState as useState9 } from "react";
9
9
  import { useIsMutating } from "@tanstack/react-query";
10
10
  import { Plus as Plus2 } from "lucide-react";
11
11
  import { Maximize2, Minimize2 } from "lucide-react";
@@ -21,9 +21,10 @@ import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUp
21
21
  import { usePopup as usePopup5 } from "@behindthemusictree/app-kit/popup";
22
22
 
23
23
  // src/genre-tree/playlist-tree/size-metric.ts
24
+ import { genrePlaylistTrackCount } from "@behindthemusictree/app-kit/genre-tree";
24
25
  var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
25
26
  var genrePlaylistSize = (genrePlaylist, metric) => {
26
- if (metric === "songs") return genrePlaylist.tracksCount;
27
+ if (metric === "songs") return genrePlaylistTrackCount(genrePlaylist);
27
28
  if (genrePlaylist.viewsCount === void 0) {
28
29
  throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
29
30
  }
@@ -47,6 +48,7 @@ function toTreeNodes(genrePlaylists, sizeMetric) {
47
48
  }
48
49
 
49
50
  // src/genre-tree/playlist-tree/TreePerRoot.tsx
51
+ import { genrePlaylistTrackCount as genrePlaylistTrackCount2 } from "@behindthemusictree/app-kit/genre-tree";
50
52
  import { useCallback as useCallback2, useMemo as useMemo2 } from "react";
51
53
  import {
52
54
  GenreTree,
@@ -60,30 +62,32 @@ import { ErrorCode } from "@behindthemusictree/app-kit/transport";
60
62
  import { useCallback, useMemo } from "react";
61
63
  import {
62
64
  TrackListOriginType,
63
- useTrackList
65
+ useTrackList,
66
+ GenreCatalogueCountsSchema,
67
+ defaultGenreMusicSelection,
68
+ genreMusicSelectionKey
64
69
  } from "@behindthemusictree/app-kit/genre-tree";
65
70
  function useGenrePlaylistPlayback({
66
71
  scope,
67
72
  genrePlaylists,
68
73
  artist = null
69
74
  }) {
70
- const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList();
75
+ const { trackList, playNewTrackListFromGenrePlaylist } = useTrackList();
71
76
  const origin = trackList?.origin;
72
77
  const playingNodeId = useMemo(() => {
73
- if (!origin) return null;
74
- if (!artist) return origin.type === TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;
75
- if (origin.type !== TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;
76
- const { genreUuid } = origin;
77
- return genreUuid ? genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null : null;
78
- }, [origin, artist, genrePlaylists]);
79
- const play = useCallback(
80
- async (genrePlaylist) => {
81
- if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);
82
- if (!genrePlaylist.criteria) return;
83
- return playNewTrackListFromArtist(artist, scope, { ordering: "title", genreUuid: genrePlaylist.criteria.uuid });
84
- },
85
- [artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist]
86
- );
78
+ if (!origin || origin.type !== TrackListOriginType.GENRE_PLAYLIST || origin.scope !== scope) return null;
79
+ const playlistOrigin = origin;
80
+ if (playlistOrigin.artistUuid !== artist?.uuid) return null;
81
+ const playlist = genrePlaylists.find((item) => item.uuid === origin.uuid) ?? playlistOrigin.criteriaPlaylist;
82
+ const expected = scope === "reference" && playlist && !("criteria" in playlist && playlist.criteria === null) ? defaultGenreMusicSelection(GenreCatalogueCountsSchema.parse(playlist)) : void 0;
83
+ if (playlistOrigin.selection && expected && genreMusicSelectionKey(playlistOrigin.selection).join() !== genreMusicSelectionKey(expected).join()) return null;
84
+ return origin.uuid;
85
+ }, [origin, artist, genrePlaylists, scope]);
86
+ const play = useCallback(async (playlist) => {
87
+ if (artist && !playlist.criteria) return;
88
+ const selection = scope === "reference" && playlist.criteria !== null ? defaultGenreMusicSelection(GenreCatalogueCountsSchema.parse(playlist)) : void 0;
89
+ return selection || artist ? playNewTrackListFromGenrePlaylist(playlist, scope, { selection, ...artist && { artistUuid: artist.uuid } }) : playNewTrackListFromGenrePlaylist(playlist, scope);
90
+ }, [artist, scope, playNewTrackListFromGenrePlaylist]);
87
91
  return useMemo(() => ({ playingNodeId, play }), [playingNodeId, play]);
88
92
  }
89
93
 
@@ -118,7 +122,7 @@ function GenrePlaylistTreePerRoot({
118
122
  id: genrePlaylist.uuid,
119
123
  parentId: genrePlaylist.parent?.uuid ?? null,
120
124
  name: genrePlaylist.name,
121
- itemCount: genrePlaylist.tracksCount,
125
+ itemCount: genrePlaylistTrackCount2(genrePlaylist),
122
126
  actionable: Boolean(genrePlaylist.criteria),
123
127
  side: genrePlaylist.criteria?.side ?? void 0,
124
128
  wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
@@ -134,7 +138,7 @@ function GenrePlaylistTreePerRoot({
134
138
  setIsPlaying(!isPlaying);
135
139
  return;
136
140
  }
137
- if (genrePlaylist.tracksCount === 0) {
141
+ if (genrePlaylistTrackCount2(genrePlaylist) === 0) {
138
142
  return;
139
143
  }
140
144
  play(genrePlaylist).catch((error) => {
@@ -198,6 +202,7 @@ function GenrePlaylistTreePerRoot({
198
202
  }
199
203
 
200
204
  // src/genre-tree/playlist-tree/TreeWheel.tsx
205
+ import { genrePlaylistTrackCount as genrePlaylistTrackCount3 } from "@behindthemusictree/app-kit/genre-tree";
201
206
  import { useCallback as useCallback3, useMemo as useMemo3 } from "react";
202
207
  import {
203
208
  GenreTreeWheel
@@ -252,7 +257,7 @@ function GenrePlaylistTreeWheel({
252
257
  setIsPlaying(!isPlaying);
253
258
  return;
254
259
  }
255
- if (genrePlaylist.tracksCount === 0) {
260
+ if (genrePlaylistTrackCount3(genrePlaylist) === 0) {
256
261
  return;
257
262
  }
258
263
  play(genrePlaylist).catch((error) => {
@@ -317,6 +322,7 @@ function GenrePlaylistTreeWheel({
317
322
  }
318
323
 
319
324
  // src/genre-tree/playlist-tree/TreeWheelRadialPopCore.tsx
325
+ import { genrePlaylistTrackCount as genrePlaylistTrackCount4 } from "@behindthemusictree/app-kit/genre-tree";
320
326
  import { useCallback as useCallback4, useMemo as useMemo4 } from "react";
321
327
  import {
322
328
  GenreTreeOutline,
@@ -364,7 +370,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
364
370
  setIsPlaying(!isPlaying);
365
371
  return;
366
372
  }
367
- if (genrePlaylist.tracksCount === 0) {
373
+ if (genrePlaylistTrackCount4(genrePlaylist) === 0) {
368
374
  return;
369
375
  }
370
376
  play(genrePlaylist).catch((error) => {
@@ -459,6 +465,9 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
459
465
  ] });
460
466
  }
461
467
 
468
+ // src/genre-tree/GenreTreeSearch.tsx
469
+ import { useDeferredValue, useState as useState3 } from "react";
470
+
462
471
  // src/genre-tree/GenreSearch.tsx
463
472
  import { ArrowLeft } from "lucide-react";
464
473
  import { SEARCH_LEADING_BUTTON_CLASS } from "@behindthemusictree/ui";
@@ -664,7 +673,51 @@ function GenreSearch({
664
673
  ) });
665
674
  }
666
675
 
676
+ // src/genre-tree/GenreTreeSearch.tsx
677
+ import { jsx as jsx9 } from "react/jsx-runtime";
678
+ function GenreTreeSearch({
679
+ artistBaseUrl,
680
+ songBaseUrl,
681
+ onArtistSelect,
682
+ onSongSelect,
683
+ artistSearch,
684
+ songSearch,
685
+ SearchGroups,
686
+ extraGroups = [],
687
+ onQueryChange,
688
+ onFocus,
689
+ ...props
690
+ }) {
691
+ const [query, setQuery] = useState3("");
692
+ const [focused, setFocused] = useState3(false);
693
+ const deferredQuery = useDeferredValue(query);
694
+ const artistGroup = useArtistSearchGroup(artistBaseUrl, deferredQuery, onArtistSelect, artistSearch);
695
+ const songGroup = useSongSearchGroup(songBaseUrl, deferredQuery, onSongSelect, songSearch);
696
+ const render = (hostGroups) => /* @__PURE__ */ jsx9(
697
+ GenreSearch,
698
+ {
699
+ ...props,
700
+ onQueryChange: (text) => {
701
+ setQuery(text);
702
+ onQueryChange?.(text);
703
+ },
704
+ onFocus: () => {
705
+ setFocused(true);
706
+ onFocus?.();
707
+ },
708
+ extraGroups: [...extraGroups, ...hostGroups, artistGroup, songGroup].filter((group) => group !== void 0)
709
+ }
710
+ );
711
+ return SearchGroups ? /* @__PURE__ */ jsx9(SearchGroups, { query: deferredQuery, focused, children: render }) : render([]);
712
+ }
713
+
667
714
  // src/genre-tree/GenreInfoPanelBody.tsx
715
+ import {
716
+ genreMusicCount,
717
+ defaultGenreMusicSelection as defaultGenreMusicSelection2,
718
+ GenreCatalogueCountsSchema as GenreCatalogueCountsSchema2
719
+ } from "@behindthemusictree/app-kit/genre-tree";
720
+ import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
668
721
  import { usePlayer as usePlayer5 } from "@behindthemusictree/app-kit/player";
669
722
 
670
723
  // src/genre-tree/GenreActionBand.tsx
@@ -675,18 +728,18 @@ import { Share2 as Share22 } from "lucide-react";
675
728
  import { usePopup as usePopup3 } from "@behindthemusictree/app-kit/popup";
676
729
 
677
730
  // src/genre-tree/SharePopup.tsx
678
- import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as useState3 } from "react";
731
+ import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as useState4 } from "react";
679
732
  import { Link2, Share2 } from "lucide-react";
680
733
  import { FaWhatsapp } from "react-icons/fa";
681
734
  import { SiGmail } from "react-icons/si";
682
735
 
683
736
  // src/genre-tree/ShareDestinationButton.tsx
684
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
737
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
685
738
  var className = "group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50";
686
739
  function Contents({ icon, label }) {
687
740
  return /* @__PURE__ */ jsxs6(Fragment4, { children: [
688
- /* @__PURE__ */ jsx9("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
689
- /* @__PURE__ */ jsx9("span", { className: "max-w-full truncate", children: label })
741
+ /* @__PURE__ */ jsx10("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
742
+ /* @__PURE__ */ jsx10("span", { className: "max-w-full truncate", children: label })
690
743
  ] });
691
744
  }
692
745
  function ShareDestinationButton({
@@ -698,29 +751,29 @@ function ShareDestinationButton({
698
751
  ariaLabel
699
752
  }) {
700
753
  if (href) {
701
- return /* @__PURE__ */ jsx9("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx9(Contents, { icon, label }) });
754
+ return /* @__PURE__ */ jsx10("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx10(Contents, { icon, label }) });
702
755
  }
703
- return /* @__PURE__ */ jsx9("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx9(Contents, { icon, label }) });
756
+ return /* @__PURE__ */ jsx10("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx10(Contents, { icon, label }) });
704
757
  }
705
758
 
706
759
  // src/genre-tree/SharePreviewCard.tsx
707
760
  import { Music, Music2, UserRound } from "lucide-react";
708
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
761
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
709
762
  function SharePreviewCard({ target, url }) {
710
763
  const Icon = { genre: Music2, artist: UserRound, song: Music }[target.kind];
711
764
  const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
712
- return /* @__PURE__ */ jsx10("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs7("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
713
- /* @__PURE__ */ jsx10("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx10(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
765
+ return /* @__PURE__ */ jsx11("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs7("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
766
+ /* @__PURE__ */ jsx11("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx11(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
714
767
  /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
715
- /* @__PURE__ */ jsx10("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
716
- /* @__PURE__ */ jsx10("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
717
- /* @__PURE__ */ jsx10("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
768
+ /* @__PURE__ */ jsx11("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
769
+ /* @__PURE__ */ jsx11("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
770
+ /* @__PURE__ */ jsx11("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
718
771
  ] })
719
772
  ] }) });
720
773
  }
721
774
 
722
775
  // src/genre-tree/SharePopup.tsx
723
- import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
776
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
724
777
  function resolveAbsoluteUrl(url) {
725
778
  try {
726
779
  const parsed = new URL(url, window.location.href);
@@ -753,11 +806,11 @@ async function copyText(text) {
753
806
  }
754
807
  }
755
808
  function SharePopup({ target, url, onClose }) {
756
- const [absoluteUrl, setAbsoluteUrl] = useState3(url);
757
- const [copyStatus, setCopyStatus] = useState3("idle");
758
- const [canUseNativeShare, setCanUseNativeShare] = useState3(false);
759
- const [shareError, setShareError] = useState3(null);
760
- const [toastId, setToastId] = useState3(0);
809
+ const [absoluteUrl, setAbsoluteUrl] = useState4(url);
810
+ const [copyStatus, setCopyStatus] = useState4("idle");
811
+ const [canUseNativeShare, setCanUseNativeShare] = useState4(false);
812
+ const [shareError, setShareError] = useState4(null);
813
+ const [toastId, setToastId] = useState4(0);
761
814
  useEffect2(() => {
762
815
  setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
763
816
  setCopyStatus("idle");
@@ -796,7 +849,7 @@ function SharePopup({ target, url, onClose }) {
796
849
  }
797
850
  };
798
851
  return /* @__PURE__ */ jsxs8(Fragment5, { children: [
799
- /* @__PURE__ */ jsx11(
852
+ /* @__PURE__ */ jsx12(
800
853
  BasePopup_default,
801
854
  {
802
855
  title: "Share",
@@ -812,64 +865,64 @@ function SharePopup({ target, url, onClose }) {
812
865
  closeButtonClassName: "!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]",
813
866
  contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
814
867
  children: /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-4", children: [
815
- /* @__PURE__ */ jsx11(SharePreviewCard, { target, url: absoluteUrl || url }),
868
+ /* @__PURE__ */ jsx12(SharePreviewCard, { target, url: absoluteUrl || url }),
816
869
  /* @__PURE__ */ jsxs8("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
817
- /* @__PURE__ */ jsx11(
870
+ /* @__PURE__ */ jsx12(
818
871
  ShareDestinationButton,
819
872
  {
820
873
  label: "Copy Link",
821
- icon: /* @__PURE__ */ jsx11(Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
874
+ icon: /* @__PURE__ */ jsx12(Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
822
875
  onClick: () => void handleCopy(),
823
876
  ariaLabel: "Copy link",
824
877
  disabled: !absoluteUrl
825
878
  }
826
879
  ),
827
- /* @__PURE__ */ jsx11(
880
+ /* @__PURE__ */ jsx12(
828
881
  ShareDestinationButton,
829
882
  {
830
883
  label: "WhatsApp",
831
- icon: /* @__PURE__ */ jsx11(FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
884
+ icon: /* @__PURE__ */ jsx12(FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
832
885
  href: destinationUrls?.whatsapp,
833
886
  ariaLabel: `Share ${target.name} via WhatsApp`,
834
887
  disabled: !absoluteUrl
835
888
  }
836
889
  ),
837
- /* @__PURE__ */ jsx11(
890
+ /* @__PURE__ */ jsx12(
838
891
  ShareDestinationButton,
839
892
  {
840
893
  label: "Gmail",
841
- icon: /* @__PURE__ */ jsx11(SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
894
+ icon: /* @__PURE__ */ jsx12(SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
842
895
  href: destinationUrls?.gmail,
843
896
  ariaLabel: `Email ${target.name} with Gmail`,
844
897
  disabled: !absoluteUrl
845
898
  }
846
899
  ),
847
- canUseNativeShare && /* @__PURE__ */ jsx11(
900
+ canUseNativeShare && /* @__PURE__ */ jsx12(
848
901
  ShareDestinationButton,
849
902
  {
850
903
  label: "More",
851
- icon: /* @__PURE__ */ jsx11(Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
904
+ icon: /* @__PURE__ */ jsx12(Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
852
905
  onClick: () => void handleNativeShare(),
853
906
  ariaLabel: "More sharing options",
854
907
  disabled: !absoluteUrl
855
908
  }
856
909
  )
857
910
  ] }),
858
- copyStatus === "failed" && /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
859
- shareError && /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
911
+ copyStatus === "failed" && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
912
+ shareError && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
860
913
  ] })
861
914
  }
862
915
  ),
863
- toastId > 0 && /* @__PURE__ */ jsx11(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
916
+ toastId > 0 && /* @__PURE__ */ jsx12(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
864
917
  ] });
865
918
  }
866
919
 
867
920
  // src/genre-tree/ShareActionButton.tsx
868
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
921
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
869
922
  function ShareActionButton({ target, url }) {
870
923
  const { showPopup, hidePopup } = usePopup3();
871
924
  const openSharePopup = () => {
872
- showPopup(/* @__PURE__ */ jsx12(SharePopup, { target, url, onClose: hidePopup }));
925
+ showPopup(/* @__PURE__ */ jsx13(SharePopup, { target, url, onClose: hidePopup }));
873
926
  };
874
927
  return /* @__PURE__ */ jsxs9(
875
928
  "button",
@@ -880,7 +933,7 @@ function ShareActionButton({ target, url }) {
880
933
  "aria-label": `Share ${target.name}`,
881
934
  title: `Share ${target.name}`,
882
935
  children: [
883
- /* @__PURE__ */ jsx12("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: /* @__PURE__ */ jsx12(Share22, { "aria-hidden": "true", className: "w-5 h-5" }) }),
936
+ /* @__PURE__ */ jsx13("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: /* @__PURE__ */ jsx13(Share22, { "aria-hidden": "true", className: "w-5 h-5" }) }),
884
937
  "Share"
885
938
  ]
886
939
  }
@@ -888,7 +941,7 @@ function ShareActionButton({ target, url }) {
888
941
  }
889
942
 
890
943
  // src/genre-tree/GenreActionBand.tsx
891
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
944
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
892
945
  function GenreActionBand({
893
946
  node,
894
947
  onPlayPause,
@@ -910,7 +963,7 @@ function GenreActionBand({
910
963
  items.push({
911
964
  key: "play-pause",
912
965
  label: isPlaying ? "Pause" : "Play",
913
- icon: isPlaying ? /* @__PURE__ */ jsx13(Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx13(Play, { "aria-hidden": "true", className: "w-5 h-5" }),
966
+ icon: isPlaying ? /* @__PURE__ */ jsx14(Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx14(Play, { "aria-hidden": "true", className: "w-5 h-5" }),
914
967
  onClick: onPlayPause
915
968
  });
916
969
  }
@@ -918,24 +971,24 @@ function GenreActionBand({
918
971
  items.push({
919
972
  key: "zoom-in",
920
973
  label: "Focus",
921
- icon: /* @__PURE__ */ jsx13(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
974
+ icon: /* @__PURE__ */ jsx14(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
922
975
  onClick: onZoomIn
923
976
  });
924
977
  }
925
978
  if (onAddChild) {
926
- adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ jsx13(Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
979
+ adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ jsx14(Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
927
980
  }
928
981
  if (onRename) {
929
- adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ jsx13(Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
982
+ adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ jsx14(Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
930
983
  }
931
984
  if (onChangeParent) {
932
- adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ jsx13(MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
985
+ adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ jsx14(MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
933
986
  }
934
987
  if (onToggleSide) {
935
988
  adminItems.push({
936
989
  key: "toggle-side",
937
990
  label: side === "pop" ? "Make core" : "Make pop",
938
- icon: /* @__PURE__ */ jsx13(ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
991
+ icon: /* @__PURE__ */ jsx14(ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
939
992
  onClick: onToggleSide,
940
993
  disabled: !!toggleSideDisabledReason,
941
994
  title: toggleSideDisabledReason
@@ -960,7 +1013,7 @@ function GenreActionBand({
960
1013
  onClick: item.onClick,
961
1014
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
962
1015
  children: [
963
- /* @__PURE__ */ jsx13("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
1016
+ /* @__PURE__ */ jsx14("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
964
1017
  item.label
965
1018
  ]
966
1019
  },
@@ -969,7 +1022,7 @@ function GenreActionBand({
969
1022
  return /* @__PURE__ */ jsxs10("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
970
1023
  (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
971
1024
  renderItems(items),
972
- shareTarget && shareUrl && /* @__PURE__ */ jsx13(ShareActionButton, { target: shareTarget, url: shareUrl })
1025
+ shareTarget && shareUrl && /* @__PURE__ */ jsx14(ShareActionButton, { target: shareTarget, url: shareUrl })
973
1026
  ] }),
974
1027
  adminItems.length > 0 && /* @__PURE__ */ jsxs10(
975
1028
  "section",
@@ -977,8 +1030,8 @@ function GenreActionBand({
977
1030
  "aria-label": "Admin actions",
978
1031
  className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
979
1032
  children: [
980
- /* @__PURE__ */ jsx13("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
981
- /* @__PURE__ */ jsx13("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1033
+ /* @__PURE__ */ jsx14("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1034
+ /* @__PURE__ */ jsx14("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
982
1035
  ]
983
1036
  }
984
1037
  )
@@ -990,20 +1043,20 @@ import { PanelSection as PanelSection2, Skeleton as Skeleton2 } from "@behindthe
990
1043
  import { useFetchGenreOverview } from "@behindthemusictree/app-kit/genre-tree";
991
1044
 
992
1045
  // src/genre-tree/ClampedText.tsx
993
- import { useLayoutEffect, useRef as useRef2, useState as useState4 } from "react";
994
- import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1046
+ import { useLayoutEffect, useRef as useRef2, useState as useState5 } from "react";
1047
+ import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
995
1048
  function ClampedText({ children, className: className2 = "" }) {
996
1049
  const ref = useRef2(null);
997
- const [expanded, setExpanded] = useState4(false);
998
- const [overflows, setOverflows] = useState4(false);
1050
+ const [expanded, setExpanded] = useState5(false);
1051
+ const [overflows, setOverflows] = useState5(false);
999
1052
  useLayoutEffect(() => {
1000
1053
  const element = ref.current;
1001
1054
  if (!element || expanded) return;
1002
1055
  setOverflows(element.scrollHeight > element.clientHeight);
1003
1056
  }, [children, expanded]);
1004
1057
  return /* @__PURE__ */ jsxs11(Fragment6, { children: [
1005
- /* @__PURE__ */ jsx14("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1006
- (overflows || expanded) && /* @__PURE__ */ jsx14(
1058
+ /* @__PURE__ */ jsx15("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1059
+ (overflows || expanded) && /* @__PURE__ */ jsx15(
1007
1060
  "button",
1008
1061
  {
1009
1062
  type: "button",
@@ -1017,7 +1070,7 @@ function ClampedText({ children, className: className2 = "" }) {
1017
1070
  }
1018
1071
 
1019
1072
  // src/genre-tree/GenrePlaylistTracks.tsx
1020
- import { useEffect as useEffect3, useState as useState5 } from "react";
1073
+ import { useEffect as useEffect3, useState as useState6 } from "react";
1021
1074
  import { useInfiniteQuery } from "@tanstack/react-query";
1022
1075
  import { z } from "zod";
1023
1076
  import { FaPlay, FaPause } from "react-icons/fa";
@@ -1030,6 +1083,7 @@ import { usePlayer as usePlayer4 } from "@behindthemusictree/app-kit/player";
1030
1083
  import { PlayStates } from "@behindthemusictree/app-kit/player";
1031
1084
  import { usePopup as usePopup4 } from "@behindthemusictree/app-kit/popup";
1032
1085
  import { ErrorCode as ErrorCode3 } from "@behindthemusictree/app-kit/transport";
1086
+ import { genreMusicQueryParams } from "@behindthemusictree/app-kit/genre-tree";
1033
1087
  import { genrePlaylistQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
1034
1088
  import { libraryEndpoints, libraryQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
1035
1089
  import { isSong, songUnplayableReason, SongDetailedSchema } from "@behindthemusictree/app-kit/genre-tree";
@@ -1037,15 +1091,15 @@ import { useTrackList as useTrackList2 } from "@behindthemusictree/app-kit/genre
1037
1091
 
1038
1092
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1039
1093
  import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
1040
- import { Fragment as Fragment7, jsx as jsx15 } from "react/jsx-runtime";
1094
+ import { Fragment as Fragment7, jsx as jsx16 } from "react/jsx-runtime";
1041
1095
  function MusicBrainzRecordingLink({
1042
1096
  song,
1043
1097
  children,
1044
1098
  className: className2 = ""
1045
1099
  }) {
1046
1100
  const url = musicBrainzRecordingUrl(song);
1047
- if (!url) return /* @__PURE__ */ jsx15(Fragment7, { children });
1048
- return /* @__PURE__ */ jsx15(
1101
+ if (!url) return /* @__PURE__ */ jsx16(Fragment7, { children });
1102
+ return /* @__PURE__ */ jsx16(
1049
1103
  "a",
1050
1104
  {
1051
1105
  href: url,
@@ -1062,7 +1116,7 @@ function MusicBrainzRecordingLink({
1062
1116
  // src/genre-tree/GenrePlaylistTracks.tsx
1063
1117
  import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
1064
1118
  import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
1065
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1119
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
1066
1120
  var CATALOGUE_PAGE_SIZE = 100;
1067
1121
  var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
1068
1122
  overallTotal: z.number().nullable(),
@@ -1082,6 +1136,7 @@ var nextCursor = (next) => {
1082
1136
  function GenrePlaylistTracks({
1083
1137
  genreUuid,
1084
1138
  genrePlaylist,
1139
+ selection,
1085
1140
  scope,
1086
1141
  getBackendBaseUrl,
1087
1142
  renderArtistLink,
@@ -1091,7 +1146,7 @@ function GenrePlaylistTracks({
1091
1146
  renderTracksToolbar,
1092
1147
  artist = null
1093
1148
  }) {
1094
- const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList2();
1149
+ const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist, playNewTrackListFromGenreCatalogue } = useTrackList2();
1095
1150
  const { session, sessionRestored } = useSession();
1096
1151
  const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer4();
1097
1152
  const { showPopup } = usePopup4();
@@ -1100,7 +1155,7 @@ function GenrePlaylistTracks({
1100
1155
  const { fetch } = useFetchWrapper(getBackendBaseUrl);
1101
1156
  const playback = usePlaybackContext();
1102
1157
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({
1103
- queryKey: [...scope === "me" ? genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), ...playback.key ? [playback.key] : []],
1158
+ queryKey: [...scope === "me" ? genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
1104
1159
  queryFn: async ({ pageParam }) => {
1105
1160
  if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1106
1161
  const response = await fetch(
@@ -1108,7 +1163,7 @@ function GenrePlaylistTracks({
1108
1163
  true,
1109
1164
  false,
1110
1165
  {},
1111
- { genre: genreUuid, ...artist && { artist: artist.uuid }, pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...pageParam && { cursor: pageParam } }
1166
+ { genre: genreUuid, ...genreMusicQueryParams(selection), ...artist && { artist: artist.uuid }, pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...pageParam && { cursor: pageParam } }
1112
1167
  );
1113
1168
  const parsed = parseWithLog(SongPageSchema, response, "GenrePlaylistTracks");
1114
1169
  return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };
@@ -1117,7 +1172,7 @@ function GenrePlaylistTracks({
1117
1172
  getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
1118
1173
  enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
1119
1174
  });
1120
- const [sentinel, setSentinel] = useState5(null);
1175
+ const [sentinel, setSentinel] = useState6(null);
1121
1176
  useEffect3(() => {
1122
1177
  if (!hasNextPage || isFetchingNextPage || !sentinel) return;
1123
1178
  const observer = new IntersectionObserver((entries) => {
@@ -1132,30 +1187,35 @@ function GenrePlaylistTracks({
1132
1187
  const tracks = pages.flatMap((page) => page.tracks);
1133
1188
  const total = pages[0]?.total ?? void 0;
1134
1189
  const play = (startSongUuid) => {
1135
- const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1190
+ const started = artist ? playNewTrackListFromArtist(artist, scope, {
1191
+ startSongUuid,
1192
+ ordering: "title",
1193
+ genreUuid,
1194
+ ...selection && { selection }
1195
+ }) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genrePlaylist.name }, scope, { startSongUuid, ...selection && { selection } }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1136
1196
  started.catch((error) => {
1137
1197
  console.error("Failed to play genre playlist:", error);
1138
- showPopup(/* @__PURE__ */ jsx16(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
1198
+ showPopup(/* @__PURE__ */ jsx17(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
1139
1199
  });
1140
1200
  };
1141
- return /* @__PURE__ */ jsx16(PanelSection, { title: `${capitalize(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ jsxs12("div", { children: [
1201
+ return /* @__PURE__ */ jsx17(PanelSection, { title: `${capitalize(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ jsxs12("div", { children: [
1142
1202
  renderTracksToolbar?.(),
1143
- isPending ? /* @__PURE__ */ jsx16(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs12("p", { role: "alert", children: [
1203
+ isPending ? /* @__PURE__ */ jsx17(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs12("p", { role: "alert", children: [
1144
1204
  "Couldn't load ",
1145
1205
  trackNoun.plural,
1146
1206
  ".",
1147
1207
  " ",
1148
- /* @__PURE__ */ jsx16("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1149
- ] }) : tracks.length === 0 ? /* @__PURE__ */ jsx16("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs12("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
1208
+ /* @__PURE__ */ jsx17("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1209
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ jsx17("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs12("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
1150
1210
  tracks.map((track, index) => {
1151
1211
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1152
1212
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1153
1213
  const isPlaying = isCurrent && playState === PlayStates.PLAYING;
1154
1214
  const text = /* @__PURE__ */ jsxs12("span", { className: TEXT_CELL, children: [
1155
- /* @__PURE__ */ jsx16("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx16(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1156
- track.artists?.length ? /* @__PURE__ */ jsx16("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1157
- /* @__PURE__ */ jsx16("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx16(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1158
- track.album && /* @__PURE__ */ jsx16("span", { className: ALBUM_CELL, children: track.album.name })
1215
+ /* @__PURE__ */ jsx17("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx17(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1216
+ track.artists?.length ? /* @__PURE__ */ jsx17("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1217
+ /* @__PURE__ */ jsx17("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx17(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1218
+ track.album && /* @__PURE__ */ jsx17("span", { className: ALBUM_CELL, children: track.album.name })
1159
1219
  ] });
1160
1220
  const leading = renderSongLeading?.(track, index);
1161
1221
  const actions = renderSongActions?.(track);
@@ -1163,7 +1223,7 @@ function GenrePlaylistTracks({
1163
1223
  if (isUnplayable) {
1164
1224
  return /* @__PURE__ */ jsxs12("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1165
1225
  leading,
1166
- /* @__PURE__ */ jsx16(
1226
+ /* @__PURE__ */ jsx17(
1167
1227
  "button",
1168
1228
  {
1169
1229
  type: "button",
@@ -1171,7 +1231,7 @@ function GenrePlaylistTracks({
1171
1231
  "aria-disabled": "true",
1172
1232
  "aria-label": label,
1173
1233
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1174
- children: /* @__PURE__ */ jsx16(FaPlay, { size: 10 })
1234
+ children: /* @__PURE__ */ jsx17(FaPlay, { size: 10 })
1175
1235
  }
1176
1236
  ),
1177
1237
  text,
@@ -1184,14 +1244,14 @@ function GenrePlaylistTracks({
1184
1244
  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)]" : ""}`,
1185
1245
  children: [
1186
1246
  leading,
1187
- /* @__PURE__ */ jsx16(
1247
+ /* @__PURE__ */ jsx17(
1188
1248
  "button",
1189
1249
  {
1190
1250
  type: "button",
1191
1251
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1192
1252
  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 !== PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
1193
1253
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1194
- children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx16(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx16(FaPause, { size: 10 }) : /* @__PURE__ */ jsx16(FaPlay, { size: 10 })
1254
+ children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx17(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx17(FaPause, { size: 10 }) : /* @__PURE__ */ jsx17(FaPlay, { size: 10 })
1195
1255
  }
1196
1256
  ),
1197
1257
  text,
@@ -1201,7 +1261,7 @@ function GenrePlaylistTracks({
1201
1261
  track.uuid
1202
1262
  );
1203
1263
  }),
1204
- hasNextPage && /* @__PURE__ */ jsx16("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1264
+ hasNextPage && /* @__PURE__ */ jsx17("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1205
1265
  ] })
1206
1266
  ] }) });
1207
1267
  }
@@ -1209,10 +1269,11 @@ function GenrePlaylistTracks({
1209
1269
  // src/genre-tree/GenreDetailExtras.tsx
1210
1270
  import { useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
1211
1271
  import { SiWikidata, SiWikipedia } from "react-icons/si";
1212
- import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1272
+ import { Fragment as Fragment8, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
1213
1273
  function GenreDetailExtras({
1214
1274
  genreUuid,
1215
1275
  genrePlaylist,
1276
+ selection,
1216
1277
  wikidataId,
1217
1278
  scope,
1218
1279
  getBackendBaseUrl,
@@ -1234,15 +1295,15 @@ function GenreDetailExtras({
1234
1295
  );
1235
1296
  if (isPending) {
1236
1297
  return /* @__PURE__ */ jsxs13(Fragment8, { children: [
1237
- /* @__PURE__ */ jsx17(PanelSection2, { title: "Summary", children: /* @__PURE__ */ jsx17("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ jsx17(Skeleton2, { className: "h-4 w-3/4" }) }) }),
1298
+ /* @__PURE__ */ jsx18(PanelSection2, { title: "Summary", children: /* @__PURE__ */ jsx18("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ jsx18(Skeleton2, { className: "h-4 w-3/4" }) }) }),
1238
1299
  family
1239
1300
  ] });
1240
1301
  }
1241
- if (!overview) return /* @__PURE__ */ jsx17(Fragment8, { children: family });
1302
+ if (!overview) return /* @__PURE__ */ jsx18(Fragment8, { children: family });
1242
1303
  const { summary, summarySourceUrl, essentialSongs } = overview;
1243
1304
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1244
1305
  return /* @__PURE__ */ jsxs13(Fragment8, { children: [
1245
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsx17(PanelSection2, { title: "Links", children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1306
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsx18(PanelSection2, { title: "Links", children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1246
1307
  summarySourceUrl && /* @__PURE__ */ jsxs13(
1247
1308
  "a",
1248
1309
  {
@@ -1251,7 +1312,7 @@ function GenreDetailExtras({
1251
1312
  rel: "noopener noreferrer",
1252
1313
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1253
1314
  children: [
1254
- /* @__PURE__ */ jsx17(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1315
+ /* @__PURE__ */ jsx18(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1255
1316
  "Wikipedia"
1256
1317
  ]
1257
1318
  }
@@ -1264,21 +1325,22 @@ function GenreDetailExtras({
1264
1325
  rel: "noopener noreferrer",
1265
1326
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1266
1327
  children: [
1267
- /* @__PURE__ */ jsx17(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1328
+ /* @__PURE__ */ jsx18(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1268
1329
  "Wikidata"
1269
1330
  ]
1270
1331
  }
1271
1332
  )
1272
1333
  ] }) }),
1273
- /* @__PURE__ */ jsx17(PanelSection2, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsx17(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1334
+ /* @__PURE__ */ jsx18(PanelSection2, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsx18(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1274
1335
  family,
1275
1336
  renderGenreDetailExtras?.(overview),
1276
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ jsx17(PanelSection2, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ jsx17("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx17("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx17("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1277
- /* @__PURE__ */ jsx17(
1337
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ jsx18(PanelSection2, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ jsx18("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx18("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx18("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1338
+ /* @__PURE__ */ jsx18(
1278
1339
  GenrePlaylistTracks,
1279
1340
  {
1280
1341
  genreUuid,
1281
1342
  genrePlaylist,
1343
+ selection,
1282
1344
  scope,
1283
1345
  getBackendBaseUrl,
1284
1346
  renderArtistLink,
@@ -1290,7 +1352,7 @@ function GenreDetailExtras({
1290
1352
  }
1291
1353
 
1292
1354
  // src/genre-tree/GenreInfoPanelBody.tsx
1293
- import { Fragment as Fragment9, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1355
+ import { Fragment as Fragment9, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
1294
1356
  import { createElement } from "react";
1295
1357
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1296
1358
  const { isPlaying, setIsPlaying } = usePlayer5();
@@ -1304,11 +1366,11 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1304
1366
  console.error("Failed to load genre playlist tracks:", error);
1305
1367
  });
1306
1368
  };
1307
- return /* @__PURE__ */ jsx18(
1369
+ return /* @__PURE__ */ jsx19(
1308
1370
  GenreActionBand,
1309
1371
  {
1310
1372
  ...bandProps,
1311
- onPlayPause: genrePlaylist.tracksCount > 0 ? handlePlayPause : void 0,
1373
+ onPlayPause: ("genreOnlyTracksCount" in genrePlaylist ? genreMusicCount(genrePlaylist) : genrePlaylist.tracksCount) > 0 ? handlePlayPause : void 0,
1312
1374
  isPlaying: isCurrentTrackList && isPlaying
1313
1375
  }
1314
1376
  );
@@ -1318,6 +1380,7 @@ function GenreInfoPanelBody({
1318
1380
  family,
1319
1381
  genreUuid,
1320
1382
  genrePlaylist,
1383
+ catalogue,
1321
1384
  wikidataId,
1322
1385
  scope,
1323
1386
  getBackendBaseUrl,
@@ -1325,9 +1388,30 @@ function GenreInfoPanelBody({
1325
1388
  band,
1326
1389
  extras
1327
1390
  }) {
1391
+ const noun = useTrackNoun3();
1392
+ const summary = scope === "reference" ? GenreCatalogueCountsSchema2.parse(genrePlaylist) : void 0;
1393
+ const selection = summary ? defaultGenreMusicSelection2(summary) : void 0;
1394
+ const describeCounts = (counts) => {
1395
+ const scopes = counts.genreAndSubgenresTracksCount;
1396
+ return "canonical" in scopes ? `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.canonical} with canonical subgenres \xB7 ${scopes.regional} with regional subgenres \xB7 ${scopes.all} with all subgenres` : `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.all} with subgenres`;
1397
+ };
1328
1398
  return /* @__PURE__ */ jsxs14(Fragment9, { children: [
1329
1399
  toolbar,
1330
- /* @__PURE__ */ jsx18(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1400
+ /* @__PURE__ */ jsx19(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1401
+ summary && catalogue && /* @__PURE__ */ jsxs14("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
1402
+ /* @__PURE__ */ jsxs14("p", { children: [
1403
+ "Catalogue ",
1404
+ noun.plural,
1405
+ ": ",
1406
+ describeCounts(catalogue)
1407
+ ] }),
1408
+ /* @__PURE__ */ jsxs14("p", { children: [
1409
+ "Playlist ",
1410
+ noun.plural,
1411
+ ": ",
1412
+ describeCounts(summary)
1413
+ ] })
1414
+ ] }),
1331
1415
  /* @__PURE__ */ createElement(
1332
1416
  GenreDetailExtras,
1333
1417
  {
@@ -1336,6 +1420,7 @@ function GenreInfoPanelBody({
1336
1420
  family,
1337
1421
  genreUuid,
1338
1422
  genrePlaylist,
1423
+ selection,
1339
1424
  wikidataId,
1340
1425
  scope,
1341
1426
  getBackendBaseUrl
@@ -1350,7 +1435,7 @@ import {
1350
1435
  getReparentCandidates,
1351
1436
  useUpdateGenre
1352
1437
  } from "@behindthemusictree/app-kit/genre-tree";
1353
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1438
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
1354
1439
  function ChangeParentPopup({
1355
1440
  genrePlaylist,
1356
1441
  genrePlaylists,
@@ -1390,7 +1475,7 @@ function ChangeParentPopup({
1390
1475
  "Current parent: ",
1391
1476
  genrePlaylist.parent?.name ?? "none"
1392
1477
  ] }),
1393
- /* @__PURE__ */ jsx19("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx19(
1478
+ /* @__PURE__ */ jsx20("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx20(
1394
1479
  GenreSearch,
1395
1480
  {
1396
1481
  genrePlaylists: candidates,
@@ -1398,7 +1483,7 @@ function ChangeParentPopup({
1398
1483
  placeholder: "Search a new parent\u2026"
1399
1484
  }
1400
1485
  ) }),
1401
- formErrors.length > 0 && /* @__PURE__ */ jsx19("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1486
+ formErrors.length > 0 && /* @__PURE__ */ jsx20("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1402
1487
  ] });
1403
1488
  }
1404
1489
 
@@ -1422,7 +1507,7 @@ import {
1422
1507
  SiYoutube
1423
1508
  } from "react-icons/si";
1424
1509
  import { Chip } from "@behindthemusictree/ui";
1425
- import { jsx as jsx20 } from "react/jsx-runtime";
1510
+ import { jsx as jsx21 } from "react/jsx-runtime";
1426
1511
  var LINK_KINDS = {
1427
1512
  official: { label: "Official website", Icon: Globe },
1428
1513
  wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
@@ -1459,7 +1544,7 @@ function artistLinks(artist) {
1459
1544
  return links;
1460
1545
  }
1461
1546
  function ArtistLinks({ links }) {
1462
- return /* @__PURE__ */ jsx20("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx20("li", { children: /* @__PURE__ */ jsx20(Chip, { href: url, icon: /* @__PURE__ */ jsx20(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1547
+ return /* @__PURE__ */ jsx21("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx21("li", { children: /* @__PURE__ */ jsx21(Chip, { href: url, icon: /* @__PURE__ */ jsx21(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1463
1548
  }
1464
1549
 
1465
1550
  // src/genre-tree/artist/formatYears.ts
@@ -1470,7 +1555,7 @@ function formatYears(beginYear, endYear, ended) {
1470
1555
  }
1471
1556
 
1472
1557
  // src/genre-tree/artist/ArtistHeader.tsx
1473
- import { jsx as jsx21 } from "react/jsx-runtime";
1558
+ import { jsx as jsx22 } from "react/jsx-runtime";
1474
1559
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1475
1560
  function ArtistHeader({ artist }) {
1476
1561
  const fields = [];
@@ -1480,35 +1565,35 @@ function ArtistHeader({ artist }) {
1480
1565
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1481
1566
  if (years) fields.push({ label: "Active", value: years });
1482
1567
  const links = artistLinks(artist);
1483
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx21(ArtistLinks, { links }) });
1568
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx22(ArtistLinks, { links }) });
1484
1569
  if (fields.length === 0) return null;
1485
- return /* @__PURE__ */ jsx21(FieldList, { items: fields });
1570
+ return /* @__PURE__ */ jsx22(FieldList, { items: fields });
1486
1571
  }
1487
1572
 
1488
1573
  // src/genre-tree/artist/ArtistPhoto.tsx
1489
- import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
1574
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
1490
1575
  function ExternalLink({ href, children }) {
1491
- return /* @__PURE__ */ jsx22("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1576
+ return /* @__PURE__ */ jsx23("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1492
1577
  }
1493
1578
  function ArtistPhoto({ image, name }) {
1494
1579
  return /* @__PURE__ */ jsxs16("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1495
- /* @__PURE__ */ jsx22("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1580
+ /* @__PURE__ */ jsx23("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1496
1581
  /* @__PURE__ */ jsxs16("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1497
1582
  "Photo: ",
1498
- /* @__PURE__ */ jsx22(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1583
+ /* @__PURE__ */ jsx23(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1499
1584
  ",",
1500
1585
  " ",
1501
- image.licenseUrl ? /* @__PURE__ */ jsx22(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1586
+ image.licenseUrl ? /* @__PURE__ */ jsx23(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1502
1587
  ] })
1503
1588
  ] });
1504
1589
  }
1505
1590
 
1506
1591
  // src/genre-tree/artist/ArtistMembers.tsx
1507
1592
  import { PanelGroup, PanelSection as PanelSection3 } from "@behindthemusictree/ui";
1508
- import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
1593
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
1509
1594
  function MembershipList({ title, memberships, renderArtistLink }) {
1510
1595
  if (memberships.length === 0) return null;
1511
- return /* @__PURE__ */ jsx23(PanelGroup, { title, children: /* @__PURE__ */ jsx23("ul", { children: memberships.map((membership, index) => {
1596
+ return /* @__PURE__ */ jsx24(PanelGroup, { title, children: /* @__PURE__ */ jsx24("ul", { children: memberships.map((membership, index) => {
1512
1597
  const details = [
1513
1598
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1514
1599
  ...membership.roles
@@ -1517,7 +1602,7 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1517
1602
  return (
1518
1603
  // A member can have several periods in the same group.
1519
1604
  /* @__PURE__ */ jsxs17("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1520
- /* @__PURE__ */ jsx23("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1605
+ /* @__PURE__ */ jsx24("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1521
1606
  detailsText && /* @__PURE__ */ jsxs17("span", { className: "text-[var(--color-text-muted)]", children: [
1522
1607
  " \u2014 ",
1523
1608
  detailsText
@@ -1530,8 +1615,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1530
1615
  if (memberships.length === 0) return null;
1531
1616
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1532
1617
  return /* @__PURE__ */ jsxs17(PanelSection3, { title, children: [
1533
- /* @__PURE__ */ jsx23(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1534
- /* @__PURE__ */ jsx23(
1618
+ /* @__PURE__ */ jsx24(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1619
+ /* @__PURE__ */ jsx24(
1535
1620
  MembershipList,
1536
1621
  {
1537
1622
  title: "Past",
@@ -1544,7 +1629,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1544
1629
 
1545
1630
  // src/genre-tree/artist/ArtistDiscography.tsx
1546
1631
  import { PanelGroup as PanelGroup2, PanelSection as PanelSection4 } from "@behindthemusictree/ui";
1547
- import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
1632
+ import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
1548
1633
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1549
1634
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1550
1635
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1564,18 +1649,18 @@ function byYear(a, b) {
1564
1649
  function ArtistDiscography({ albums }) {
1565
1650
  if (albums.length === 0) return null;
1566
1651
  const sorted = [...albums].sort(byYear);
1567
- return /* @__PURE__ */ jsx24(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
1652
+ return /* @__PURE__ */ jsx25(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
1568
1653
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1569
1654
  if (groupAlbums.length === 0) return null;
1570
- return /* @__PURE__ */ jsx24(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx24("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs18("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1571
- /* @__PURE__ */ jsx24("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1572
- /* @__PURE__ */ jsx24("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1655
+ return /* @__PURE__ */ jsx25(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx25("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs18("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1656
+ /* @__PURE__ */ jsx25("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1657
+ /* @__PURE__ */ jsx25("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1573
1658
  ] }, album.uuid)) }) }, group);
1574
1659
  }) });
1575
1660
  }
1576
1661
 
1577
1662
  // src/genre-tree/artist/ArtistSongs.tsx
1578
- import { useState as useState6 } from "react";
1663
+ import { useState as useState7 } from "react";
1579
1664
  import { keepPreviousData } from "@tanstack/react-query";
1580
1665
  import { Play as Play2 } from "lucide-react";
1581
1666
  import { Pagination, PanelSection as PanelSection5, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
@@ -1596,7 +1681,7 @@ import { useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre
1596
1681
  import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
1597
1682
  import { usePlayer as usePlayer6 } from "@behindthemusictree/app-kit/player";
1598
1683
  import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
1599
- import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
1684
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
1600
1685
  function TrackPositionPlayPause({
1601
1686
  position,
1602
1687
  uuid,
@@ -1617,7 +1702,7 @@ function TrackPositionPlayPause({
1617
1702
  style: { minWidth: "4rem", maxWidth: "4rem" },
1618
1703
  onClick: handlePlayPauseClick,
1619
1704
  children: [
1620
- /* @__PURE__ */ jsx25("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs19(
1705
+ /* @__PURE__ */ jsx26("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs19(
1621
1706
  "span",
1622
1707
  {
1623
1708
  className: "track-position-play-pause__bars",
@@ -1625,18 +1710,18 @@ function TrackPositionPlayPause({
1625
1710
  role: "img",
1626
1711
  "aria-label": isPlaying ? "Playing" : "Paused",
1627
1712
  children: [
1628
- /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__bar" }),
1629
- /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__bar" }),
1630
- /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__bar" })
1713
+ /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__bar" }),
1714
+ /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__bar" }),
1715
+ /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__bar" })
1631
1716
  ]
1632
1717
  }
1633
1718
  ) : position }),
1634
- /* @__PURE__ */ jsx25(
1719
+ /* @__PURE__ */ jsx26(
1635
1720
  "div",
1636
1721
  {
1637
1722
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1638
1723
  "aria-hidden": "true",
1639
- children: isAwaitingPlayback ? /* @__PURE__ */ jsx25("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx25(FaPause2, {}) : /* @__PURE__ */ jsx25(FaPlay2, {})
1724
+ children: isAwaitingPlayback ? /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx26(FaPause2, {}) : /* @__PURE__ */ jsx26(FaPlay2, {})
1640
1725
  }
1641
1726
  )
1642
1727
  ]
@@ -1647,7 +1732,7 @@ function TrackPositionPlayPause({
1647
1732
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1648
1733
  import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
1649
1734
  import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
1650
- import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
1735
+ import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
1651
1736
  function TrackItem({
1652
1737
  track,
1653
1738
  position,
@@ -1676,7 +1761,7 @@ function TrackItem({
1676
1761
  {
1677
1762
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
1678
1763
  children: [
1679
- isUnplayable ? /* @__PURE__ */ jsx26(
1764
+ isUnplayable ? /* @__PURE__ */ jsx27(
1680
1765
  "div",
1681
1766
  {
1682
1767
  className: "flex items-center justify-center text-lg w-16",
@@ -1684,7 +1769,7 @@ function TrackItem({
1684
1769
  "aria-disabled": "true",
1685
1770
  children: position
1686
1771
  }
1687
- ) : /* @__PURE__ */ jsx26(
1772
+ ) : /* @__PURE__ */ jsx27(
1688
1773
  TrackPositionPlayPause,
1689
1774
  {
1690
1775
  position,
@@ -1694,11 +1779,11 @@ function TrackItem({
1694
1779
  }
1695
1780
  ),
1696
1781
  /* @__PURE__ */ jsxs20("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1697
- /* @__PURE__ */ jsx26("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx26("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx26(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1698
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx26("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx26(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
1782
+ /* @__PURE__ */ jsx27("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx27("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx27(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1783
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx27("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx27(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
1699
1784
  ] }),
1700
- /* @__PURE__ */ jsx26("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx26("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1701
- renderDuration && /* @__PURE__ */ jsx26(
1785
+ /* @__PURE__ */ jsx27("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx27("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1786
+ renderDuration && /* @__PURE__ */ jsx27(
1702
1787
  "div",
1703
1788
  {
1704
1789
  className: "duration flex text-base w-16 items-center justify-center",
@@ -1706,22 +1791,22 @@ function TrackItem({
1706
1791
  children: renderDuration(track)
1707
1792
  }
1708
1793
  ),
1709
- renderActions && /* @__PURE__ */ jsx26("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1794
+ renderActions && /* @__PURE__ */ jsx27("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1710
1795
  ]
1711
1796
  }
1712
1797
  );
1713
1798
  }
1714
1799
 
1715
1800
  // src/genre-tree/artist/ArtistSongs.tsx
1716
- import { Fragment as Fragment10, jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
1801
+ import { Fragment as Fragment10, jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
1717
1802
  var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
1718
1803
  var SORTS = [
1719
1804
  { ordering: "title", label: "Title" },
1720
1805
  { ordering: "-albumYear", label: "Year" }
1721
1806
  ];
1722
1807
  function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
1723
- const [page, setPage] = useState6(1);
1724
- const [ordering, setOrdering] = useState6("title");
1808
+ const [page, setPage] = useState7(1);
1809
+ const [ordering, setOrdering] = useState7("title");
1725
1810
  const { fetch } = useFetchWrapper2(getBackendBaseUrl);
1726
1811
  const { playNewTrackListFromArtist } = useTrackList4();
1727
1812
  const { playerTrackObject } = usePlayer8();
@@ -1746,7 +1831,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1746
1831
  event.stopPropagation();
1747
1832
  void play(song.uuid);
1748
1833
  };
1749
- return /* @__PURE__ */ jsx27(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx27(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx27("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx27("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs21(Fragment10, { children: [
1834
+ return /* @__PURE__ */ jsx28(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx28(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx28("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx28("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs21(Fragment10, { children: [
1750
1835
  /* @__PURE__ */ jsxs21("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1751
1836
  /* @__PURE__ */ jsxs21(
1752
1837
  "button",
@@ -1755,12 +1840,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1755
1840
  onClick: () => void play(),
1756
1841
  className: "flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700",
1757
1842
  children: [
1758
- /* @__PURE__ */ jsx27(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1843
+ /* @__PURE__ */ jsx28(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1759
1844
  "Play all"
1760
1845
  ]
1761
1846
  }
1762
1847
  ),
1763
- /* @__PURE__ */ jsx27("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx27(
1848
+ /* @__PURE__ */ jsx28("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx28(
1764
1849
  "button",
1765
1850
  {
1766
1851
  type: "button",
@@ -1775,7 +1860,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1775
1860
  sort.ordering
1776
1861
  )) })
1777
1862
  ] }),
1778
- /* @__PURE__ */ jsx27("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx27("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx27(
1863
+ /* @__PURE__ */ jsx28("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx28("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx28(
1779
1864
  TrackItem,
1780
1865
  {
1781
1866
  track: song,
@@ -1784,12 +1869,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1784
1869
  renderSongLink
1785
1870
  }
1786
1871
  ) }, song.uuid)) }),
1787
- /* @__PURE__ */ jsx27(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
1872
+ /* @__PURE__ */ jsx28(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
1788
1873
  ] }) });
1789
1874
  }
1790
1875
 
1791
1876
  // src/genre-tree/artist/useArtistPanel.tsx
1792
- import { Fragment as Fragment11, jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
1877
+ import { Fragment as Fragment11, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
1793
1878
  function useArtistPanel({
1794
1879
  uuid,
1795
1880
  scope,
@@ -1811,25 +1896,25 @@ function useArtistPanel({
1811
1896
  ...panel,
1812
1897
  title: "Artist",
1813
1898
  content: /* @__PURE__ */ jsxs22("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
1814
- /* @__PURE__ */ jsx28(Skeleton4, { className: "h-3 w-16" }),
1815
- /* @__PURE__ */ jsx28(Skeleton4, { className: "h-4 w-32" }),
1816
- /* @__PURE__ */ jsx28(Skeleton4, { className: "h-3 w-16" }),
1817
- /* @__PURE__ */ jsx28(Skeleton4, { className: "h-4 w-24" }),
1818
- /* @__PURE__ */ jsx28(Skeleton4, { className: "h-16 mt-2.5" }),
1819
- /* @__PURE__ */ jsx28(Skeleton4, { className: "h-16" })
1899
+ /* @__PURE__ */ jsx29(Skeleton4, { className: "h-3 w-16" }),
1900
+ /* @__PURE__ */ jsx29(Skeleton4, { className: "h-4 w-32" }),
1901
+ /* @__PURE__ */ jsx29(Skeleton4, { className: "h-3 w-16" }),
1902
+ /* @__PURE__ */ jsx29(Skeleton4, { className: "h-4 w-24" }),
1903
+ /* @__PURE__ */ jsx29(Skeleton4, { className: "h-16 mt-2.5" }),
1904
+ /* @__PURE__ */ jsx29(Skeleton4, { className: "h-16" })
1820
1905
  ] })
1821
1906
  };
1822
1907
  case "not-found":
1823
1908
  return {
1824
1909
  ...panel,
1825
1910
  title: "Artist not found",
1826
- content: /* @__PURE__ */ jsx28("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
1911
+ content: /* @__PURE__ */ jsx29("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
1827
1912
  };
1828
1913
  case "error":
1829
1914
  return {
1830
1915
  ...panel,
1831
1916
  title: "Artist",
1832
- content: /* @__PURE__ */ jsx28("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
1917
+ content: /* @__PURE__ */ jsx29("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
1833
1918
  };
1834
1919
  case "success": {
1835
1920
  const { artist } = result;
@@ -1839,12 +1924,12 @@ function useArtistPanel({
1839
1924
  ...panel,
1840
1925
  title: artist.name,
1841
1926
  content: /* @__PURE__ */ jsxs22(Fragment11, { children: [
1842
- shareUrl && /* @__PURE__ */ jsx28("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx28("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx28(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
1843
- artist.image && /* @__PURE__ */ jsx28(ArtistPhoto, { image: artist.image, name: artist.name }),
1844
- /* @__PURE__ */ jsx28(ArtistHeader, { artist }),
1845
- /* @__PURE__ */ jsx28(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
1846
- /* @__PURE__ */ jsx28(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
1847
- artist.tracksCount > 0 && /* @__PURE__ */ jsx28(
1927
+ shareUrl && /* @__PURE__ */ jsx29("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx29("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx29(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
1928
+ artist.image && /* @__PURE__ */ jsx29(ArtistPhoto, { image: artist.image, name: artist.name }),
1929
+ /* @__PURE__ */ jsx29(ArtistHeader, { artist }),
1930
+ /* @__PURE__ */ jsx29(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
1931
+ /* @__PURE__ */ jsx29(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
1932
+ artist.tracksCount > 0 && /* @__PURE__ */ jsx29(
1848
1933
  ArtistSongs,
1849
1934
  {
1850
1935
  artist: { uuid: artist.uuid, name: artist.name },
@@ -1854,7 +1939,7 @@ function useArtistPanel({
1854
1939
  }
1855
1940
  )
1856
1941
  ] }),
1857
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx28(ArtistDiscography, { albums: artist.albums }) : void 0
1942
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx29(ArtistDiscography, { albums: artist.albums }) : void 0
1858
1943
  };
1859
1944
  }
1860
1945
  }
@@ -1867,7 +1952,7 @@ import {
1867
1952
  } from "@behindthemusictree/app-kit/genre-tree";
1868
1953
 
1869
1954
  // src/genre-tree/song/SongPanelContent.tsx
1870
- import { useState as useState7 } from "react";
1955
+ import { useState as useState8 } from "react";
1871
1956
  import { Pause as Pause2, Play as Play3 } from "lucide-react";
1872
1957
  import { FieldList as FieldList2, PanelSection as PanelSection6, Skeleton as Skeleton5 } from "@behindthemusictree/ui";
1873
1958
  import { PlayStates as PlayStates3, usePlayer as usePlayer9 } from "@behindthemusictree/app-kit/player";
@@ -1878,19 +1963,19 @@ import {
1878
1963
  useSongHistory,
1879
1964
  useTrackList as useTrackList5
1880
1965
  } from "@behindthemusictree/app-kit/genre-tree";
1881
- import { Fragment as Fragment12, jsx as jsx29, jsxs as jsxs23 } from "react/jsx-runtime";
1966
+ import { Fragment as Fragment12, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
1882
1967
  var numberFormat = new Intl.NumberFormat("en");
1883
1968
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
1884
1969
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
1885
1970
  function SongHistory({ uuid, getBackendBaseUrl }) {
1886
- const [open, setOpen] = useState7(false);
1971
+ const [open, setOpen] = useState8(false);
1887
1972
  const history = useSongHistory(uuid, getBackendBaseUrl, open);
1888
1973
  let body = null;
1889
- if (history.status === "loading") body = /* @__PURE__ */ jsx29(Skeleton5, { className: "h-10", "aria-busy": "true" });
1890
- else if (history.status === "error") body = /* @__PURE__ */ jsx29("p", { className: "text-red-600", children: "Could not load the history." });
1974
+ if (history.status === "loading") body = /* @__PURE__ */ jsx30(Skeleton5, { className: "h-10", "aria-busy": "true" });
1975
+ else if (history.status === "error") body = /* @__PURE__ */ jsx30("p", { className: "text-red-600", children: "Could not load the history." });
1891
1976
  else if (history.status === "success")
1892
- body = history.entries.length === 0 ? /* @__PURE__ */ jsx29("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx29("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs23("li", { className: "py-1.5 text-sm", children: [
1893
- /* @__PURE__ */ jsx29("span", { className: "font-medium", children: entry.action }),
1977
+ body = history.entries.length === 0 ? /* @__PURE__ */ jsx30("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx30("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs23("li", { className: "py-1.5 text-sm", children: [
1978
+ /* @__PURE__ */ jsx30("span", { className: "font-medium", children: entry.action }),
1894
1979
  entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs23("span", { className: "text-[var(--color-text-muted)]", children: [
1895
1980
  " ",
1896
1981
  "\u2014 ",
@@ -1910,8 +1995,8 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
1910
1995
  onToggle: (event) => setOpen(event.currentTarget.open),
1911
1996
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
1912
1997
  children: [
1913
- /* @__PURE__ */ jsx29("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
1914
- /* @__PURE__ */ jsx29("div", { className: "mt-1.5", children: body })
1998
+ /* @__PURE__ */ jsx30("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
1999
+ /* @__PURE__ */ jsx30("div", { className: "mt-1.5", children: body })
1915
2000
  ]
1916
2001
  }
1917
2002
  );
@@ -1937,13 +2022,13 @@ function SongPanelContent({
1937
2022
  const path = genrePath.length > 0 ? genrePath : null;
1938
2023
  fields.push({
1939
2024
  label: "Genre",
1940
- value: path ? /* @__PURE__ */ jsx29("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs23("span", { children: [
1941
- index > 0 && /* @__PURE__ */ jsx29("span", { "aria-hidden": "true", children: "\u203A " }),
1942
- /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2025
+ value: path ? /* @__PURE__ */ jsx30("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs23("span", { children: [
2026
+ index > 0 && /* @__PURE__ */ jsx30("span", { "aria-hidden": "true", children: "\u203A " }),
2027
+ /* @__PURE__ */ jsx30("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
1943
2028
  ] }, genrePlaylist.uuid)) }) : song.genre.name
1944
2029
  });
1945
2030
  } else {
1946
- fields.push({ label: "Genre", value: /* @__PURE__ */ jsx29("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2031
+ fields.push({ label: "Genre", value: /* @__PURE__ */ jsx30("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
1947
2032
  }
1948
2033
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
1949
2034
  const viewCount = song.youtubeVideo?.viewCount;
@@ -1953,11 +2038,11 @@ function SongPanelContent({
1953
2038
  const links = [];
1954
2039
  if (musicBrainzUrl)
1955
2040
  links.push(
1956
- /* @__PURE__ */ jsx29("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2041
+ /* @__PURE__ */ jsx30("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
1957
2042
  );
1958
2043
  if (song.youtubeVideo)
1959
2044
  links.push(
1960
- /* @__PURE__ */ jsx29(
2045
+ /* @__PURE__ */ jsx30(
1961
2046
  "a",
1962
2047
  {
1963
2048
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -1972,33 +2057,33 @@ function SongPanelContent({
1972
2057
  if (links.length > 0)
1973
2058
  fields.push({
1974
2059
  label: "Links",
1975
- value: /* @__PURE__ */ jsx29("span", { className: "flex gap-3", children: links })
2060
+ value: /* @__PURE__ */ jsx30("span", { className: "flex gap-3", children: links })
1976
2061
  });
1977
2062
  return /* @__PURE__ */ jsxs23(Fragment12, { children: [
1978
- /* @__PURE__ */ jsx29("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs23("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1979
- unplayableReason ? /* @__PURE__ */ jsx29("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs23(
2063
+ /* @__PURE__ */ jsx30("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs23("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2064
+ unplayableReason ? /* @__PURE__ */ jsx30("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs23(
1980
2065
  "button",
1981
2066
  {
1982
2067
  type: "button",
1983
2068
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
1984
2069
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
1985
2070
  children: [
1986
- /* @__PURE__ */ jsx29("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ jsx29(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx29(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2071
+ /* @__PURE__ */ jsx30("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ jsx30(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx30(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1987
2072
  isPlaying ? "Pause" : "Play"
1988
2073
  ]
1989
2074
  }
1990
2075
  ),
1991
- shareUrl && /* @__PURE__ */ jsx29(ShareActionButton, { target: shareTarget, url: shareUrl })
2076
+ shareUrl && /* @__PURE__ */ jsx30(ShareActionButton, { target: shareTarget, url: shareUrl })
1992
2077
  ] }) }),
1993
- song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx29("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx29(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
1994
- /* @__PURE__ */ jsx29(FieldList2, { items: fields }),
1995
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx29(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx29("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx29("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
1996
- /* @__PURE__ */ jsx29(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2078
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx30("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx30(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
2079
+ /* @__PURE__ */ jsx30(FieldList2, { items: fields }),
2080
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx30(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx30("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx30("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2081
+ /* @__PURE__ */ jsx30(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
1997
2082
  ] });
1998
2083
  }
1999
2084
 
2000
2085
  // src/genre-tree/song/useSongPanel.tsx
2001
- import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
2086
+ import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
2002
2087
  function useSongPanel({
2003
2088
  uuid,
2004
2089
  scope,
@@ -2021,23 +2106,23 @@ function useSongPanel({
2021
2106
  ...panel,
2022
2107
  title: "Song",
2023
2108
  content: /* @__PURE__ */ jsxs24("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2024
- /* @__PURE__ */ jsx30(Skeleton6, { className: "h-3 w-16" }),
2025
- /* @__PURE__ */ jsx30(Skeleton6, { className: "h-4 w-32" }),
2026
- /* @__PURE__ */ jsx30(Skeleton6, { className: "h-3 w-16" }),
2027
- /* @__PURE__ */ jsx30(Skeleton6, { className: "h-4 w-24" })
2109
+ /* @__PURE__ */ jsx31(Skeleton6, { className: "h-3 w-16" }),
2110
+ /* @__PURE__ */ jsx31(Skeleton6, { className: "h-4 w-32" }),
2111
+ /* @__PURE__ */ jsx31(Skeleton6, { className: "h-3 w-16" }),
2112
+ /* @__PURE__ */ jsx31(Skeleton6, { className: "h-4 w-24" })
2028
2113
  ] })
2029
2114
  };
2030
2115
  case "not-found":
2031
2116
  return {
2032
2117
  ...panel,
2033
2118
  title: "Song not found",
2034
- content: /* @__PURE__ */ jsx30("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2119
+ content: /* @__PURE__ */ jsx31("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2035
2120
  };
2036
2121
  case "error":
2037
2122
  return {
2038
2123
  ...panel,
2039
2124
  title: "Song",
2040
- content: /* @__PURE__ */ jsx30("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2125
+ content: /* @__PURE__ */ jsx31("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2041
2126
  };
2042
2127
  case "success": {
2043
2128
  const { song } = result;
@@ -2045,7 +2130,7 @@ function useSongPanel({
2045
2130
  return {
2046
2131
  ...panel,
2047
2132
  title: song.title,
2048
- content: /* @__PURE__ */ jsx30(
2133
+ content: /* @__PURE__ */ jsx31(
2049
2134
  SongPanelContent,
2050
2135
  {
2051
2136
  song,
@@ -2065,12 +2150,12 @@ function useSongPanel({
2065
2150
  // src/genre-tree/SizeMetricToggle.tsx
2066
2151
  import { MdMusicNote, MdVisibility } from "react-icons/md";
2067
2152
  import { SegmentedControl } from "@behindthemusictree/ui";
2068
- import { jsx as jsx31 } from "react/jsx-runtime";
2153
+ import { jsx as jsx32 } from "react/jsx-runtime";
2069
2154
  function SizeMetricToggle({
2070
2155
  value,
2071
2156
  onChange
2072
2157
  }) {
2073
- return /* @__PURE__ */ jsx31(
2158
+ return /* @__PURE__ */ jsx32(
2074
2159
  SegmentedControl,
2075
2160
  {
2076
2161
  variant: "floating",
@@ -2078,15 +2163,15 @@ function SizeMetricToggle({
2078
2163
  value,
2079
2164
  onChange,
2080
2165
  options: [
2081
- { value: "songs", label: /* @__PURE__ */ jsx31(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2082
- { value: "views", label: /* @__PURE__ */ jsx31(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2166
+ { value: "songs", label: /* @__PURE__ */ jsx32(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2167
+ { value: "views", label: /* @__PURE__ */ jsx32(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2083
2168
  ]
2084
2169
  }
2085
2170
  );
2086
2171
  }
2087
2172
 
2088
2173
  // src/genre-tree/GenreTreeView.tsx
2089
- import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
2174
+ import { Fragment as Fragment13, jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
2090
2175
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2091
2176
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2092
2177
  var noop = () => {
@@ -2121,6 +2206,7 @@ function GenreTreeView({
2121
2206
  onArtistExpandedChange,
2122
2207
  getShareUrl,
2123
2208
  extraSearchGroups = [],
2209
+ SearchGroups,
2124
2210
  onSearchQueryChange,
2125
2211
  onSearchFocus,
2126
2212
  artistSearch = true,
@@ -2149,19 +2235,19 @@ function GenreTreeView({
2149
2235
  }) {
2150
2236
  const getShareUrlRef = useRef3(getShareUrl);
2151
2237
  getShareUrlRef.current = getShareUrl;
2152
- const [internalArtistUuid, setInternalArtistUuid] = useState8(null);
2238
+ const [internalArtistUuid, setInternalArtistUuid] = useState9(null);
2153
2239
  const isArtistControlled = controlledArtistUuid !== void 0;
2154
2240
  const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
2155
- const [internalSongUuid, setInternalSongUuid] = useState8(null);
2241
+ const [internalSongUuid, setInternalSongUuid] = useState9(null);
2156
2242
  const isSongControlled = controlledSongUuid !== void 0;
2157
2243
  const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
2158
2244
  const changeSong = (uuid) => {
2159
2245
  if (!isSongControlled) setInternalSongUuid(uuid);
2160
2246
  onSongUuidChange?.(uuid);
2161
2247
  };
2162
- const [internalSizeMetric, setInternalSizeMetric] = useState8("songs");
2248
+ const [internalSizeMetric, setInternalSizeMetric] = useState9("songs");
2163
2249
  const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
2164
- const deferredSizeMetric = useDeferredValue(sizeMetric);
2250
+ const deferredSizeMetric = useDeferredValue2(sizeMetric);
2165
2251
  const changeSizeMetric = (metric) => {
2166
2252
  if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
2167
2253
  onSizeMetricChange?.(metric);
@@ -2198,7 +2284,7 @@ function GenreTreeView({
2198
2284
  artistUuid
2199
2285
  });
2200
2286
  const restoreRevision = viewportRestore?.revision;
2201
- const [stackedRestoreOwner, setStackedRestoreOwner] = useState8({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2287
+ const [stackedRestoreOwner, setStackedRestoreOwner] = useState9({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
2202
2288
  let restoreRootUuid = stackedRestoreOwner.rootUuid;
2203
2289
  if (stackedRestoreOwner.revision !== restoreRevision) {
2204
2290
  restoreRootUuid = selectedGenrePlaylist?.root.uuid;
@@ -2232,11 +2318,11 @@ function GenreTreeView({
2232
2318
  const openSongRef = useRef3(openSong);
2233
2319
  openSongRef.current = openSong;
2234
2320
  const resolvedRenderSongLink = useCallback6(
2235
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2321
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx33("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2236
2322
  [renderSongLink]
2237
2323
  );
2238
- const [expandedSongUuid, setExpandedSongUuid] = useState8(null);
2239
- const [expandedArtistUuid, setExpandedArtistUuid] = useState8(null);
2324
+ const [expandedSongUuid, setExpandedSongUuid] = useState9(null);
2325
+ const [expandedArtistUuid, setExpandedArtistUuid] = useState9(null);
2240
2326
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
2241
2327
  const songBaseUrl = getBackendBaseUrl;
2242
2328
  const artistPanel = useArtistPanel({
@@ -2244,7 +2330,7 @@ function GenreTreeView({
2244
2330
  scope,
2245
2331
  getBackendBaseUrl: artistBaseUrl,
2246
2332
  getShareUrl: getShareUrlRef.current,
2247
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2333
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx33("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2248
2334
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2249
2335
  onExpandedChange: (on) => {
2250
2336
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2253,7 +2339,7 @@ function GenreTreeView({
2253
2339
  renderSongLink: resolvedRenderSongLink,
2254
2340
  onClose: () => changeArtist(null)
2255
2341
  });
2256
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx32("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2342
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx33("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2257
2343
  const getGenrePath = (genre) => {
2258
2344
  const path = [];
2259
2345
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2285,8 +2371,8 @@ function GenreTreeView({
2285
2371
  const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
2286
2372
  const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
2287
2373
  const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
2288
- const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState8(false);
2289
- const [uncontrolledExpanded, setUncontrolledExpanded] = useState8(false);
2374
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState9(false);
2375
+ const [uncontrolledExpanded, setUncontrolledExpanded] = useState9(false);
2290
2376
  const isCollapsed = collapsed ?? uncontrolledCollapsed;
2291
2377
  const isExpanded = expanded ?? uncontrolledExpanded;
2292
2378
  const changeCollapsed = (next) => {
@@ -2300,10 +2386,10 @@ function GenreTreeView({
2300
2386
  useEffect5(() => {
2301
2387
  setUncontrolledCollapsed(false);
2302
2388
  }, [selectedNodeId, artistUuid, songUuid]);
2303
- const [isWheelRevealed, setIsWheelRevealed] = useState8(false);
2389
+ const [isWheelRevealed, setIsWheelRevealed] = useState9(false);
2304
2390
  const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
2305
2391
  const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
2306
- const [resolveStyle, setResolveStyle] = useState8(null);
2392
+ const [resolveStyle, setResolveStyle] = useState9(null);
2307
2393
  const handleNodeStyleResolverChange = useCallback6(
2308
2394
  (resolver) => setResolveStyle(() => resolver),
2309
2395
  []
@@ -2318,7 +2404,7 @@ function GenreTreeView({
2318
2404
  !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
2319
2405
  resolveStyle
2320
2406
  );
2321
- const [selectionHistory, setSelectionHistory] = useState8({
2407
+ const [selectionHistory, setSelectionHistory] = useState9({
2322
2408
  current: selectedNodeId,
2323
2409
  previous: null
2324
2410
  });
@@ -2331,28 +2417,6 @@ function GenreTreeView({
2331
2417
  const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
2332
2418
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2333
2419
  const { showPopup, hidePopup } = usePopup5();
2334
- const [searchText, setSearchText] = useState8("");
2335
- const artistGroup = useArtistSearchGroup(
2336
- artistBaseUrl,
2337
- searchText,
2338
- (artist) => {
2339
- openArtist(artist.uuid);
2340
- onArtistSelect?.(artist);
2341
- },
2342
- Boolean(artistSearch)
2343
- );
2344
- const songGroup = useSongSearchGroup(
2345
- songBaseUrl,
2346
- searchText,
2347
- (song) => {
2348
- openSong(song.uuid);
2349
- onSongSelect?.(song);
2350
- },
2351
- Boolean(songSearch)
2352
- );
2353
- const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
2354
- (group) => group !== void 0
2355
- );
2356
2420
  const handleSelectedNodeChange = (node) => {
2357
2421
  if (node !== null && songUuid !== null) changeSong(null);
2358
2422
  selectNode(node);
@@ -2376,7 +2440,7 @@ function GenreTreeView({
2376
2440
  };
2377
2441
  const renderGenreBody = (relation) => {
2378
2442
  const { node } = relation;
2379
- const family = /* @__PURE__ */ jsx32(
2443
+ const family = /* @__PURE__ */ jsx33(
2380
2444
  GenreNodeFamily,
2381
2445
  {
2382
2446
  node,
@@ -2387,7 +2451,7 @@ function GenreTreeView({
2387
2451
  onSelectNode: selectGenreFromPanel
2388
2452
  }
2389
2453
  );
2390
- const ancestors = /* @__PURE__ */ jsx32(GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2454
+ const ancestors = /* @__PURE__ */ jsx33(GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2391
2455
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2392
2456
  const criteria = genrePlaylist?.criteria;
2393
2457
  if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
@@ -2411,7 +2475,7 @@ function GenreTreeView({
2411
2475
  ) : void 0;
2412
2476
  return {
2413
2477
  primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2414
- /* @__PURE__ */ jsx32(
2478
+ /* @__PURE__ */ jsx33(
2415
2479
  GenreInfoPanelBody,
2416
2480
  {
2417
2481
  family: /* @__PURE__ */ jsxs25(Fragment13, { children: [
@@ -2420,6 +2484,7 @@ function GenreTreeView({
2420
2484
  ] }),
2421
2485
  genreUuid,
2422
2486
  genrePlaylist,
2487
+ catalogue: genrePlaylist.catalogue,
2423
2488
  wikidataId: criteria.wikidataId,
2424
2489
  scope,
2425
2490
  getBackendBaseUrl,
@@ -2432,7 +2497,7 @@ function GenreTreeView({
2432
2497
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2433
2498
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2434
2499
  onChangeParent: readOnly ? void 0 : () => showPopup(
2435
- /* @__PURE__ */ jsx32(
2500
+ /* @__PURE__ */ jsx33(
2436
2501
  ChangeParentPopup,
2437
2502
  {
2438
2503
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2507,19 +2572,19 @@ function GenreTreeView({
2507
2572
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2508
2573
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2509
2574
  } : void 0;
2510
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx32(
2575
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx33(
2511
2576
  IconButton,
2512
2577
  {
2513
2578
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2514
2579
  style: activePanel.headerAccent && { color: "inherit" },
2515
2580
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2516
- children: activePanel.expanded ? /* @__PURE__ */ jsx32(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx32(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2581
+ children: activePanel.expanded ? /* @__PURE__ */ jsx33(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx33(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2517
2582
  }
2518
2583
  ) : null;
2519
2584
  const renderArtistFilter = () => {
2520
2585
  if (isLoading || !filterArtist) return null;
2521
2586
  return /* @__PURE__ */ jsxs25("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2522
- /* @__PURE__ */ jsx32(
2587
+ /* @__PURE__ */ jsx33(
2523
2588
  "button",
2524
2589
  {
2525
2590
  type: "button",
@@ -2531,7 +2596,7 @@ function GenreTreeView({
2531
2596
  children: filterArtist.name
2532
2597
  }
2533
2598
  ),
2534
- /* @__PURE__ */ jsx32(
2599
+ /* @__PURE__ */ jsx33(
2535
2600
  "button",
2536
2601
  {
2537
2602
  type: "button",
@@ -2544,25 +2609,35 @@ function GenreTreeView({
2544
2609
  ] });
2545
2610
  };
2546
2611
  const actions = /* @__PURE__ */ jsxs25(Fragment13, { children: [
2547
- !isLoading && /* @__PURE__ */ jsx32(
2548
- GenreSearch,
2612
+ !isLoading && /* @__PURE__ */ jsx33(
2613
+ GenreTreeSearch,
2549
2614
  {
2615
+ artistBaseUrl,
2616
+ songBaseUrl,
2617
+ artistSearch: Boolean(artistSearch),
2618
+ songSearch: Boolean(songSearch),
2619
+ onArtistSelect: (artist) => {
2620
+ openArtist(artist.uuid);
2621
+ onArtistSelect?.(artist);
2622
+ },
2623
+ onSongSelect: (song) => {
2624
+ openSong(song.uuid);
2625
+ onSongSelect?.(song);
2626
+ },
2627
+ SearchGroups,
2550
2628
  genrePlaylists: visibleGenrePlaylists,
2551
2629
  onSelect: handleGenreSearchSelect,
2552
2630
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2553
2631
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2554
2632
  onPrevious: handlePrevious,
2555
- onQueryChange: (text) => {
2556
- setSearchText(text);
2557
- onSearchQueryChange?.(text);
2558
- },
2633
+ onQueryChange: onSearchQueryChange,
2559
2634
  onFocus: onSearchFocus,
2560
- extraGroups
2635
+ extraGroups: extraSearchGroups
2561
2636
  }
2562
2637
  ),
2563
- !isLoading && filterArtist && /* @__PURE__ */ jsx32("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2638
+ !isLoading && filterArtist && /* @__PURE__ */ jsx33("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2564
2639
  /* @__PURE__ */ jsxs25("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: [
2565
- !isLoading && !isControlled && /* @__PURE__ */ jsx32(
2640
+ !isLoading && !isControlled && /* @__PURE__ */ jsx33(
2566
2641
  SegmentedControl2,
2567
2642
  {
2568
2643
  "aria-label": "Tree view mode",
@@ -2576,13 +2651,13 @@ function GenreTreeView({
2576
2651
  ]
2577
2652
  }
2578
2653
  ),
2579
- !isLoading && showDisplayOptions && /* @__PURE__ */ jsx32(
2654
+ !isLoading && showDisplayOptions && /* @__PURE__ */ jsx33(
2580
2655
  "div",
2581
2656
  {
2582
2657
  className: "flex items-center gap-1",
2583
2658
  role: "group",
2584
2659
  "aria-label": "Tree display options",
2585
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx32(
2660
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx33(
2586
2661
  Button,
2587
2662
  {
2588
2663
  variant: allowWheelRotation ? "default" : "outline",
@@ -2593,7 +2668,7 @@ function GenreTreeView({
2593
2668
  )
2594
2669
  }
2595
2670
  ),
2596
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx32(
2671
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx33(
2597
2672
  IconTextButton,
2598
2673
  {
2599
2674
  icon: Plus2,
@@ -2609,28 +2684,28 @@ function GenreTreeView({
2609
2684
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2610
2685
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2611
2686
  children: [
2612
- /* @__PURE__ */ jsx32(
2687
+ /* @__PURE__ */ jsx33(
2613
2688
  "div",
2614
2689
  {
2615
2690
  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))] 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"}`,
2616
2691
  children: actions
2617
2692
  }
2618
2693
  ),
2619
- !isLoading && filterArtist && /* @__PURE__ */ jsx32(
2694
+ !isLoading && filterArtist && /* @__PURE__ */ jsx33(
2620
2695
  "div",
2621
2696
  {
2622
2697
  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))]" : "top-5"}`,
2623
2698
  children: renderArtistFilter()
2624
2699
  }
2625
2700
  ),
2626
- /* @__PURE__ */ jsx32("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs25("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2627
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx32("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2628
- isLoading ? /* @__PURE__ */ jsx32(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2701
+ /* @__PURE__ */ jsx33("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs25("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2702
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx33("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2703
+ isLoading ? /* @__PURE__ */ jsx33(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2629
2704
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2630
2705
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2631
2706
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2632
2707
  // The panel's own Zoom in button is hidden: the search bar carries it.
2633
- /* @__PURE__ */ jsx32("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx32(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx32(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx32(
2708
+ /* @__PURE__ */ jsx33("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx33(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx33(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx33(
2634
2709
  GenrePlaylistTreeWheel,
2635
2710
  {
2636
2711
  scope,
@@ -2649,7 +2724,7 @@ function GenreTreeView({
2649
2724
  busy,
2650
2725
  floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2651
2726
  floatingControlsLeading,
2652
- canSizeByViews && /* @__PURE__ */ jsx32(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2727
+ canSizeByViews && /* @__PURE__ */ jsx33(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2653
2728
  ] }) : void 0,
2654
2729
  defaultViewport,
2655
2730
  viewportRestore,
@@ -2658,7 +2733,7 @@ function GenreTreeView({
2658
2733
  ) }) })
2659
2734
  ) : viewMode === "pop-core" ? (
2660
2735
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2661
- /* @__PURE__ */ jsx32("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx32(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx32(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx32(
2736
+ /* @__PURE__ */ jsx33("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx33(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx33(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx33(
2662
2737
  GenrePlaylistTreeWheelRadialPopCore,
2663
2738
  {
2664
2739
  scope,
@@ -2677,7 +2752,7 @@ function GenreTreeView({
2677
2752
  busy,
2678
2753
  floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2679
2754
  floatingControlsLeading,
2680
- canSizeByViews && /* @__PURE__ */ jsx32(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2755
+ canSizeByViews && /* @__PURE__ */ jsx33(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2681
2756
  ] }) : void 0,
2682
2757
  defaultViewport,
2683
2758
  viewportRestore,
@@ -2685,7 +2760,7 @@ function GenreTreeView({
2685
2760
  ...focus
2686
2761
  }
2687
2762
  ) }) })
2688
- ) : viewMode === "outline" ? /* @__PURE__ */ jsx32("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx32(
2763
+ ) : viewMode === "outline" ? /* @__PURE__ */ jsx33("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx33(
2689
2764
  GenrePlaylistTreeWheelRadialPopCore,
2690
2765
  {
2691
2766
  outline: true,
@@ -2701,13 +2776,13 @@ function GenreTreeView({
2701
2776
  selectedNodeId,
2702
2777
  readOnly
2703
2778
  }
2704
- ) }) : /* @__PURE__ */ jsx32("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(
2779
+ ) }) : /* @__PURE__ */ jsx33("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(
2705
2780
  ([uuid, genrePlaylistTreePerRoot]) => {
2706
- return /* @__PURE__ */ jsx32(
2781
+ return /* @__PURE__ */ jsx33(
2707
2782
  "div",
2708
2783
  {
2709
2784
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2710
- children: /* @__PURE__ */ jsx32("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx32(
2785
+ children: /* @__PURE__ */ jsx33("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx33(
2711
2786
  GenrePlaylistTreePerRoot,
2712
2787
  {
2713
2788
  scope,
@@ -2733,7 +2808,7 @@ function GenreTreeView({
2733
2808
  );
2734
2809
  }
2735
2810
  ) }),
2736
- /* @__PURE__ */ jsx32(
2811
+ /* @__PURE__ */ jsx33(
2737
2812
  DetailPanel,
2738
2813
  {
2739
2814
  ref: panelRef,
@@ -2775,9 +2850,11 @@ import {
2775
2850
  import { DetailPanel as DetailPanel2, IconButton as IconButton2, Skeleton as Skeleton7 } from "@behindthemusictree/ui";
2776
2851
  import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
2777
2852
  import {
2778
- useFetchGenreDetail
2853
+ useFetchGenreDetail,
2854
+ genreMusicCount as genreMusicCount2,
2855
+ GenrePlaylistSummarySchema
2779
2856
  } from "@behindthemusictree/app-kit/genre-tree";
2780
- import { Fragment as Fragment14, jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
2857
+ import { Fragment as Fragment14, jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
2781
2858
  var RELATIVE_ITEM_COUNT = 1;
2782
2859
  function toNode(genre, parentId) {
2783
2860
  return {
@@ -2820,7 +2897,7 @@ function GenreDetailPanel({
2820
2897
  id: detail.uuid,
2821
2898
  parentId: detail.parent?.uuid ?? null,
2822
2899
  name: detail.name,
2823
- itemCount: detail.tracksCount,
2900
+ itemCount: scope === "reference" ? genreMusicCount2(GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist)) : detail.tracksCount,
2824
2901
  wikidataId: detail.wikidataId
2825
2902
  },
2826
2903
  fill: rootColor,
@@ -2830,19 +2907,19 @@ function GenreDetailPanel({
2830
2907
  ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
2831
2908
  )
2832
2909
  };
2833
- }, [detail]);
2910
+ }, [detail, scope]);
2834
2911
  if (isError) {
2835
- return /* @__PURE__ */ jsx33("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
2912
+ return /* @__PURE__ */ jsx34("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
2836
2913
  }
2837
2914
  if (isPending || !detail || !view) {
2838
2915
  return /* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
2839
- /* @__PURE__ */ jsx33(Skeleton7, { className: "h-6 w-2/3" }),
2840
- /* @__PURE__ */ jsx33(Skeleton7, { className: "h-4 w-full" }),
2841
- /* @__PURE__ */ jsx33(Skeleton7, { className: "h-4 w-3/4" })
2916
+ /* @__PURE__ */ jsx34(Skeleton7, { className: "h-6 w-2/3" }),
2917
+ /* @__PURE__ */ jsx34(Skeleton7, { className: "h-4 w-full" }),
2918
+ /* @__PURE__ */ jsx34(Skeleton7, { className: "h-4 w-3/4" })
2842
2919
  ] });
2843
2920
  }
2844
2921
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
2845
- const family = /* @__PURE__ */ jsx33(
2922
+ const family = /* @__PURE__ */ jsx34(
2846
2923
  GenreNodeFamily2,
2847
2924
  {
2848
2925
  node: view.node,
@@ -2853,20 +2930,20 @@ function GenreDetailPanel({
2853
2930
  onSelectNode: selectGenre
2854
2931
  }
2855
2932
  );
2856
- const ancestors = /* @__PURE__ */ jsx33(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2933
+ const ancestors = /* @__PURE__ */ jsx34(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2857
2934
  return /* @__PURE__ */ jsxs26(
2858
2935
  DetailPanel2,
2859
2936
  {
2860
2937
  side: "left",
2861
2938
  title: view.node.name,
2862
2939
  headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
2863
- headerActions: onExpandedChange ? /* @__PURE__ */ jsx33(
2940
+ headerActions: onExpandedChange ? /* @__PURE__ */ jsx34(
2864
2941
  IconButton2,
2865
2942
  {
2866
2943
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
2867
2944
  style: { color: "inherit" },
2868
2945
  onClick: () => onExpandedChange(!expanded),
2869
- children: expanded ? /* @__PURE__ */ jsx33(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx33(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
2946
+ children: expanded ? /* @__PURE__ */ jsx34(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx34(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
2870
2947
  }
2871
2948
  ) : void 0,
2872
2949
  onClose,
@@ -2875,7 +2952,7 @@ function GenreDetailPanel({
2875
2952
  onExpandedChange,
2876
2953
  edgeTab,
2877
2954
  children: [
2878
- /* @__PURE__ */ jsx33(
2955
+ /* @__PURE__ */ jsx34(
2879
2956
  GenreInfoPanelBody,
2880
2957
  {
2881
2958
  family: /* @__PURE__ */ jsxs26(Fragment14, { children: [
@@ -2883,7 +2960,8 @@ function GenreDetailPanel({
2883
2960
  ancestors
2884
2961
  ] }),
2885
2962
  genreUuid: detail.uuid,
2886
- genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
2963
+ genrePlaylist: scope === "reference" ? GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
2964
+ catalogue: detail.catalogue,
2887
2965
  wikidataId: detail.wikidataId,
2888
2966
  scope,
2889
2967
  getBackendBaseUrl,
@@ -2911,8 +2989,8 @@ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useRef as
2911
2989
  import { ListMusic } from "lucide-react";
2912
2990
  import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
2913
2991
  import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
2914
- import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
2915
- import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
2992
+ import { useTrackNoun as useTrackNoun4 } from "@behindthemusictree/app-kit/track-noun";
2993
+ import { jsx as jsx35, jsxs as jsxs27 } from "react/jsx-runtime";
2916
2994
  var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
2917
2995
  function TrackListSidebar({
2918
2996
  className: className2 = "",
@@ -2926,7 +3004,7 @@ function TrackListSidebar({
2926
3004
  renderSongLink
2927
3005
  }) {
2928
3006
  const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList6();
2929
- const trackNoun = useTrackNoun3();
3007
+ const trackNoun = useTrackNoun4();
2930
3008
  const trackListRef = useRef4(null);
2931
3009
  const previousOriginRef = useRef4(trackList?.origin ?? null);
2932
3010
  const handledAutoAdvanceRequestIdRef = useRef4(null);
@@ -3024,15 +3102,15 @@ function TrackListSidebar({
3024
3102
  /* @__PURE__ */ jsxs27("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3025
3103
  /* @__PURE__ */ jsxs27("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3026
3104
  /* @__PURE__ */ jsxs27("div", { className: "origin flex text-xl", children: [
3027
- /* @__PURE__ */ jsx34("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3028
- /* @__PURE__ */ jsx34("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__ */ jsx34("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3105
+ /* @__PURE__ */ jsx35("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3106
+ /* @__PURE__ */ jsx35("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__ */ jsx35("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3029
3107
  ] }),
3030
3108
  /* @__PURE__ */ jsxs27("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3031
3109
  trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3032
3110
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3033
3111
  ] })
3034
3112
  ] }),
3035
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx34(
3113
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx35(
3036
3114
  "button",
3037
3115
  {
3038
3116
  type: "button",
@@ -3041,7 +3119,7 @@ function TrackListSidebar({
3041
3119
  "aria-expanded": !isCollapsed,
3042
3120
  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"}`,
3043
3121
  onClick: isCollapsed ? onExpand : onMinimize,
3044
- children: /* @__PURE__ */ jsx34(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3122
+ children: /* @__PURE__ */ jsx35(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3045
3123
  }
3046
3124
  )
3047
3125
  ] }),
@@ -3055,7 +3133,7 @@ function TrackListSidebar({
3055
3133
  onPointerDown: markUserScrollIntent,
3056
3134
  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",
3057
3135
  children: [
3058
- trackList.tracks.map((track, index) => /* @__PURE__ */ jsx34("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx34(
3136
+ trackList.tracks.map((track, index) => /* @__PURE__ */ jsx35("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx35(
3059
3137
  TrackItem,
3060
3138
  {
3061
3139
  track,
@@ -3066,7 +3144,7 @@ function TrackListSidebar({
3066
3144
  renderSongLink
3067
3145
  }
3068
3146
  ) }, track.uuid)),
3069
- hasMore && /* @__PURE__ */ jsx34("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3147
+ hasMore && /* @__PURE__ */ jsx35("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3070
3148
  ]
3071
3149
  }
3072
3150
  )
@@ -3076,7 +3154,7 @@ function TrackListSidebar({
3076
3154
  }
3077
3155
 
3078
3156
  // src/genre-tree/PlayerPanel.tsx
3079
- import { useEffect as useEffect7, useState as useState9 } from "react";
3157
+ import { useEffect as useEffect7, useState as useState10 } from "react";
3080
3158
  import { twMerge } from "tailwind-merge";
3081
3159
  import {
3082
3160
  usePlayer as usePlayer11,
@@ -3091,7 +3169,7 @@ import { PanelEdgeTab } from "@behindthemusictree/ui";
3091
3169
  import { Pause as Pause3, Play as Play4, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
3092
3170
  import { formatTime, useTrackList as useTrackList7 } from "@behindthemusictree/app-kit/genre-tree";
3093
3171
  import { PlayStates as PlayStates4, useCurrentTime, usePlayer as usePlayer10 } from "@behindthemusictree/app-kit/player";
3094
- import { jsx as jsx35, jsxs as jsxs28 } from "react/jsx-runtime";
3172
+ import { jsx as jsx36, jsxs as jsxs28 } from "react/jsx-runtime";
3095
3173
  function PlayerControls() {
3096
3174
  const {
3097
3175
  duration,
@@ -3117,8 +3195,8 @@ function PlayerControls() {
3117
3195
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3118
3196
  children: [
3119
3197
  /* @__PURE__ */ jsxs28("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3120
- /* @__PURE__ */ jsx35("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
3121
- /* @__PURE__ */ jsx35(
3198
+ /* @__PURE__ */ jsx36("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
3199
+ /* @__PURE__ */ jsx36(
3122
3200
  "input",
3123
3201
  {
3124
3202
  "aria-label": "Seek",
@@ -3132,11 +3210,11 @@ function PlayerControls() {
3132
3210
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3133
3211
  }
3134
3212
  ),
3135
- /* @__PURE__ */ jsx35("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
3213
+ /* @__PURE__ */ jsx36("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
3136
3214
  ] }),
3137
3215
  /* @__PURE__ */ jsxs28("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3138
3216
  /* @__PURE__ */ jsxs28("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3139
- /* @__PURE__ */ jsx35(
3217
+ /* @__PURE__ */ jsx36(
3140
3218
  "button",
3141
3219
  {
3142
3220
  type: "button",
@@ -3145,10 +3223,10 @@ function PlayerControls() {
3145
3223
  disabled: !isReady || !hasPreviousTrack,
3146
3224
  onClick: playPreviousTrack,
3147
3225
  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",
3148
- children: /* @__PURE__ */ jsx35(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3226
+ children: /* @__PURE__ */ jsx36(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3149
3227
  }
3150
3228
  ),
3151
- /* @__PURE__ */ jsx35(
3229
+ /* @__PURE__ */ jsx36(
3152
3230
  "button",
3153
3231
  {
3154
3232
  type: "button",
@@ -3157,10 +3235,10 @@ function PlayerControls() {
3157
3235
  disabled: !isReady,
3158
3236
  onClick: handlePlayPauseAction,
3159
3237
  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",
3160
- children: isPlaying ? /* @__PURE__ */ jsx35(Pause3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx35(Play4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3238
+ children: isPlaying ? /* @__PURE__ */ jsx36(Pause3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx36(Play4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3161
3239
  }
3162
3240
  ),
3163
- /* @__PURE__ */ jsx35(
3241
+ /* @__PURE__ */ jsx36(
3164
3242
  "button",
3165
3243
  {
3166
3244
  type: "button",
@@ -3169,14 +3247,14 @@ function PlayerControls() {
3169
3247
  disabled: !isReady || !hasNextTrack,
3170
3248
  onClick: playNextTrack,
3171
3249
  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",
3172
- children: /* @__PURE__ */ jsx35(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3250
+ children: /* @__PURE__ */ jsx36(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3173
3251
  }
3174
3252
  )
3175
3253
  ] }),
3176
3254
  /* @__PURE__ */ jsxs28("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: [
3177
- volume === 0 ? /* @__PURE__ */ jsx35(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx35(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3178
- /* @__PURE__ */ jsx35("span", { className: "sr-only", children: "Volume" }),
3179
- /* @__PURE__ */ jsx35(
3255
+ volume === 0 ? /* @__PURE__ */ jsx36(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx36(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3256
+ /* @__PURE__ */ jsx36("span", { className: "sr-only", children: "Volume" }),
3257
+ /* @__PURE__ */ jsx36(
3180
3258
  "input",
3181
3259
  {
3182
3260
  "aria-label": "Volume",
@@ -3198,7 +3276,7 @@ function PlayerControls() {
3198
3276
  }
3199
3277
 
3200
3278
  // src/genre-tree/PlayerPanel.tsx
3201
- import { jsx as jsx36, jsxs as jsxs29 } from "react/jsx-runtime";
3279
+ import { jsx as jsx37, jsxs as jsxs29 } from "react/jsx-runtime";
3202
3280
  var PANEL_ID = "player-panel";
3203
3281
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3204
3282
  function PlayerPanel({
@@ -3212,12 +3290,12 @@ function PlayerPanel({
3212
3290
  }) {
3213
3291
  const { playerTrackObject, retryPlayback } = usePlayer11();
3214
3292
  const { toggleTrackListSidebar, isTrackListSidebarVisible } = useTrackListSidebarVisibility();
3215
- const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState9("");
3293
+ const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState10("");
3216
3294
  const loadedTrackTitle = playerTrackObject?.track.title;
3217
3295
  useEffect7(() => {
3218
3296
  if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
3219
3297
  }, [loadedTrackTitle]);
3220
- const [uncontrolledExtended, setUncontrolledExtended] = useState9(false);
3298
+ const [uncontrolledExtended, setUncontrolledExtended] = useState10(false);
3221
3299
  const isExtendRequested = expanded ?? uncontrolledExtended;
3222
3300
  const setIsExtendRequested = (next) => {
3223
3301
  setUncontrolledExtended(next);
@@ -3251,7 +3329,7 @@ function PlayerPanel({
3251
3329
  ),
3252
3330
  style: isExtended ? { width: extendedWidth } : void 0,
3253
3331
  children: [
3254
- /* @__PURE__ */ jsx36(
3332
+ /* @__PURE__ */ jsx37(
3255
3333
  PanelEdgeTab,
3256
3334
  {
3257
3335
  side: "right",
@@ -3270,19 +3348,19 @@ function PlayerPanel({
3270
3348
  ),
3271
3349
  children: [
3272
3350
  /* @__PURE__ */ jsxs29("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3273
- /* @__PURE__ */ jsx36("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx36("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3351
+ /* @__PURE__ */ jsx37("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx37("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3274
3352
  /* @__PURE__ */ jsxs29("div", { className: "relative w-full shrink-0", children: [
3275
- /* @__PURE__ */ jsx36(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3353
+ /* @__PURE__ */ jsx37(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3276
3354
  playerTrackObject?.loadError && /* @__PURE__ */ jsxs29("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3277
3355
  playerTrackObject.loadError,
3278
- /* @__PURE__ */ jsx36("button", { type: "button", className: "ml-2 underline", onClick: () => {
3356
+ /* @__PURE__ */ jsx37("button", { type: "button", className: "ml-2 underline", onClick: () => {
3279
3357
  void retryPlayback();
3280
3358
  }, children: "Retry" })
3281
3359
  ] })
3282
3360
  ] }),
3283
- /* @__PURE__ */ jsx36(PlayerControls, {})
3361
+ /* @__PURE__ */ jsx37(PlayerControls, {})
3284
3362
  ] }),
3285
- /* @__PURE__ */ jsx36(
3363
+ /* @__PURE__ */ jsx37(
3286
3364
  TrackListSidebar,
3287
3365
  {
3288
3366
  layout: "inline",
@@ -3309,11 +3387,11 @@ function PlayerPanel({
3309
3387
  import { v4 as uuidv4 } from "uuid";
3310
3388
  import { AiFillStar, AiOutlineStar } from "react-icons/ai";
3311
3389
  import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
3312
- import { jsx as jsx37, jsxs as jsxs30 } from "react/jsx-runtime";
3390
+ import { jsx as jsx38, jsxs as jsxs30 } from "react/jsx-runtime";
3313
3391
  function Rating({ rating, handleChange }) {
3314
3392
  const instanceId = uuidv4();
3315
- return /* @__PURE__ */ jsx37("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs30("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3316
- /* @__PURE__ */ jsx37(
3393
+ return /* @__PURE__ */ jsx38("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs30("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3394
+ /* @__PURE__ */ jsx38(
3317
3395
  "input",
3318
3396
  {
3319
3397
  id: `star-${instanceId}-${i}`,
@@ -3325,16 +3403,16 @@ function Rating({ rating, handleChange }) {
3325
3403
  onChange: handleChange
3326
3404
  }
3327
3405
  ),
3328
- /* @__PURE__ */ jsx37("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs30("div", { className: "flex", children: [
3329
- /* @__PURE__ */ jsx37(
3406
+ /* @__PURE__ */ jsx38("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs30("div", { className: "flex", children: [
3407
+ /* @__PURE__ */ jsx38(
3330
3408
  AiOutlineStar,
3331
3409
  {
3332
3410
  className: `w-7 h-7 absolute top-0 left-0 ${rating === FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3333
3411
  }
3334
3412
  ),
3335
- /* @__PURE__ */ jsx37("span", { className: "flex-grow w-8" }),
3413
+ /* @__PURE__ */ jsx38("span", { className: "flex-grow w-8" }),
3336
3414
  " "
3337
- ] }) : /* @__PURE__ */ jsx37(
3415
+ ] }) : /* @__PURE__ */ jsx38(
3338
3416
  AiFillStar,
3339
3417
  {
3340
3418
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3367,4 +3445,4 @@ export {
3367
3445
  GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
3368
3446
  GenreTreeOutlineSkeleton
3369
3447
  };
3370
- //# sourceMappingURL=chunk-QMELESFB.js.map
3448
+ //# sourceMappingURL=chunk-JP7PID7L.js.map