@behindthemusictree/app-kit-ui 33.0.0 → 33.2.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.
@@ -32,10 +32,16 @@ __export(genre_tree_exports, {
32
32
  GenreTreeWheelSkeleton: () => import_genre_tree_view6.GenreTreeWheelSkeleton,
33
33
  PlayerPanel: () => PlayerPanel,
34
34
  Rating: () => Rating,
35
+ SEARCH_FAILED_STATUS: () => SEARCH_FAILED_STATUS,
36
+ SearchBox: () => SearchBox,
35
37
  TrackItem: () => TrackItem,
36
38
  TrackListSidebar: () => TrackListSidebar,
37
39
  TrackPositionPlayPause: () => TrackPositionPlayPause,
38
- useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
40
+ searchStatus: () => searchStatus,
41
+ useArtistSearchGroup: () => useArtistSearchGroup,
42
+ useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
43
+ useGenreRemoteSearchGroup: () => useGenreRemoteSearchGroup,
44
+ useSongSearchGroup: () => useSongSearchGroup
39
45
  });
40
46
  module.exports = __toCommonJS(genre_tree_exports);
41
47
 
@@ -44,10 +50,9 @@ var import_react16 = require("react");
44
50
  var import_react_query3 = require("@tanstack/react-query");
45
51
  var import_lucide_react11 = require("lucide-react");
46
52
  var import_lucide_react12 = require("lucide-react");
47
- var import_ui15 = require("@behindthemusictree/ui");
53
+ var import_ui16 = require("@behindthemusictree/ui");
48
54
  var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
49
- var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
50
- var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
55
+ var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
51
56
  var import_popup5 = require("@behindthemusictree/app-kit/popup");
52
57
 
53
58
  // src/genre-tree/playlist-tree/size-metric.ts
@@ -695,11 +700,143 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
695
700
  }
696
701
 
697
702
  // src/genre-tree/GenreSearch.tsx
698
- var import_react7 = require("react");
699
703
  var import_lucide_react3 = require("lucide-react");
704
+ var import_ui4 = require("@behindthemusictree/ui");
705
+ var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
706
+
707
+ // src/genre-tree/search/SearchBox.tsx
708
+ var import_react7 = require("react");
700
709
  var import_ui3 = require("@behindthemusictree/ui");
701
- var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
702
710
  var import_jsx_runtime9 = require("react/jsx-runtime");
711
+ function SearchBox({
712
+ label,
713
+ placeholder,
714
+ value,
715
+ onValueChange,
716
+ groups,
717
+ onPicked,
718
+ onClear,
719
+ leading,
720
+ className: className2,
721
+ onFocus
722
+ }) {
723
+ const [open, setOpen] = (0, import_react7.useState)(false);
724
+ const loading = groups.some((group) => group.loading);
725
+ const status = groups.map((group) => group.status).filter(Boolean).join(". ");
726
+ const handlePick = (groupKey, itemKey) => {
727
+ setOpen(false);
728
+ groups.find((group) => group.key === groupKey)?.items.find((item) => item.key === itemKey)?.onSelect();
729
+ onPicked?.(groupKey, itemKey);
730
+ };
731
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: className2, onFocusCapture: onFocus, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
732
+ import_ui3.SearchCombobox,
733
+ {
734
+ ariaLabel: label,
735
+ listLabel: `${label} results`,
736
+ value,
737
+ placeholder: placeholder ?? `${label}\u2026`,
738
+ groups: groups.map((group) => ({
739
+ key: group.key,
740
+ label: group.label,
741
+ options: group.items.map((item) => ({ key: item.key, content: item.content }))
742
+ })),
743
+ open,
744
+ onOpenChange: setOpen,
745
+ onValueChange,
746
+ onPick: handlePick,
747
+ status,
748
+ loading,
749
+ onClear,
750
+ leading,
751
+ inputClassName: "focus-visible:ring-0"
752
+ }
753
+ ) });
754
+ }
755
+
756
+ // src/genre-tree/search/SearchGroup.ts
757
+ var SEARCH_FAILED_STATUS = "Search failed";
758
+ var searchStatus = (state, count, noun) => state.isError ? SEARCH_FAILED_STATUS : state.settled && count === 0 ? `No ${noun} found` : "";
759
+
760
+ // src/genre-tree/search/useArtistSearchGroup.tsx
761
+ var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
762
+ var import_jsx_runtime10 = require("react/jsx-runtime");
763
+ function useArtistSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
764
+ const search = (0, import_genre_tree2.useArtistSearch)(getBaseUrl, enabled ? text : "");
765
+ if (!enabled) return void 0;
766
+ return {
767
+ key: "artists",
768
+ label: "Artists",
769
+ items: search.artists.map((artist) => ({
770
+ key: artist.uuid,
771
+ content: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
772
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "block truncate font-medium", children: [
773
+ artist.name,
774
+ artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
775
+ " \xB7 ",
776
+ artist.disambiguation
777
+ ] })
778
+ ] }),
779
+ artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
780
+ "also known as ",
781
+ artist.matchedAlias
782
+ ] })
783
+ ] }),
784
+ onSelect: () => onSelect(artist)
785
+ })),
786
+ status: searchStatus(search, search.artists.length, "artists"),
787
+ loading: search.pending
788
+ };
789
+ }
790
+
791
+ // src/genre-tree/search/useSongSearchGroup.tsx
792
+ var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
793
+ var import_jsx_runtime11 = require("react/jsx-runtime");
794
+ function useSongSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
795
+ const search = (0, import_genre_tree3.useSongSearch)(getBaseUrl, enabled ? text : "");
796
+ if (!enabled) return void 0;
797
+ return {
798
+ key: "songs",
799
+ label: "Songs",
800
+ items: search.songs.map((song) => ({
801
+ key: song.uuid,
802
+ content: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: (0, import_genre_tree3.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
803
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "block truncate font-medium", children: song.title }),
804
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
805
+ ] }),
806
+ onSelect: () => onSelect(song)
807
+ })),
808
+ status: searchStatus(search, search.songs.length, "songs"),
809
+ loading: search.pending
810
+ };
811
+ }
812
+
813
+ // src/genre-tree/search/useGenreRemoteSearchGroup.tsx
814
+ var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
815
+ var import_jsx_runtime12 = require("react/jsx-runtime");
816
+ function useGenreRemoteSearchGroup(getBaseUrl, text, onSelect, { key = "genres", label = "Genres", filter, describe, ...options } = {}) {
817
+ const search = (0, import_genre_tree4.useGenreRemoteSearch)(getBaseUrl, text, options);
818
+ const genres = filter ? search.genres.filter(filter) : search.genres;
819
+ return {
820
+ key,
821
+ label,
822
+ items: genres.map((genre) => ({
823
+ key: genre.uuid,
824
+ content: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
825
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "block truncate font-medium", children: [
826
+ genre.name,
827
+ genre.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ml-2 text-xs font-normal italic text-[var(--color-text-muted)]", children: "umbrella" })
828
+ ] }),
829
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: describe?.(genre) ?? `${genre.treeName} \xB7 ${genre.root.name}` })
830
+ ] }),
831
+ onSelect: () => onSelect(genre)
832
+ })),
833
+ status: searchStatus(search, genres.length, "genres"),
834
+ loading: search.pending
835
+ };
836
+ }
837
+
838
+ // src/genre-tree/GenreSearch.tsx
839
+ var import_jsx_runtime13 = require("react/jsx-runtime");
703
840
  function GenreSearch({
704
841
  genrePlaylists,
705
842
  onSelect,
@@ -708,120 +845,58 @@ function GenreSearch({
708
845
  onPrevious,
709
846
  placeholder,
710
847
  onQueryChange,
711
- extraGroups = []
848
+ extraGroups = [],
849
+ onFocus
712
850
  }) {
713
- const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
851
+ const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree5.useGenreSearch)({
714
852
  genrePlaylists,
715
853
  onSelect,
716
854
  selectedName,
717
855
  onClear
718
856
  });
719
- const [open, setOpen] = (0, import_react7.useState)(false);
720
857
  const active = query !== "" && query !== selectedName;
721
858
  const hasExtras = extraGroups.length > 0;
722
859
  const label = hasExtras ? "Search" : "Search a genre";
723
- const loading = active && extraGroups.some((group) => group.loading);
724
- const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
725
860
  const groups = [
726
861
  {
727
862
  key: "genres",
728
863
  label: hasExtras ? "Genres" : void 0,
729
- options: results.map((genrePlaylist) => ({
864
+ items: results.map((genrePlaylist) => ({
730
865
  key: genrePlaylist.uuid,
731
- content: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
866
+ content: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
732
867
  genrePlaylist.name,
733
- genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
734
- ] })
868
+ genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
869
+ ] }),
870
+ onSelect: () => select(genrePlaylist)
735
871
  }))
736
872
  },
737
- ...extraGroups.map((group) => ({
738
- key: group.key,
739
- label: group.label,
740
- options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
741
- }))
873
+ ...extraGroups.map(
874
+ (group) => active ? group : { ...group, items: [], status: "", loading: false }
875
+ )
742
876
  ];
743
- const handlePick = (groupKey, optionKey) => {
744
- setOpen(false);
745
- if (groupKey === "genres") {
746
- const genrePlaylist = results.find((result) => result.uuid === optionKey);
747
- if (!genrePlaylist) return;
748
- select(genrePlaylist);
749
- onQueryChange?.("");
750
- return;
877
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "genre-search w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
878
+ SearchBox,
879
+ {
880
+ label,
881
+ placeholder,
882
+ value: query,
883
+ onValueChange: (value) => {
884
+ setQuery(value);
885
+ onQueryChange?.(value);
886
+ },
887
+ groups,
888
+ onPicked: (groupKey) => {
889
+ if (groupKey !== "genres") setQuery("");
890
+ onQueryChange?.("");
891
+ },
892
+ onClear: canClear ? () => {
893
+ clear();
894
+ onQueryChange?.("");
895
+ } : void 0,
896
+ leading: onPrevious && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", "aria-label": "Previous", title: "Previous", className: import_ui4.SEARCH_LEADING_BUTTON_CLASS, onClick: onPrevious, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.ArrowLeft, { "aria-hidden": "true", className: "h-4 w-4" }) }),
897
+ onFocus
751
898
  }
752
- const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
753
- if (!item) return;
754
- setQuery("");
755
- onQueryChange?.("");
756
- item.onSelect();
757
- };
758
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
759
- onPrevious && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
760
- "button",
761
- {
762
- type: "button",
763
- "aria-label": "Previous",
764
- title: "Previous",
765
- className: "genre-search-previous absolute left-2 top-1/2 z-10 flex items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
766
- onClick: onPrevious,
767
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
768
- }
769
- ),
770
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
771
- import_ui3.Combobox,
772
- {
773
- ariaLabel: label,
774
- listLabel: `${label} results`,
775
- value: query,
776
- placeholder: placeholder ?? `${label}\u2026`,
777
- groups,
778
- open,
779
- onOpenChange: setOpen,
780
- onValueChange: (value) => {
781
- setQuery(value);
782
- onQueryChange?.(value);
783
- },
784
- onPick: handlePick,
785
- status,
786
- loading: loading ? "Searching\u2026" : void 0,
787
- inputClassName: `h-[var(--layout-search-h,3rem)] ${onPrevious ? "pl-11" : "pl-6"} pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md`,
788
- className: "genre-search-results z-40 rounded-xl shadow-md",
789
- listClassName: "max-h-[min(20rem,50dvh)]",
790
- optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
791
- }
792
- ),
793
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
794
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
795
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
796
- import_lucide_react3.Search,
797
- {
798
- "aria-hidden": "true",
799
- className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
800
- }
801
- ),
802
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
803
- import_lucide_react3.LoaderCircle,
804
- {
805
- "aria-hidden": "true",
806
- className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
807
- }
808
- )
809
- ] }),
810
- canClear && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
811
- "button",
812
- {
813
- type: "button",
814
- "aria-label": "Clear search",
815
- className: "flex items-center justify-center p-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
816
- onClick: () => {
817
- clear();
818
- onQueryChange?.("");
819
- },
820
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.X, { "aria-hidden": "true", className: "w-6 h-6" })
821
- }
822
- )
823
- ] })
824
- ] });
899
+ ) });
825
900
  }
