@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.
package/dist/index.cjs CHANGED
@@ -40,13 +40,19 @@ __export(src_exports, {
40
40
  PopupButtons: () => PopupButtons,
41
41
  PopupTitle: () => PopupTitle,
42
42
  Rating: () => Rating,
43
+ SEARCH_FAILED_STATUS: () => SEARCH_FAILED_STATUS,
44
+ SearchBox: () => SearchBox,
43
45
  SpotifyAuthErrorPopup: () => SpotifyAuthErrorPopup,
44
46
  Toast: () => Toast,
45
47
  TrackItem: () => TrackItem,
46
48
  TrackListSidebar: () => TrackListSidebar,
47
49
  TrackPositionPlayPause: () => TrackPositionPlayPause,
48
50
  TrackUploadPopup: () => TrackUploadPopup,
49
- useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
51
+ searchStatus: () => searchStatus,
52
+ useArtistSearchGroup: () => useArtistSearchGroup,
53
+ useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
54
+ useGenreRemoteSearchGroup: () => useGenreRemoteSearchGroup,
55
+ useSongSearchGroup: () => useSongSearchGroup
50
56
  });
51
57
  module.exports = __toCommonJS(src_exports);
52
58
 
@@ -585,10 +591,9 @@ var import_react16 = require("react");
585
591
  var import_react_query3 = require("@tanstack/react-query");
586
592
  var import_lucide_react15 = require("lucide-react");
587
593
  var import_lucide_react16 = require("lucide-react");
588
- var import_ui18 = require("@behindthemusictree/ui");
594
+ var import_ui19 = require("@behindthemusictree/ui");
589
595
  var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
590
- var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
591
- var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
596
+ var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
592
597
  var import_popup6 = require("@behindthemusictree/app-kit/popup");
593
598
 
594
599
  // src/genre-tree/playlist-tree/size-metric.ts
@@ -1020,11 +1025,143 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
1020
1025
  }
1021
1026
 
1022
1027
  // src/genre-tree/GenreSearch.tsx
1023
- var import_react8 = require("react");
1024
1028
  var import_lucide_react7 = require("lucide-react");
1029
+ var import_ui7 = require("@behindthemusictree/ui");
1030
+ var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
1031
+
1032
+ // src/genre-tree/search/SearchBox.tsx
1033
+ var import_react8 = require("react");
1025
1034
  var import_ui6 = require("@behindthemusictree/ui");
1026
- var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
1027
1035
  var import_jsx_runtime16 = require("react/jsx-runtime");
1036
+ function SearchBox({
1037
+ label,
1038
+ placeholder,
1039
+ value,
1040
+ onValueChange,
1041
+ groups,
1042
+ onPicked,
1043
+ onClear,
1044
+ leading,
1045
+ className: className2,
1046
+ onFocus
1047
+ }) {
1048
+ const [open, setOpen] = (0, import_react8.useState)(false);
1049
+ const loading = groups.some((group) => group.loading);
1050
+ const status = groups.map((group) => group.status).filter(Boolean).join(". ");
1051
+ const handlePick = (groupKey, itemKey) => {
1052
+ setOpen(false);
1053
+ groups.find((group) => group.key === groupKey)?.items.find((item) => item.key === itemKey)?.onSelect();
1054
+ onPicked?.(groupKey, itemKey);
1055
+ };
1056
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: className2, onFocusCapture: onFocus, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1057
+ import_ui6.SearchCombobox,
1058
+ {
1059
+ ariaLabel: label,
1060
+ listLabel: `${label} results`,
1061
+ value,
1062
+ placeholder: placeholder ?? `${label}\u2026`,
1063
+ groups: groups.map((group) => ({
1064
+ key: group.key,
1065
+ label: group.label,
1066
+ options: group.items.map((item) => ({ key: item.key, content: item.content }))
1067
+ })),
1068
+ open,
1069
+ onOpenChange: setOpen,
1070
+ onValueChange,
1071
+ onPick: handlePick,
1072
+ status,
1073
+ loading,
1074
+ onClear,
1075
+ leading,
1076
+ inputClassName: "focus-visible:ring-0"
1077
+ }
1078
+ ) });
1079
+ }
1080
+
1081
+ // src/genre-tree/search/SearchGroup.ts
1082
+ var SEARCH_FAILED_STATUS = "Search failed";
1083
+ var searchStatus = (state, count, noun) => state.isError ? SEARCH_FAILED_STATUS : state.settled && count === 0 ? `No ${noun} found` : "";
1084
+
1085
+ // src/genre-tree/search/useArtistSearchGroup.tsx
1086
+ var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
1087
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1088
+ function useArtistSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
1089
+ const search = (0, import_genre_tree2.useArtistSearch)(getBaseUrl, enabled ? text : "");
1090
+ if (!enabled) return void 0;
1091
+ return {
1092
+ key: "artists",
1093
+ label: "Artists",
1094
+ items: search.artists.map((artist) => ({
1095
+ key: artist.uuid,
1096
+ content: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
1097
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "block truncate font-medium", children: [
1098
+ artist.name,
1099
+ artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
1100
+ " \xB7 ",
1101
+ artist.disambiguation
1102
+ ] })
1103
+ ] }),
1104
+ artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
1105
+ "also known as ",
1106
+ artist.matchedAlias
1107
+ ] })
1108
+ ] }),
1109
+ onSelect: () => onSelect(artist)
1110
+ })),
1111
+ status: searchStatus(search, search.artists.length, "artists"),
1112
+ loading: search.pending
1113
+ };
1114
+ }
1115
+
1116
+ // src/genre-tree/search/useSongSearchGroup.tsx
1117
+ var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
1118
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1119
+ function useSongSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
1120
+ const search = (0, import_genre_tree3.useSongSearch)(getBaseUrl, enabled ? text : "");
1121
+ if (!enabled) return void 0;
1122
+ return {
1123
+ key: "songs",
1124
+ label: "Songs",
1125
+ items: search.songs.map((song) => ({
1126
+ key: song.uuid,
1127
+ content: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: (0, import_genre_tree3.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
1128
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "block truncate font-medium", children: song.title }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime18.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 ") })
1130
+ ] }),
1131
+ onSelect: () => onSelect(song)
1132
+ })),
1133
+ status: searchStatus(search, search.songs.length, "songs"),
1134
+ loading: search.pending
1135
+ };
1136
+ }
1137
+
1138
+ // src/genre-tree/search/useGenreRemoteSearchGroup.tsx
1139
+ var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
1140
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1141
+ function useGenreRemoteSearchGroup(getBaseUrl, text, onSelect, { key = "genres", label = "Genres", filter, describe, ...options } = {}) {
1142
+ const search = (0, import_genre_tree4.useGenreRemoteSearch)(getBaseUrl, text, options);
1143
+ const genres = filter ? search.genres.filter(filter) : search.genres;
1144
+ return {
1145
+ key,
1146
+ label,
1147
+ items: genres.map((genre) => ({
1148
+ key: genre.uuid,
1149
+ content: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1150
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "block truncate font-medium", children: [
1151
+ genre.name,
1152
+ genre.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "ml-2 text-xs font-normal italic text-[var(--color-text-muted)]", children: "umbrella" })
1153
+ ] }),
1154
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: describe?.(genre) ?? `${genre.treeName} \xB7 ${genre.root.name}` })
1155
+ ] }),
1156
+ onSelect: () => onSelect(genre)
1157
+ })),
1158
+ status: searchStatus(search, genres.length, "genres"),
1159
+ loading: search.pending
1160
+ };
1161
+ }
1162
+
1163
+ // src/genre-tree/GenreSearch.tsx
1164
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1028
1165
  function GenreSearch({
1029
1166
  genrePlaylists,
1030
1167
  onSelect,
@@ -1033,120 +1170,58 @@ function GenreSearch({
1033
1170
  onPrevious,
1034
1171
  placeholder,
1035
1172
  onQueryChange,
1036
- extraGroups = []
1173
+ extraGroups = [],
1174
+ onFocus
1037
1175
  }) {
1038
- const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
1176
+ const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree5.useGenreSearch)({
1039
1177
  genrePlaylists,
1040
1178
  onSelect,
1041
1179
  selectedName,
1042
1180
  onClear
1043
1181
  });
1044
- const [open, setOpen] = (0, import_react8.useState)(false);
1045
1182
  const active = query !== "" && query !== selectedName;
1046
1183
  const hasExtras = extraGroups.length > 0;
1047
1184
  const label = hasExtras ? "Search" : "Search a genre";
1048
- const loading = active && extraGroups.some((group) => group.loading);
1049
- const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
1050
1185
  const groups = [
1051
1186
  {
1052
1187
  key: "genres",
1053
1188
  label: hasExtras ? "Genres" : void 0,
1054
- options: results.map((genrePlaylist) => ({
1189
+ items: results.map((genrePlaylist) => ({
1055
1190
  key: genrePlaylist.uuid,
1056
- content: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1191
+ content: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
1057
1192
  genrePlaylist.name,
1058
- genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
1059
- ] })
1193
+ genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
1194
+ ] }),
1195
+ onSelect: () => select(genrePlaylist)
1060
1196
  }))
1061
1197
  },
1062
- ...extraGroups.map((group) => ({
1063
- key: group.key,
1064
- label: group.label,
1065
- options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
1066
- }))
1198
+ ...extraGroups.map(
1199
+ (group) => active ? group : { ...group, items: [], status: "", loading: false }
1200
+ )
1067
1201
  ];
1068
- const handlePick = (groupKey, optionKey) => {
1069
- setOpen(false);
1070
- if (groupKey === "genres") {
1071
- const genrePlaylist = results.find((result) => result.uuid === optionKey);
1072
- if (!genrePlaylist) return;
1073
- select(genrePlaylist);
1074
- onQueryChange?.("");
1075
- return;
1202
+ return /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)(
1203
+ SearchBox,
1204
+ {
1205
+ label,
1206
+ placeholder,
1207
+ value: query,
1208
+ onValueChange: (value) => {
1209
+ setQuery(value);
1210
+ onQueryChange?.(value);
1211
+ },
1212
+ groups,
1213
+ onPicked: (groupKey) => {
1214
+ if (groupKey !== "genres") setQuery("");
1215
+ onQueryChange?.("");
1216
+ },
1217
+ onClear: canClear ? () => {
1218
+ clear();
1219
+ onQueryChange?.("");
1220
+ } : void 0,
1221
+ leading: onPrevious && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { type: "button", "aria-label": "Previous", title: "Previous", className: import_ui7.SEARCH_LEADING_BUTTON_CLASS, onClick: onPrevious, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.ArrowLeft, { "aria-hidden": "true", className: "h-4 w-4" }) }),
1222
+ onFocus
1076
1223
  }
