@behindthemusictree/app-kit-ui 33.1.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 focus-visible:ring-0`,
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,7 +1353,7 @@ 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)();
@@ -1287,11 +1362,11 @@ function GenrePlaylistTracks({
1287
1362
  const { fetch } = (0, import_transport3.useFetchWrapper)(getBackendBaseUrl);
1288
1363
  const playback = (0, import_transport3.usePlaybackContext)();
1289
1364
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
1290
- 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), ...playback.key ? [playback.key] : []],
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] : []],
1291
1366
  queryFn: async ({ pageParam }) => {
1292
1367
  if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1293
1368
  const response = await fetch(
1294
- import_genre_tree5.libraryEndpoints.reference.songs.list(),
1369
+ import_genre_tree8.libraryEndpoints.reference.songs.list(),
1295
1370
  true,
1296
1371
  false,
1297
1372
  {},
@@ -1322,35 +1397,35 @@ function GenrePlaylistTracks({
1322
1397
  const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1323
1398
  started.catch((error) => {
1324
1399
  console.error("Failed to play genre playlist:", error);
1325
- 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 }));
1326
1401
  });
1327
1402
  };
1328
- 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: [
1329
1404
  renderTracksToolbar?.(),
1330
- 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: [
1331
1406
  "Couldn't load ",
1332
1407
  trackNoun.plural,
1333
1408
  ".",
1334
1409
  " ",
1335
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1336
- ] }) : 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: [
1337
1412
  tracks.map((track, index) => {
1338
1413
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1339
1414
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1340
1415
  const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
1341
- const text = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: TEXT_CELL, children: [
1342
- /* @__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 }),
1343
- track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1344
- /* @__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 }),
1345
- 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 })
1346
1421
  ] });
1347
1422
  const leading = renderSongLeading?.(track, index);
1348
1423
  const actions = renderSongActions?.(track);
1349
- 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;
1350
1425
  if (isUnplayable) {
1351
- 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: [
1352
1427
  leading,
1353
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1428
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1354
1429
  "button",
1355
1430
  {
1356
1431
  type: "button",
@@ -1358,27 +1433,27 @@ function GenrePlaylistTracks({
1358
1433
  "aria-disabled": "true",
1359
1434
  "aria-label": label,
1360
1435
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1361
- 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 })
1362
1437
  }
1363
1438
  ),
1364
1439
  text,
1365
1440
  actions
1366
1441
  ] }, track.uuid);
1367
1442
  }
1368
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1443
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1369
1444
  "li",
1370
1445
  {
1371
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)]" : ""}`,
1372
1447
  children: [
1373
1448
  leading,
1374
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1449
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1375
1450
  "button",
1376
1451
  {
1377
1452
  type: "button",
1378
1453
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1379
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"}`,
1380
1455
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1381
- 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 })
1382
1457
  }
1383
1458
  ),
1384
1459
  text,
@@ -1388,7 +1463,7 @@ function GenrePlaylistTracks({
1388
1463
  track.uuid
1389
1464
  );
1390
1465
  }),
1391
- 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" })
1392
1467
  ] })
1393
1468
  ] }) });
1394
1469
  }