826
901
 
827
902
  // src/genre-tree/GenreInfoPanelBody.tsx
@@ -843,7 +918,7 @@ var import_si = require("react-icons/si");
843
918
  // src/popup/Toast.tsx
844
919
  var import_react8 = require("react");
845
920
  var import_react_dom2 = require("react-dom");
846
- var import_jsx_runtime10 = require("react/jsx-runtime");
921
+ var import_jsx_runtime14 = require("react/jsx-runtime");
847
922
  function Toast({ message, onClose, durationMs = 2200 }) {
848
923
  const [mounted, setMounted] = (0, import_react8.useState)(false);
849
924
  (0, import_react8.useEffect)(() => {
@@ -853,7 +928,7 @@ function Toast({ message, onClose, durationMs = 2200 }) {
853
928
  }, [durationMs, onClose]);
854
929
  if (!mounted) return null;
855
930
  return (0, import_react_dom2.createPortal)(
856
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
931
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
857
932
  "div",
858
933
  {
859
934
  role: "status",
@@ -868,12 +943,12 @@ function Toast({ message, onClose, durationMs = 2200 }) {
868
943
  }
869
944
 
870
945
  // src/genre-tree/ShareDestinationButton.tsx
871
- var import_jsx_runtime11 = require("react/jsx-runtime");
946
+ var import_jsx_runtime15 = require("react/jsx-runtime");
872
947
  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";
873
948
  function Contents({ icon, label }) {
874
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
875
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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 }),
876
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "max-w-full truncate", children: label })
949
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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 }),
951
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "max-w-full truncate", children: label })
877
952
  ] });
878
953
  }
879
954
  function ShareDestinationButton({
@@ -885,29 +960,29 @@ function ShareDestinationButton({
885
960
  ariaLabel
886
961
  }) {
887
962
  if (href) {
888
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Contents, { icon, label }) });
963
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Contents, { icon, label }) });
889
964
  }
890
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Contents, { icon, label }) });
965
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Contents, { icon, label }) });
891
966
  }
892
967
 
893
968
  // src/genre-tree/SharePreviewCard.tsx
894
969
  var import_lucide_react4 = require("lucide-react");
895
- var import_jsx_runtime12 = require("react/jsx-runtime");
970
+ var import_jsx_runtime16 = require("react/jsx-runtime");
896
971
  function SharePreviewCard({ target, url }) {
897
972
  const Icon = { genre: import_lucide_react4.Music2, artist: import_lucide_react4.UserRound, song: import_lucide_react4.Music }[target.kind];
898
973
  const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
899
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
900
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
901
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
902
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
903
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
904
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
974
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
975
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
976
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
977
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
978
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
979
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
905
980
  ] })
906
981
  ] }) });
907
982
  }
908
983
 
909
984
  // src/genre-tree/SharePopup.tsx
910
- var import_jsx_runtime13 = require("react/jsx-runtime");
985
+ var import_jsx_runtime17 = require("react/jsx-runtime");
911
986
  function resolveAbsoluteUrl(url) {
912
987
  try {
913
988
  const parsed = new URL(url, window.location.href);
@@ -982,8 +1057,8 @@ function SharePopup({ target, url, onClose }) {
982
1057
  setShareError("Could not open the share options.");
983
1058
  }
984
1059
  };
985
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
986
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1060
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
1061
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
987
1062
  BasePopup_default,
988
1063
  {
989
1064
  title: "Share",
@@ -998,67 +1073,67 @@ function SharePopup({ target, url, onClose }) {
998
1073
  titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
999
1074
  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)]",
1000
1075
  contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
1001
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col gap-4", children: [
1002
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1003
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1004
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1076
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-col gap-4", children: [
1077
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1079
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1005
1080
  ShareDestinationButton,
1006
1081
  {
1007
1082
  label: "Copy Link",
1008
- icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1083
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1009
1084
  onClick: () => void handleCopy(),
1010
1085
  ariaLabel: "Copy link",
1011
1086
  disabled: !absoluteUrl
1012
1087
  }
1013
1088
  ),
1014
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1089
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1015
1090
  ShareDestinationButton,
1016
1091
  {
1017
1092
  label: "WhatsApp",
1018
- icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fa.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1093
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_fa.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1019
1094
  href: destinationUrls?.whatsapp,
1020
1095
  ariaLabel: `Share ${target.name} via WhatsApp`,
1021
1096
  disabled: !absoluteUrl
1022
1097
  }
1023
1098
  ),
1024
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1099
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1025
1100
  ShareDestinationButton,
1026
1101
  {
1027
1102
  label: "Gmail",
1028
- icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1103
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1029
1104
  href: destinationUrls?.gmail,
1030
1105
  ariaLabel: `Email ${target.name} with Gmail`,
1031
1106
  disabled: !absoluteUrl
1032
1107
  }
1033
1108
  ),
1034
- canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1109
+ canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1035
1110
  ShareDestinationButton,
1036
1111
  {
1037
1112
  label: "More",
1038
- icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1113
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1039
1114
  onClick: () => void handleNativeShare(),
1040
1115
  ariaLabel: "More sharing options",
1041
1116
  disabled: !absoluteUrl
1042
1117
  }
1043
1118
  )
1044
1119
  ] }),
1045
- copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1046
- shareError && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1120
+ copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1121
+ shareError && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1047
1122
  ] })
1048
1123
  }
1049
1124
  ),
1050
- toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1125
+ toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1051
1126
  ] });
1052
1127
  }
1053
1128
 
1054
1129
  // src/genre-tree/ShareActionButton.tsx