1077
- const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
1078
- if (!item) return;
1079
- setQuery("");
1080
- onQueryChange?.("");
1081
- item.onSelect();
1082
- };
1083
- return /* @__PURE__ */ (0, import_jsx_runtime16.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: [
1084
- onPrevious && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1085
- "button",
1086
- {
1087
- type: "button",
1088
- "aria-label": "Previous",
1089
- title: "Previous",
1090
- 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)]",
1091
- onClick: onPrevious,
1092
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
1093
- }
1094
- ),
1095
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1096
- import_ui6.Combobox,
1097
- {
1098
- ariaLabel: label,
1099
- listLabel: `${label} results`,
1100
- value: query,
1101
- placeholder: placeholder ?? `${label}\u2026`,
1102
- groups,
1103
- open,
1104
- onOpenChange: setOpen,
1105
- onValueChange: (value) => {
1106
- setQuery(value);
1107
- onQueryChange?.(value);
1108
- },
1109
- onPick: handlePick,
1110
- status,
1111
- loading: loading ? "Searching\u2026" : void 0,
1112
- 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`,
1113
- className: "genre-search-results z-40 rounded-xl shadow-md",
1114
- listClassName: "max-h-[min(20rem,50dvh)]",
1115
- optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
1116
- }
1117
- ),
1118
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
1119
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
1120
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1121
- import_lucide_react7.Search,
1122
- {
1123
- "aria-hidden": "true",
1124
- className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
1125
- }
1126
- ),
1127
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1128
- import_lucide_react7.LoaderCircle,
1129
- {
1130
- "aria-hidden": "true",
1131
- 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"}`
1132
- }
1133
- )
1134
- ] }),
1135
- canClear && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1136
- "button",
1137
- {
1138
- type: "button",
1139
- "aria-label": "Clear search",
1140
- 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)]",
1141
- onClick: () => {
1142
- clear();
1143
- onQueryChange?.("");
1144
- },
1145
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.X, { "aria-hidden": "true", className: "w-6 h-6" })
1146
- }
1147
- )
1148
- ] })
1149
- ] });
1224
+ ) });
1150
1225
  }
1151
1226
 
1152
1227
  // src/genre-tree/GenreInfoPanelBody.tsx
@@ -1166,12 +1241,12 @@ var import_fa3 = require("react-icons/fa");
1166
1241
  var import_si = require("react-icons/si");
1167
1242
 
1168
1243
  // src/genre-tree/ShareDestinationButton.tsx
1169
- var import_jsx_runtime17 = require("react/jsx-runtime");
1244
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1170
1245
  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";
1171
1246
  function Contents({ icon, label }) {
1172
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
1173
- /* @__PURE__ */ (0, import_jsx_runtime17.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 }),
1174
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "max-w-full truncate", children: label })
1247
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
1248
+ /* @__PURE__ */ (0, import_jsx_runtime21.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 }),
1249
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "max-w-full truncate", children: label })
1175
1250
  ] });
1176
1251
  }
1177
1252
  function ShareDestinationButton({
@@ -1183,29 +1258,29 @@ function ShareDestinationButton({
1183
1258
  ariaLabel
1184
1259
  }) {
1185
1260
  if (href) {
1186
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Contents, { icon, label }) });
1261
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Contents, { icon, label }) });
1187
1262
  }
1188
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Contents, { icon, label }) });
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Contents, { icon, label }) });
1189
1264
  }
1190
1265
 
1191
1266
  // src/genre-tree/SharePreviewCard.tsx
1192
1267
  var import_lucide_react8 = require("lucide-react");
1193
- var import_jsx_runtime18 = require("react/jsx-runtime");
1268
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1194
1269
  function SharePreviewCard({ target, url }) {
1195
1270
  const Icon = { genre: import_lucide_react8.Music2, artist: import_lucide_react8.UserRound, song: import_lucide_react8.Music }[target.kind];
1196
1271
  const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
1197
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
1198
- /* @__PURE__ */ (0, import_jsx_runtime18.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_runtime18.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
1199
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
1200
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
1201
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
1202
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
1272
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
1273
+ /* @__PURE__ */ (0, import_jsx_runtime22.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_runtime22.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
1274
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
1275
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
1276
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
1277
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
1203
1278
  ] })
1204
1279
  ] }) });
1205
1280
  }
1206
1281
 
1207
1282
  // src/genre-tree/SharePopup.tsx
1208
- var import_jsx_runtime19 = require("react/jsx-runtime");
1283
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1209
1284
  function resolveAbsoluteUrl(url) {
1210
1285
  try {
1211
1286
  const parsed = new URL(url, window.location.href);
@@ -1280,8 +1355,8 @@ function SharePopup({ target, url, onClose }) {
1280
1355
  setShareError("Could not open the share options.");
1281
1356
  }
1282
1357
  };
1283
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1284
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1358
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1359
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1285
1360
  BasePopup_default,
1286
1361
  {
1287
1362
  title: "Share",
@@ -1296,67 +1371,67 @@ function SharePopup({ target, url, onClose }) {
1296
1371
  titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
1297
1372
  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)]",
1298
1373
  contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
1299
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col gap-4", children: [
1300
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1301
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1302
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1374
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col gap-4", children: [
1375
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
1376
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
1377
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1303
1378
  ShareDestinationButton,
1304
1379
  {
1305
1380
  label: "Copy Link",
1306
- icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1381
+ icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react9.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
1307
1382
  onClick: () => void handleCopy(),
1308
1383
  ariaLabel: "Copy link",
1309
1384
  disabled: !absoluteUrl
1310
1385
  }
1311
1386
  ),
1312
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1387
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1313
1388
  ShareDestinationButton,
1314
1389
  {
1315
1390
  label: "WhatsApp",
1316
- icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_fa3.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1391
+ icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fa3.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
1317
1392
  href: destinationUrls?.whatsapp,
1318
1393
  ariaLabel: `Share ${target.name} via WhatsApp`,
1319
1394
  disabled: !absoluteUrl
1320
1395
  }
1321
1396
  ),
1322
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1397
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1323
1398
  ShareDestinationButton,
1324
1399
  {
1325
1400
  label: "Gmail",
1326
- icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1401
+ icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
1327
1402
  href: destinationUrls?.gmail,
1328
1403
  ariaLabel: `Email ${target.name} with Gmail`,
1329
1404
  disabled: !absoluteUrl
1330
1405
  }
1331
1406
  ),
1332
- canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1407
+ canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1333
1408
  ShareDestinationButton,
1334
1409
  {
1335
1410
  label: "More",
1336
- icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1411
+ icon: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react9.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
1337
1412
  onClick: () => void handleNativeShare(),
1338
1413
  ariaLabel: "More sharing options",
1339
1414
  disabled: !absoluteUrl
1340
1415
  }
1341
1416
  )
1342
1417
  ] }),
1343
- copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1344
- shareError && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1418
+ copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1419
+ shareError && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1345
1420
  ] })
1346
1421
  }
1347
1422
  ),
1348
- toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1423
+ toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
1349
1424
  ] });
1350
1425
  }
1351
1426
 
1352
1427
  // src/genre-tree/ShareActionButton.tsx
1353
- var import_jsx_runtime20 = require("react/jsx-runtime");
1428
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1354
1429
  function ShareActionButton({ target, url }) {
1355
1430
  const { showPopup, hidePopup } = (0, import_popup4.usePopup)();
1356
1431
  const openSharePopup = () => {
1357
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1432
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SharePopup, { target, url, onClose: hidePopup }));
1358
1433
  };
1359
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1434
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1360
1435
  "button",