@@ -1396,7 +1471,7 @@ function GenrePlaylistTracks({
1396
1471
  // src/genre-tree/GenreDetailExtras.tsx
1397
1472
  var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1398
1473
  var import_si2 = require("react-icons/si");
1399
- var import_jsx_runtime19 = require("react/jsx-runtime");
1474
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1400
1475
  function GenreDetailExtras({
1401
1476
  genreUuid,
1402
1477
  genrePlaylist,
@@ -1413,24 +1488,24 @@ function GenreDetailExtras({
1413
1488
  ...trackSlots
1414
1489
  }) {
1415
1490
  const trackNoun = (0, import_track_noun2.useTrackNoun)();
1416
- const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
1491
+ const { data: overview, isPending } = (0, import_genre_tree12.useFetchGenreOverview)(
1417
1492
  genreUuid,
1418
1493
  scope,
1419
1494
  getBackendBaseUrl,
1420
1495
  criteriaOverviewSchema
1421
1496
  );
1422
1497
  if (isPending) {
1423
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1424
- /* @__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" }) }) }),
1425
1500
  family
1426
1501
  ] });
1427
1502
  }
1428
- 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 });
1429
1504
  const { summary, summarySourceUrl, essentialSongs } = overview;
1430
1505
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1431
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1432
- (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: [
1433
- 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)(
1434
1509
  "a",
1435
1510
  {
1436
1511
  href: summarySourceUrl,
@@ -1438,12 +1513,12 @@ function GenreDetailExtras({
1438
1513
  rel: "noopener noreferrer",
1439
1514
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1440
1515
  children: [
1441
- /* @__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" }),
1442
1517
  "Wikipedia"
1443
1518
  ]
1444
1519
  }
1445
1520
  ),
1446
- wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1521
+ wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1447
1522
  "a",
1448
1523
  {
1449
1524
  href: wikidataUrl,
@@ -1451,17 +1526,17 @@ function GenreDetailExtras({
1451
1526
  rel: "noopener noreferrer",
1452
1527
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1453
1528
  children: [
1454
- /* @__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" }),
1455
1530
  "Wikidata"
1456
1531
  ]
1457
1532
  }
1458
1533
  )
1459
1534
  ] }) }),
1460
- /* @__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) }),
1461
1536
  family,
1462
1537
  renderGenreDetailExtras?.(overview),
1463
- (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" }) }),
1464
- /* @__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)(
1465
1540
  GenrePlaylistTracks,
1466
1541
  {
1467
1542
  genreUuid,
@@ -1477,7 +1552,7 @@ function GenreDetailExtras({
1477
1552
  }
1478
1553
 
1479
1554
  // src/genre-tree/GenreInfoPanelBody.tsx
1480
- var import_jsx_runtime20 = require("react/jsx-runtime");
1555
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1481
1556
  var import_react12 = require("react");
1482
1557
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1483
1558
  const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
@@ -1491,7 +1566,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1491
1566
  console.error("Failed to load genre playlist tracks:", error);
1492
1567
  });
1493
1568
  };
1494
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1569
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1495
1570
  GenreActionBand,
1496
1571
  {
1497
1572
  ...bandProps,
@@ -1512,9 +1587,9 @@ function GenreInfoPanelBody({
1512
1587
  band,
1513
1588
  extras
1514
1589
  }) {
1515
- 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: [
1516
1591
  toolbar,
1517
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1592
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1518
1593
  /* @__PURE__ */ (0, import_react12.createElement)(
1519
1594
  GenreDetailExtras,
1520
1595
  {
@@ -1533,8 +1608,8 @@ function GenreInfoPanelBody({
1533
1608
 
1534
1609
  // src/genre-tree/ChangeParentPopup.tsx
1535
1610
  var import_react13 = require("react");
1536
- var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1537
- 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");
1538
1613
  function ChangeParentPopup({
1539
1614
  genrePlaylist,
1540
1615
  genrePlaylists,
@@ -1543,9 +1618,9 @@ function ChangeParentPopup({
1543
1618
  onClose,
1544
1619
  onParentChanged
1545
1620
  }) {
1546
- const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
1621
+ const { mutate, isPending, formErrors } = (0, import_genre_tree13.useUpdateGenre)(scope, getBackendBaseUrl);
1547
1622
  const candidates = (0, import_react13.useMemo)(
1548
- () => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1623
+ () => (0, import_genre_tree13.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1549
1624
  [genrePlaylists, genrePlaylist.uuid]
1550
1625
  );
1551
1626
  const handleSelect = (newParent) => {
@@ -1569,12 +1644,12 @@ function ChangeParentPopup({
1569
1644
  window.addEventListener("keydown", handleKeyDown);
1570
1645
  return () => window.removeEventListener("keydown", handleKeyDown);
1571
1646
  }, [onClose]);
1572
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1573
- /* @__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: [
1574
1649
  "Current parent: ",
1575
1650
  genrePlaylist.parent?.name ?? "none"
1576
1651
  ] }),
1577
- /* @__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)(
1578
1653
  GenreSearch,
1579
1654
  {
1580
1655
  genrePlaylists: candidates,
@@ -1582,22 +1657,22 @@ function ChangeParentPopup({
1582
1657
  placeholder: "Search a new parent\u2026"
1583
1658
  }
1584
1659
  ) }),
1585
- 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(" ") })
1586
1661
  ] });
1587
1662
  }
1588
1663
 
1589
1664
  // src/genre-tree/artist/useArtistPanel.tsx
1590
- var import_ui11 = require("@behindthemusictree/ui");
1591
- 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");
1592
1667
 
1593
1668
  // src/genre-tree/artist/ArtistHeader.tsx
1594
- var import_ui7 = require("@behindthemusictree/ui");
1669
+ var import_ui8 = require("@behindthemusictree/ui");
1595
1670
 
1596
1671
  // src/genre-tree/artist/ArtistLinks.tsx
1597
1672
  var import_lucide_react8 = require("lucide-react");
1598
1673
  var import_si3 = require("react-icons/si");
1599
- var import_ui6 = require("@behindthemusictree/ui");
1600
- var import_jsx_runtime22 = require("react/jsx-runtime");
1674
+ var import_ui7 = require("@behindthemusictree/ui");
1675
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1601
1676
  var LINK_KINDS = {
1602
1677
  official: { label: "Official website", Icon: import_lucide_react8.Globe },
1603
1678
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
@@ -1634,7 +1709,7 @@ function artistLinks(artist) {
1634
1709
  return links;
1635
1710
  }
1636
1711
  function ArtistLinks({ links }) {
1637
- 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)) });
1638
1713
  }
1639
1714
 
1640
1715
  // src/genre-tree/artist/formatYears.ts
@@ -1645,7 +1720,7 @@ function formatYears(beginYear, endYear, ended) {
1645
1720
  }
1646
1721
 
1647
1722
  // src/genre-tree/artist/ArtistHeader.tsx
1648
- var import_jsx_runtime23 = require("react/jsx-runtime");
1723
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1649
1724
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1650
1725
  function ArtistHeader({ artist }) {
1651
1726
  const fields = [];
@@ -1655,35 +1730,35 @@ function ArtistHeader({ artist }) {
1655
1730
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1656
1731
  if (years) fields.push({ label: "Active", value: years });
1657
1732
  const links = artistLinks(artist);
1658
- 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 }) });
1659
1734
  if (fields.length === 0) return null;
1660
- 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 });
1661
1736
  }
1662
1737
 
1663
1738
  // src/genre-tree/artist/ArtistPhoto.tsx
1664
- var import_jsx_runtime24 = require("react/jsx-runtime");
1739
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1665
1740
  function ExternalLink({ href, children }) {
1666
- 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 });
1667
1742
  }
1668
1743
  function ArtistPhoto({ image, name }) {
1669
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1670
- /* @__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" }),
1671
- /* @__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: [
1672
1747
  "Photo: ",
1673
- /* @__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" }),
1674
1749
  ",",
1675
1750
  " ",
1676
- 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
1677
1752
  ] })
1678
1753
  ] });
1679
1754
  }
1680
1755
 
1681
1756
  // src/genre-tree/artist/ArtistMembers.tsx
1682
- var import_ui8 = require("@behindthemusictree/ui");
1683
- var import_jsx_runtime25 = require("react/jsx-runtime");
1757
+ var import_ui9 = require("@behindthemusictree/ui");
1758
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1684
1759
  function MembershipList({ title, memberships, renderArtistLink }) {
1685
1760
  if (memberships.length === 0) return null;
1686
- 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) => {
1687
1762
  const details = [
1688
1763
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1689
1764
  ...membership.roles
@@ -1691,9 +1766,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1691
1766
  const detailsText = details.filter(Boolean).join(" \xB7 ");
1692
1767
  return (
1693
1768
  // A member can have several periods in the same group.
1694
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1695
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1696
- 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: [
1697
1772
  " \u2014 ",
1698
1773
  detailsText
1699
1774
  ] })
@@ -1704,9 +1779,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1704
1779
  function ArtistMembers({ title, memberships, renderArtistLink }) {
1705
1780
  if (memberships.length === 0) return null;
1706
1781
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1707
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ui8.PanelSection, { title, children: [
1708
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1709
- /* @__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)(
1710
1785
  MembershipList,
1711
1786
  {
1712
1787
  title: "Past",
@@ -1718,8 +1793,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1718
1793
  }
1719
1794
 
1720
1795
  // src/genre-tree/artist/ArtistDiscography.tsx
1721
- var import_ui9 = require("@behindthemusictree/ui");
1722
- var import_jsx_runtime26 = require("react/jsx-runtime");
1796
+ var import_ui10 = require("@behindthemusictree/ui");
1797
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1723
1798
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1724
1799
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1725
1800
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1739,12 +1814,12 @@ function byYear(a, b) {
1739
1814
  function ArtistDiscography({ albums }) {
1740
1815
  if (albums.length === 0) return null;
1741
1816
  const sorted = [...albums].sort(byYear);
1742
- 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) => {
1743
1818
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1744
1819
  if (groupAlbums.length === 0) return null;
1745
- 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: [
1746
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1747
- /* @__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 })
1748
1823
  ] }, album.uuid)) }) }, group);
1749
1824
  }) });
1750
1825
  }
@@ -1753,20 +1828,20 @@ function ArtistDiscography({ albums }) {
1753
1828
  var import_react14 = require("react");
1754
1829
  var import_react_query2 = require("@tanstack/react-query");
1755
1830
  var import_lucide_react9 = require("lucide-react");
1756
- var import_ui10 = require("@behindthemusictree/ui");
1831
+ var import_ui11 = require("@behindthemusictree/ui");
1757
1832
  var import_player10 = require("@behindthemusictree/app-kit/player");
1758
- var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
1833
+ var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
1759
1834
  var import_transport7 = require("@behindthemusictree/app-kit/transport");
1760
1835
 
1761
1836
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1762
1837
  var import_player9 = require("@behindthemusictree/app-kit/player");
1763
- var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1838
+ var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
1764
1839
 
1765
1840
  // src/genre-tree/TrackPositionPlayPause.tsx
1766
1841
  var import_fa3 = require("react-icons/fa");
1767
1842
  var import_player7 = require("@behindthemusictree/app-kit/player");
1768
1843
  var import_player8 = require("@behindthemusictree/app-kit/player");
1769
- var import_jsx_runtime27 = require("react/jsx-runtime");
1844
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1770
1845
  function TrackPositionPlayPause({
1771
1846
  position,
1772
1847
  uuid,
@@ -1780,14 +1855,14 @@ function TrackPositionPlayPause({
1780
1855
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
1781
1856
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
1782
1857
  const isCurrentTrackActive = isPlaying || isPaused;
1783
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
1858
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1784
1859
  "div",
1785
1860
  {
1786
1861
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
1787
1862
  style: { minWidth: "4rem", maxWidth: "4rem" },
1788
1863
  onClick: handlePlayPauseClick,
1789
1864
  children: [
1790
- /* @__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)(
1791
1866
  "span",
1792
1867
  {
1793
1868
  className: "track-position-play-pause__bars",
@@ -1795,18 +1870,18 @@ function TrackPositionPlayPause({
1795
1870
  role: "img",
1796
1871
  "aria-label": isPlaying ? "Playing" : "Paused",
1797
1872
  children: [
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" }),
1800
- /* @__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" })
1801
1876
  ]
1802
1877
  }
1803
1878
  ) : position }),
1804
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1879
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1805
1880
  "div",
1806
1881
  {
1807
1882
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1808
1883
  "aria-hidden": "true",
1809
- 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, {})
1810
1885
  }
1811
1886
  )
1812
1887
  ]
@@ -1815,9 +1890,9 @@ function TrackPositionPlayPause({
1815
1890
  }
1816
1891
 
1817
1892
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1818
- var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
1819
- var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
1820
- 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");
1821
1896
  function TrackItem({
1822
1897
  track,
1823
1898
  position,
@@ -1827,11 +1902,11 @@ function TrackItem({
1827
1902
  renderSongLink
1828
1903
  }) {
1829
1904
  const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
1830
- const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
1905
+ const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree14.useTrackList)();
1831
1906
  const scope = trackList?.origin?.scope ?? null;
1832
1907
  const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
1833
1908
  const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
1834
- 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;
1835
1910
  const handlePlayPauseClick = (event) => {
1836
1911
  event.stopPropagation();
1837
1912
  if (isCurrentTrack) {
@@ -1841,12 +1916,12 @@ function TrackItem({
1841
1916
  loadTrackForPlayer(track.uuid);
1842
1917
  }
1843
1918
  };
1844
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1919
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1845
1920
  "div",
1846
1921
  {
1847
1922
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
1848
1923
  children: [
1849
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1924
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1850
1925
  "div",
1851
1926
  {
1852
1927
  className: "flex items-center justify-center text-lg w-16",
@@ -1854,7 +1929,7 @@ function TrackItem({
1854
1929
  "aria-disabled": "true",
1855
1930
  children: position
1856
1931
  }
1857
- ) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1932
+ ) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1858
1933
  TrackPositionPlayPause,
1859
1934
  {
1860
1935
  position,
@@ -1863,12 +1938,12 @@ function TrackItem({
1863
1938
  isSelected: selectedTrack?.uuid === track.uuid
1864
1939
  }
1865
1940
  ),
1866
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1867
- /* @__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 }) }),
1868
- 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 }) }) : ""
1869
1944
  ] }),
1870
- /* @__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 }) : "" }),
1871
- 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)(
1872
1947
  "div",
1873
1948
  {
1874
1949
  className: "duration flex text-base w-16 items-center justify-center",
@@ -1876,15 +1951,15 @@ function TrackItem({
1876
1951
  children: renderDuration(track)
1877
1952
  }
1878
1953
  ),
1879
- 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) })
1880
1955
  ]
1881
1956
  }
1882
1957
  );
1883
1958
  }
1884
1959
 
1885
1960
  // src/genre-tree/artist/ArtistSongs.tsx
1886
- var import_jsx_runtime29 = require("react/jsx-runtime");
1887
- 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);
1888
1963
  var SORTS = [
1889
1964
  { ordering: "title", label: "Title" },
1890
1965
  { ordering: "-albumYear", label: "Year" }
@@ -1893,11 +1968,11 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1893
1968
  const [page, setPage] = (0, import_react14.useState)(1);
1894
1969
  const [ordering, setOrdering] = (0, import_react14.useState)("title");
1895
1970
  const { fetch } = (0, import_transport7.useFetchWrapper)(getBackendBaseUrl);
1896
- const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
1971
+ const { playNewTrackListFromArtist } = (0, import_genre_tree17.useTrackList)();
1897
1972
  const { playerTrackObject } = (0, import_player10.usePlayer)();
1898
1973
  const { data, isPending, isError } = (0, import_transport7.useQueryWithParse)({
1899
1974
  queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
1900
- 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, {
1901
1976
  artist: artist.uuid,
1902
1977
  ordering,
1903
1978
  page
@@ -1912,25 +1987,25 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1912
1987
  });
1913
1988
  const handleRowClickCapture = (event, song) => {
1914
1989
  if (!(event.target instanceof Element) || !event.target.closest(".track-position-play-pause")) return;
1915
- 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;
1916
1991
  event.stopPropagation();
1917
1992
  void play(song.uuid);
1918
1993
  };
1919
- 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: [
1920
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1921
- /* @__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)(
1922
1997
  "button",
1923
1998
  {
1924
1999
  type: "button",
1925
2000
  onClick: () => void play(),
1926
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",
1927
2002
  children: [
1928
- /* @__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" }),
1929
2004
  "Play all"
1930
2005
  ]
1931
2006
  }
1932
2007
  ),
1933
- /* @__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)(
1934
2009
  "button",
1935
2010
  {
1936
2011
  type: "button",
@@ -1945,7 +2020,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1945
2020
  sort.ordering
1946
2021
  )) })
1947
2022
  ] }),
1948
- /* @__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)(
1949
2024
  TrackItem,
1950
2025
  {
1951
2026
  track: song,
@@ -1954,12 +2029,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1954
2029
  renderSongLink
1955
2030
  }
1956
2031
  ) }, song.uuid)) }),
1957
- /* @__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 })
1958
2033
  ] }) });
1959
2034
  }
1960
2035
 
1961
2036
  // src/genre-tree/artist/useArtistPanel.tsx
1962
- var import_jsx_runtime30 = require("react/jsx-runtime");
2037
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1963
2038
  function useArtistPanel({
1964
2039
  uuid,
1965
2040
  scope,
@@ -1971,7 +2046,7 @@ function useArtistPanel({
1971
2046
  onExpandedChange,
1972
2047
  onClose
1973
2048
  }) {
1974
- const result = (0, import_genre_tree15.useArtist)(uuid, getBackendBaseUrl);
2049
+ const result = (0, import_genre_tree18.useArtist)(uuid, getBackendBaseUrl);
1975
2050
  const panel = { expanded, onExpandedChange, onClose };
1976
2051
  switch (result.status) {
1977
2052
  case "idle":
@@ -1980,26 +2055,26 @@ function useArtistPanel({
1980
2055
  return {
1981
2056
  ...panel,
1982
2057
  title: "Artist",
1983
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
1984
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
1985
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-32" }),
1986
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
1987
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-24" }),
1988
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16 mt-2.5" }),
1989
- /* @__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" })
1990
2065
  ] })
1991
2066
  };
1992
2067
  case "not-found":
1993
2068
  return {
1994
2069
  ...panel,
1995
2070
  title: "Artist not found",
1996
- 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." })
1997
2072
  };
1998
2073
  case "error":
1999
2074
  return {
2000
2075
  ...panel,
2001
2076
  title: "Artist",
2002
- 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." })
2003
2078
  };
2004
2079
  case "success": {
2005
2080
  const { artist } = result;
@@ -2008,13 +2083,13 @@ function useArtistPanel({
2008
2083
  return {
2009
2084
  ...panel,
2010
2085
  title: artist.name,
2011
- content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
2012
- 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 }) }) }),
2013
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2014
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistHeader, { artist }),
2015
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2016
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2017
- 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)(
2018
2093
  ArtistSongs,
2019
2094
  {
2020
2095
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2024,56 +2099,56 @@ function useArtistPanel({
2024
2099
  }
2025
2100
  )
2026
2101
  ] }),
2027
- 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
2028
2103
  };
2029
2104
  }
2030
2105
  }
2031
2106
  }
2032
2107
 
2033
2108
  // src/genre-tree/song/useSongPanel.tsx
2034
- var import_ui13 = require("@behindthemusictree/ui");
2035
- 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");
2036
2111
 
2037
2112
  // src/genre-tree/song/SongPanelContent.tsx
2038
2113
  var import_react15 = require("react");
2039
2114
  var import_lucide_react10 = require("lucide-react");
2040
- var import_ui12 = require("@behindthemusictree/ui");
2115
+ var import_ui13 = require("@behindthemusictree/ui");
2041
2116
  var import_player11 = require("@behindthemusictree/app-kit/player");
2042
- var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
2043
- 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");
2044
2119
  var numberFormat = new Intl.NumberFormat("en");
2045
2120
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2046
2121
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
2047
2122
  function SongHistory({ uuid, getBackendBaseUrl }) {
2048
2123
  const [open, setOpen] = (0, import_react15.useState)(false);
2049
- const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
2124
+ const history = (0, import_genre_tree19.useSongHistory)(uuid, getBackendBaseUrl, open);
2050
2125
  let body = null;
2051
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.Skeleton, { className: "h-10", "aria-busy": "true" });
2052
- 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." });
2053
2128
  else if (history.status === "success")
2054
- 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: [
2055
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium", children: entry.action }),
2056
- 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: [
2057
2132
  " ",
2058
2133
  "\u2014 ",
2059
2134
  entry.oldValue ?? "none",
2060
2135
  " \u2192 ",
2061
2136
  entry.newValue ?? "none"
2062
2137
  ] }) : null,
2063
- /* @__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: [
2064
2139
  entry.actorPseudo ?? "Anonymous",
2065
2140
  " \xB7 ",
2066
2141
  dateFormat.format(new Date(entry.createdOn))
2067
2142
  ] })
2068
2143
  ] }, entry.uuid)) });
2069
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2144
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2070
2145
  "details",
2071
2146
  {
2072
2147
  onToggle: (event) => setOpen(event.currentTarget.open),
2073
2148
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
2074
2149
  children: [
2075
- /* @__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" }),
2076
- /* @__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 })
2077
2152
  ]
2078
2153
  }
2079
2154
  );
@@ -2087,25 +2162,25 @@ function SongPanelContent({
2087
2162
  genrePath,
2088
2163
  onSelectGenre
2089
2164
  }) {
2090
- const { playNewTrackListFromTrackUuid } = (0, import_genre_tree16.useTrackList)();
2165
+ const { playNewTrackListFromTrackUuid } = (0, import_genre_tree19.useTrackList)();
2091
2166
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
2092
- const unplayableReason = (0, import_genre_tree16.songUnplayableReason)(song);
2167
+ const unplayableReason = (0, import_genre_tree19.songUnplayableReason)(song);
2093
2168
  const isCurrent = playerTrackObject?.track.id === song.uuid;
2094
2169
  const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
2095
2170
  const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
2096
- const musicBrainzUrl = (0, import_genre_tree16.musicBrainzRecordingUrl)(song);
2171
+ const musicBrainzUrl = (0, import_genre_tree19.musicBrainzRecordingUrl)(song);
2097
2172
  const fields = [];
2098
2173
  if (song.genre) {
2099
2174
  const path = genrePath.length > 0 ? genrePath : null;
2100
2175
  fields.push({
2101
2176
  label: "Genre",
2102
- 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: [
2103
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2104
- /* @__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 })
2105
2180
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2106
2181
  });
2107
2182
  } else {
2108
- 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" }) });
2109
2184
  }
2110
2185
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2111
2186
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2115,11 +2190,11 @@ function SongPanelContent({
2115
2190
  const links = [];
2116
2191
  if (musicBrainzUrl)
2117
2192
  links.push(
2118
- /* @__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")
2119
2194
  );
2120
2195
  if (song.youtubeVideo)
2121
2196
  links.push(
2122
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2197
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2123
2198
  "a",
2124
2199
  {
2125
2200
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2134,33 +2209,33 @@ function SongPanelContent({
2134
2209
  if (links.length > 0)
2135
2210
  fields.push({
2136
2211
  label: "Links",
2137
- 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 })
2138
2213
  });
2139
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
2140
- /* @__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: [
2141
- 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)(
2142
2217
  "button",
2143
2218
  {
2144
2219
  type: "button",
2145
2220
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2146
2221
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2147
2222
  children: [
2148
- /* @__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" }) }),
2149
2224
  isPlaying ? "Pause" : "Play"
2150
2225
  ]
2151
2226
  }
2152
2227
  ),
2153
- 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 })
2154
2229
  ] }) }),
2155
- 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 }) }),
2156
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.FieldList, { items: fields }),
2157
- 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)) }) }),
2158
- /* @__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 })
2159
2234
  ] });
2160
2235
  }
2161
2236
 
2162
2237
  // src/genre-tree/song/useSongPanel.tsx
2163
- var import_jsx_runtime32 = require("react/jsx-runtime");
2238
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2164
2239
  function useSongPanel({
2165
2240
  uuid,
2166
2241
  scope,
@@ -2173,7 +2248,7 @@ function useSongPanel({
2173
2248
  onExpandedChange,
2174
2249
  onClose
2175
2250
  }) {
2176
- const result = (0, import_genre_tree17.useSong)(uuid, getBackendBaseUrl);
2251
+ const result = (0, import_genre_tree20.useSong)(uuid, getBackendBaseUrl);
2177
2252
  const panel = { expanded, onExpandedChange, onClose };
2178
2253
  switch (result.status) {
2179
2254
  case "idle":
@@ -2182,24 +2257,24 @@ function useSongPanel({
2182
2257
  return {
2183
2258
  ...panel,
2184
2259
  title: "Song",
2185
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2186
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2187
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
2188
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
2189
- /* @__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" })
2190
2265
  ] })
2191
2266
  };
2192
2267
  case "not-found":
2193
2268
  return {
2194
2269
  ...panel,
2195
2270
  title: "Song not found",
2196
- 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." })
2197
2272
  };
2198
2273
  case "error":
2199
2274
  return {
2200
2275
  ...panel,
2201
2276
  title: "Song",
2202
- 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." })
2203
2278
  };
2204
2279
  case "success": {
2205
2280
  const { song } = result;
@@ -2207,7 +2282,7 @@ function useSongPanel({
2207
2282
  return {
2208
2283
  ...panel,
2209
2284
  title: song.title,
2210
- content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2285
+ content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2211
2286
  SongPanelContent,
2212
2287
  {
2213
2288
  song,
@@ -2226,29 +2301,29 @@ function useSongPanel({
2226
2301
 
2227
2302
  // src/genre-tree/SizeMetricToggle.tsx
2228
2303
  var import_md = require("react-icons/md");
2229
- var import_ui14 = require("@behindthemusictree/ui");
2230
- var import_jsx_runtime33 = require("react/jsx-runtime");
2304
+ var import_ui15 = require("@behindthemusictree/ui");
2305
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2231
2306
  function SizeMetricToggle({
2232
2307
  value,
2233
2308
  onChange
2234
2309
  }) {
2235
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2236
- import_ui14.SegmentedControl,
2310
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2311
+ import_ui15.SegmentedControl,
2237
2312
  {
2238
2313
  variant: "floating",
2239
2314
  "aria-label": "Size nodes by",
2240
2315
  value,
2241
2316
  onChange,
2242
2317
  options: [
2243
- { 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" },
2244
- { 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" }
2245
2320
  ]
2246
2321
  }
2247
2322
  );
2248
2323
  }
2249
2324
 
2250
2325
  // src/genre-tree/GenreTreeView.tsx
2251
- var import_jsx_runtime34 = require("react/jsx-runtime");
2326
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2252
2327
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2253
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)]";
2254
2329
  var noop = () => {
@@ -2282,6 +2357,7 @@ function GenreTreeView({
2282
2357
  artistExpanded: controlledArtistExpanded,
2283
2358
  onArtistExpandedChange,
2284
2359
  getShareUrl,
2360
+ extraSearchGroups = [],
2285
2361
  artistSearch = true,
2286
2362
  onArtistSelect,
2287
2363
  artistSearchBaseUrl,
@@ -2347,7 +2423,7 @@ function GenreTreeView({
2347
2423
  allowWheelRotation: internalAllowWheelRotation,
2348
2424
  toggleWheelRotation,
2349
2425
  onNodeHover: handleNodeHover
2350
- } = (0, import_genre_tree19.useGenreTreeViewState)({
2426
+ } = (0, import_genre_tree21.useGenreTreeViewState)({
2351
2427
  scope,
2352
2428
  getBackendBaseUrl,
2353
2429
  viewMode: controlledViewMode,
@@ -2366,12 +2442,12 @@ function GenreTreeView({
2366
2442
  restoreRootUuid = selectedGenrePlaylist.root.uuid;
2367
2443
  setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
2368
2444
  }
2369
- const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree19.genreUpdateMutationKey }) > 0;
2370
- 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);
2371
2447
  const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
2372
2448
  const isArtistFiltered = artistUuid !== null;
2373
2449
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2374
- const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2450
+ const artistResult = (0, import_genre_tree21.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2375
2451
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2376
2452
  const filterArtist = (0, import_react16.useMemo)(
2377
2453
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
@@ -2391,7 +2467,7 @@ function GenreTreeView({
2391
2467
  const openSongRef = (0, import_react16.useRef)(openSong);
2392
2468
  openSongRef.current = openSong;
2393
2469
  const resolvedRenderSongLink = (0, import_react16.useCallback)(
2394
- (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 }),
2395
2471
  [renderSongLink]
2396
2472
  );
2397
2473
  const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
@@ -2403,7 +2479,7 @@ function GenreTreeView({
2403
2479
  scope,
2404
2480
  getBackendBaseUrl: artistBaseUrl,
2405
2481
  getShareUrl: getShareUrlRef.current,
2406
- 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 })),
2407
2483
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2408
2484
  onExpandedChange: (on) => {
2409
2485
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2412,7 +2488,7 @@ function GenreTreeView({
2412
2488
  renderSongLink: resolvedRenderSongLink,
2413
2489
  onClose: () => changeArtist(null)
2414
2490
  });
2415
- 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 }));
2416
2492
  const getGenrePath = (genre) => {
2417
2493
  const path = [];
2418
2494
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2468,7 +2544,7 @@ function GenreTreeView({
2468
2544
  []
2469
2545
  );
2470
2546
  const panelRef = (0, import_react16.useRef)(null);
2471
- const obscuredInset = (0, import_ui15.useObscuredInset)(panelRef);
2547
+ const obscuredInset = (0, import_ui16.useObscuredInset)(panelRef);
2472
2548
  const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2473
2549
  const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2474
2550
  const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
@@ -2491,52 +2567,27 @@ function GenreTreeView({
2491
2567
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2492
2568
  const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
2493
2569
  const [searchText, setSearchText] = (0, import_react16.useState)("");
2494
- const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
2495
- const artistGroup = artistSearch ? {
2496
- key: "artists",
2497
- label: "Artists",
2498
- items: artistSearchResult.artists.map((artist) => ({
2499
- key: artist.uuid,
2500
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2501
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate font-medium", children: [
2502
- artist.name,
2503
- artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2504
- " \xB7 ",
2505
- artist.disambiguation
2506
- ] })
2507
- ] }),
2508
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2509
- "also known as ",
2510
- artist.matchedAlias
2511
- ] })
2512
- ] }),
2513
- onSelect: () => {
2514
- openArtist(artist.uuid);
2515
- onArtistSelect?.(artist);
2516
- }
2517
- })),
2518
- status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
2519
- loading: artistSearchResult.pending
2520
- } : void 0;
2521
- const songSearchResult = (0, import_genre_tree18.useSongSearch)(songBaseUrl, songSearch ? searchText : "");
2522
- const songGroup = songSearch ? {
2523
- key: "songs",
2524
- label: "Songs",
2525
- items: songSearchResult.songs.map((song) => ({
2526
- key: song.uuid,
2527
- content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
2528
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "block truncate font-medium", children: song.title }),
2529
- /* @__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 ") })
2530
- ] }),
2531
- onSelect: () => {
2532
- openSong(song.uuid);
2533
- onSongSelect?.(song);
2534
- }
2535
- })),
2536
- status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
2537
- loading: songSearchResult.pending
2538
- } : void 0;
2539
- 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
+ );
2540
2591
  const handleSelectedNodeChange = (node) => {
2541
2592
  if (node !== null && songUuid !== null) changeSong(null);
2542
2593
  selectNode(node);
@@ -2560,7 +2611,7 @@ function GenreTreeView({
2560
2611
  };
2561
2612
  const renderGenreBody = (relation) => {
2562
2613
  const { node } = relation;
2563
- const family = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2614
+ const family = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2564
2615
  import_genre_tree_view4.GenreNodeFamily,
2565
2616
  {
2566
2617
  node,
@@ -2571,10 +2622,10 @@ function GenreTreeView({
2571
2622
  onSelectNode: selectGenreFromPanel
2572
2623
  }
2573
2624
  );
2574
- 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 });
2575
2626
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2576
2627
  const criteria = genrePlaylist?.criteria;
2577
- 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: [
2578
2629
  family,
2579
2630
  ancestors
2580
2631
  ] }) };
@@ -2594,11 +2645,11 @@ function GenreTreeView({
2594
2645
  (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2595
2646
  ) : void 0;
2596
2647
  return {
2597
- primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2598
- /* @__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)(
2599
2650
  GenreInfoPanelBody,
2600
2651
  {
2601
- 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: [
2602
2653
  family,
2603
2654
  ancestors
2604
2655
  ] }),
@@ -2616,7 +2667,7 @@ function GenreTreeView({
2616
2667
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2617
2668
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2618
2669
  onChangeParent: readOnly ? void 0 : () => showPopup(
2619
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2670
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2620
2671
  ChangeParentPopup,
2621
2672
  {
2622
2673
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2691,19 +2742,19 @@ function GenreTreeView({
2691
2742
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2692
2743
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2693
2744
  } : void 0;
2694
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2695
- import_ui15.IconButton,
2745
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2746
+ import_ui16.IconButton,
2696
2747
  {
2697
2748
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2698
2749
  style: activePanel.headerAccent && { color: "inherit" },
2699
2750
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2700
- 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" })
2701
2752
  }
2702
2753
  ) : null;
2703
2754
  const renderArtistFilter = () => {
2704
2755
  if (isLoading || !filterArtist) return null;
2705
- 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: [
2706
- /* @__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)(
2707
2758
  "button",
2708
2759
  {
2709
2760
  type: "button",
@@ -2715,7 +2766,7 @@ function GenreTreeView({
2715
2766
  children: filterArtist.name
2716
2767
  }
2717
2768
  ),
2718
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2769
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2719
2770
  "button",
2720
2771
  {
2721
2772
  type: "button",
@@ -2727,8 +2778,8 @@ function GenreTreeView({
2727
2778
  )
2728
2779
  ] });
2729
2780
  };
2730
- const actions = /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2731
- !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)(
2732
2783
  GenreSearch,
2733
2784
  {
2734
2785
  genrePlaylists: visibleGenrePlaylists,
@@ -2736,14 +2787,14 @@ function GenreTreeView({
2736
2787
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2737
2788
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2738
2789
  onPrevious: handlePrevious,
2739
- onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
2790
+ onQueryChange: artistSearch || songSearch || extraSearchGroups.length > 0 ? setSearchText : void 0,
2740
2791
  extraGroups
2741
2792
  }
2742
2793
  ),
2743
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2744
- /* @__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: [
2745
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2746
- 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,
2747
2798
  {
2748
2799
  "aria-label": "Tree view mode",
2749
2800
  value: viewMode,
@@ -2756,14 +2807,14 @@ function GenreTreeView({
2756
2807
  ]
2757
2808
  }
2758
2809
  ),
2759
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2810
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2760
2811
  "div",
2761
2812
  {
2762
2813
  className: "flex items-center gap-1",
2763
2814
  role: "group",
2764
2815
  "aria-label": "Tree display options",
2765
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2766
- import_ui15.Button,
2816
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2817
+ import_ui16.Button,
2767
2818
  {
2768
2819
  variant: allowWheelRotation ? "default" : "outline",
2769
2820
  size: "sm",
@@ -2773,8 +2824,8 @@ function GenreTreeView({
2773
2824
  )
2774
2825
  }
2775
2826
  ),
2776
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2777
- import_ui15.IconTextButton,
2827
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2828
+ import_ui16.IconTextButton,
2778
2829
  {
2779
2830
  icon: import_lucide_react11.Plus,
2780
2831
  text: "Add root",
@@ -2783,34 +2834,34 @@ function GenreTreeView({
2783
2834
  )
2784
2835
  ] })
2785
2836
  ] });
2786
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2837
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2787
2838
  "div",
2788
2839
  {
2789
2840
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2790
2841
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2791
2842
  children: [
2792
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2843
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2793
2844
  "div",
2794
2845
  {
2795
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"}`,
2796
2847
  children: actions
2797
2848
  }
2798
2849
  ),
2799
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2850
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2800
2851
  "div",
2801
2852
  {
2802
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"}`,
2803
2854
  children: renderArtistFilter()
2804
2855
  }
2805
2856
  ),
2806
- /* @__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: [
2807
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2808
- 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" ? (
2809
2860
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2810
2861
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2811
2862
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2812
2863
  // The panel's own Zoom in button is hidden: the search bar carries it.
2813
- /* @__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)(
2814
2865
  GenrePlaylistTreeWheel,
2815
2866
  {
2816
2867
  scope,
@@ -2827,9 +2878,9 @@ function GenreTreeView({
2827
2878
  allowWheelRotation,
2828
2879
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2829
2880
  busy,
2830
- 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: [
2831
2882
  floatingControlsLeading,
2832
- 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 })
2833
2884
  ] }) : void 0,
2834
2885
  defaultViewport,
2835
2886
  viewportRestore,
@@ -2838,7 +2889,7 @@ function GenreTreeView({
2838
2889
  ) }) })
2839
2890
  ) : viewMode === "pop-core" ? (
2840
2891
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2841
- /* @__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)(
2842
2893
  GenrePlaylistTreeWheelRadialPopCore,
2843
2894
  {
2844
2895
  scope,
@@ -2855,9 +2906,9 @@ function GenreTreeView({
2855
2906
  allowWheelRotation,
2856
2907
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2857
2908
  busy,
2858
- 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: [
2859
2910
  floatingControlsLeading,
2860
- 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 })
2861
2912
  ] }) : void 0,
2862
2913
  defaultViewport,
2863
2914
  viewportRestore,
@@ -2865,7 +2916,7 @@ function GenreTreeView({
2865
2916
  ...focus
2866
2917
  }
2867
2918
  ) }) })
2868
- ) : 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)(
2869
2920
  GenrePlaylistTreeWheelRadialPopCore,
2870
2921
  {
2871
2922
  outline: true,
@@ -2881,13 +2932,13 @@ function GenreTreeView({
2881
2932
  selectedNodeId,
2882
2933
  readOnly
2883
2934
  }
2884
- ) }) : /* @__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(
2885
2936
  ([uuid, genrePlaylistTreePerRoot]) => {
2886
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2937
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2887
2938
  "div",
2888
2939
  {
2889
2940
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2890
- 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)(
2891
2942
  GenrePlaylistTreePerRoot,
2892
2943
  {
2893
2944
  scope,
@@ -2913,8 +2964,8 @@ function GenreTreeView({
2913
2964
  );
2914
2965
  }
2915
2966
  ) }),
2916
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2917
- import_ui15.DetailPanel,
2967
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2968
+ import_ui16.DetailPanel,
2918
2969
  {
2919
2970
  ref: panelRef,
2920
2971
  side: "left",
@@ -2930,7 +2981,7 @@ function GenreTreeView({
2930
2981
  aside: activePanel?.aside,
2931
2982
  edgeTab: detailTab,
2932
2983
  headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
2933
- 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: [
2934
2985
  activePanel.content,
2935
2986
  activePanel.aside
2936
2987
  ] }) : activePanel?.content
@@ -2945,10 +2996,10 @@ function GenreTreeView({
2945
2996
  // src/genre-tree/GenreDetailPanel.tsx
2946
2997
  var import_react17 = require("react");
2947
2998
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
2948
- var import_ui16 = require("@behindthemusictree/ui");
2999
+ var import_ui17 = require("@behindthemusictree/ui");
2949
3000
  var import_lucide_react13 = require("lucide-react");
2950
- var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2951
- 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");
2952
3003
  var RELATIVE_ITEM_COUNT = 1;
2953
3004
  function toNode(genre, parentId) {
2954
3005
  return {
@@ -2975,7 +3026,7 @@ function GenreDetailPanel({
2975
3026
  edgeTab,
2976
3027
  extras
2977
3028
  }) {
2978
- 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);
2979
3030
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
2980
3031
  const view = (0, import_react17.useMemo)(() => {
2981
3032
  if (!detail) return null;
@@ -3003,17 +3054,17 @@ function GenreDetailPanel({
3003
3054
  };
3004
3055
  }, [detail]);
3005
3056
  if (isError) {
3006
- 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." });
3007
3058
  }
3008
3059
  if (isPending || !detail || !view) {
3009
- 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: [
3010
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-6 w-2/3" }),
3011
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-full" }),
3012
- /* @__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" })
3013
3064
  ] });
3014
3065
  }
3015
3066
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
3016
- const family = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3067
+ const family = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3017
3068
  import_genre_tree_view5.GenreNodeFamily,
3018
3069
  {
3019
3070
  node: view.node,
@@ -3024,20 +3075,20 @@ function GenreDetailPanel({
3024
3075
  onSelectNode: selectGenre
3025
3076
  }
3026
3077
  );
3027
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3028
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
3029
- 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,
3030
3081
  {
3031
3082
  side: "left",
3032
3083
  title: view.node.name,
3033
3084
  headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
3034
- headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3035
- import_ui16.IconButton,
3085
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3086
+ import_ui17.IconButton,
3036
3087
  {
3037
3088
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
3038
3089
  style: { color: "inherit" },
3039
3090
  onClick: () => onExpandedChange(!expanded),
3040
- 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" })
3041
3092
  }
3042
3093
  ) : void 0,
3043
3094
  onClose,
@@ -3046,10 +3097,10 @@ function GenreDetailPanel({
3046
3097
  onExpandedChange,
3047
3098
  edgeTab,
3048
3099
  children: [
3049
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3100
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3050
3101
  GenreInfoPanelBody,
3051
3102
  {
3052
- 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: [
3053
3104
  family,
3054
3105
  ancestors
3055
3106
  ] }),
@@ -3075,10 +3126,10 @@ var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3075
3126
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3076
3127
  var import_react18 = require("react");
3077
3128
  var import_lucide_react14 = require("lucide-react");
3078
- var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
3079
- 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");
3080
3131
  var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
3081
- var import_jsx_runtime36 = require("react/jsx-runtime");
3132
+ var import_jsx_runtime40 = require("react/jsx-runtime");
3082
3133
  var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
3083
3134
  function TrackListSidebar({
3084
3135
  className: className2 = "",
@@ -3091,7 +3142,7 @@ function TrackListSidebar({
3091
3142
  renderArtistLink,
3092
3143
  renderSongLink
3093
3144
  }) {
3094
- const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
3145
+ const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree23.useTrackList)();
3095
3146
  const trackNoun = (0, import_track_noun3.useTrackNoun)();
3096
3147
  const trackListRef = (0, import_react18.useRef)(null);
3097
3148
  const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
@@ -3180,25 +3231,25 @@ function TrackListSidebar({
3180
3231
  }
3181
3232
  };
3182
3233
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3183
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
3234
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3184
3235
  "div",
3185
3236
  {
3186
3237
  "data-theme": "dark",
3187
3238
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3188
3239
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3189
3240
  children: [
3190
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3191
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3192
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "origin flex text-xl", children: [
3193
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3194
- /* @__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 }) })
3195
3246
  ] }),
3196
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3197
- 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 `,
3198
3249
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3199
3250
  ] })
3200
3251
  ] }),
3201
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
3252
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3202
3253
  "button",
3203
3254
  {
3204
3255
  type: "button",
@@ -3207,11 +3258,11 @@ function TrackListSidebar({
3207
3258
  "aria-expanded": !isCollapsed,
3208
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"}`,
3209
3260
  onClick: isCollapsed ? onExpand : onMinimize,
3210
- 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" })
3211
3262
  }
3212
3263
  )
3213
3264
  ] }),
3214
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
3265
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3215
3266
  "ul",
3216
3267
  {
3217
3268
  ref: trackListRef,
@@ -3221,7 +3272,7 @@ function TrackListSidebar({
3221
3272
  onPointerDown: markUserScrollIntent,
3222
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",
3223
3274
  children: [
3224
- 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)(
3225
3276
  TrackItem,
3226
3277
  {
3227
3278
  track,
@@ -3232,7 +3283,7 @@ function TrackListSidebar({
3232
3283
  renderSongLink
3233
3284
  }
3234
3285
  ) }, track.uuid)),
3235
- 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" })
3236
3287
  ]
3237
3288
  }
3238
3289
  )
@@ -3245,14 +3296,14 @@ function TrackListSidebar({
3245
3296
  var import_react19 = require("react");
3246
3297
  var import_tailwind_merge = require("tailwind-merge");
3247
3298
  var import_player13 = require("@behindthemusictree/app-kit/player");
3248
- var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3249
- var import_ui17 = require("@behindthemusictree/ui");
3299
+ var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
3300
+ var import_ui18 = require("@behindthemusictree/ui");
3250
3301
 
3251
3302
  // src/genre-tree/PlayerControls.tsx
3252
3303
  var import_lucide_react15 = require("lucide-react");
3253
- var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3304
+ var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3254
3305
  var import_player12 = require("@behindthemusictree/app-kit/player");
3255
- var import_jsx_runtime37 = require("react/jsx-runtime");
3306
+ var import_jsx_runtime41 = require("react/jsx-runtime");
3256
3307
  function PlayerControls() {
3257
3308
  const {
3258
3309
  duration,
@@ -3264,22 +3315,22 @@ function PlayerControls() {
3264
3315
  setVolume,
3265
3316
  volume
3266
3317
  } = (0, import_player12.usePlayer)();
3267
- const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree23.useTrackList)();
3318
+ const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree25.useTrackList)();
3268
3319
  const currentTime = (0, import_player12.useCurrentTime)();
3269
3320
  const isBusy = isLoading || playState === import_player12.PlayStates.LOADING;
3270
3321
  const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
3271
3322
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3272
3323
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3273
3324
  const isPlaying = playState === import_player12.PlayStates.PLAYING;
3274
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3325
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3275
3326
  "section",
3276
3327
  {
3277
3328
  "aria-label": "Player controls",
3278
3329
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3279
3330
  children: [
3280
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3281
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
3282
- /* @__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)(
3283
3334
  "input",
3284
3335
  {
3285
3336
  "aria-label": "Seek",
@@ -3293,11 +3344,11 @@ function PlayerControls() {
3293
3344
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3294
3345
  }
3295
3346
  ),
3296
- /* @__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) })
3297
3348
  ] }),
3298
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3299
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3300
- /* @__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)(
3301
3352
  "button",
3302
3353
  {
3303
3354
  type: "button",
@@ -3306,10 +3357,10 @@ function PlayerControls() {
3306
3357
  disabled: !isReady || !hasPreviousTrack,
3307
3358
  onClick: playPreviousTrack,
3308
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",
3309
- 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" })
3310
3361
  }
3311
3362
  ),
3312
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3363
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3313
3364
  "button",
3314
3365
  {
3315
3366
  type: "button",
@@ -3318,10 +3369,10 @@ function PlayerControls() {
3318
3369
  disabled: !isReady,
3319
3370
  onClick: handlePlayPauseAction,
3320
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",
3321
- 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" })
3322
3373
  }
3323
3374
  ),
3324
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3375
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3325
3376
  "button",
3326
3377
  {
3327
3378
  type: "button",
@@ -3330,14 +3381,14 @@ function PlayerControls() {
3330
3381
  disabled: !isReady || !hasNextTrack,
3331
3382
  onClick: playNextTrack,
3332
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",
3333
- 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" })
3334
3385
  }
3335
3386
  )
3336
3387
  ] }),
3337
- /* @__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: [
3338
- 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" }),
3339
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sr-only", children: "Volume" }),
3340
- /* @__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)(
3341
3392
  "input",
3342
3393
  {
3343
3394
  "aria-label": "Volume",
@@ -3359,7 +3410,7 @@ function PlayerControls() {
3359
3410
  }
3360
3411
 
3361
3412
  // src/genre-tree/PlayerPanel.tsx
3362
- var import_jsx_runtime38 = require("react/jsx-runtime");
3413
+ var import_jsx_runtime42 = require("react/jsx-runtime");
3363
3414
  var PANEL_ID = "player-panel";
3364
3415
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3365
3416
  function PlayerPanel({
@@ -3372,7 +3423,7 @@ function PlayerPanel({
3372
3423
  renderSongLink
3373
3424
  }) {
3374
3425
  const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
3375
- const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
3426
+ const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree26.useTrackListSidebarVisibility)();
3376
3427
  const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3377
3428
  const loadedTrackTitle = playerTrackObject?.track.title;
3378
3429
  (0, import_react19.useEffect)(() => {
@@ -3399,7 +3450,7 @@ function PlayerPanel({
3399
3450
  onClick: () => setIsExtendRequested(true),
3400
3451
  expanded: false
3401
3452
  } : null;
3402
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3453
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3403
3454
  "div",
3404
3455
  {
3405
3456
  id: PANEL_ID,
@@ -3412,8 +3463,8 @@ function PlayerPanel({
3412
3463
  ),
3413
3464
  style: isExtended ? { width: extendedWidth } : void 0,
3414
3465
  children: [
3415
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3416
- import_ui17.PanelEdgeTab,
3466
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3467
+ import_ui18.PanelEdgeTab,
3417
3468
  {
3418
3469
  side: "right",
3419
3470
  edge: tabEdge,
@@ -3422,7 +3473,7 @@ function PlayerPanel({
3422
3473
  className: "absolute right-full top-1/2 -translate-y-1/2"
3423
3474
  }
3424
3475
  ),
3425
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3476
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3426
3477
  "div",
3427
3478
  {
3428
3479
  className: (0, import_tailwind_merge.twMerge)(
@@ -3430,20 +3481,20 @@ function PlayerPanel({
3430
3481
  isExtended ? "flex-row" : "flex-col"
3431
3482
  ),
3432
3483
  children: [
3433
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3434
- /* @__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 }) }),
3435
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "relative w-full shrink-0", children: [
3436
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3437
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
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: [
3438
3489
  playerTrackObject.loadError,
3439
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3490
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3440
3491
  void retryPlayback();
3441
3492
  }, children: "Retry" })
3442
3493
  ] })
3443
3494
  ] }),
3444
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(PlayerControls, {})
3495
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PlayerControls, {})
3445
3496
  ] }),
3446
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3497
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3447
3498
  TrackListSidebar,
3448
3499
  {
3449
3500
  layout: "inline",
@@ -3469,33 +3520,33 @@ function PlayerPanel({
3469
3520
  // src/genre-tree/Rating.tsx
3470
3521
  var import_uuid = require("uuid");
3471
3522
  var import_ai = require("react-icons/ai");
3472
- var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3473
- 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");
3474
3525
  function Rating({ rating, handleChange }) {
3475
3526
  const instanceId = (0, import_uuid.v4)();
3476
- 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: [
3477
- /* @__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)(
3478
3529
  "input",
3479
3530
  {
3480
3531
  id: `star-${instanceId}-${i}`,
3481
3532
  className: "w-6 h-6 opacity-0",
3482
3533
  type: "radio",
3483
3534
  name: "rating",
3484
- value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
3535
+ value: i === 0 ? import_genre_tree27.FORM_RATING_NULL_VALUE : i,
3485
3536
  checked: rating === (i === 0 ? null : i),
3486
3537
  onChange: handleChange
3487
3538
  }
3488
3539
  ),
3489
- /* @__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: [
3490
- /* @__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)(
3491
3542
  import_ai.AiOutlineStar,
3492
3543
  {
3493
- 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)]"}`
3494
3545
  }
3495
3546
  ),
3496
- /* @__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" }),
3497
3548
  " "
3498
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
3549
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3499
3550
  import_ai.AiFillStar,
3500
3551
  {
3501
3552
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3517,9 +3568,15 @@ function Rating({ rating, handleChange }) {
3517
3568
  GenreTreeWheelSkeleton,
3518
3569
  PlayerPanel,
3519
3570
  Rating,
3571
+ SEARCH_FAILED_STATUS,
3572
+ SearchBox,
3520
3573
  TrackItem,
3521
3574
  TrackListSidebar,
3522
3575
  TrackPositionPlayPause,
3523
- useGenrePlaylistPlayback
3576
+ searchStatus,
3577
+ useArtistSearchGroup,
3578
+ useGenrePlaylistPlayback,
3579
+ useGenreRemoteSearchGroup,
3580
+ useSongSearchGroup
3524
3581
  });
3525
3582
  //# sourceMappingURL=index.cjs.map