1055
- var import_jsx_runtime14 = require("react/jsx-runtime");
1130
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1056
1131
  function ShareActionButton({ target, url }) {
1057
1132
  const { showPopup, hidePopup } = (0, import_popup3.usePopup)();
1058
1133
  const openSharePopup = () => {
1059
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1134
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1060
1135
  };
1061
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1136
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1062
1137
  "button",
1063
1138
  {
1064
1139
  type: "button",
@@ -1067,7 +1142,7 @@ function ShareActionButton({ target, url }) {
1067
1142
  "aria-label": `Share ${target.name}`,
1068
1143
  title: `Share ${target.name}`,
1069
1144
  children: [
1070
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("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__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1145
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("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__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react6.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1071
1146
  "Share"
1072
1147
  ]
1073
1148
  }
@@ -1075,7 +1150,7 @@ function ShareActionButton({ target, url }) {
1075
1150
  }
1076
1151
 
1077
1152
  // src/genre-tree/GenreActionBand.tsx
1078
- var import_jsx_runtime15 = require("react/jsx-runtime");
1153
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1079
1154
  function GenreActionBand({
1080
1155
  node,
1081
1156
  onPlayPause,
@@ -1097,7 +1172,7 @@ function GenreActionBand({
1097
1172
  items.push({
1098
1173
  key: "play-pause",
1099
1174
  label: isPlaying ? "Pause" : "Play",
1100
- icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1175
+ icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1101
1176
  onClick: onPlayPause
1102
1177
  });
1103
1178
  }
@@ -1105,24 +1180,24 @@ function GenreActionBand({
1105
1180
  items.push({
1106
1181
  key: "zoom-in",
1107
1182
  label: "Focus",
1108
- icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1183
+ icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1109
1184
  onClick: onZoomIn
1110
1185
  });
1111
1186
  }
1112
1187
  if (onAddChild) {
1113
- adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1188
+ adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1114
1189
  }
1115
1190
  if (onRename) {
1116
- adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1191
+ adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1117
1192
  }
1118
1193
  if (onChangeParent) {
1119
- adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1194
+ adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1120
1195
  }
1121
1196
  if (onToggleSide) {
1122
1197
  adminItems.push({
1123
1198
  key: "toggle-side",
1124
1199
  label: side === "pop" ? "Make core" : "Make pop",
1125
- icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react7.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1200
+ icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react7.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1126
1201
  onClick: onToggleSide,
1127
1202
  disabled: !!toggleSideDisabledReason,
1128
1203
  title: toggleSideDisabledReason
@@ -1138,7 +1213,7 @@ function GenreActionBand({
1138
1213
  });
1139
1214
  }
1140
1215
  if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
1141
- const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1216
+ const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1142
1217
  "button",
1143
1218
  {
1144
1219
  type: "button",
@@ -1147,25 +1222,25 @@ function GenreActionBand({
1147
1222
  onClick: item.onClick,
1148
1223
  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",
1149
1224
  children: [
1150
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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 }),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("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 }),
1151
1226
  item.label
1152
1227
  ]
1153
1228
  },
1154
1229
  item.key
1155
1230
  ));
1156
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1157
- (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1231
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1232
+ (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1158
1233
  renderItems(items),
1159
- shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1234
+ shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1160
1235
  ] }),
1161
- adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1236
+ adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1162
1237
  "section",
1163
1238
  {
1164
1239
  "aria-label": "Admin actions",
1165
1240
  className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
1166
1241
  children: [
1167
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1168
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1242
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1243
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1169
1244
  ]
1170
1245
  }
1171
1246
  )
@@ -1173,12 +1248,12 @@ function GenreActionBand({
1173
1248
  }
1174
1249
 
1175
1250
  // src/genre-tree/GenreDetailExtras.tsx
1176
- var import_ui5 = require("@behindthemusictree/ui");
1177
- var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
1251
+ var import_ui6 = require("@behindthemusictree/ui");
1252
+ var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
1178
1253
 
1179
1254
  // src/genre-tree/ClampedText.tsx
1180
1255
  var import_react10 = require("react");
1181
- var import_jsx_runtime16 = require("react/jsx-runtime");
1256
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1182
1257
  function ClampedText({ children, className: className2 = "" }) {
1183
1258
  const ref = (0, import_react10.useRef)(null);
1184
1259
  const [expanded, setExpanded] = (0, import_react10.useState)(false);
@@ -1188,9 +1263,9 @@ function ClampedText({ children, className: className2 = "" }) {
1188
1263
  if (!element || expanded) return;
1189
1264
  setOverflows(element.scrollHeight > element.clientHeight);
1190
1265
  }, [children, expanded]);
1191
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1192
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1193
- (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1266
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1268
+ (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1194
1269
  "button",
1195
1270
  {
1196
1271
  type: "button",
@@ -1208,7 +1283,7 @@ var import_react11 = require("react");
1208
1283
  var import_react_query = require("@tanstack/react-query");
1209
1284
  var import_zod = require("zod");
1210
1285
  var import_fa2 = require("react-icons/fa");
1211
- var import_ui4 = require("@behindthemusictree/ui");
1286
+ var import_ui5 = require("@behindthemusictree/ui");
1212
1287
  var import_auth = require("@behindthemusictree/app-kit/auth");
1213
1288
  var import_transport3 = require("@behindthemusictree/app-kit/transport");
1214
1289
  var import_transport4 = require("@behindthemusictree/app-kit/transport");
@@ -1217,22 +1292,22 @@ var import_player4 = require("@behindthemusictree/app-kit/player");
1217
1292
  var import_player5 = require("@behindthemusictree/app-kit/player");
1218
1293
  var import_popup4 = require("@behindthemusictree/app-kit/popup");
1219
1294
  var import_transport6 = require("@behindthemusictree/app-kit/transport");
1220
- var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
1221
- var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
1222
- var import_genre_tree6 = require("@behindthemusictree/app-kit/genre-tree");
1223
1295
  var import_genre_tree7 = require("@behindthemusictree/app-kit/genre-tree");
1296
+ var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
1297
+ var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
1298
+ var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1224
1299
 
1225
1300
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1226
- var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
1227
- var import_jsx_runtime17 = require("react/jsx-runtime");
1301
+ var import_genre_tree6 = require("@behindthemusictree/app-kit/genre-tree");
1302
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1228
1303
  function MusicBrainzRecordingLink({
1229
1304
  song,
1230
1305
  children,
1231
1306
  className: className2 = ""
1232
1307
  }) {
1233
- const url = (0, import_genre_tree3.musicBrainzRecordingUrl)(song);
1234
- if (!url) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children });
1235
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1308
+ const url = (0, import_genre_tree6.musicBrainzRecordingUrl)(song);
1309
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_jsx_runtime21.Fragment, { children });
1310
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1236
1311
  "a",
1237
1312
  {
1238
1313
  href: url,
@@ -1247,11 +1322,11 @@ function MusicBrainzRecordingLink({
1247
1322
  }
1248
1323
 
1249
1324
  // src/genre-tree/GenrePlaylistTracks.tsx
1250
- var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
1325
+ var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1251
1326
  var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
1252
- var import_jsx_runtime18 = require("react/jsx-runtime");
1327
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1253
1328
  var CATALOGUE_PAGE_SIZE = 100;
1254
- var SongPageSchema = (0, import_transport4.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
1329
+ var SongPageSchema = (0, import_transport4.PaginatedResponseSchema)(import_genre_tree9.SongDetailedSchema).extend({
1255
1330
  overallTotal: import_zod.z.number().nullable(),
1256
1331
  page: import_zod.z.number().nullable(),
1257
1332
  totalPages: import_zod.z.number().nullable()
@@ -1278,19 +1353,20 @@ function GenrePlaylistTracks({
1278
1353
  renderTracksToolbar,
1279
1354
  artist = null
1280
1355
  }) {
1281
- const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree7.useTrackList)();
1356
+ const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree10.useTrackList)();
1282
1357
  const { session, sessionRestored } = (0, import_auth.useSession)();
1283
1358
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
1284
1359
  const { showPopup } = (0, import_popup4.usePopup)();
1285
1360
  const trackNoun = (0, import_track_noun.useTrackNoun)();
1286
1361
  const { uuid } = genrePlaylist;
1287
1362
  const { fetch } = (0, import_transport3.useFetchWrapper)(getBackendBaseUrl);
1363
+ const playback = (0, import_transport3.usePlaybackContext)();
1288
1364
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
1289
- queryKey: scope === "me" ? import_genre_tree4.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid),
1365
+ queryKey: [...scope === "me" ? import_genre_tree7.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree8.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree8.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), ...playback.key ? [playback.key] : []],
1290
1366
  queryFn: async ({ pageParam }) => {
1291
1367
  if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1292
1368
  const response = await fetch(
1293
- import_genre_tree5.libraryEndpoints.reference.songs.list(),
1369
+ import_genre_tree8.libraryEndpoints.reference.songs.list(),
1294
1370
  true,
1295
1371
  false,
1296
1372
  {},
@@ -1301,7 +1377,7 @@ function GenrePlaylistTracks({
1301
1377
  },
1302
1378
  initialPageParam: scope === "me" ? 1 : "",
1303
1379
  getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
1304
- enabled: scope !== "me" || sessionRestored && !!session?.accessToken
1380
+ enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
1305
1381
  });
1306
1382
  const [sentinel, setSentinel] = (0, import_react11.useState)(null);
1307
1383
  (0, import_react11.useEffect)(() => {
@@ -1321,35 +1397,35 @@ function GenrePlaylistTracks({
1321
1397
  const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1322
1398
  started.catch((error) => {
1323
1399
  console.error("Failed to play genre playlist:", error);
1324
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(InternalErrorPopup, { errorCode: import_transport6.ErrorCode.CLIENT_UNKNOWN }));
1400
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(InternalErrorPopup, { errorCode: import_transport6.ErrorCode.CLIENT_UNKNOWN }));
1325
1401
  });
1326
1402
  };
1327
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ui4.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
1403
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui5.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
1328
1404
  renderTracksToolbar?.(),
1329
- isPending ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ui4.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { role: "alert", children: [
1405
+ isPending ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui5.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("p", { role: "alert", children: [
1330
1406
  "Couldn't load ",
1331
1407
  trackNoun.plural,
1332
1408
  ".",
1333
1409
  " ",
1334
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1335
- ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("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: [
1410
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1411
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("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: [
1336
1412
  tracks.map((track, index) => {
1337
1413
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1338
1414
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1339
1415
  const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
1340
- const text = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: TEXT_CELL, children: [
1341
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1342
- track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1343
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_genre_tree8.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1344
- track.album && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1416
+ const text = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: TEXT_CELL, children: [
1417
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree9.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1418
+ track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1419
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_genre_tree11.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1420
+ track.album && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1345
1421
  ] });
1346
1422
  const leading = renderSongLeading?.(track, index);
1347
1423
  const actions = renderSongActions?.(track);
1348
- const isUnplayable = (0, import_genre_tree6.isSong)(track) && (0, import_genre_tree6.songUnplayableReason)(track) !== null;
1424
+ const isUnplayable = (0, import_genre_tree9.isSong)(track) && (0, import_genre_tree9.songUnplayableReason)(track) !== null;
1349
1425
  if (isUnplayable) {
1350
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1426
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1351
1427
  leading,
1352
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1428
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1353
1429
  "button",
1354
1430
  {
1355
1431
  type: "button",
@@ -1357,27 +1433,27 @@ function GenrePlaylistTracks({
1357
1433
  "aria-disabled": "true",
1358
1434
  "aria-label": label,
1359
1435
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1360
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fa2.FaPlay, { size: 10 })
1436
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_fa2.FaPlay, { size: 10 })
1361
1437
  }
1362
1438
  ),
1363
1439
  text,
1364
1440
  actions
1365
1441
  ] }, track.uuid);
1366
1442
  }
1367
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1443
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1368
1444
  "li",
1369
1445
  {
1370
1446
  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)]" : ""}`,
1371
1447
  children: [
1372
1448
  leading,
1373
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1449
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1374
1450
  "button",
1375
1451
  {
1376
1452
  type: "button",
1377
1453
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1378
1454
  className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== import_player5.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
1379
1455
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1380
- children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ui4.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fa2.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fa2.FaPlay, { size: 10 })
1456
+ children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui5.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_fa2.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_fa2.FaPlay, { size: 10 })
1381
1457
  }
1382
1458
  ),
1383
1459
  text,
@@ -1387,7 +1463,7 @@ function GenrePlaylistTracks({
1387
1463
  track.uuid
1388
1464
  );
1389
1465
  }),
1390
- hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1466
+ hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1391
1467
  ] })
1392
1468
  ] }) });
1393
1469
  }
@@ -1395,7 +1471,7 @@ function GenrePlaylistTracks({
1395
1471
  // src/genre-tree/GenreDetailExtras.tsx
1396
1472
  var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1397
1473
  var import_si2 = require("react-icons/si");
1398
- var import_jsx_runtime19 = require("react/jsx-runtime");
1474
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1399
1475
  function GenreDetailExtras({
1400
1476
  genreUuid,
1401
1477
  genrePlaylist,
@@ -1412,24 +1488,24 @@ function GenreDetailExtras({
1412
1488
  ...trackSlots
1413
1489
  }) {
1414
1490
  const trackNoun = (0, import_track_noun2.useTrackNoun)();
1415
- const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
1491
+ const { data: overview, isPending } = (0, import_genre_tree12.useFetchGenreOverview)(
1416
1492
  genreUuid,
1417
1493
  scope,
1418
1494
  getBackendBaseUrl,
1419
1495
  criteriaOverviewSchema
1420
1496
  );
1421
1497
  if (isPending) {
1422
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1423
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.Skeleton, { className: "h-4 w-3/4" }) }) }),
1498
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1499
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.Skeleton, { className: "h-4 w-3/4" }) }) }),
1424
1500
  family
1425
1501
  ] });
1426
1502
  }
1427
- if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children: family });
1503
+ if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children: family });
1428
1504
  const { summary, summarySourceUrl, essentialSongs } = overview;
1429
1505
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1430
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1431
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1432
- summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1506
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1507
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1508
+ summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1433
1509
  "a",
1434
1510
  {
1435
1511
  href: summarySourceUrl,
@@ -1437,12 +1513,12 @@ function GenreDetailExtras({
1437
1513
  rel: "noopener noreferrer",
1438
1514
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1439
1515
  children: [
1440
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1516
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1441
1517
  "Wikipedia"
1442
1518
  ]
1443
1519
  }
1444
1520
  ),
1445
- wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1521
+ wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1446
1522
  "a",
1447
1523
  {
1448
1524
  href: wikidataUrl,
@@ -1450,17 +1526,17 @@ function GenreDetailExtras({
1450
1526
  rel: "noopener noreferrer",
1451
1527
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1452
1528
  children: [
1453
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1529
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1454
1530
  "Wikidata"
1455
1531
  ]
1456
1532
  }
1457
1533
  )
1458
1534
  ] }) }),
1459
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1460
1536
  family,
1461
1537
  renderGenreDetailExtras?.(overview),