1361
1436
  {
1362
1437
  type: "button",
@@ -1365,7 +1440,7 @@ function ShareActionButton({ target, url }) {
1365
1440
  "aria-label": `Share ${target.name}`,
1366
1441
  title: `Share ${target.name}`,
1367
1442
  children: [
1368
- /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)(import_lucide_react10.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1443
+ /* @__PURE__ */ (0, import_jsx_runtime24.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_runtime24.jsx)(import_lucide_react10.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
1369
1444
  "Share"
1370
1445
  ]
1371
1446
  }
@@ -1373,7 +1448,7 @@ function ShareActionButton({ target, url }) {
1373
1448
  }
1374
1449
 
1375
1450
  // src/genre-tree/GenreActionBand.tsx
1376
- var import_jsx_runtime21 = require("react/jsx-runtime");
1451
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1377
1452
  function GenreActionBand({
1378
1453
  node,
1379
1454
  onPlayPause,
@@ -1395,7 +1470,7 @@ function GenreActionBand({
1395
1470
  items.push({
1396
1471
  key: "play-pause",
1397
1472
  label: isPlaying ? "Pause" : "Play",
1398
- icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1473
+ icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
1399
1474
  onClick: onPlayPause
1400
1475
  });
1401
1476
  }
@@ -1403,24 +1478,24 @@ function GenreActionBand({
1403
1478
  items.push({
1404
1479
  key: "zoom-in",
1405
1480
  label: "Focus",
1406
- icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1481
+ icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
1407
1482
  onClick: onZoomIn
1408
1483
  });
1409
1484
  }
1410
1485
  if (onAddChild) {
1411
- adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1486
+ adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
1412
1487
  }
1413
1488
  if (onRename) {
1414
- adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1489
+ adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
1415
1490
  }
1416
1491
  if (onChangeParent) {
1417
- adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1492
+ adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
1418
1493
  }
1419
1494
  if (onToggleSide) {
1420
1495
  adminItems.push({
1421
1496
  key: "toggle-side",
1422
1497
  label: side === "pop" ? "Make core" : "Make pop",
1423
- icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1498
+ icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
1424
1499
  onClick: onToggleSide,
1425
1500
  disabled: !!toggleSideDisabledReason,
1426
1501
  title: toggleSideDisabledReason
@@ -1436,7 +1511,7 @@ function GenreActionBand({
1436
1511
  });
1437
1512
  }
1438
1513
  if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
1439
- const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1514
+ const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1440
1515
  "button",
1441
1516
  {
1442
1517
  type: "button",
@@ -1445,25 +1520,25 @@ function GenreActionBand({
1445
1520
  onClick: item.onClick,
1446
1521
  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",
1447
1522
  children: [
1448
- /* @__PURE__ */ (0, import_jsx_runtime21.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 }),
1523
+ /* @__PURE__ */ (0, import_jsx_runtime25.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 }),
1449
1524
  item.label
1450
1525
  ]
1451
1526
  },
1452
1527
  item.key
1453
1528
  ));
1454
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1455
- (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1529
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
1530
+ (items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
1456
1531
  renderItems(items),
1457
- shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1532
+ shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
1458
1533
  ] }),
1459
- adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1534
+ adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1460
1535
  "section",
1461
1536
  {
1462
1537
  "aria-label": "Admin actions",
1463
1538
  className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
1464
1539
  children: [
1465
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1466
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1540
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1541
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1467
1542
  ]
1468
1543
  }
1469
1544
  )
@@ -1471,12 +1546,12 @@ function GenreActionBand({
1471
1546
  }
1472
1547
 
1473
1548
  // src/genre-tree/GenreDetailExtras.tsx
1474
- var import_ui8 = require("@behindthemusictree/ui");
1475
- var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
1549
+ var import_ui9 = require("@behindthemusictree/ui");
1550
+ var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
1476
1551
 
1477
1552
  // src/genre-tree/ClampedText.tsx
1478
1553
  var import_react10 = require("react");
1479
- var import_jsx_runtime22 = require("react/jsx-runtime");
1554
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1480
1555
  function ClampedText({ children, className: className2 = "" }) {
1481
1556
  const ref = (0, import_react10.useRef)(null);
1482
1557
  const [expanded, setExpanded] = (0, import_react10.useState)(false);
@@ -1486,9 +1561,9 @@ function ClampedText({ children, className: className2 = "" }) {
1486
1561
  if (!element || expanded) return;
1487
1562
  setOverflows(element.scrollHeight > element.clientHeight);
1488
1563
  }, [children, expanded]);
1489
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
1490
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1491
- (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1564
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
1565
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
1566
+ (overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1492
1567
  "button",
1493
1568
  {
1494
1569
  type: "button",
@@ -1506,7 +1581,7 @@ var import_react11 = require("react");
1506
1581
  var import_react_query = require("@tanstack/react-query");
1507
1582
  var import_zod = require("zod");
1508
1583
  var import_fa4 = require("react-icons/fa");
1509
- var import_ui7 = require("@behindthemusictree/ui");
1584
+ var import_ui8 = require("@behindthemusictree/ui");
1510
1585
  var import_auth2 = require("@behindthemusictree/app-kit/auth");
1511
1586
  var import_transport5 = require("@behindthemusictree/app-kit/transport");
1512
1587
  var import_transport6 = require("@behindthemusictree/app-kit/transport");
@@ -1515,22 +1590,22 @@ var import_player4 = require("@behindthemusictree/app-kit/player");
1515
1590
  var import_player5 = require("@behindthemusictree/app-kit/player");
1516
1591
  var import_popup5 = require("@behindthemusictree/app-kit/popup");
1517
1592
  var import_transport8 = require("@behindthemusictree/app-kit/transport");
1518
- var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
1519
- var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
1520
- var import_genre_tree6 = require("@behindthemusictree/app-kit/genre-tree");
1521
1593
  var import_genre_tree7 = require("@behindthemusictree/app-kit/genre-tree");
1594
+ var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
1595
+ var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
1596
+ var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1522
1597
 
1523
1598
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1524
- var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
1525
- var import_jsx_runtime23 = require("react/jsx-runtime");
1599
+ var import_genre_tree6 = require("@behindthemusictree/app-kit/genre-tree");
1600
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1526
1601
  function MusicBrainzRecordingLink({
1527
1602
  song,
1528
1603
  children,
1529
1604
  className: className2 = ""
1530
1605
  }) {
1531
- const url = (0, import_genre_tree3.musicBrainzRecordingUrl)(song);
1532
- if (!url) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children });
1533
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1606
+ const url = (0, import_genre_tree6.musicBrainzRecordingUrl)(song);
1607
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_jsx_runtime27.Fragment, { children });
1608
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1534
1609
  "a",
1535
1610
  {
1536
1611
  href: url,
@@ -1545,11 +1620,11 @@ function MusicBrainzRecordingLink({
1545
1620
  }
1546
1621
 
1547
1622
  // src/genre-tree/GenrePlaylistTracks.tsx
1548
- var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
1623
+ var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1549
1624
  var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1550
- var import_jsx_runtime24 = require("react/jsx-runtime");
1625
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1551
1626
  var CATALOGUE_PAGE_SIZE = 100;
1552
- var SongPageSchema = (0, import_transport6.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
1627
+ var SongPageSchema = (0, import_transport6.PaginatedResponseSchema)(import_genre_tree9.SongDetailedSchema).extend({
1553
1628
  overallTotal: import_zod.z.number().nullable(),
1554
1629
  page: import_zod.z.number().nullable(),
1555
1630
  totalPages: import_zod.z.number().nullable()
@@ -1576,7 +1651,7 @@ function GenrePlaylistTracks({
1576
1651
  renderTracksToolbar,
1577
1652
  artist = null
1578
1653
  }) {
1579
- const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree7.useTrackList)();
1654
+ const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree10.useTrackList)();
1580
1655
  const { session, sessionRestored } = (0, import_auth2.useSession)();
1581
1656
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
1582
1657
  const { showPopup } = (0, import_popup5.usePopup)();
@@ -1585,11 +1660,11 @@ function GenrePlaylistTracks({
1585
1660
  const { fetch } = (0, import_transport5.useFetchWrapper)(getBackendBaseUrl);
1586
1661
  const playback = (0, import_transport5.usePlaybackContext)();
1587
1662
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
1588
- 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] : []],
1663
+ 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] : []],
1589
1664
  queryFn: async ({ pageParam }) => {
1590
1665
  if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1591
1666
  const response = await fetch(
1592
- import_genre_tree5.libraryEndpoints.reference.songs.list(),
1667
+ import_genre_tree8.libraryEndpoints.reference.songs.list(),
1593
1668
  true,
1594
1669
  false,
1595
1670
  {},
@@ -1620,35 +1695,35 @@ function GenrePlaylistTracks({
1620
1695
  const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1621
1696
  started.catch((error) => {
1622
1697
  console.error("Failed to play genre playlist:", error);
1623
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(InternalErrorPopup, { errorCode: import_transport8.ErrorCode.CLIENT_UNKNOWN }));
1698
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime28.jsx)(InternalErrorPopup, { errorCode: import_transport8.ErrorCode.CLIENT_UNKNOWN }));
1624
1699
  });
1625
1700
  };
1626
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui7.PanelSection, { title: `${(0, import_track_noun2.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { children: [
1701
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.PanelSection, { title: `${(0, import_track_noun2.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { children: [
1627
1702
  renderTracksToolbar?.(),
1628
- isPending ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui7.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { role: "alert", children: [
1703
+ isPending ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { role: "alert", children: [
1629
1704
  "Couldn't load ",
1630
1705
  trackNoun.plural,
1631
1706
  ".",
1632
1707
  " ",
1633
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1634
- ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime24.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: [
1708
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1709
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
1635
1710
  tracks.map((track, index) => {
1636
1711
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1637
1712
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1638
1713
  const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
1639
- const text = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: TEXT_CELL, children: [
1640
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1641
- track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1642
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_genre_tree8.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1643
- track.album && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1714
+ const text = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: TEXT_CELL, children: [
1715
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree9.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1716
+ track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1717
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree11.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1718
+ track.album && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1644
1719
  ] });
1645
1720
  const leading = renderSongLeading?.(track, index);
1646
1721
  const actions = renderSongActions?.(track);
1647
- const isUnplayable = (0, import_genre_tree6.isSong)(track) && (0, import_genre_tree6.songUnplayableReason)(track) !== null;
1722
+ const isUnplayable = (0, import_genre_tree9.isSong)(track) && (0, import_genre_tree9.songUnplayableReason)(track) !== null;
1648
1723
  if (isUnplayable) {
1649
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1724
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1650
1725
  leading,
1651
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1726
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1652
1727
  "button",
1653
1728
  {
1654
1729
  type: "button",
@@ -1656,27 +1731,27 @@ function GenrePlaylistTracks({
1656
1731
  "aria-disabled": "true",
1657
1732
  "aria-label": label,
1658
1733
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1659
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPlay, { size: 10 })
1734
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa4.FaPlay, { size: 10 })
1660
1735
  }
1661
1736
  ),
1662
1737
  text,
1663
1738
  actions
1664
1739
  ] }, track.uuid);
1665
1740
  }
1666
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1741
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1667
1742
  "li",
1668
1743
  {
1669
1744
  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)]" : ""}`,
1670
1745
  children: [
1671
1746
  leading,
1672
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1747
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1673
1748
  "button",
1674
1749
  {
1675
1750
  type: "button",
1676
1751
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1677
1752
  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"}`,
1678
1753
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1679
- children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui7.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPlay, { size: 10 })
1754
+ children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_fa4.FaPlay, { size: 10 })
1680
1755
  }
1681
1756
  ),
1682
1757
  text,
@@ -1686,7 +1761,7 @@ function GenrePlaylistTracks({
1686
1761
  track.uuid
1687
1762
  );
1688
1763
  }),
1689
- hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1764
+ hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1690
1765
  ] })
1691
1766
  ] }) });
1692
1767
  }
@@ -1694,7 +1769,7 @@ function GenrePlaylistTracks({
1694
1769
  // src/genre-tree/GenreDetailExtras.tsx
1695
1770
  var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
1696
1771
  var import_si2 = require("react-icons/si");
1697
- var import_jsx_runtime25 = require("react/jsx-runtime");
1772
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1698
1773
  function GenreDetailExtras({
1699
1774
  genreUuid,
1700
1775
  genrePlaylist,
@@ -1711,24 +1786,24 @@ function GenreDetailExtras({
1711
1786
  ...trackSlots
1712
1787
  }) {
1713
1788
  const trackNoun = (0, import_track_noun3.useTrackNoun)();
1714
- const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
1789
+ const { data: overview, isPending } = (0, import_genre_tree12.useFetchGenreOverview)(
1715
1790
  genreUuid,
1716
1791
  scope,
1717
1792
  getBackendBaseUrl,
1718
1793
  criteriaOverviewSchema
1719
1794
  );
1720
1795
  if (isPending) {
1721
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
1722
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4" }) }) }),
1796
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1797
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.Skeleton, { className: "h-4 w-3/4" }) }) }),
1723
1798
  family
1724
1799
  ] });
1725
1800
  }
1726
- if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_jsx_runtime25.Fragment, { children: family });
1801
+ if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_jsx_runtime29.Fragment, { children: family });
1727
1802
  const { summary, summarySourceUrl, essentialSongs } = overview;
1728
1803
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1729
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
1730
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1731
- summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1804
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
1805
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1806
+ summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1732
1807
  "a",
1733
1808
  {
1734
1809
  href: summarySourceUrl,
@@ -1736,12 +1811,12 @@ function GenreDetailExtras({
1736
1811
  rel: "noopener noreferrer",
1737
1812
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1738
1813
  children: [
1739
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1814
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1740
1815
  "Wikipedia"
1741
1816
  ]
1742
1817
  }
1743
1818
  ),
1744
- wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1819
+ wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1745
1820
  "a",
1746
1821
  {
1747
1822
  href: wikidataUrl,
@@ -1749,17 +1824,17 @@ function GenreDetailExtras({
1749
1824
  rel: "noopener noreferrer",
1750
1825
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1751
1826
  children: [
1752
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1827
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1753
1828
  "Wikidata"
1754
1829
  ]
1755
1830
  }
1756
1831
  )
1757
1832
  ] }) }),
1758
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1833
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1759
1834
  family,
1760
1835
  renderGenreDetailExtras?.(overview),
1761
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1762
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1836
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1837
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1763
1838
  GenrePlaylistTracks,
1764
1839
  {
1765
1840
  genreUuid,
@@ -1775,7 +1850,7 @@ function GenreDetailExtras({
1775
1850
  }
1776
1851
 
1777
1852
  // src/genre-tree/GenreInfoPanelBody.tsx
1778
- var import_jsx_runtime26 = require("react/jsx-runtime");
1853
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1779
1854
  var import_react12 = require("react");
1780
1855
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1781
1856
  const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
@@ -1789,7 +1864,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1789
1864
  console.error("Failed to load genre playlist tracks:", error);
1790
1865
  });
1791
1866
  };
1792
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1867
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1793
1868
  GenreActionBand,
1794
1869
  {
1795
1870
  ...bandProps,
@@ -1810,9 +1885,9 @@ function GenreInfoPanelBody({
1810
1885
  band,
1811
1886
  extras
1812
1887
  }) {
1813
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
1888
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
1814
1889
  toolbar,
1815
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1890
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1816
1891
  /* @__PURE__ */ (0, import_react12.createElement)(
1817
1892
  GenreDetailExtras,
1818
1893
  {
@@ -1831,8 +1906,8 @@ function GenreInfoPanelBody({
1831
1906
 
1832
1907
  // src/genre-tree/ChangeParentPopup.tsx
1833
1908
  var import_react13 = require("react");
1834
- var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
1835
- var import_jsx_runtime27 = require("react/jsx-runtime");
1909
+ var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
1910
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1836
1911
  function ChangeParentPopup({
1837
1912
  genrePlaylist,
1838
1913
  genrePlaylists,
@@ -1841,9 +1916,9 @@ function ChangeParentPopup({
1841
1916
  onClose,
1842
1917
  onParentChanged
1843
1918
  }) {
1844
- const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
1919
+ const { mutate, isPending, formErrors } = (0, import_genre_tree13.useUpdateGenre)(scope, getBackendBaseUrl);
1845
1920
  const candidates = (0, import_react13.useMemo)(
1846
- () => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1921
+ () => (0, import_genre_tree13.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
1847
1922
  [genrePlaylists, genrePlaylist.uuid]
1848
1923
  );
1849
1924
  const handleSelect = (newParent) => {
@@ -1867,12 +1942,12 @@ function ChangeParentPopup({
1867
1942
  window.addEventListener("keydown", handleKeyDown);
1868
1943
  return () => window.removeEventListener("keydown", handleKeyDown);
1869
1944
  }, [onClose]);
1870
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1871
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1945
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1946
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1872
1947
  "Current parent: ",
1873
1948
  genrePlaylist.parent?.name ?? "none"
1874
1949
  ] }),
1875
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1950
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1876
1951
  GenreSearch,
1877
1952
  {
1878
1953
  genrePlaylists: candidates,
@@ -1880,22 +1955,22 @@ function ChangeParentPopup({
1880
1955
  placeholder: "Search a new parent\u2026"
1881
1956
  }
1882
1957
  ) }),
1883
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1958
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1884
1959
  ] });
1885
1960
  }
1886
1961
 
1887
1962
  // src/genre-tree/artist/useArtistPanel.tsx
1888
- var import_ui14 = require("@behindthemusictree/ui");
1889
- var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
1963
+ var import_ui15 = require("@behindthemusictree/ui");
1964
+ var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
1890
1965
 
1891
1966
  // src/genre-tree/artist/ArtistHeader.tsx
1892
- var import_ui10 = require("@behindthemusictree/ui");
1967
+ var import_ui11 = require("@behindthemusictree/ui");
1893
1968
 
1894
1969
  // src/genre-tree/artist/ArtistLinks.tsx
1895
1970
  var import_lucide_react12 = require("lucide-react");
1896
1971
  var import_si3 = require("react-icons/si");
1897
- var import_ui9 = require("@behindthemusictree/ui");
1898
- var import_jsx_runtime28 = require("react/jsx-runtime");
1972
+ var import_ui10 = require("@behindthemusictree/ui");
1973
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1899
1974
  var LINK_KINDS = {
1900
1975
  official: { label: "Official website", Icon: import_lucide_react12.Globe },
1901
1976
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
@@ -1932,7 +2007,7 @@ function artistLinks(artist) {
1932
2007
  return links;
1933
2008
  }
1934
2009
  function ArtistLinks({ links }) {
1935
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui9.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
2010
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui10.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1936
2011
  }
1937
2012
 
1938
2013
  // src/genre-tree/artist/formatYears.ts
@@ -1943,7 +2018,7 @@ function formatYears(beginYear, endYear, ended) {
1943
2018
  }
1944
2019
 
1945
2020
  // src/genre-tree/artist/ArtistHeader.tsx
1946
- var import_jsx_runtime29 = require("react/jsx-runtime");
2021
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1947
2022
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1948
2023
  function ArtistHeader({ artist }) {
1949
2024
  const fields = [];
@@ -1953,35 +2028,35 @@ function ArtistHeader({ artist }) {
1953
2028
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1954
2029
  if (years) fields.push({ label: "Active", value: years });
1955
2030
  const links = artistLinks(artist);
1956
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ArtistLinks, { links }) });
2031
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ArtistLinks, { links }) });
1957
2032
  if (fields.length === 0) return null;
1958
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.FieldList, { items: fields });
2033
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.FieldList, { items: fields });
1959
2034
  }
1960
2035
 
1961
2036
  // src/genre-tree/artist/ArtistPhoto.tsx
1962
- var import_jsx_runtime30 = require("react/jsx-runtime");
2037
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1963
2038
  function ExternalLink({ href, children }) {
1964
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
2039
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1965
2040
  }
1966
2041
  function ArtistPhoto({ image, name }) {
1967
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1968
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1969
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
2042
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
2044
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1970
2045
  "Photo: ",
1971
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
2046
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1972
2047
  ",",
1973
2048
  " ",
1974
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
2049
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1975
2050
  ] })
1976
2051
  ] });
1977
2052
  }
1978
2053
 
1979
2054
  // src/genre-tree/artist/ArtistMembers.tsx
1980
- var import_ui11 = require("@behindthemusictree/ui");
1981
- var import_jsx_runtime31 = require("react/jsx-runtime");
2055
+ var import_ui12 = require("@behindthemusictree/ui");
2056
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1982
2057
  function MembershipList({ title, memberships, renderArtistLink }) {
1983
2058
  if (memberships.length === 0) return null;
1984
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: memberships.map((membership, index) => {
2059
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { children: memberships.map((membership, index) => {
1985
2060
  const details = [
1986
2061
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1987
2062
  ...membership.roles
@@ -1989,9 +2064,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1989
2064
  const detailsText = details.filter(Boolean).join(" \xB7 ");
1990
2065
  return (
1991
2066
  // A member can have several periods in the same group.
1992
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1993
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1994
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2067
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2068
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2069
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
1995
2070
  " \u2014 ",
1996
2071
  detailsText
1997
2072
  ] })
@@ -2002,9 +2077,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2002
2077
  function ArtistMembers({ title, memberships, renderArtistLink }) {
2003
2078
  if (memberships.length === 0) return null;
2004
2079
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
2005
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ui11.PanelSection, { title, children: [
2006
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2007
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2080
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_ui12.PanelSection, { title, children: [
2081
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2082
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2008
2083
  MembershipList,
2009
2084
  {
2010
2085
  title: "Past",
@@ -2016,8 +2091,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
2016
2091
  }
2017
2092
 
2018
2093
  // src/genre-tree/artist/ArtistDiscography.tsx
2019
- var import_ui12 = require("@behindthemusictree/ui");
2020
- var import_jsx_runtime32 = require("react/jsx-runtime");
2094
+ var import_ui13 = require("@behindthemusictree/ui");
2095
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2021
2096
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
2022
2097
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
2023
2098
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -2037,12 +2112,12 @@ function byYear(a, b) {
2037
2112
  function ArtistDiscography({ albums }) {
2038
2113
  if (albums.length === 0) return null;
2039
2114
  const sorted = [...albums].sort(byYear);
2040
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2115
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2041
2116
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
2042
2117
  if (groupAlbums.length === 0) return null;
2043
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2044
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2045
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2118
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2119
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2120
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2046
2121
  ] }, album.uuid)) }) }, group);
2047
2122
  }) });
2048
2123
  }