1462
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ui5.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1463
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1538
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui6.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1539
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1464
1540
  GenrePlaylistTracks,
1465
1541
  {
1466
1542
  genreUuid,
@@ -1476,7 +1552,7 @@ function GenreDetailExtras({
1476
1552
  }
1477
1553
 
1478
1554
  // src/genre-tree/GenreInfoPanelBody.tsx
1479
- var import_jsx_runtime20 = require("react/jsx-runtime");
1555
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1480
1556
  var import_react12 = require("react");
1481
1557
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1482
1558
  const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
@@ -1490,7 +1566,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1490
1566
  console.error("Failed to load genre playlist tracks:", error);
1491
1567
  });
1492
1568
  };
1493
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1569
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1494
1570
  GenreActionBand,
1495
1571
  {
1496
1572
  ...bandProps,
@@ -1511,9 +1587,9 @@ function GenreInfoPanelBody({
1511
1587
  band,
1512
1588
  extras
1513
1589
  }) {
1514
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
1590
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
1515
1591
  toolbar,
1516
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1592
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1517
1593
  /* @__PURE__ */ (0, import_react12.createElement)(
1518
1594
  GenreDetailExtras,
1519
1595
  {
@@ -1532,8 +1608,8 @@ function GenreInfoPanelBody({
1532
1608
 
1533
1609
  // src/genre-tree/ChangeParentPopup.tsx
1534
1610
  var import_react13 = require("react");
1535
- var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1536
- var import_jsx_runtime21 = require("react/jsx-runtime");
1611
+ var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
1612
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1537
1613
  function ChangeParentPopup({
1538
1614
  genrePlaylist,
1539
1615
  genrePlaylists,
@@ -1542,9 +1618,9 @@ function ChangeParentPopup({
1542
1618
  onClose,
1543
1619
  onParentChanged
1544
1620
  }) {
1545
- const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
1621
+ const { mutate, isPending, formErrors } = (0, import_genre_tree13.useUpdateGenre)(scope, getBackendBaseUrl);
1546
1622
  const candidates = (0, import_react13.useMemo)(
1547
- () => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1623
+ () => (0, import_genre_tree13.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1548
1624
  [genrePlaylists, genrePlaylist.uuid]
1549
1625
  );
1550
1626
  const handleSelect = (newParent) => {
@@ -1568,12 +1644,12 @@ function ChangeParentPopup({
1568
1644
  window.addEventListener("keydown", handleKeyDown);
1569
1645
  return () => window.removeEventListener("keydown", handleKeyDown);
1570
1646
  }, [onClose]);
1571
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1572
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1647
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1648
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1573
1649
  "Current parent: ",
1574
1650
  genrePlaylist.parent?.name ?? "none"
1575
1651
  ] }),
1576
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1652
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1577
1653
  GenreSearch,
1578
1654
  {
1579
1655
  genrePlaylists: candidates,
@@ -1581,22 +1657,22 @@ function ChangeParentPopup({
1581
1657
  placeholder: "Search a new parent\u2026"
1582
1658
  }
1583
1659
  ) }),
1584
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1660
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1585
1661
  ] });
1586
1662
  }
1587
1663
 
1588
1664
  // src/genre-tree/artist/useArtistPanel.tsx
1589
- var import_ui11 = require("@behindthemusictree/ui");
1590
- var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
1665
+ var import_ui12 = require("@behindthemusictree/ui");
1666
+ var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
1591
1667
 
1592
1668
  // src/genre-tree/artist/ArtistHeader.tsx
1593
- var import_ui7 = require("@behindthemusictree/ui");
1669
+ var import_ui8 = require("@behindthemusictree/ui");
1594
1670
 
1595
1671
  // src/genre-tree/artist/ArtistLinks.tsx
1596
1672
  var import_lucide_react8 = require("lucide-react");
1597
1673
  var import_si3 = require("react-icons/si");
1598
- var import_ui6 = require("@behindthemusictree/ui");
1599
- var import_jsx_runtime22 = require("react/jsx-runtime");
1674
+ var import_ui7 = require("@behindthemusictree/ui");
1675
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1600
1676
  var LINK_KINDS = {
1601
1677
  official: { label: "Official website", Icon: import_lucide_react8.Globe },
1602
1678
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
@@ -1633,7 +1709,7 @@ function artistLinks(artist) {
1633
1709
  return links;
1634
1710
  }
1635
1711
  function ArtistLinks({ links }) {
1636
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui6.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1712
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1637
1713
  }
1638
1714
 
1639
1715
  // src/genre-tree/artist/formatYears.ts
@@ -1644,7 +1720,7 @@ function formatYears(beginYear, endYear, ended) {
1644
1720
  }
1645
1721
 
1646
1722
  // src/genre-tree/artist/ArtistHeader.tsx
1647
- var import_jsx_runtime23 = require("react/jsx-runtime");
1723
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1648
1724
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1649
1725
  function ArtistHeader({ artist }) {
1650
1726
  const fields = [];
@@ -1654,35 +1730,35 @@ function ArtistHeader({ artist }) {
1654
1730
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1655
1731
  if (years) fields.push({ label: "Active", value: years });
1656
1732
  const links = artistLinks(artist);
1657
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ArtistLinks, { links }) });
1733
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ArtistLinks, { links }) });
1658
1734
  if (fields.length === 0) return null;
1659
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui7.FieldList, { items: fields });
1735
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui8.FieldList, { items: fields });
1660
1736
  }
1661
1737
 
1662
1738
  // src/genre-tree/artist/ArtistPhoto.tsx
1663
- var import_jsx_runtime24 = require("react/jsx-runtime");
1739
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1664
1740
  function ExternalLink({ href, children }) {
1665
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1741
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1666
1742
  }
1667
1743
  function ArtistPhoto({ image, name }) {
1668
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1669
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1670
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1744
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1745
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1746
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1671
1747
  "Photo: ",
1672
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1748
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1673
1749
  ",",
1674
1750
  " ",
1675
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1751
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1676
1752
  ] })
1677
1753
  ] });
1678
1754
  }
1679
1755
 
1680
1756
  // src/genre-tree/artist/ArtistMembers.tsx
1681
- var import_ui8 = require("@behindthemusictree/ui");
1682
- var import_jsx_runtime25 = require("react/jsx-runtime");
1757
+ var import_ui9 = require("@behindthemusictree/ui");
1758
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1683
1759
  function MembershipList({ title, memberships, renderArtistLink }) {
1684
1760
  if (memberships.length === 0) return null;
1685
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { children: memberships.map((membership, index) => {
1761
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { children: memberships.map((membership, index) => {
1686
1762
  const details = [
1687
1763
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1688
1764
  ...membership.roles
@@ -1690,9 +1766,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1690
1766
  const detailsText = details.filter(Boolean).join(" \xB7 ");
1691
1767
  return (
1692
1768
  // A member can have several periods in the same group.
1693
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1694
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1695
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1770
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1771
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1696
1772
  " \u2014 ",
1697
1773
  detailsText
1698
1774
  ] })
@@ -1703,9 +1779,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1703
1779
  function ArtistMembers({ title, memberships, renderArtistLink }) {
1704
1780
  if (memberships.length === 0) return null;
1705
1781
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1706
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ui8.PanelSection, { title, children: [
1707
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1708
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1782
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_ui9.PanelSection, { title, children: [
1783
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1784
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1709
1785
  MembershipList,
1710
1786
  {
1711
1787
  title: "Past",
@@ -1717,8 +1793,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1717
1793
  }
1718
1794
 
1719
1795
  // src/genre-tree/artist/ArtistDiscography.tsx
1720
- var import_ui9 = require("@behindthemusictree/ui");
1721
- var import_jsx_runtime26 = require("react/jsx-runtime");
1796
+ var import_ui10 = require("@behindthemusictree/ui");
1797
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1722
1798
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1723
1799
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1724
1800
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1738,12 +1814,12 @@ function byYear(a, b) {
1738
1814
  function ArtistDiscography({ albums }) {
1739
1815
  if (albums.length === 0) return null;
1740
1816
  const sorted = [...albums].sort(byYear);
1741
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui9.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
1817
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
1742
1818
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1743
1819
  if (groupAlbums.length === 0) return null;
1744
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui9.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1745
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1746
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1820
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1821
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1822
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1747
1823
  ] }, album.uuid)) }) }, group);
1748
1824
  }) });
1749
1825
  }