@@ -2051,20 +2126,20 @@ function ArtistDiscography({ albums }) {
2051
2126
  var import_react14 = require("react");
2052
2127
  var import_react_query2 = require("@tanstack/react-query");
2053
2128
  var import_lucide_react13 = require("lucide-react");
2054
- var import_ui13 = require("@behindthemusictree/ui");
2129
+ var import_ui14 = require("@behindthemusictree/ui");
2055
2130
  var import_player10 = require("@behindthemusictree/app-kit/player");
2056
- var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
2131
+ var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
2057
2132
  var import_transport9 = require("@behindthemusictree/app-kit/transport");
2058
2133
 
2059
2134
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
2060
2135
  var import_player9 = require("@behindthemusictree/app-kit/player");
2061
- var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
2136
+ var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
2062
2137
 
2063
2138
  // src/genre-tree/TrackPositionPlayPause.tsx
2064
2139
  var import_fa5 = require("react-icons/fa");
2065
2140
  var import_player7 = require("@behindthemusictree/app-kit/player");
2066
2141
  var import_player8 = require("@behindthemusictree/app-kit/player");
2067
- var import_jsx_runtime33 = require("react/jsx-runtime");
2142
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2068
2143
  function TrackPositionPlayPause({
2069
2144
  position,
2070
2145
  uuid,
@@ -2078,14 +2153,14 @@ function TrackPositionPlayPause({
2078
2153
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
2079
2154
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
2080
2155
  const isCurrentTrackActive = isPlaying || isPaused;
2081
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
2156
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2082
2157
  "div",
2083
2158
  {
2084
2159
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
2085
2160
  style: { minWidth: "4rem", maxWidth: "4rem" },
2086
2161
  onClick: handlePlayPauseClick,
2087
2162
  children: [
2088
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
2163
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2089
2164
  "span",
2090
2165
  {
2091
2166
  className: "track-position-play-pause__bars",
@@ -2093,18 +2168,18 @@ function TrackPositionPlayPause({
2093
2168
  role: "img",
2094
2169
  "aria-label": isPlaying ? "Playing" : "Paused",
2095
2170
  children: [
2096
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
2097
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
2098
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" })
2171
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__bar" }),
2172
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__bar" }),
2173
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__bar" })
2099
2174
  ]
2100
2175
  }
2101
2176
  ) : position }),
2102
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2177
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2103
2178
  "div",
2104
2179
  {
2105
2180
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
2106
2181
  "aria-hidden": "true",
2107
- children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa5.FaPlay, {})
2182
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fa5.FaPlay, {})
2108
2183
  }
2109
2184
  )
2110
2185
  ]
@@ -2113,9 +2188,9 @@ function TrackPositionPlayPause({
2113
2188
  }
2114
2189
 
2115
2190
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
2116
- var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
2117
- var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
2118
- var import_jsx_runtime34 = require("react/jsx-runtime");
2191
+ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
2192
+ var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
2193
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2119
2194
  function TrackItem({
2120
2195
  track,
2121
2196
  position,
@@ -2125,11 +2200,11 @@ function TrackItem({
2125
2200
  renderSongLink
2126
2201
  }) {
2127
2202
  const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
2128
- const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
2203
+ const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree14.useTrackList)();
2129
2204
  const scope = trackList?.origin?.scope ?? null;
2130
2205
  const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
2131
2206
  const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
2132
- const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree12.isSong)(track) && (0, import_genre_tree12.songUnplayableReason)(track) !== null;
2207
+ const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree15.isSong)(track) && (0, import_genre_tree15.songUnplayableReason)(track) !== null;
2133
2208
  const handlePlayPauseClick = (event) => {
2134
2209
  event.stopPropagation();
2135
2210
  if (isCurrentTrack) {
@@ -2139,12 +2214,12 @@ function TrackItem({
2139
2214
  loadTrackForPlayer(track.uuid);
2140
2215
  }
2141
2216
  };
2142
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2217
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2143
2218
  "div",
2144
2219
  {
2145
2220
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
2146
2221
  children: [
2147
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2222
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2148
2223
  "div",
2149
2224
  {
2150
2225
  className: "flex items-center justify-center text-lg w-16",
@@ -2152,7 +2227,7 @@ function TrackItem({
2152
2227
  "aria-disabled": "true",
2153
2228
  children: position
2154
2229
  }
2155
- ) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2230
+ ) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2156
2231
  TrackPositionPlayPause,
2157
2232
  {
2158
2233
  position,
@@ -2161,12 +2236,12 @@ function TrackItem({
2161
2236
  isSelected: selectedTrack?.uuid === track.uuid
2162
2237
  }
2163
2238
  ),
2164
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2165
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime34.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_runtime34.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2166
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2239
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2240
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.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_runtime38.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2241
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2167
2242
  ] }),
2168
- /* @__PURE__ */ (0, import_jsx_runtime34.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_runtime34.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
2169
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2243
+ /* @__PURE__ */ (0, import_jsx_runtime38.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_runtime38.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
2244
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2170
2245
  "div",
2171
2246
  {
2172
2247
  className: "duration flex text-base w-16 items-center justify-center",
@@ -2174,15 +2249,15 @@ function TrackItem({
2174
2249
  children: renderDuration(track)
2175
2250
  }
2176
2251
  ),
2177
- renderActions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2252
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2178
2253
  ]
2179
2254
  }
2180
2255
  );
2181
2256
  }
2182
2257
 
2183
2258
  // src/genre-tree/artist/ArtistSongs.tsx
2184
- var import_jsx_runtime35 = require("react/jsx-runtime");
2185
- var SongPageSchema2 = (0, import_transport9.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
2259
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2260
+ var SongPageSchema2 = (0, import_transport9.PaginatedResponseSchema)(import_genre_tree17.SongDetailedSchema);
2186
2261
  var SORTS = [
2187
2262
  { ordering: "title", label: "Title" },
2188
2263
  { ordering: "-albumYear", label: "Year" }
@@ -2191,11 +2266,11 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2191
2266
  const [page, setPage] = (0, import_react14.useState)(1);
2192
2267
  const [ordering, setOrdering] = (0, import_react14.useState)("title");
2193
2268
  const { fetch } = (0, import_transport9.useFetchWrapper)(getBackendBaseUrl);
2194
- const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
2269
+ const { playNewTrackListFromArtist } = (0, import_genre_tree17.useTrackList)();
2195
2270
  const { playerTrackObject } = (0, import_player10.usePlayer)();
2196
2271
  const { data, isPending, isError } = (0, import_transport9.useQueryWithParse)({
2197
2272
  queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
2198
- queryFn: () => fetch(import_genre_tree14.libraryEndpoints.reference.songs.list(), true, false, void 0, {
2273
+ queryFn: () => fetch(import_genre_tree17.libraryEndpoints.reference.songs.list(), true, false, void 0, {
2199
2274
  artist: artist.uuid,
2200
2275
  ordering,
2201
2276
  page
@@ -2210,25 +2285,25 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2210
2285
  });
2211
2286
  const handleRowClickCapture = (event, song) => {
2212
2287
  if (!(event.target instanceof Element) || !event.target.closest(".track-position-play-pause")) return;
2213
- if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree14.songUnplayableReason)(song) !== null) return;
2288
+ if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree17.songUnplayableReason)(song) !== null) return;
2214
2289
  event.stopPropagation();
2215
2290
  void play(song.uuid);
2216
2291
  };
2217
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2218
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2219
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2292
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui14.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui14.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
2293
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2294
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
2220
2295
  "button",
2221
2296
  {
2222
2297
  type: "button",
2223
2298
  onClick: () => void play(),
2224
2299
  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",
2225
2300
  children: [
2226
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2301
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react13.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2227
2302
  "Play all"
2228
2303
  ]
2229
2304
  }
2230
2305
  ),
2231
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2306
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2232
2307
  "button",
2233
2308
  {
2234
2309
  type: "button",
@@ -2243,7 +2318,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2243
2318
  sort.ordering
2244
2319
  )) })
2245
2320
  ] }),
2246
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2321
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2247
2322
  TrackItem,
2248
2323
  {
2249
2324
  track: song,
@@ -2252,12 +2327,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2252
2327
  renderSongLink
2253
2328
  }
2254
2329
  ) }, song.uuid)) }),
2255
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2330
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui14.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2256
2331
  ] }) });
2257
2332
  }
2258
2333
 
2259
2334
  // src/genre-tree/artist/useArtistPanel.tsx
2260
- var import_jsx_runtime36 = require("react/jsx-runtime");
2335
+ var import_jsx_runtime40 = require("react/jsx-runtime");
2261
2336
  function useArtistPanel({
2262
2337
  uuid,
2263
2338
  scope,
@@ -2269,7 +2344,7 @@ function useArtistPanel({
2269
2344
  onExpandedChange,
2270
2345
  onClose
2271
2346
  }) {
2272
- const result = (0, import_genre_tree15.useArtist)(uuid, getBackendBaseUrl);
2347
+ const result = (0, import_genre_tree18.useArtist)(uuid, getBackendBaseUrl);
2273
2348
  const panel = { expanded, onExpandedChange, onClose };
2274
2349
  switch (result.status) {
2275
2350
  case "idle":
@@ -2278,26 +2353,26 @@ function useArtistPanel({
2278
2353
  return {
2279
2354
  ...panel,
2280
2355
  title: "Artist",
2281
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2282
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2283
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
2284
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
2285
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" }),
2286
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-16 mt-2.5" }),
2287
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-16" })
2356
+ content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2357
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
2358
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-4 w-32" }),
2359
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
2360
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-4 w-24" }),
2361
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-16 mt-2.5" }),
2362
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Skeleton, { className: "h-16" })
2288
2363
  ] })
2289
2364
  };
2290
2365
  case "not-found":
2291
2366
  return {
2292
2367
  ...panel,
2293
2368
  title: "Artist not found",
2294
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2369
+ content: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2295
2370
  };
2296
2371
  case "error":
2297
2372
  return {
2298
2373
  ...panel,
2299
2374
  title: "Artist",
2300
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2375
+ content: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2301
2376
  };
2302
2377
  case "success": {
2303
2378
  const { artist } = result;
@@ -2306,13 +2381,13 @@ function useArtistPanel({
2306
2381
  return {
2307
2382
  ...panel,
2308
2383
  title: artist.name,
2309
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
2310
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2311
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2312
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistHeader, { artist }),
2313
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2314
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2315
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2384
+ content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2385
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2386
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2387
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistHeader, { artist }),
2388
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2389
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2390
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2316
2391
  ArtistSongs,
2317
2392
  {
2318
2393
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2322,56 +2397,56 @@ function useArtistPanel({
2322
2397
  }
2323
2398
  )
2324
2399
  ] }),
2325
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2400
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2326
2401
  };
2327
2402
  }
2328
2403
  }
2329
2404
  }
2330
2405
 
2331
2406
  // src/genre-tree/song/useSongPanel.tsx
2332
- var import_ui16 = require("@behindthemusictree/ui");
2333
- var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
2407
+ var import_ui17 = require("@behindthemusictree/ui");
2408
+ var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
2334
2409
 
2335
2410
  // src/genre-tree/song/SongPanelContent.tsx
2336
2411
  var import_react15 = require("react");
2337
2412
  var import_lucide_react14 = require("lucide-react");
2338
- var import_ui15 = require("@behindthemusictree/ui");
2413
+ var import_ui16 = require("@behindthemusictree/ui");
2339
2414
  var import_player11 = require("@behindthemusictree/app-kit/player");
2340
- var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
2341
- var import_jsx_runtime37 = require("react/jsx-runtime");
2415
+ var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
2416
+ var import_jsx_runtime41 = require("react/jsx-runtime");
2342
2417
  var numberFormat = new Intl.NumberFormat("en");
2343
2418
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2344
2419
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
2345
2420
  function SongHistory({ uuid, getBackendBaseUrl }) {
2346
2421
  const [open, setOpen] = (0, import_react15.useState)(false);
2347
- const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
2422
+ const history = (0, import_genre_tree19.useSongHistory)(uuid, getBackendBaseUrl, open);
2348
2423
  let body = null;
2349
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.Skeleton, { className: "h-10", "aria-busy": "true" });
2350
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2424
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.Skeleton, { className: "h-10", "aria-busy": "true" });
2425
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2351
2426
  else if (history.status === "success")
2352
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "py-1.5 text-sm", children: [
2353
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "font-medium", children: entry.action }),
2354
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2427
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("li", { className: "py-1.5 text-sm", children: [
2428
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: entry.action }),
2429
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2355
2430
  " ",
2356
2431
  "\u2014 ",
2357
2432
  entry.oldValue ?? "none",
2358
2433
  " \u2192 ",
2359
2434
  entry.newValue ?? "none"
2360
2435
  ] }) : null,
2361
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2436
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2362
2437
  entry.actorPseudo ?? "Anonymous",
2363
2438
  " \xB7 ",
2364
2439
  dateFormat.format(new Date(entry.createdOn))
2365
2440
  ] })
2366
2441
  ] }, entry.uuid)) });
2367
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2442
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2368
2443
  "details",
2369
2444
  {
2370
2445
  onToggle: (event) => setOpen(event.currentTarget.open),
2371
2446
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
2372
2447
  children: [
2373
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2374
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "mt-1.5", children: body })
2448
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2449
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "mt-1.5", children: body })
2375
2450
  ]
2376
2451
  }
2377
2452
  );
@@ -2385,25 +2460,25 @@ function SongPanelContent({
2385
2460
  genrePath,
2386
2461
  onSelectGenre
2387
2462
  }) {
2388
- const { playNewTrackListFromTrackUuid } = (0, import_genre_tree16.useTrackList)();
2463
+ const { playNewTrackListFromTrackUuid } = (0, import_genre_tree19.useTrackList)();
2389
2464
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
2390
- const unplayableReason = (0, import_genre_tree16.songUnplayableReason)(song);
2465
+ const unplayableReason = (0, import_genre_tree19.songUnplayableReason)(song);
2391
2466
  const isCurrent = playerTrackObject?.track.id === song.uuid;
2392
2467
  const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
2393
2468
  const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
2394
- const musicBrainzUrl = (0, import_genre_tree16.musicBrainzRecordingUrl)(song);
2469
+ const musicBrainzUrl = (0, import_genre_tree19.musicBrainzRecordingUrl)(song);
2395
2470
  const fields = [];
2396
2471
  if (song.genre) {
2397
2472
  const path = genrePath.length > 0 ? genrePath : null;
2398
2473
  fields.push({
2399
2474
  label: "Genre",
2400
- value: path ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
2401
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2402
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2475
+ value: path ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
2476
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2477
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2403
2478
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2404
2479
  });
2405
2480
  } else {
2406
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2481
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2407
2482
  }
2408
2483
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2409
2484
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2413,11 +2488,11 @@ function SongPanelContent({
2413
2488
  const links = [];
2414
2489
  if (musicBrainzUrl)
2415
2490
  links.push(
2416
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2491
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2417
2492
  );
2418
2493
  if (song.youtubeVideo)
2419
2494
  links.push(
2420
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2495
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2421
2496
  "a",
2422
2497
  {
2423
2498
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2432,33 +2507,33 @@ function SongPanelContent({
2432
2507
  if (links.length > 0)
2433
2508
  fields.push({
2434
2509
  label: "Links",
2435
- value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex gap-3", children: links })
2510
+ value: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "flex gap-3", children: links })
2436
2511
  });
2437
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
2438
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2439
- unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2512
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
2513
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2514
+ unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2440
2515
  "button",
2441
2516
  {
2442
2517
  type: "button",
2443
2518
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2444
2519
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2445
2520
  children: [
2446
- /* @__PURE__ */ (0, import_jsx_runtime37.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_runtime37.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2521
+ /* @__PURE__ */ (0, import_jsx_runtime41.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_runtime41.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2447
2522
  isPlaying ? "Pause" : "Play"
2448
2523
  ]
2449
2524
  }
2450
2525
  ),
2451
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2526
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2452
2527
  ] }) }),
2453
- song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2454
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.FieldList, { items: fields }),
2455
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2456
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2528
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree19.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2529
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.FieldList, { items: fields }),
2530
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2531
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2457
2532
  ] });
2458
2533
  }
2459
2534
 
2460
2535
  // src/genre-tree/song/useSongPanel.tsx
2461
- var import_jsx_runtime38 = require("react/jsx-runtime");
2536
+ var import_jsx_runtime42 = require("react/jsx-runtime");
2462
2537
  function useSongPanel({
2463
2538
  uuid,
2464
2539
  scope,
@@ -2471,7 +2546,7 @@ function useSongPanel({
2471
2546
  onExpandedChange,
2472
2547
  onClose
2473
2548
  }) {
2474
- const result = (0, import_genre_tree17.useSong)(uuid, getBackendBaseUrl);
2549
+ const result = (0, import_genre_tree20.useSong)(uuid, getBackendBaseUrl);
2475
2550
  const panel = { expanded, onExpandedChange, onClose };
2476
2551
  switch (result.status) {
2477
2552
  case "idle":
@@ -2480,24 +2555,24 @@ function useSongPanel({
2480
2555
  return {
2481
2556
  ...panel,
2482
2557
  title: "Song",
2483
- content: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2484
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
2485
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-4 w-32" }),
2486
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
2487
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-4 w-24" })
2558
+ content: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2559
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2560
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-4 w-32" }),
2561
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-3 w-16" }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.Skeleton, { className: "h-4 w-24" })
2488
2563
  ] })
2489
2564
  };
2490
2565
  case "not-found":
2491
2566
  return {
2492
2567
  ...panel,
2493
2568
  title: "Song not found",
2494
- content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2569
+ content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2495
2570
  };
2496
2571
  case "error":
2497
2572
  return {
2498
2573
  ...panel,
2499
2574
  title: "Song",
2500
- content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2575
+ content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2501
2576
  };
2502
2577
  case "success": {
2503
2578
  const { song } = result;
@@ -2505,7 +2580,7 @@ function useSongPanel({
2505
2580
  return {
2506
2581
  ...panel,
2507
2582
  title: song.title,
2508
- content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2583
+ content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2509
2584
  SongPanelContent,
2510
2585
  {
2511
2586
  song,
@@ -2524,29 +2599,29 @@ function useSongPanel({
2524
2599
 
2525
2600
  // src/genre-tree/SizeMetricToggle.tsx
2526
2601
  var import_md2 = require("react-icons/md");
2527
- var import_ui17 = require("@behindthemusictree/ui");
2528
- var import_jsx_runtime39 = require("react/jsx-runtime");
2602
+ var import_ui18 = require("@behindthemusictree/ui");
2603
+ var import_jsx_runtime43 = require("react/jsx-runtime");
2529
2604
  function SizeMetricToggle({
2530
2605
  value,
2531
2606
  onChange
2532
2607
  }) {
2533
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2534
- import_ui17.SegmentedControl,
2608
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2609
+ import_ui18.SegmentedControl,
2535
2610
  {
2536
2611
  variant: "floating",
2537
2612
  "aria-label": "Size nodes by",
2538
2613
  value,
2539
2614
  onChange,
2540
2615
  options: [
2541
- { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2542
- { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2616
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2617
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2543
2618
  ]
2544
2619
  }
2545
2620
  );
2546
2621
  }
2547
2622
 
2548
2623
  // src/genre-tree/GenreTreeView.tsx
2549
- var import_jsx_runtime40 = require("react/jsx-runtime");
2624
+ var import_jsx_runtime44 = require("react/jsx-runtime");
2550
2625
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2551
2626
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2552
2627
  var noop = () => {
@@ -2580,6 +2655,7 @@ function GenreTreeView({
2580
2655
  artistExpanded: controlledArtistExpanded,
2581
2656
  onArtistExpandedChange,
2582
2657
  getShareUrl,
2658
+ extraSearchGroups = [],
2583
2659
  artistSearch = true,
2584
2660
  onArtistSelect,
2585
2661
  artistSearchBaseUrl,
@@ -2645,7 +2721,7 @@ function GenreTreeView({
2645
2721
  allowWheelRotation: internalAllowWheelRotation,
2646
2722
  toggleWheelRotation,
2647
2723
  onNodeHover: handleNodeHover
2648
- } = (0, import_genre_tree19.useGenreTreeViewState)({
2724
+ } = (0, import_genre_tree21.useGenreTreeViewState)({
2649
2725
  scope,
2650
2726
  getBackendBaseUrl,
2651
2727
  viewMode: controlledViewMode,
@@ -2664,12 +2740,12 @@ function GenreTreeView({
2664
2740
  restoreRootUuid = selectedGenrePlaylist.root.uuid;
2665
2741
  setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
2666
2742
  }
2667
- const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree19.genreUpdateMutationKey }) > 0;
2668
- const { mutate: updateGenre } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
2743
+ const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree21.genreUpdateMutationKey }) > 0;
2744
+ const { mutate: updateGenre } = (0, import_genre_tree21.useUpdateGenre)(scope, getBackendBaseUrl);
2669
2745
  const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
2670
2746
  const isArtistFiltered = artistUuid !== null;
2671
2747
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2672
- const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2748
+ const artistResult = (0, import_genre_tree21.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2673
2749
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2674
2750
  const filterArtist = (0, import_react16.useMemo)(
2675
2751
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
@@ -2689,7 +2765,7 @@ function GenreTreeView({
2689
2765
  const openSongRef = (0, import_react16.useRef)(openSong);
2690
2766
  openSongRef.current = openSong;
2691
2767
  const resolvedRenderSongLink = (0, import_react16.useCallback)(
2692
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2768
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2693
2769
  [renderSongLink]
2694
2770
  );
2695
2771
  const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
@@ -2701,7 +2777,7 @@ function GenreTreeView({
2701
2777
  scope,
2702
2778
  getBackendBaseUrl: artistBaseUrl,
2703
2779
  getShareUrl: getShareUrlRef.current,
2704
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2780
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2705
2781
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2706
2782
  onExpandedChange: (on) => {
2707
2783
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2710,7 +2786,7 @@ function GenreTreeView({
2710
2786
  renderSongLink: resolvedRenderSongLink,
2711
2787
  onClose: () => changeArtist(null)
2712
2788
  });
2713
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2789
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2714
2790
  const getGenrePath = (genre) => {
2715
2791
  const path = [];
2716
2792
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2766,7 +2842,7 @@ function GenreTreeView({
2766
2842
  []
2767
2843
  );
2768
2844
  const panelRef = (0, import_react16.useRef)(null);
2769
- const obscuredInset = (0, import_ui18.useObscuredInset)(panelRef);
2845
+ const obscuredInset = (0, import_ui19.useObscuredInset)(panelRef);
2770
2846
  const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2771
2847
  const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2772
2848
  const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
@@ -2789,52 +2865,27 @@ function GenreTreeView({
2789
2865
  const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2790
2866
  const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
2791
2867
  const [searchText, setSearchText] = (0, import_react16.useState)("");
2792
- const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
2793
- const artistGroup = artistSearch ? {
2794
- key: "artists",
2795
- label: "Artists",
2796
- items: artistSearchResult.artists.map((artist) => ({
2797
- key: artist.uuid,
2798
- content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2799
- /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "block truncate font-medium", children: [
2800
- artist.name,
2801
- artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
2802
- " \xB7 ",
2803
- artist.disambiguation
2804
- ] })
2805
- ] }),
2806
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
2807
- "also known as ",
2808
- artist.matchedAlias
2809
- ] })
2810
- ] }),
2811
- onSelect: () => {
2812
- openArtist(artist.uuid);
2813
- onArtistSelect?.(artist);
2814
- }
2815
- })),
2816
- status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
2817
- loading: artistSearchResult.pending
2818
- } : void 0;
2819
- const songSearchResult = (0, import_genre_tree18.useSongSearch)(songBaseUrl, songSearch ? searchText : "");
2820
- const songGroup = songSearch ? {
2821
- key: "songs",
2822
- label: "Songs",
2823
- items: songSearchResult.songs.map((song) => ({
2824
- key: song.uuid,
2825
- content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
2826
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "block truncate font-medium", children: song.title }),
2827
- /* @__PURE__ */ (0, import_jsx_runtime40.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 ") })
2828
- ] }),
2829
- onSelect: () => {
2830
- openSong(song.uuid);
2831
- onSongSelect?.(song);
2832
- }
2833
- })),
2834
- status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
2835
- loading: songSearchResult.pending
2836
- } : void 0;
2837
- const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
2868
+ const artistGroup = useArtistSearchGroup(
2869
+ artistBaseUrl,
2870
+ searchText,
2871
+ (artist) => {
2872
+ openArtist(artist.uuid);
2873
+ onArtistSelect?.(artist);
2874
+ },
2875
+ Boolean(artistSearch)
2876
+ );
2877
+ const songGroup = useSongSearchGroup(
2878
+ songBaseUrl,
2879
+ searchText,
2880
+ (song) => {
2881
+ openSong(song.uuid);
2882
+ onSongSelect?.(song);
2883
+ },
2884
+ Boolean(songSearch)
2885
+ );
2886
+ const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
2887
+ (group) => group !== void 0
2888
+ );
2838
2889
  const handleSelectedNodeChange = (node) => {
2839
2890
  if (node !== null && songUuid !== null) changeSong(null);
2840
2891
  selectNode(node);
@@ -2858,7 +2909,7 @@ function GenreTreeView({
2858
2909
  };
2859
2910
  const renderGenreBody = (relation) => {
2860
2911
  const { node } = relation;
2861
- const family = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2912
+ const family = /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2862
2913
  import_genre_tree_view4.GenreNodeFamily,
2863
2914
  {
2864
2915
  node,
@@ -2869,10 +2920,10 @@ function GenreTreeView({
2869
2920
  onSelectNode: selectGenreFromPanel
2870
2921
  }
2871
2922
  );
2872
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2923
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2873
2924
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2874
2925
  const criteria = genrePlaylist?.criteria;
2875
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2926
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
2876
2927
  family,
2877
2928
  ancestors
2878
2929
  ] }) };
@@ -2892,11 +2943,11 @@ function GenreTreeView({
2892
2943
  (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2893
2944
  ) : void 0;
2894
2945
  return {
2895
- primary: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2896
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2946
+ primary: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
2947
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2897
2948
  GenreInfoPanelBody,
2898
2949
  {
2899
- family: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
2950
+ family: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
2900
2951
  family,
2901
2952
  ancestors
2902
2953
  ] }),
@@ -2914,7 +2965,7 @@ function GenreTreeView({
2914
2965
  onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2915
2966
  onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2916
2967
  onChangeParent: readOnly ? void 0 : () => showPopup(
2917
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2968
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2918
2969
  ChangeParentPopup,
2919
2970
  {
2920
2971
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2989,19 +3040,19 @@ function GenreTreeView({
2989
3040
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2990
3041
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2991
3042
  } : void 0;
2992
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2993
- import_ui18.IconButton,
3043
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3044
+ import_ui19.IconButton,
2994
3045
  {
2995
3046
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2996
3047
  style: activePanel.headerAccent && { color: "inherit" },
2997
3048
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2998
- children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react16.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react16.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3049
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react16.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react16.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2999
3050
  }
3000
3051
  ) : null;
3001
3052
  const renderArtistFilter = () => {
3002
3053
  if (isLoading || !filterArtist) return null;
3003
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
3004
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3054
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
3055
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3005
3056
  "button",
3006
3057
  {
3007
3058
  type: "button",
@@ -3013,7 +3064,7 @@ function GenreTreeView({
3013
3064
  children: filterArtist.name
3014
3065
  }
3015
3066
  ),
3016
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3067
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3017
3068
  "button",
3018
3069
  {
3019
3070
  type: "button",
@@ -3025,8 +3076,8 @@ function GenreTreeView({
3025
3076
  )
3026
3077
  ] });
3027
3078
  };
3028
- const actions = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
3029
- !isLoading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3079
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
3080
+ !isLoading && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3030
3081
  GenreSearch,
3031
3082
  {
3032
3083
  genrePlaylists: visibleGenrePlaylists,
@@ -3034,14 +3085,14 @@ function GenreTreeView({
3034
3085
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
3035
3086
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
3036
3087
  onPrevious: handlePrevious,
3037
- onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
3088
+ onQueryChange: artistSearch || songSearch || extraSearchGroups.length > 0 ? setSearchText : void 0,
3038
3089
  extraGroups
3039
3090
  }
3040
3091
  ),
3041
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3042
- /* @__PURE__ */ (0, import_jsx_runtime40.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: [
3043
- !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3044
- import_ui18.SegmentedControl,
3092
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3093
+ /* @__PURE__ */ (0, import_jsx_runtime44.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: [
3094
+ !isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3095
+ import_ui19.SegmentedControl,
3045
3096
  {
3046
3097
  "aria-label": "Tree view mode",
3047
3098
  value: viewMode,
@@ -3054,14 +3105,14 @@ function GenreTreeView({
3054
3105
  ]
3055
3106
  }
3056
3107
  ),
3057
- !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3108
+ !isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3058
3109
  "div",
3059
3110
  {
3060
3111
  className: "flex items-center gap-1",
3061
3112
  role: "group",
3062
3113
  "aria-label": "Tree display options",
3063
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3064
- import_ui18.Button,
3114
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3115
+ import_ui19.Button,
3065
3116
  {
3066
3117
  variant: allowWheelRotation ? "default" : "outline",
3067
3118
  size: "sm",
@@ -3071,8 +3122,8 @@ function GenreTreeView({
3071
3122
  )
3072
3123
  }
3073
3124
  ),
3074
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3075
- import_ui18.IconTextButton,
3125
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3126
+ import_ui19.IconTextButton,
3076
3127
  {
3077
3128
  icon: import_lucide_react15.Plus,
3078
3129
  text: "Add root",
@@ -3081,34 +3132,34 @@ function GenreTreeView({
3081
3132
  )
3082
3133
  ] })
3083
3134
  ] });
3084
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
3135
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
3085
3136
  "div",
3086
3137
  {
3087
3138
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
3088
3139
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
3089
3140
  children: [
3090
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3141
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3091
3142
  "div",
3092
3143
  {
3093
3144
  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"}`,
3094
3145
  children: actions
3095
3146
  }
3096
3147
  ),
3097
- !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3148
+ !isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3098
3149
  "div",
3099
3150
  {
3100
3151
  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"}`,
3101
3152
  children: renderArtistFilter()
3102
3153
  }
3103
3154
  ),
3104
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3105
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3106
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
3155
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3156
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3157
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
3107
3158
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
3108
3159
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
3109
3160
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
3110
3161
  // The panel's own Zoom in button is hidden: the search bar carries it.
3111
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3162
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3112
3163
  GenrePlaylistTreeWheel,
3113
3164
  {
3114
3165
  scope,
@@ -3125,9 +3176,9 @@ function GenreTreeView({
3125
3176
  allowWheelRotation,
3126
3177
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3127
3178
  busy,
3128
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
3179
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex items-center gap-2", children: [
3129
3180
  floatingControlsLeading,
3130
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3181
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3131
3182
  ] }) : void 0,
3132
3183
  defaultViewport,
3133
3184
  viewportRestore,
@@ -3136,7 +3187,7 @@ function GenreTreeView({
3136
3187
  ) }) })
3137
3188
  ) : viewMode === "pop-core" ? (
3138
3189
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
3139
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3190
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3140
3191
  GenrePlaylistTreeWheelRadialPopCore,
3141
3192
  {
3142
3193
  scope,
@@ -3153,9 +3204,9 @@ function GenreTreeView({
3153
3204
  allowWheelRotation,
3154
3205
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3155
3206
  busy,
3156
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
3207
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex items-center gap-2", children: [
3157
3208
  floatingControlsLeading,
3158
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3209
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3159
3210
  ] }) : void 0,
3160
3211
  defaultViewport,
3161
3212
  viewportRestore,
@@ -3163,7 +3214,7 @@ function GenreTreeView({
3163
3214
  ...focus
3164
3215
  }
3165
3216
  ) }) })
3166
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3217
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3167
3218
  GenrePlaylistTreeWheelRadialPopCore,
3168
3219
  {
3169
3220
  outline: true,
@@ -3179,13 +3230,13 @@ function GenreTreeView({
3179
3230
  selectedNodeId,
3180
3231
  readOnly
3181
3232
  }
3182
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime40.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(
3233
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime44.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(
3183
3234
  ([uuid, genrePlaylistTreePerRoot]) => {
3184
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3235
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3185
3236
  "div",
3186
3237
  {
3187
3238
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
3188
- children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3239
+ children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3189
3240
  GenrePlaylistTreePerRoot,
3190
3241
  {
3191
3242
  scope,
@@ -3211,8 +3262,8 @@ function GenreTreeView({
3211
3262
  );
3212
3263
  }
3213
3264
  ) }),
3214
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3215
- import_ui18.DetailPanel,
3265
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3266
+ import_ui19.DetailPanel,
3216
3267
  {
3217
3268
  ref: panelRef,
3218
3269
  side: "left",
@@ -3228,7 +3279,7 @@ function GenreTreeView({
3228
3279
  aside: activePanel?.aside,
3229
3280
  edgeTab: detailTab,
3230
3281
  headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
3231
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
3282
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
3232
3283
  activePanel.content,
3233
3284
  activePanel.aside
3234
3285
  ] }) : activePanel?.content
@@ -3243,10 +3294,10 @@ function GenreTreeView({
3243
3294
  // src/genre-tree/GenreDetailPanel.tsx
3244
3295
  var import_react17 = require("react");
3245
3296
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
3246
- var import_ui19 = require("@behindthemusictree/ui");
3297
+ var import_ui20 = require("@behindthemusictree/ui");
3247
3298
  var import_lucide_react17 = require("lucide-react");
3248
- var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
3249
- var import_jsx_runtime41 = require("react/jsx-runtime");
3299
+ var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
3300
+ var import_jsx_runtime45 = require("react/jsx-runtime");
3250
3301
  var RELATIVE_ITEM_COUNT = 1;
3251
3302
  function toNode(genre, parentId) {
3252
3303
  return {
@@ -3273,7 +3324,7 @@ function GenreDetailPanel({
3273
3324
  edgeTab,
3274
3325
  extras
3275
3326
  }) {
3276
- const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
3327
+ const { data: detail, isPending, isError } = (0, import_genre_tree22.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
3277
3328
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
3278
3329
  const view = (0, import_react17.useMemo)(() => {
3279
3330
  if (!detail) return null;
@@ -3301,17 +3352,17 @@ function GenreDetailPanel({
3301
3352
  };
3302
3353
  }, [detail]);
3303
3354
  if (isError) {
3304
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3355
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
3305
3356
  }
3306
3357
  if (isPending || !detail || !view) {
3307
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3308
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-6 w-2/3" }),
3309
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-4 w-full" }),
3310
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-4 w-3/4" })
3358
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
3359
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-6 w-2/3" }),
3360
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-4 w-full" }),
3361
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-4 w-3/4" })
3311
3362
  ] });
3312
3363
  }
3313
3364
  const selectGenre = (nodeId) => onSelectGenre(nodeId);
3314
- const family = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3365
+ const family = /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3315
3366
  import_genre_tree_view5.GenreNodeFamily,
3316
3367
  {
3317
3368
  node: view.node,
@@ -3322,20 +3373,20 @@ function GenreDetailPanel({
3322
3373
  onSelectNode: selectGenre
3323
3374
  }
3324
3375
  );
3325
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3326
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
3327
- import_ui19.DetailPanel,
3376
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
3377
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3378
+ import_ui20.DetailPanel,
3328
3379
  {
3329
3380
  side: "left",
3330
3381
  title: view.node.name,
3331
3382
  headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
3332
- headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3333
- import_ui19.IconButton,
3383
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3384
+ import_ui20.IconButton,
3334
3385
  {
3335
3386
  "aria-label": expanded ? "Shrink panel" : "Extend panel",
3336
3387
  style: { color: "inherit" },
3337
3388
  onClick: () => onExpandedChange(!expanded),
3338
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react17.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react17.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3389
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react17.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react17.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3339
3390
  }
3340
3391
  ) : void 0,
3341
3392
  onClose,
@@ -3344,10 +3395,10 @@ function GenreDetailPanel({
3344
3395
  onExpandedChange,
3345
3396
  edgeTab,
3346
3397
  children: [
3347
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
3398
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3348
3399
  GenreInfoPanelBody,
3349
3400
  {
3350
- family: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
3401
+ family: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
3351
3402
  family,
3352
3403
  ancestors
3353
3404
  ] }),
@@ -3373,10 +3424,10 @@ var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3373
3424
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3374
3425
  var import_react18 = require("react");
3375
3426
  var import_lucide_react18 = require("lucide-react");
3376
- var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
3377
- var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
3427
+ var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3428
+ var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3378
3429
  var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
3379
- var import_jsx_runtime42 = require("react/jsx-runtime");
3430
+ var import_jsx_runtime46 = require("react/jsx-runtime");
3380
3431
  var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
3381
3432
  function TrackListSidebar({
3382
3433
  className: className2 = "",
@@ -3389,7 +3440,7 @@ function TrackListSidebar({
3389
3440
  renderArtistLink,
3390
3441
  renderSongLink
3391
3442
  }) {
3392
- const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
3443
+ const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree23.useTrackList)();
3393
3444
  const trackNoun = (0, import_track_noun4.useTrackNoun)();
3394
3445
  const trackListRef = (0, import_react18.useRef)(null);
3395
3446
  const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
@@ -3478,25 +3529,25 @@ function TrackListSidebar({
3478
3529
  }
3479
3530
  };
3480
3531
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3481
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3532
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
3482
3533
  "div",
3483
3534
  {
3484
3535
  "data-theme": "dark",
3485
3536
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3486
3537
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3487
3538
  children: [
3488
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3489
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3490
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "origin flex text-xl", children: [
3491
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3492
- /* @__PURE__ */ (0, import_jsx_runtime42.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_runtime42.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3539
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3540
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3541
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "origin flex text-xl", children: [
3542
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3543
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3493
3544
  ] }),
3494
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3495
- 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 `,
3545
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3546
+ 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 `,
3496
3547
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3497
3548
  ] })
3498
3549
  ] }),
3499
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3550
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3500
3551
  "button",
3501
3552
  {
3502
3553
  type: "button",
@@ -3505,11 +3556,11 @@ function TrackListSidebar({
3505
3556
  "aria-expanded": !isCollapsed,
3506
3557
  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"}`,
3507
3558
  onClick: isCollapsed ? onExpand : onMinimize,
3508
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react18.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3559
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react18.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3509
3560
  }
3510
3561
  )
3511
3562
  ] }),
3512
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
3563
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
3513
3564
  "ul",
3514
3565
  {
3515
3566
  ref: trackListRef,
@@ -3519,7 +3570,7 @@ function TrackListSidebar({
3519
3570
  onPointerDown: markUserScrollIntent,
3520
3571
  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",
3521
3572
  children: [
3522
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
3573
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
3523
3574
  TrackItem,
3524
3575
  {
3525
3576
  track,
@@ -3530,7 +3581,7 @@ function TrackListSidebar({
3530
3581
  renderSongLink
3531
3582
  }
3532
3583
  ) }, track.uuid)),
3533
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3584
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3534
3585
  ]
3535
3586
  }
3536
3587
  )
@@ -3543,14 +3594,14 @@ function TrackListSidebar({
3543
3594
  var import_react19 = require("react");
3544
3595
  var import_tailwind_merge = require("tailwind-merge");
3545
3596
  var import_player13 = require("@behindthemusictree/app-kit/player");
3546
- var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
3547
- var import_ui20 = require("@behindthemusictree/ui");
3597
+ var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
3598
+ var import_ui21 = require("@behindthemusictree/ui");
3548
3599
 
3549
3600
  // src/genre-tree/PlayerControls.tsx
3550
3601
  var import_lucide_react19 = require("lucide-react");
3551
- var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
3602
+ var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3552
3603
  var import_player12 = require("@behindthemusictree/app-kit/player");
3553
- var import_jsx_runtime43 = require("react/jsx-runtime");
3604
+ var import_jsx_runtime47 = require("react/jsx-runtime");
3554
3605
  function PlayerControls() {
3555
3606
  const {
3556
3607
  duration,
@@ -3562,22 +3613,22 @@ function PlayerControls() {
3562
3613
  setVolume,
3563
3614
  volume
3564
3615
  } = (0, import_player12.usePlayer)();
3565
- const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree23.useTrackList)();
3616
+ const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree25.useTrackList)();
3566
3617
  const currentTime = (0, import_player12.useCurrentTime)();
3567
3618
  const isBusy = isLoading || playState === import_player12.PlayStates.LOADING;
3568
3619
  const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
3569
3620
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3570
3621
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3571
3622
  const isPlaying = playState === import_player12.PlayStates.PLAYING;
3572
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
3623
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
3573
3624
  "section",
3574
3625
  {
3575
3626
  "aria-label": "Player controls",
3576
3627
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3577
3628
  children: [
3578
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3579
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
3580
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3629
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3630
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree25.formatTime)(safeCurrentTime) }),
3631
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3581
3632
  "input",
3582
3633
  {
3583
3634
  "aria-label": "Seek",
@@ -3591,11 +3642,11 @@ function PlayerControls() {
3591
3642
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3592
3643
  }
3593
3644
  ),
3594
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
3645
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree25.formatTime)(safeDuration) })
3595
3646
  ] }),
3596
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3597
- /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3598
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3647
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3648
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3649
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3599
3650
  "button",
3600
3651
  {
3601
3652
  type: "button",
@@ -3604,10 +3655,10 @@ function PlayerControls() {
3604
3655
  disabled: !isReady || !hasPreviousTrack,
3605
3656
  onClick: playPreviousTrack,
3606
3657
  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",
3607
- children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3658
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3608
3659
  }
3609
3660
  ),
3610
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3661
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3611
3662
  "button",
3612
3663
  {
3613
3664
  type: "button",
@@ -3616,10 +3667,10 @@ function PlayerControls() {
3616
3667
  disabled: !isReady,
3617
3668
  onClick: handlePlayPauseAction,
3618
3669
  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",
3619
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3670
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3620
3671
  }
3621
3672
  ),
3622
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3673
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3623
3674
  "button",
3624
3675
  {
3625
3676
  type: "button",
@@ -3628,14 +3679,14 @@ function PlayerControls() {
3628
3679
  disabled: !isReady || !hasNextTrack,
3629
3680
  onClick: playNextTrack,
3630
3681
  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",
3631
- children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3682
+ children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3632
3683
  }
3633
3684
  )
3634
3685
  ] }),
3635
- /* @__PURE__ */ (0, import_jsx_runtime43.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: [
3636
- volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3637
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "sr-only", children: "Volume" }),
3638
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3686
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
3687
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react19.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3688
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "sr-only", children: "Volume" }),
3689
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
3639
3690
  "input",
3640
3691
  {
3641
3692
  "aria-label": "Volume",
@@ -3657,7 +3708,7 @@ function PlayerControls() {
3657
3708
  }
3658
3709
 
3659
3710
  // src/genre-tree/PlayerPanel.tsx
3660
- var import_jsx_runtime44 = require("react/jsx-runtime");
3711
+ var import_jsx_runtime48 = require("react/jsx-runtime");
3661
3712
  var PANEL_ID = "player-panel";
3662
3713
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3663
3714
  function PlayerPanel({
@@ -3670,7 +3721,7 @@ function PlayerPanel({
3670
3721
  renderSongLink
3671
3722
  }) {
3672
3723
  const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
3673
- const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
3724
+ const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree26.useTrackListSidebarVisibility)();
3674
3725
  const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3675
3726
  const loadedTrackTitle = playerTrackObject?.track.title;
3676
3727
  (0, import_react19.useEffect)(() => {
@@ -3697,7 +3748,7 @@ function PlayerPanel({
3697
3748
  onClick: () => setIsExtendRequested(true),
3698
3749
  expanded: false
3699
3750
  } : null;
3700
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
3751
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
3701
3752
  "div",
3702
3753
  {
3703
3754
  id: PANEL_ID,
@@ -3710,8 +3761,8 @@ function PlayerPanel({
3710
3761
  ),
3711
3762
  style: isExtended ? { width: extendedWidth } : void 0,
3712
3763
  children: [
3713
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3714
- import_ui20.PanelEdgeTab,
3764
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3765
+ import_ui21.PanelEdgeTab,
3715
3766
  {
3716
3767
  side: "right",
3717
3768
  edge: tabEdge,
@@ -3720,7 +3771,7 @@ function PlayerPanel({
3720
3771
  className: "absolute right-full top-1/2 -translate-y-1/2"
3721
3772
  }
3722
3773
  ),
3723
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
3774
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
3724
3775
  "div",
3725
3776
  {
3726
3777
  className: (0, import_tailwind_merge.twMerge)(
@@ -3728,20 +3779,20 @@ function PlayerPanel({
3728
3779
  isExtended ? "flex-row" : "flex-col"
3729
3780
  ),
3730
3781
  children: [
3731
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3732
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3733
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "relative w-full shrink-0", children: [
3734
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3735
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3782
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3783
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3784
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "relative w-full shrink-0", children: [
3785
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3786
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3736
3787
  playerTrackObject.loadError,
3737
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3788
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3738
3789
  void retryPlayback();
3739
3790
  }, children: "Retry" })
3740
3791
  ] })
3741
3792
  ] }),
3742
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(PlayerControls, {})
3793
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(PlayerControls, {})
3743
3794
  ] }),
3744
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
3795
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
3745
3796
  TrackListSidebar,
3746
3797
  {
3747
3798
  layout: "inline",
@@ -3767,33 +3818,33 @@ function PlayerPanel({
3767
3818
  // src/genre-tree/Rating.tsx
3768
3819
  var import_uuid = require("uuid");
3769
3820
  var import_ai = require("react-icons/ai");
3770
- var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
3771
- var import_jsx_runtime45 = require("react/jsx-runtime");
3821
+ var import_genre_tree27 = require("@behindthemusictree/app-kit/genre-tree");
3822
+ var import_jsx_runtime49 = require("react/jsx-runtime");
3772
3823
  function Rating({ rating, handleChange }) {
3773
3824
  const instanceId = (0, import_uuid.v4)();
3774
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3775
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3825
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3826
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3776
3827
  "input",
3777
3828
  {
3778
3829
  id: `star-${instanceId}-${i}`,
3779
3830
  className: "w-6 h-6 opacity-0",
3780
3831
  type: "radio",
3781
3832
  name: "rating",
3782
- value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
3833
+ value: i === 0 ? import_genre_tree27.FORM_RATING_NULL_VALUE : i,
3783
3834
  checked: rating === (i === 0 ? null : i),
3784
3835
  onChange: handleChange
3785
3836
  }
3786
3837
  ),
3787
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex", children: [
3788
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3838
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex", children: [
3839
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3789
3840
  import_ai.AiOutlineStar,
3790
3841
  {
3791
- 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)]"}`
3842
+ 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)]"}`
3792
3843
  }
3793
3844
  ),
3794
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "flex-grow w-8" }),
3845
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "flex-grow w-8" }),
3795
3846
  " "
3796
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3847
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3797
3848
  import_ai.AiFillStar,
3798
3849
  {
3799
3850
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3823,12 +3874,18 @@ function Rating({ rating, handleChange }) {
3823
3874
  PopupButtons,
3824
3875
  PopupTitle,
3825
3876
  Rating,
3877
+ SEARCH_FAILED_STATUS,
3878
+ SearchBox,
3826
3879
  SpotifyAuthErrorPopup,
3827
3880
  Toast,
3828
3881
  TrackItem,
3829
3882
  TrackListSidebar,
3830
3883
  TrackPositionPlayPause,
3831
3884
  TrackUploadPopup,
3832
- useGenrePlaylistPlayback
3885
+ searchStatus,
3886
+ useArtistSearchGroup,
3887
+ useGenrePlaylistPlayback,
3888
+ useGenreRemoteSearchGroup,
3889
+ useSongSearchGroup
3833
3890
  });
3834
3891
  //# sourceMappingURL=index.cjs.map