@@ -1752,20 +1828,20 @@ function ArtistDiscography({ albums }) {
1752
1828
  var import_react14 = require("react");
1753
1829
  var import_react_query2 = require("@tanstack/react-query");
1754
1830
  var import_lucide_react9 = require("lucide-react");
1755
- var import_ui10 = require("@behindthemusictree/ui");
1831
+ var import_ui11 = require("@behindthemusictree/ui");
1756
1832
  var import_player10 = require("@behindthemusictree/app-kit/player");
1757
- var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
1833
+ var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
1758
1834
  var import_transport7 = require("@behindthemusictree/app-kit/transport");
1759
1835
 
1760
1836
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1761
1837
  var import_player9 = require("@behindthemusictree/app-kit/player");
1762
- var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1838
+ var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
1763
1839
 
1764
1840
  // src/genre-tree/TrackPositionPlayPause.tsx
1765
1841
  var import_fa3 = require("react-icons/fa");
1766
1842
  var import_player7 = require("@behindthemusictree/app-kit/player");
1767
1843
  var import_player8 = require("@behindthemusictree/app-kit/player");
1768
- var import_jsx_runtime27 = require("react/jsx-runtime");
1844
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1769
1845
  function TrackPositionPlayPause({
1770
1846
  position,
1771
1847
  uuid,
@@ -1779,14 +1855,14 @@ function TrackPositionPlayPause({
1779
1855
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
1780
1856
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
1781
1857
  const isCurrentTrackActive = isPlaying || isPaused;
1782
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1858
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1783
1859
  "div",
1784
1860
  {
1785
1861
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
1786
1862
  style: { minWidth: "4rem", maxWidth: "4rem" },
1787
1863
  onClick: handlePlayPauseClick,
1788
1864
  children: [
1789
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1865
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1790
1866
  "span",
1791
1867
  {
1792
1868
  className: "track-position-play-pause__bars",
@@ -1794,18 +1870,18 @@ function TrackPositionPlayPause({
1794
1870
  role: "img",
1795
1871
  "aria-label": isPlaying ? "Playing" : "Paused",
1796
1872
  children: [
1797
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" }),
1798
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" }),
1799
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" })
1873
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" }),
1874
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" }),
1875
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__bar" })
1800
1876
  ]
1801
1877
  }
1802
1878
  ) : position }),
1803
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1879
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1804
1880
  "div",
1805
1881
  {
1806
1882
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1807
1883
  "aria-hidden": "true",
1808
- children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa3.FaPlay, {})
1884
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_fa3.FaPlay, {})
1809
1885
  }
1810
1886
  )
1811
1887
  ]
@@ -1814,9 +1890,9 @@ function TrackPositionPlayPause({
1814
1890
  }
1815
1891
 
1816
1892
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1817
- var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
1818
- var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
1819
- var import_jsx_runtime28 = require("react/jsx-runtime");
1893
+ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
1894
+ var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
1895
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1820
1896
  function TrackItem({
1821
1897
  track,
1822
1898
  position,
@@ -1826,11 +1902,11 @@ function TrackItem({
1826
1902
  renderSongLink
1827
1903
  }) {
1828
1904
  const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
1829
- const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
1905
+ const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree14.useTrackList)();
1830
1906
  const scope = trackList?.origin?.scope ?? null;
1831
1907
  const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
1832
1908
  const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
1833
- const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree12.isSong)(track) && (0, import_genre_tree12.songUnplayableReason)(track) !== null;
1909
+ const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree15.isSong)(track) && (0, import_genre_tree15.songUnplayableReason)(track) !== null;
1834
1910
  const handlePlayPauseClick = (event) => {
1835
1911
  event.stopPropagation();
1836
1912
  if (isCurrentTrack) {
@@ -1840,12 +1916,12 @@ function TrackItem({
1840
1916
  loadTrackForPlayer(track.uuid);
1841
1917
  }
1842
1918
  };
1843
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1919
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1844
1920
  "div",
1845
1921
  {
1846
1922
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
1847
1923
  children: [
1848
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1924
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1849
1925
  "div",
1850
1926
  {
1851
1927
  className: "flex items-center justify-center text-lg w-16",
@@ -1853,7 +1929,7 @@ function TrackItem({
1853
1929
  "aria-disabled": "true",
1854
1930
  children: position
1855
1931
  }
1856
- ) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1932
+ ) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1857
1933
  TrackPositionPlayPause,
1858
1934
  {
1859
1935
  position,
@@ -1862,12 +1938,12 @@ function TrackItem({
1862
1938
  isSelected: selectedTrack?.uuid === track.uuid
1863
1939
  }
1864
1940
  ),
1865
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1866
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1867
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
1941
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1942
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree15.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1943
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
1868
1944
  ] }),
1869
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1870
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1945
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1946
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1871
1947
  "div",
1872
1948
  {
1873
1949
  className: "duration flex text-base w-16 items-center justify-center",
@@ -1875,15 +1951,15 @@ function TrackItem({
1875
1951
  children: renderDuration(track)
1876
1952
  }
1877
1953
  ),
1878
- renderActions && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1954
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1879
1955
  ]
1880
1956
  }
1881
1957
  );
1882
1958
  }
1883
1959
 
1884
1960
  // src/genre-tree/artist/ArtistSongs.tsx
1885
- var import_jsx_runtime29 = require("react/jsx-runtime");
1886
- var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
1961
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1962
+ var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree17.SongDetailedSchema);
1887
1963
  var SORTS = [
1888
1964
  { ordering: "title", label: "Title" },
1889
1965
  { ordering: "-albumYear", label: "Year" }
@@ -1892,11 +1968,11 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1892
1968
  const [page, setPage] = (0, import_react14.useState)(1);
1893
1969
  const [ordering, setOrdering] = (0, import_react14.useState)("title");
1894
1970
  const { fetch } = (0, import_transport7.useFetchWrapper)(getBackendBaseUrl);
1895
- const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
1971
+ const { playNewTrackListFromArtist } = (0, import_genre_tree17.useTrackList)();
1896
1972
  const { playerTrackObject } = (0, import_player10.usePlayer)();
1897
1973
  const { data, isPending, isError } = (0, import_transport7.useQueryWithParse)({
1898
1974
  queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
1899
- queryFn: () => fetch(import_genre_tree14.libraryEndpoints.reference.songs.list(), true, false, void 0, {
1975
+ queryFn: () => fetch(import_genre_tree17.libraryEndpoints.reference.songs.list(), true, false, void 0, {
1900
1976
  artist: artist.uuid,
1901
1977
  ordering,
1902
1978
  page
@@ -1911,25 +1987,25 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1911
1987
  });
1912
1988
  const handleRowClickCapture = (event, song) => {
1913
1989
  if (!(event.target instanceof Element) || !event.target.closest(".track-position-play-pause")) return;
1914
- if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree14.songUnplayableReason)(song) !== null) return;
1990
+ if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree17.songUnplayableReason)(song) !== null) return;
1915
1991
  event.stopPropagation();
1916
1992
  void play(song.uuid);
1917
1993
  };
1918
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1919
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1920
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1994
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
1995
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1996
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1921
1997
  "button",
1922
1998
  {
1923
1999
  type: "button",
1924
2000
  onClick: () => void play(),
1925
2001
  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",
1926
2002
  children: [
1927
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react9.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2003
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react9.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1928
2004
  "Play all"
1929
2005
  ]
1930
2006
  }
1931
2007
  ),
1932
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2008
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1933
2009
  "button",
1934
2010
  {
1935
2011
  type: "button",
@@ -1944,7 +2020,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1944
2020
  sort.ordering
1945
2021
  )) })
1946
2022
  ] }),
1947
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2023
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1948
2024
  TrackItem,
1949
2025
  {
1950
2026
  track: song,
@@ -1953,12 +2029,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1953
2029
  renderSongLink
1954
2030
  }
1955
2031
  ) }, song.uuid)) }),
1956
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2032
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
1957
2033
  ] }) });
1958
2034
  }
1959
2035
 
1960
2036
  // src/genre-tree/artist/useArtistPanel.tsx
1961
- var import_jsx_runtime30 = require("react/jsx-runtime");
2037
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1962
2038
  function useArtistPanel({
1963
2039
  uuid,
1964
2040
  scope,
@@ -1970,7 +2046,7 @@ function useArtistPanel({
1970
2046
  onExpandedChange,
1971
2047
  onClose
1972
2048
  }) {
1973
- const result = (0, import_genre_tree15.useArtist)(uuid, getBackendBaseUrl);
2049
+ const result = (0, import_genre_tree18.useArtist)(uuid, getBackendBaseUrl);
1974
2050
  const panel = { expanded, onExpandedChange, onClose };
1975
2051
  switch (result.status) {
1976
2052
  case "idle":
@@ -1979,26 +2055,26 @@ function useArtistPanel({
1979
2055
  return {
1980
2056
  ...panel,
1981
2057
  title: "Artist",
1982
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
1983
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
1984
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-32" }),
1985
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
1986
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-24" }),
1987
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16 mt-2.5" }),
1988
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16" })
2058
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2059
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-3 w-16" }),
2060
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-4 w-32" }),
2061
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-3 w-16" }),
2062
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-4 w-24" }),
2063
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-16 mt-2.5" }),
2064
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-16" })
1989
2065
  ] })
1990
2066
  };
1991
2067
  case "not-found":
1992
2068
  return {
1993
2069
  ...panel,
1994
2070
  title: "Artist not found",
1995
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2071
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
1996
2072
  };
1997
2073
  case "error":
1998
2074
  return {
1999
2075
  ...panel,
2000
2076
  title: "Artist",
2001
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2077
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2002
2078
  };
2003
2079
  case "success": {
2004
2080
  const { artist } = result;
@@ -2007,13 +2083,13 @@ function useArtistPanel({
2007
2083
  return {
2008
2084
  ...panel,
2009
2085
  title: artist.name,
2010
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
2011
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2012
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2013
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistHeader, { artist }),
2014
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2015
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2016
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2086
+ content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2087
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2088
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2089
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistHeader, { artist }),
2090
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2091
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2092
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2017
2093
  ArtistSongs,
2018
2094
  {
2019
2095
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2023,56 +2099,56 @@ function useArtistPanel({
2023
2099
  }
2024
2100
  )
2025
2101
  ] }),
2026
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2102
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2027
2103
  };
2028
2104
  }
2029
2105
  }
2030
2106
  }
2031
2107
 
2032
2108
  // src/genre-tree/song/useSongPanel.tsx
2033
- var import_ui13 = require("@behindthemusictree/ui");
2034
- var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
2109
+ var import_ui14 = require("@behindthemusictree/ui");
2110
+ var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2035
2111
 
2036
2112
  // src/genre-tree/song/SongPanelContent.tsx
2037
2113
  var import_react15 = require("react");
2038
2114
  var import_lucide_react10 = require("lucide-react");
2039
- var import_ui12 = require("@behindthemusictree/ui");
2115
+ var import_ui13 = require("@behindthemusictree/ui");
2040
2116
  var import_player11 = require("@behindthemusictree/app-kit/player");
2041
- var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
2042
- var import_jsx_runtime31 = require("react/jsx-runtime");
2117
+ var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
2118
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2043
2119
  var numberFormat = new Intl.NumberFormat("en");
2044
2120
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2045
2121
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
2046
2122
  function SongHistory({ uuid, getBackendBaseUrl }) {
2047
2123
  const [open, setOpen] = (0, import_react15.useState)(false);
2048
- const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
2124
+ const history = (0, import_genre_tree19.useSongHistory)(uuid, getBackendBaseUrl, open);
2049
2125
  let body = null;
2050
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.Skeleton, { className: "h-10", "aria-busy": "true" });
2051
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2126
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Skeleton, { className: "h-10", "aria-busy": "true" });
2127
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2052
2128
  else if (history.status === "success")
2053
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "py-1.5 text-sm", children: [
2054
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium", children: entry.action }),
2055
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2129
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { className: "py-1.5 text-sm", children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "font-medium", children: entry.action }),
2131
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2056
2132
  " ",
2057
2133
  "\u2014 ",
2058
2134
  entry.oldValue ?? "none",
2059
2135
  " \u2192 ",
2060
2136
  entry.newValue ?? "none"
2061
2137
  ] }) : null,
2062
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2138
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2063
2139
  entry.actorPseudo ?? "Anonymous",
2064
2140
  " \xB7 ",
2065
2141
  dateFormat.format(new Date(entry.createdOn))
2066
2142
  ] })
2067
2143
  ] }, entry.uuid)) });
2068
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2144
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2069
2145
  "details",
2070
2146
  {
2071
2147
  onToggle: (event) => setOpen(event.currentTarget.open),
2072
2148
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
2073
2149
  children: [
2074
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2075
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "mt-1.5", children: body })
2150
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2151
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "mt-1.5", children: body })
2076
2152
  ]
2077
2153
  }
2078
2154
  );
@@ -2086,25 +2162,25 @@ function SongPanelContent({
2086
2162
  genrePath,
2087
2163
  onSelectGenre
2088
2164
  }) {
2089
- const { playNewTrackListFromTrackUuid } = (0, import_genre_tree16.useTrackList)();
2165
+ const { playNewTrackListFromTrackUuid } = (0, import_genre_tree19.useTrackList)();
2090
2166
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
2091
- const unplayableReason = (0, import_genre_tree16.songUnplayableReason)(song);
2167
+ const unplayableReason = (0, import_genre_tree19.songUnplayableReason)(song);
2092
2168
  const isCurrent = playerTrackObject?.track.id === song.uuid;
2093
2169
  const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
2094
2170
  const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
2095
- const musicBrainzUrl = (0, import_genre_tree16.musicBrainzRecordingUrl)(song);
2171
+ const musicBrainzUrl = (0, import_genre_tree19.musicBrainzRecordingUrl)(song);
2096
2172
  const fields = [];
2097
2173
  if (song.genre) {
2098
2174
  const path = genrePath.length > 0 ? genrePath : null;
2099
2175
  fields.push({
2100
2176
  label: "Genre",
2101
- value: path ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { children: [
2102
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2103
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2177
+ value: path ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { children: [
2178
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2179
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2104
2180
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2105
2181
  });
2106
2182
  } else {
2107
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2183
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2108
2184
  }
2109
2185
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2110
2186
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2114,11 +2190,11 @@ function SongPanelContent({
2114
2190
  const links = [];
2115
2191
  if (musicBrainzUrl)
2116
2192
  links.push(
2117
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2193
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2118
2194
  );
2119
2195
  if (song.youtubeVideo)
2120
2196
  links.push(
2121
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2197
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2122
2198
  "a",
2123
2199
  {
2124
2200
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2133,33 +2209,33 @@ function SongPanelContent({
2133
2209
  if (links.length > 0)
2134
2210
  fields.push({
2135
2211
  label: "Links",
2136
- value: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex gap-3", children: links })
2212
+ value: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex gap-3", children: links })
2137
2213
  });
2138
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
2139
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2140
- unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2214
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2215
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2216
+ unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2141
2217
  "button",
2142
2218
  {
2143
2219
  type: "button",
2144
2220
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2145
2221
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2146
2222
  children: [
2147
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("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__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2223
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("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__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2148
2224
  isPlaying ? "Pause" : "Play"
2149
2225
  ]
2150
2226
  }
2151
2227
  ),
2152
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2228
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2153
2229
  ] }) }),
2154
- song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2155
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.FieldList, { items: fields }),
2156
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2157
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2230
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree19.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2231
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.FieldList, { items: fields }),
2232
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2233
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2158
2234
  ] });
2159
2235
  }
2160
2236
 
2161
2237
  // src/genre-tree/song/useSongPanel.tsx
2162
- var import_jsx_runtime32 = require("react/jsx-runtime");
2238
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2163
2239
  function useSongPanel({
2164
2240
  uuid,
2165
2241
  scope,
@@ -2172,7 +2248,7 @@ function useSongPanel({
2172
2248
  onExpandedChange,
2173
2249
  onClose
2174
2250
  }) {
2175
- const result = (0, import_genre_tree17.useSong)(uuid, getBackendBaseUrl);
2251
+ const result = (0, import_genre_tree20.useSong)(uuid, getBackendBaseUrl);
2176
2252
  const panel = { expanded, onExpandedChange, onClose };
2177
2253
  switch (result.status) {
2178
2254
  case "idle":
@@ -2181,24 +2257,24 @@ function useSongPanel({
2181
2257
  return {
2182
2258
  ...panel,
2183
2259
  title: "Song",
2184
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2185
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2186
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
2187
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2188
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-24" })
2260
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2261
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2262
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
2263
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2264
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" })
2189
2265
  ] })
2190
2266
  };
2191
2267
  case "not-found":
2192
2268
  return {
2193
2269
  ...panel,
2194
2270
  title: "Song not found",
2195
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2271
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2196
2272
  };
2197
2273
  case "error":
2198
2274
  return {
2199
2275
  ...panel,
2200
2276
  title: "Song",
2201
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2277
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2202
2278
  };
2203
2279
  case "success": {
2204
2280
  const { song } = result;
@@ -2206,7 +2282,7 @@ function useSongPanel({
2206
2282
  return {
2207
2283
  ...panel,
2208
2284
  title: song.title,
2209
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2285
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2210
2286
  SongPanelContent,
2211
2287
  {
2212
2288
  song,
@@ -2225,29 +2301,29 @@ function useSongPanel({
2225
2301
 
2226
2302
  // src/genre-tree/SizeMetricToggle.tsx
2227
2303
  var import_md = require("react-icons/md");
2228
- var import_ui14 = require("@behindthemusictree/ui");
2229
- var import_jsx_runtime33 = require("react/jsx-runtime");
2304
+ var import_ui15 = require("@behindthemusictree/ui");
2305
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2230
2306
  function SizeMetricToggle({
2231
2307
  value,
2232
2308
  onChange
2233
2309
  }) {
2234
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2235
- import_ui14.SegmentedControl,
2310
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2311
+ import_ui15.SegmentedControl,
2236
2312
  {
2237
2313
  variant: "floating",
2238
2314
  "aria-label": "Size nodes by",
2239
2315
  value,
2240
2316
  onChange,
2241
2317
  options: [
2242
- { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2243
- { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2318
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2319
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2244
2320
  ]
2245
2321
  }
2246
2322
  );
2247
2323
  }
2248
2324
 
2249
2325
  // src/genre-tree/GenreTreeView.tsx
2250
- var import_jsx_runtime34 = require("react/jsx-runtime");
2326
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2251
2327
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2252
2328
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2253
2329
  var noop = () => {
@@ -2281,6 +2357,7 @@ function GenreTreeView({
2281
2357
  artistExpanded: controlledArtistExpanded,
2282
2358
  onArtistExpandedChange,
2283
2359
  getShareUrl,
2360
+ extraSearchGroups = [],
2284
2361
  artistSearch = true,
2285
2362
  onArtistSelect,
2286
2363
  artistSearchBaseUrl,
@@ -2346,7 +2423,7 @@ function GenreTreeView({
2346
2423
  allowWheelRotation: internalAllowWheelRotation,
2347
2424
  toggleWheelRotation,
2348
2425
  onNodeHover: handleNodeHover
2349
- } = (0, import_genre_tree19.useGenreTreeViewState)({
2426
+ } = (0, import_genre_tree21.useGenreTreeViewState)({
2350
2427
  scope,
2351
2428
  getBackendBaseUrl,
2352
2429
  viewMode: controlledViewMode,
@@ -2365,12 +2442,12 @@ function GenreTreeView({
2365
2442
  restoreRootUuid = selectedGenrePlaylist.root.uuid;
2366
2443
  setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
2367
2444
  }
2368
- const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree19.genreUpdateMutationKey }) > 0;
2369
- const { mutate: updateGenre } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
2445
+ const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree21.genreUpdateMutationKey }) > 0;
2446
+ const { mutate: updateGenre } = (0, import_genre_tree21.useUpdateGenre)(scope, getBackendBaseUrl);
2370
2447
  const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
2371
2448
  const isArtistFiltered = artistUuid !== null;
2372
2449
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2373
- const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2450
+ const artistResult = (0, import_genre_tree21.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2374
2451
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2375
2452
  const filterArtist = (0, import_react16.useMemo)(
2376
2453
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
@@ -2390,7 +2467,7 @@ function GenreTreeView({
2390
2467
  const openSongRef = (0, import_react16.useRef)(openSong);
2391
2468
  openSongRef.current = openSong;
2392
2469
  const resolvedRenderSongLink = (0, import_react16.useCallback)(
2393
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2470
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2394
2471
  [renderSongLink]
2395
2472
  );
2396
2473
  const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
@@ -2402,7 +2479,7 @@ function GenreTreeView({
2402
2479
  scope,
2403
2480
  getBackendBaseUrl: artistBaseUrl,
2404
2481
  getShareUrl: getShareUrlRef.current,
2405
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2482
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2406
2483
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2407
2484
  onExpandedChange: (on) => {
2408
2485
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2411,7 +2488,7 @@ function GenreTreeView({
2411
2488
  renderSongLink: resolvedRenderSongLink,
2412
2489
  onClose: () => changeArtist(null)
2413
2490
  });
2414
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2491
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2415
2492
  const getGenrePath = (genre) => {
2416
2493
  const path = [];
2417
2494
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2467,7 +2544,7 @@ function GenreTreeView({
2467
2544
  []
2468
2545
  );
2469
2546
  const panelRef = (0, import_react16.useRef)(null);
2470
- const obscuredInset = (0, import_ui15.useObscuredInset)(panelRef);
2547
+ const obscuredInset = (0, import_ui16.useObscuredInset)(panelRef);
2471
2548
  const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2472
2549
  const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2473
2550
  const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
@@ -2490,52 +2567,27 @@ function GenreTreeView({
2490
2567
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2491
2568
  const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
2492
2569
  const [searchText, setSearchText] = (0, import_react16.useState)("");
2493
- const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
2494
- const artistGroup = artistSearch ? {
2495
- key: "artists",
2496
- label: "Artists",
2497
- items: artistSearchResult.artists.map((artist) => ({
2498
- key: artist.uuid,
2499
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2500
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate font-medium", children: [
2501
- artist.name,
2502
- artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2503
- " \xB7 ",
2504
- artist.disambiguation
2505
- ] })
2506
- ] }),
2507
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2508
- "also known as ",
2509
- artist.matchedAlias
2510
- ] })
2511
- ] }),
2512
- onSelect: () => {
2513
- openArtist(artist.uuid);
2514
- onArtistSelect?.(artist);
2515
- }
2516
- })),
2517
- status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
2518
- loading: artistSearchResult.pending
2519
- } : void 0;
2520
- const songSearchResult = (0, import_genre_tree18.useSongSearch)(songBaseUrl, songSearch ? searchText : "");
2521
- const songGroup = songSearch ? {
2522
- key: "songs",
2523
- label: "Songs",
2524
- items: songSearchResult.songs.map((song) => ({
2525
- key: song.uuid,
2526
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
2527
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "block truncate font-medium", children: song.title }),
2528
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
2529
- ] }),
2530
- onSelect: () => {
2531
- openSong(song.uuid);
2532
- onSongSelect?.(song);
2533
- }
2534
- })),
2535
- status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
2536
- loading: songSearchResult.pending
2537
- } : void 0;
2538
- const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
2570
+ const artistGroup = useArtistSearchGroup(
2571
+ artistBaseUrl,
2572
+ searchText,
2573
+ (artist) => {
2574
+ openArtist(artist.uuid);
2575
+ onArtistSelect?.(artist);
2576
+ },
2577
+ Boolean(artistSearch)
2578
+ );
2579
+ const songGroup = useSongSearchGroup(
2580
+ songBaseUrl,
2581
+ searchText,
2582
+ (song) => {
2583
+ openSong(song.uuid);
2584
+ onSongSelect?.(song);
2585
+ },
2586
+ Boolean(songSearch)
2587
+ );
2588
+ const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
2589
+ (group) => group !== void 0
2590
+ );
2539
2591
  const handleSelectedNodeChange = (node) => {
2540
2592
  if (node !== null && songUuid !== null) changeSong(null);
2541
2593
  selectNode(node);
@@ -2559,7 +2611,7 @@ function GenreTreeView({
2559
2611
  };
2560
2612
  const renderGenreBody = (relation) => {
2561
2613
  const { node } = relation;
2562
- const family = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2614
+ const family = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2563
2615
  import_genre_tree_view4.GenreNodeFamily,
2564
2616
  {
2565
2617
  node,
@@ -2570,10 +2622,10 @@ function GenreTreeView({
2570
2622
  onSelectNode: selectGenreFromPanel
2571
2623
  }
2572
2624
  );
2573
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2625
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2574
2626
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2575
2627
  const criteria = genrePlaylist?.criteria;
2576
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2628
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2577
2629
  family,
2578
2630
  ancestors
2579
2631
  ] }) };
@@ -2593,11 +2645,11 @@ function GenreTreeView({
2593
2645
  (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2594
2646
  ) : void 0;
2595
2647
  return {
2596
- primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2597
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2648
+ primary: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2649
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2598
2650
  GenreInfoPanelBody,
2599
2651
  {
2600
- family: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2652
+ family: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2601
2653
  family,
2602
2654
  ancestors
2603
2655
  ] }),
@@ -2615,7 +2667,7 @@ function GenreTreeView({
2615
2667
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2616
2668
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2617
2669
  onChangeParent: readOnly ? void 0 : () => showPopup(
2618
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2670
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2619
2671
  ChangeParentPopup,
2620
2672
  {
2621
2673
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2690,19 +2742,19 @@ function GenreTreeView({
2690
2742
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2691
2743
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2692
2744
  } : void 0;
2693
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2694
- import_ui15.IconButton,
2745
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2746
+ import_ui16.IconButton,
2695
2747
  {
2696
2748
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2697
2749
  style: activePanel.headerAccent && { color: "inherit" },
2698
2750
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2699
- children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2751
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2700
2752
  }
2701
2753
  ) : null;
2702
2754
  const renderArtistFilter = () => {
2703
2755
  if (isLoading || !filterArtist) return null;
2704
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2705
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2756
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2757
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2706
2758
  "button",
2707
2759
  {
2708
2760
  type: "button",
@@ -2714,7 +2766,7 @@ function GenreTreeView({
2714
2766
  children: filterArtist.name
2715
2767
  }
2716
2768
  ),
2717
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2769
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2718
2770
  "button",
2719
2771
  {
2720
2772
  type: "button",
@@ -2726,8 +2778,8 @@ function GenreTreeView({
2726
2778
  )
2727
2779
  ] });
2728
2780
  };
2729
- const actions = /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2730
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2781
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2782
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2731
2783
  GenreSearch,
2732
2784
  {
2733
2785
  genrePlaylists: visibleGenrePlaylists,
@@ -2735,14 +2787,14 @@ function GenreTreeView({
2735
2787
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2736
2788
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2737
2789
  onPrevious: handlePrevious,
2738
- onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
2790
+ onQueryChange: artistSearch || songSearch || extraSearchGroups.length > 0 ? setSearchText : void 0,
2739
2791
  extraGroups
2740
2792
  }
2741
2793
  ),
2742
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2743
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
2744
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2745
- import_ui15.SegmentedControl,
2794
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2795
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
2796
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2797
+ import_ui16.SegmentedControl,
2746
2798
  {
2747
2799
  "aria-label": "Tree view mode",
2748
2800
  value: viewMode,
@@ -2755,14 +2807,14 @@ function GenreTreeView({
2755
2807
  ]
2756
2808
  }
2757
2809
  ),
2758
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2810
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2759
2811
  "div",
2760
2812
  {
2761
2813
  className: "flex items-center gap-1",
2762
2814
  role: "group",
2763
2815
  "aria-label": "Tree display options",
2764
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2765
- import_ui15.Button,
2816
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2817
+ import_ui16.Button,
2766
2818
  {
2767
2819
  variant: allowWheelRotation ? "default" : "outline",
2768
2820
  size: "sm",
@@ -2772,8 +2824,8 @@ function GenreTreeView({
2772
2824
  )
2773
2825
  }
2774
2826
  ),
2775
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2776
- import_ui15.IconTextButton,
2827
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2828
+ import_ui16.IconTextButton,
2777
2829
  {
2778
2830
  icon: import_lucide_react11.Plus,
2779
2831
  text: "Add root",
@@ -2782,34 +2834,34 @@ function GenreTreeView({
2782
2834
  )
2783
2835
  ] })
2784
2836
  ] });
2785
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2837
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2786
2838
  "div",
2787
2839
  {
2788
2840
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2789
2841
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2790
2842
  children: [
2791
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2843
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2792
2844
  "div",
2793
2845
  {
2794
2846
  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"}`,
2795
2847
  children: actions
2796
2848
  }
2797
2849
  ),
2798
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2850
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2799
2851
  "div",
2800
2852
  {
2801
2853
  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"}`,
2802
2854
  children: renderArtistFilter()
2803
2855
  }
2804
2856
  ),
2805
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2806
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2807
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2857
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2858
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2859
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2808
2860
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2809
2861
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2810
2862
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2811
2863
  // The panel's own Zoom in button is hidden: the search bar carries it.
2812
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2864
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2813
2865
  GenrePlaylistTreeWheel,
2814
2866
  {
2815
2867
  scope,
@@ -2826,9 +2878,9 @@ function GenreTreeView({
2826
2878
  allowWheelRotation,
2827
2879
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2828
2880
  busy,
2829
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
2881
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-2", children: [
2830
2882
  floatingControlsLeading,
2831
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2883
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2832
2884
  ] }) : void 0,
2833
2885
  defaultViewport,
2834
2886
  viewportRestore,
@@ -2837,7 +2889,7 @@ function GenreTreeView({
2837
2889
  ) }) })
2838
2890
  ) : viewMode === "pop-core" ? (
2839
2891
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2840
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2892
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2841
2893
  GenrePlaylistTreeWheelRadialPopCore,
2842
2894
  {
2843
2895
  scope,
@@ -2854,9 +2906,9 @@ function GenreTreeView({
2854
2906
  allowWheelRotation,
2855
2907
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2856
2908
  busy,
2857
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
2909
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-2", children: [
2858
2910
  floatingControlsLeading,
2859
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2911
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2860
2912
  ] }) : void 0,
2861
2913
  defaultViewport,
2862
2914
  viewportRestore,
@@ -2864,7 +2916,7 @@ function GenreTreeView({
2864
2916
  ...focus
2865
2917
  }
2866
2918
  ) }) })
2867
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2919
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2868
2920
  GenrePlaylistTreeWheelRadialPopCore,
2869
2921
  {
2870
2922
  outline: true,
@@ -2880,13 +2932,13 @@ function GenreTreeView({
2880
2932
  selectedNodeId,
2881
2933
  readOnly
2882
2934
  }
2883
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2935
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2884
2936
  ([uuid, genrePlaylistTreePerRoot]) => {
2885
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2937
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2886
2938
  "div",
2887
2939
  {
2888
2940
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2889
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2941
+ children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2890
2942
  GenrePlaylistTreePerRoot,
2891
2943
  {
2892
2944
  scope,
@@ -2912,8 +2964,8 @@ function GenreTreeView({
2912
2964
  );
2913
2965
  }
2914
2966
  ) }),
2915
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2916
- import_ui15.DetailPanel,
2967
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2968
+ import_ui16.DetailPanel,
2917
2969
  {
2918
2970
  ref: panelRef,
2919
2971
  side: "left",
@@ -2929,7 +2981,7 @@ function GenreTreeView({
2929
2981
  aside: activePanel?.aside,
2930
2982
  edgeTab: detailTab,
2931
2983
  headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
2932
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2984
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
2933
2985
  activePanel.content,
2934
2986
  activePanel.aside
2935
2987
  ] }) : activePanel?.content
@@ -2944,10 +2996,10 @@ function GenreTreeView({
2944
2996
  // src/genre-tree/GenreDetailPanel.tsx
2945
2997
  var import_react17 = require("react");
2946
2998
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
2947
- var import_ui16 = require("@behindthemusictree/ui");
2999
+ var import_ui17 = require("@behindthemusictree/ui");
2948
3000
  var import_lucide_react13 = require("lucide-react");
2949
- var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2950
- var import_jsx_runtime35 = require("react/jsx-runtime");
3001
+ var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
3002
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2951
3003
  var RELATIVE_ITEM_COUNT = 1;
2952
3004
  function toNode(genre, parentId) {
2953
3005
  return {
@@ -2974,7 +3026,7 @@ function GenreDetailPanel({
2974
3026
  edgeTab,
2975
3027
  extras
2976
3028
  }) {
2977
- const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
3029
+ const { data: detail, isPending, isError } = (0, import_genre_tree22.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
2978
3030
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
2979
3031
  const view = (0, import_react17.useMemo)(() => {
2980
3032
  if (!detail) return null;
@@ -3002,17 +3054,17 @@ function GenreDetailPanel({
3002
3054
  };
3003
3055
  }, [detail]);
3004
3056
  if (isError) {
3005
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3057
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3006
3058
  }
3007
3059
  if (isPending || !detail || !view) {
3008
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3009
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-6 w-2/3" }),
3010
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-full" }),
3011
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-3/4" })
3060
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3061
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-6 w-2/3" }),
3062
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-4 w-full" }),
3063
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-4 w-3/4" })
3012
3064
  ] });
3013
3065
  }
3014
3066
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
3015
- const family = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3067
+ const family = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3016
3068
  import_genre_tree_view5.GenreNodeFamily,
3017
3069
  {
3018
3070
  node: view.node,
@@ -3023,20 +3075,20 @@ function GenreDetailPanel({
3023
3075
  onSelectNode: selectGenre
3024
3076
  }
3025
3077
  );
3026
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3027
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
3028
- import_ui16.DetailPanel,
3078
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3079
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
3080
+ import_ui17.DetailPanel,
3029
3081
  {
3030
3082
  side: "left",
3031
3083
  title: view.node.name,
3032
3084
  headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
3033
- headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3034
- import_ui16.IconButton,
3085
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3086
+ import_ui17.IconButton,
3035
3087
  {
3036
3088
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
3037
3089
  style: { color: "inherit" },
3038
3090
  onClick: () => onExpandedChange(!expanded),
3039
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3091
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3040
3092
  }
3041
3093
  ) : void 0,
3042
3094
  onClose,
@@ -3045,10 +3097,10 @@ function GenreDetailPanel({
3045
3097
  onExpandedChange,
3046
3098
  edgeTab,
3047
3099
  children: [
3048
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3100
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3049
3101
  GenreInfoPanelBody,
3050
3102
  {
3051
- family: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
3103
+ family: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
3052
3104
  family,
3053
3105
  ancestors
3054
3106
  ] }),
@@ -3074,10 +3126,10 @@ var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3074
3126
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3075
3127
  var import_react18 = require("react");
3076
3128
  var import_lucide_react14 = require("lucide-react");
3077
- var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
3078
- var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
3129
+ var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3130
+ var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3079
3131
  var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
3080
- var import_jsx_runtime36 = require("react/jsx-runtime");
3132
+ var import_jsx_runtime40 = require("react/jsx-runtime");
3081
3133
  var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
3082
3134
  function TrackListSidebar({
3083
3135
  className: className2 = "",
@@ -3090,7 +3142,7 @@ function TrackListSidebar({
3090
3142
  renderArtistLink,
3091
3143
  renderSongLink
3092
3144
  }) {
3093
- const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
3145
+ const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree23.useTrackList)();
3094
3146
  const trackNoun = (0, import_track_noun3.useTrackNoun)();
3095
3147
  const trackListRef = (0, import_react18.useRef)(null);
3096
3148
  const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
@@ -3179,25 +3231,25 @@ function TrackListSidebar({
3179
3231
  }
3180
3232
  };
3181
3233
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3182
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
3234
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3183
3235
  "div",
3184
3236
  {
3185
3237
  "data-theme": "dark",
3186
3238
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3187
3239
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3188
3240
  children: [
3189
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3190
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3191
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "origin flex text-xl", children: [
3192
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3193
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3241
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3242
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3243
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "origin flex text-xl", children: [
3244
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3245
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3194
3246
  ] }),
3195
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3196
- trackList.origin.type === import_genre_tree22.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree22.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3247
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3248
+ trackList.origin.type === import_genre_tree24.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree24.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3197
3249
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3198
3250
  ] })
3199
3251
  ] }),
3200
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3252
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3201
3253
  "button",
3202
3254
  {
3203
3255
  type: "button",
@@ -3206,11 +3258,11 @@ function TrackListSidebar({
3206
3258
  "aria-expanded": !isCollapsed,
3207
3259
  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"}`,
3208
3260
  onClick: isCollapsed ? onExpand : onMinimize,
3209
- children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3261
+ children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3210
3262
  }
3211
3263
  )
3212
3264
  ] }),
3213
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
3265
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3214
3266
  "ul",
3215
3267
  {
3216
3268
  ref: trackListRef,
@@ -3220,7 +3272,7 @@ function TrackListSidebar({
3220
3272
  onPointerDown: markUserScrollIntent,
3221
3273
  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",
3222
3274
  children: [
3223
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3275
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3224
3276
  TrackItem,
3225
3277
  {
3226
3278
  track,
@@ -3231,7 +3283,7 @@ function TrackListSidebar({
3231
3283
  renderSongLink
3232
3284
  }
3233
3285
  ) }, track.uuid)),
3234
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3286
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3235
3287
  ]
3236
3288
  }
3237
3289
  )
@@ -3244,14 +3296,14 @@ function TrackListSidebar({
3244
3296
  var import_react19 = require("react");
3245
3297
  var import_tailwind_merge = require("tailwind-merge");
3246
3298
  var import_player13 = require("@behindthemusictree/app-kit/player");
3247
- var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3248
- var import_ui17 = require("@behindthemusictree/ui");
3299
+ var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
3300
+ var import_ui18 = require("@behindthemusictree/ui");
3249
3301
 
3250
3302
  // src/genre-tree/PlayerControls.tsx
3251
3303
  var import_lucide_react15 = require("lucide-react");
3252
- var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3304
+ var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3253
3305
  var import_player12 = require("@behindthemusictree/app-kit/player");
3254
- var import_jsx_runtime37 = require("react/jsx-runtime");
3306
+ var import_jsx_runtime41 = require("react/jsx-runtime");
3255
3307
  function PlayerControls() {
3256
3308
  const {
3257
3309
  duration,
@@ -3263,22 +3315,22 @@ function PlayerControls() {
3263
3315
  setVolume,
3264
3316
  volume
3265
3317
  } = (0, import_player12.usePlayer)();
3266
- const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree23.useTrackList)();
3318
+ const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree25.useTrackList)();
3267
3319
  const currentTime = (0, import_player12.useCurrentTime)();
3268
3320
  const isBusy = isLoading || playState === import_player12.PlayStates.LOADING;
3269
3321
  const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
3270
3322
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3271
3323
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3272
3324
  const isPlaying = playState === import_player12.PlayStates.PLAYING;
3273
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3325
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3274
3326
  "section",
3275
3327
  {
3276
3328
  "aria-label": "Player controls",
3277
3329
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3278
3330
  children: [
3279
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3280
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
3281
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3331
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3332
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree25.formatTime)(safeCurrentTime) }),
3333
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3282
3334
  "input",
3283
3335
  {
3284
3336
  "aria-label": "Seek",
@@ -3292,11 +3344,11 @@ function PlayerControls() {
3292
3344
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3293
3345
  }
3294
3346
  ),
3295
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
3347
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree25.formatTime)(safeDuration) })
3296
3348
  ] }),
3297
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3298
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3299
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3349
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3350
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3351
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3300
3352
  "button",
3301
3353
  {
3302
3354
  type: "button",
@@ -3305,10 +3357,10 @@ function PlayerControls() {
3305
3357
  disabled: !isReady || !hasPreviousTrack,
3306
3358
  onClick: playPreviousTrack,
3307
3359
  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",
3308
- children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3360
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3309
3361
  }
3310
3362
  ),
3311
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3363
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3312
3364
  "button",
3313
3365
  {
3314
3366
  type: "button",
@@ -3317,10 +3369,10 @@ function PlayerControls() {
3317
3369
  disabled: !isReady,
3318
3370
  onClick: handlePlayPauseAction,
3319
3371
  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",
3320
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3372
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3321
3373
  }
3322
3374
  ),
3323
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3375
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3324
3376
  "button",
3325
3377
  {
3326
3378
  type: "button",
@@ -3329,14 +3381,14 @@ function PlayerControls() {
3329
3381
  disabled: !isReady || !hasNextTrack,
3330
3382
  onClick: playNextTrack,
3331
3383
  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",
3332
- children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3384
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3333
3385
  }
3334
3386
  )
3335
3387
  ] }),
3336
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
3337
- volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3338
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sr-only", children: "Volume" }),
3339
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3388
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
3389
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react15.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3390
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "sr-only", children: "Volume" }),
3391
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3340
3392
  "input",
3341
3393
  {
3342
3394
  "aria-label": "Volume",
@@ -3358,7 +3410,7 @@ function PlayerControls() {
3358
3410
  }
3359
3411
 
3360
3412
  // src/genre-tree/PlayerPanel.tsx
3361
- var import_jsx_runtime38 = require("react/jsx-runtime");
3413
+ var import_jsx_runtime42 = require("react/jsx-runtime");
3362
3414
  var PANEL_ID = "player-panel";
3363
3415
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3364
3416
  function PlayerPanel({
@@ -3370,8 +3422,8 @@ function PlayerPanel({
3370
3422
  renderArtistLink,
3371
3423
  renderSongLink
3372
3424
  }) {
3373
- const { playerTrackObject } = (0, import_player13.usePlayer)();
3374
- const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
3425
+ const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
3426
+ const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree26.useTrackListSidebarVisibility)();
3375
3427
  const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3376
3428
  const loadedTrackTitle = playerTrackObject?.track.title;
3377
3429
  (0, import_react19.useEffect)(() => {
@@ -3398,7 +3450,7 @@ function PlayerPanel({
3398
3450
  onClick: () => setIsExtendRequested(true),
3399
3451
  expanded: false
3400
3452
  } : null;
3401
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3453
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3402
3454
  "div",
3403
3455
  {
3404
3456
  id: PANEL_ID,
@@ -3411,8 +3463,8 @@ function PlayerPanel({
3411
3463
  ),
3412
3464
  style: isExtended ? { width: extendedWidth } : void 0,
3413
3465
  children: [
3414
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3415
- import_ui17.PanelEdgeTab,
3466
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3467
+ import_ui18.PanelEdgeTab,
3416
3468
  {
3417
3469
  side: "right",
3418
3470
  edge: tabEdge,
@@ -3421,7 +3473,7 @@ function PlayerPanel({
3421
3473
  className: "absolute right-full top-1/2 -translate-y-1/2"
3422
3474
  }
3423
3475
  ),
3424
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3476
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3425
3477
  "div",
3426
3478
  {
3427
3479
  className: (0, import_tailwind_merge.twMerge)(
@@ -3429,15 +3481,20 @@ function PlayerPanel({
3429
3481
  isExtended ? "flex-row" : "flex-col"
3430
3482
  ),
3431
3483
  children: [
3432
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3433
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3434
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "relative w-full shrink-0", children: [
3435
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3436
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
3484
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3485
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3486
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative w-full shrink-0", children: [
3487
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3488
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3489
+ playerTrackObject.loadError,
3490
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3491
+ void retryPlayback();
3492
+ }, children: "Retry" })
3493
+ ] })
3437
3494
  ] }),
3438
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(PlayerControls, {})
3495
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PlayerControls, {})
3439
3496
  ] }),
3440
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3497
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3441
3498
  TrackListSidebar,
3442
3499
  {
3443
3500
  layout: "inline",
@@ -3463,33 +3520,33 @@ function PlayerPanel({
3463
3520
  // src/genre-tree/Rating.tsx
3464
3521
  var import_uuid = require("uuid");
3465
3522
  var import_ai = require("react-icons/ai");
3466
- var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3467
- var import_jsx_runtime39 = require("react/jsx-runtime");
3523
+ var import_genre_tree27 = require("@behindthemusictree/app-kit/genre-tree");
3524
+ var import_jsx_runtime43 = require("react/jsx-runtime");
3468
3525
  function Rating({ rating, handleChange }) {
3469
3526
  const instanceId = (0, import_uuid.v4)();
3470
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3471
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3527
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3528
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3472
3529
  "input",
3473
3530
  {
3474
3531
  id: `star-${instanceId}-${i}`,
3475
3532
  className: "w-6 h-6 opacity-0",
3476
3533
  type: "radio",
3477
3534
  name: "rating",
3478
- value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
3535
+ value: i === 0 ? import_genre_tree27.FORM_RATING_NULL_VALUE : i,
3479
3536
  checked: rating === (i === 0 ? null : i),
3480
3537
  onChange: handleChange
3481
3538
  }
3482
3539
  ),
3483
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex", children: [
3484
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3540
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex", children: [
3541
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3485
3542
  import_ai.AiOutlineStar,
3486
3543
  {
3487
- className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree25.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3544
+ className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree27.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3488
3545
  }
3489
3546
  ),
3490
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "flex-grow w-8" }),
3547
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex-grow w-8" }),
3491
3548
  " "
3492
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3549
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3493
3550
  import_ai.AiFillStar,
3494
3551
  {
3495
3552
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3511,9 +3568,15 @@ function Rating({ rating, handleChange }) {
3511
3568
  GenreTreeWheelSkeleton,
3512
3569
  PlayerPanel,
3513
3570
  Rating,
3571
+ SEARCH_FAILED_STATUS,
3572
+ SearchBox,
3514
3573
  TrackItem,
3515
3574
  TrackListSidebar,
3516
3575
  TrackPositionPlayPause,
3517
- useGenrePlaylistPlayback
3576
+ searchStatus,
3577
+ useArtistSearchGroup,
3578
+ useGenrePlaylistPlayback,
3579
+ useGenreRemoteSearchGroup,
3580
+ useSongSearchGroup
3518
3581
  });
3519
3582
  //# sourceMappingURL=index.cjs.map