@signalsandsorcery/plugin-sdk 2.35.2 → 2.35.3

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.mjs CHANGED
@@ -65,11 +65,11 @@ var EMPTY_FX_DETAIL_STATE = {
65
65
  };
66
66
 
67
67
  // src/components/TrackRow.tsx
68
- import React8 from "react";
68
+ import React9 from "react";
69
69
  import { AlertCircle, ChevronDown, GripVertical } from "lucide-react";
70
70
 
71
71
  // src/components/TrackDrawer.tsx
72
- import { useState as useState2, useMemo as useMemo2 } from "react";
72
+ import { useState as useState4, useMemo as useMemo3 } from "react";
73
73
 
74
74
  // src/constants/fx-presets.ts
75
75
  var EQ_PRESETS = {
@@ -789,8 +789,250 @@ function PianoRollEditor({
789
789
  ] });
790
790
  }
791
791
 
792
- // src/components/TrackDrawer.tsx
792
+ // src/components/TrackExternalFxSection.tsx
793
+ import { useMemo as useMemo2, useState as useState3 } from "react";
794
+
795
+ // src/hooks/useTrackExternalFx.ts
796
+ import { useCallback as useCallback2, useEffect, useRef as useRef2, useState as useState2 } from "react";
797
+ function useTrackExternalFx(host, trackId) {
798
+ const supported = typeof host.getTrackExternalFx === "function";
799
+ const [fx, setFx] = useState2(null);
800
+ const [availableFx, setAvailableFx] = useState2([]);
801
+ const [fxLoading, setFxLoading] = useState2(false);
802
+ const [pickerOpen, setPickerOpen] = useState2(false);
803
+ const fxLoadedRef = useRef2(false);
804
+ const loadSeqRef = useRef2(0);
805
+ const reload = useCallback2(async () => {
806
+ if (!supported || !trackId || !host.getTrackExternalFx) {
807
+ setFx(null);
808
+ return;
809
+ }
810
+ const seq = ++loadSeqRef.current;
811
+ try {
812
+ const list = await host.getTrackExternalFx(trackId);
813
+ if (loadSeqRef.current === seq) setFx(list);
814
+ } catch {
815
+ }
816
+ }, [host, trackId, supported]);
817
+ useEffect(() => {
818
+ setFx(null);
819
+ setPickerOpen(false);
820
+ void reload();
821
+ }, [reload]);
822
+ const loadFxList = useCallback2(
823
+ async (force) => {
824
+ if (!supported || !host.getAvailableFx) return;
825
+ if (fxLoadedRef.current && !force) return;
826
+ setFxLoading(true);
827
+ try {
828
+ const list = await host.getAvailableFx();
829
+ setAvailableFx(list);
830
+ fxLoadedRef.current = true;
831
+ } catch {
832
+ } finally {
833
+ setFxLoading(false);
834
+ }
835
+ },
836
+ [host, supported]
837
+ );
838
+ const openPicker = useCallback2(
839
+ (open) => {
840
+ setPickerOpen(open);
841
+ if (open) void loadFxList(false);
842
+ },
843
+ [loadFxList]
844
+ );
845
+ const mutate = useCallback2(
846
+ (fn) => {
847
+ if (!fn || !trackId) return;
848
+ void (async () => {
849
+ try {
850
+ await fn();
851
+ } catch {
852
+ }
853
+ await reload();
854
+ })();
855
+ },
856
+ [trackId, reload]
857
+ );
858
+ return {
859
+ supported,
860
+ fx,
861
+ availableFx,
862
+ fxLoading,
863
+ pickerOpen,
864
+ setPickerOpen: openPicker,
865
+ refreshFx: () => void loadFxList(true),
866
+ reload,
867
+ onAddFx: (pluginId) => mutate(host.loadTrackExternalFx && (async () => {
868
+ await host.loadTrackExternalFx(trackId, pluginId);
869
+ })),
870
+ onRemoveFx: (fxIndex) => mutate(host.removeTrackExternalFx && (() => host.removeTrackExternalFx(trackId, fxIndex))),
871
+ onToggleFxEnabled: (fxIndex, enabled) => mutate(
872
+ host.setTrackExternalFxEnabled && (() => host.setTrackExternalFxEnabled(trackId, fxIndex, enabled))
873
+ ),
874
+ onShowFxEditor: (fxIndex) => mutate(
875
+ host.showTrackExternalFxEditor && (() => host.showTrackExternalFxEditor(trackId, fxIndex))
876
+ )
877
+ };
878
+ }
879
+
880
+ // src/components/TrackExternalFxSection.tsx
793
881
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
882
+ function TrackExternalFxSection({
883
+ host,
884
+ trackId,
885
+ disabled = false
886
+ }) {
887
+ const {
888
+ supported,
889
+ fx,
890
+ availableFx,
891
+ fxLoading,
892
+ pickerOpen,
893
+ setPickerOpen,
894
+ refreshFx,
895
+ onAddFx,
896
+ onRemoveFx,
897
+ onToggleFxEnabled,
898
+ onShowFxEditor
899
+ } = useTrackExternalFx(host, trackId);
900
+ const [search, setSearch] = useState3("");
901
+ const filtered = useMemo2(() => {
902
+ const q = search.trim().toLowerCase();
903
+ if (!q) return availableFx;
904
+ return availableFx.filter(
905
+ (candidate) => candidate.name.toLowerCase().includes(q) || candidate.manufacturer.toLowerCase().includes(q)
906
+ );
907
+ }, [availableFx, search]);
908
+ if (!supported) return null;
909
+ const entries = fx ?? [];
910
+ return /* @__PURE__ */ jsxs3(
911
+ "div",
912
+ {
913
+ "data-testid": "track-external-fx-section",
914
+ className: "flex flex-col gap-1.5 pt-2 mt-1 border-t border-sas-border/60",
915
+ children: [
916
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
917
+ /* @__PURE__ */ jsx3(
918
+ "span",
919
+ {
920
+ className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
921
+ title: "Third-party FX inserts (VST3/AU) on this track, before its fader. Settings persist with the project.",
922
+ children: "3RD-PARTY FX"
923
+ }
924
+ ),
925
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto", children: [
926
+ entries.map((entry) => /* @__PURE__ */ jsxs3(
927
+ "span",
928
+ {
929
+ "data-testid": `track-fx-chip-${entry.index}`,
930
+ className: `flex items-center gap-1 px-1.5 py-0.5 rounded-sm border text-[10px] whitespace-nowrap ${entry.enabled ? "border-sas-accent/60 text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted/50 bg-sas-panel"}`,
931
+ title: `${entry.name}${entry.enabled ? "" : " (bypassed)"}`,
932
+ children: [
933
+ /* @__PURE__ */ jsx3(
934
+ "button",
935
+ {
936
+ "data-testid": `track-fx-toggle-${entry.index}`,
937
+ onClick: () => onToggleFxEnabled(entry.index, !entry.enabled),
938
+ disabled,
939
+ className: "hover:opacity-70 disabled:opacity-50",
940
+ title: entry.enabled ? `Bypass ${entry.name}` : `Enable ${entry.name}`,
941
+ children: entry.enabled ? "\u25CF" : "\u25CB"
942
+ }
943
+ ),
944
+ /* @__PURE__ */ jsx3(
945
+ "button",
946
+ {
947
+ "data-testid": `track-fx-edit-${entry.index}`,
948
+ onClick: () => onShowFxEditor(entry.index),
949
+ disabled,
950
+ className: "max-w-[110px] truncate hover:underline disabled:opacity-50",
951
+ title: `Open ${entry.name} editor`,
952
+ children: entry.name
953
+ }
954
+ ),
955
+ /* @__PURE__ */ jsx3(
956
+ "button",
957
+ {
958
+ "data-testid": `track-fx-remove-${entry.index}`,
959
+ onClick: () => onRemoveFx(entry.index),
960
+ disabled,
961
+ className: "text-sas-muted/60 hover:text-sas-danger disabled:opacity-50",
962
+ title: `Remove ${entry.name} from this track`,
963
+ children: "\u2715"
964
+ }
965
+ )
966
+ ]
967
+ },
968
+ `${entry.index}:${entry.pluginId}`
969
+ )),
970
+ entries.length === 0 && /* @__PURE__ */ jsx3("span", { className: "text-[10px] text-sas-muted/40 select-none", children: "none" })
971
+ ] }),
972
+ /* @__PURE__ */ jsx3(
973
+ "button",
974
+ {
975
+ "data-testid": "track-fx-add-button",
976
+ onClick: () => setPickerOpen(!pickerOpen),
977
+ disabled,
978
+ className: `px-1.5 py-0.5 rounded-sm border text-xs whitespace-nowrap transition-colors ${pickerOpen ? "border-sas-accent text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent"} disabled:opacity-50`,
979
+ title: pickerOpen ? "Close the FX picker" : "Add a VST3/AU FX plugin to this track",
980
+ children: pickerOpen ? "FX \u25B4" : "FX +"
981
+ }
982
+ )
983
+ ] }),
984
+ pickerOpen && /* @__PURE__ */ jsxs3("div", { "data-testid": "track-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
985
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
986
+ /* @__PURE__ */ jsx3(
987
+ "input",
988
+ {
989
+ type: "text",
990
+ value: search,
991
+ onChange: (e) => setSearch(e.target.value),
992
+ placeholder: "Search FX...",
993
+ className: "sas-input flex-1 px-2 py-1 text-xs"
994
+ }
995
+ ),
996
+ /* @__PURE__ */ jsx3(
997
+ "button",
998
+ {
999
+ onClick: () => refreshFx(),
1000
+ disabled: fxLoading,
1001
+ className: "px-2 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-accent hover:border-sas-accent transition-colors disabled:opacity-50",
1002
+ title: "Re-scan plugins",
1003
+ children: fxLoading ? "..." : "Refresh"
1004
+ }
1005
+ )
1006
+ ] }),
1007
+ fxLoading && availableFx.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
1008
+ filtered.map((candidate) => /* @__PURE__ */ jsxs3(
1009
+ "button",
1010
+ {
1011
+ "data-testid": `track-fx-pick-${candidate.pluginId}`,
1012
+ onClick: () => {
1013
+ onAddFx(candidate.pluginId);
1014
+ setPickerOpen(false);
1015
+ },
1016
+ disabled,
1017
+ className: "flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent disabled:opacity-50",
1018
+ title: `${candidate.name} by ${candidate.manufacturer} (${candidate.type.toUpperCase()})`,
1019
+ children: [
1020
+ /* @__PURE__ */ jsx3("span", { className: "text-xs font-medium truncate w-full", children: candidate.name }),
1021
+ /* @__PURE__ */ jsx3("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: candidate.manufacturer || candidate.type.toUpperCase() })
1022
+ ]
1023
+ },
1024
+ candidate.pluginId
1025
+ )),
1026
+ filtered.length === 0 && /* @__PURE__ */ jsx3("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
1027
+ ] })
1028
+ ] })
1029
+ ]
1030
+ }
1031
+ );
1032
+ }
1033
+
1034
+ // src/components/TrackDrawer.tsx
1035
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
794
1036
  var TAB_LABELS = {
795
1037
  fx: "FX",
796
1038
  pick: "Pick",
@@ -807,6 +1049,7 @@ function TrackDrawer({
807
1049
  onFxPresetChange,
808
1050
  onFxDryWetChange,
809
1051
  fxDisabled = false,
1052
+ externalFxHost,
810
1053
  instruments = [],
811
1054
  currentPluginId = null,
812
1055
  isLoading = false,
@@ -829,13 +1072,13 @@ function TrackDrawer({
829
1072
  editSnap,
830
1073
  onAuditionNote
831
1074
  }) {
832
- const [search, setSearch] = useState2("");
1075
+ const [search, setSearch] = useState4("");
833
1076
  const fxEnabled = !!onFxToggle;
834
1077
  const pickEnabled = !!onSelect;
835
1078
  const historyEnabled = !!onRestoreSound;
836
1079
  const importEnabled = !!onImportSound;
837
1080
  const editEnabled = !!onNotesChange;
838
- const enabledTabs = useMemo2(() => {
1081
+ const enabledTabs = useMemo3(() => {
839
1082
  const tabs = [];
840
1083
  if (fxEnabled) tabs.push("fx");
841
1084
  if (pickEnabled) tabs.push("pick");
@@ -845,7 +1088,7 @@ function TrackDrawer({
845
1088
  return tabs;
846
1089
  }, [fxEnabled, pickEnabled, historyEnabled, importEnabled, editEnabled]);
847
1090
  const SURGE_XT_DEFAULT_ID = "Surge XT";
848
- const filtered = useMemo2(() => {
1091
+ const filtered = useMemo3(() => {
849
1092
  let all = instruments.filter((i) => i.name !== "Surge XT");
850
1093
  if (search.trim()) {
851
1094
  const q = search.toLowerCase();
@@ -865,12 +1108,12 @@ function TrackDrawer({
865
1108
  const history = soundHistory ?? [];
866
1109
  const effectiveTab = enabledTabs.includes(activeTab) ? activeTab : enabledTabs[0] ?? "fx";
867
1110
  const tabClass = (active) => `px-2 py-0.5 text-xs rounded-sm transition-colors ${active ? "bg-sas-accent/20 text-sas-accent font-medium" : "text-sas-muted hover:text-sas-accent"}`;
868
- const strip = enabledTabs.length > 1 ? /* @__PURE__ */ jsx3(
1111
+ const strip = enabledTabs.length > 1 ? /* @__PURE__ */ jsx4(
869
1112
  "div",
870
1113
  {
871
1114
  className: "flex items-center gap-1 border-b border-sas-border pb-1",
872
1115
  "data-testid": "sdk-drawer-tabs",
873
- children: enabledTabs.map((tab) => /* @__PURE__ */ jsx3(
1116
+ children: enabledTabs.map((tab) => /* @__PURE__ */ jsx4(
874
1117
  "button",
875
1118
  {
876
1119
  type: "button",
@@ -884,9 +1127,9 @@ function TrackDrawer({
884
1127
  }
885
1128
  ) : null;
886
1129
  const currentSound = soundHistoryCursor >= 0 && soundHistoryCursor < history.length ? history[soundHistoryCursor].label : null;
887
- const header = strip || currentSound ? /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", "data-testid": "sdk-drawer-header", children: [
1130
+ const header = strip || currentSound ? /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", "data-testid": "sdk-drawer-header", children: [
888
1131
  strip,
889
- currentSound && /* @__PURE__ */ jsx3(
1132
+ currentSound && /* @__PURE__ */ jsx4(
890
1133
  "span",
891
1134
  {
892
1135
  className: "text-[10px] text-sas-muted/60 truncate px-0.5",
@@ -896,9 +1139,9 @@ function TrackDrawer({
896
1139
  )
897
1140
  ] }) : null;
898
1141
  if (effectiveTab === "edit") {
899
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-edit", children: [
1142
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-edit", children: [
900
1143
  header,
901
- /* @__PURE__ */ jsx3(
1144
+ /* @__PURE__ */ jsx4(
902
1145
  PianoRollEditor,
903
1146
  {
904
1147
  notes: editNotes ?? [],
@@ -913,9 +1156,9 @@ function TrackDrawer({
913
1156
  ] });
914
1157
  }
915
1158
  if (effectiveTab === "fx") {
916
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-fx", children: [
1159
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-fx", children: [
917
1160
  header,
918
- /* @__PURE__ */ jsx3(
1161
+ /* @__PURE__ */ jsx4(
919
1162
  FxToggleBar,
920
1163
  {
921
1164
  trackId,
@@ -925,20 +1168,21 @@ function TrackDrawer({
925
1168
  onDryWetChange: (_t, category, value) => onFxDryWetChange?.(category, value),
926
1169
  disabled: fxDisabled
927
1170
  }
928
- )
1171
+ ),
1172
+ externalFxHost && /* @__PURE__ */ jsx4(TrackExternalFxSection, { host: externalFxHost, trackId, disabled: fxDisabled })
929
1173
  ] });
930
1174
  }
931
1175
  if (effectiveTab === "import") {
932
1176
  const soundNoun = /preset/i.test(importSoundLabel ?? "") ? "preset" : /sample/i.test(importSoundLabel ?? "") ? "sample" : "sound";
933
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-import", children: [
1177
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-import", children: [
934
1178
  header,
935
- /* @__PURE__ */ jsxs3("p", { className: "text-[11px] text-sas-muted/70 leading-snug", children: [
1179
+ /* @__PURE__ */ jsxs4("p", { className: "text-[11px] text-sas-muted/70 leading-snug", children: [
936
1180
  "Copy the sound from a matching track in another scene \u2014 your MIDI stays, only the",
937
1181
  " ",
938
1182
  soundNoun,
939
1183
  " changes."
940
1184
  ] }),
941
- /* @__PURE__ */ jsxs3(
1185
+ /* @__PURE__ */ jsxs4(
942
1186
  "button",
943
1187
  {
944
1188
  type: "button",
@@ -956,16 +1200,16 @@ function TrackDrawer({
956
1200
  }
957
1201
  if (effectiveTab === "history") {
958
1202
  const order = history.map((_, i) => i).reverse();
959
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
1203
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
960
1204
  header,
961
- history.length === 0 ? /* @__PURE__ */ jsx3(
1205
+ history.length === 0 ? /* @__PURE__ */ jsx4(
962
1206
  "div",
963
1207
  {
964
1208
  className: "text-xs text-sas-muted/60 text-center py-3",
965
1209
  "data-testid": "sdk-history-empty",
966
1210
  children: "No sounds yet \u2014 shuffle to build history."
967
1211
  }
968
- ) : /* @__PURE__ */ jsx3(
1212
+ ) : /* @__PURE__ */ jsx4(
969
1213
  "ul",
970
1214
  {
971
1215
  className: "flex flex-col gap-1 max-h-[160px] overflow-y-auto",
@@ -973,8 +1217,8 @@ function TrackDrawer({
973
1217
  children: order.map((i) => {
974
1218
  const entry = history[i];
975
1219
  const isCurrent = i === soundHistoryCursor;
976
- return /* @__PURE__ */ jsxs3("li", { className: "flex items-center gap-1", children: [
977
- /* @__PURE__ */ jsxs3(
1220
+ return /* @__PURE__ */ jsxs4("li", { className: "flex items-center gap-1", children: [
1221
+ /* @__PURE__ */ jsxs4(
978
1222
  "button",
979
1223
  {
980
1224
  type: "button",
@@ -984,12 +1228,12 @@ function TrackDrawer({
984
1228
  className: `flex-1 min-w-0 flex items-center justify-between px-2 py-1.5 rounded-sm border text-left text-xs transition-colors ${isCurrent ? "border-sas-accent bg-sas-accent/20 text-sas-accent cursor-default" : "border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent"}`,
985
1229
  title: isCurrent ? "Current sound" : `Restore: ${entry.label}`,
986
1230
  children: [
987
- /* @__PURE__ */ jsx3("span", { className: "truncate", children: entry.label }),
988
- /* @__PURE__ */ jsx3("span", { className: "text-[10px] text-sas-muted/60 flex-shrink-0 ml-2", children: isCurrent ? "\u25CF current" : "restore" })
1231
+ /* @__PURE__ */ jsx4("span", { className: "truncate", children: entry.label }),
1232
+ /* @__PURE__ */ jsx4("span", { className: "text-[10px] text-sas-muted/60 flex-shrink-0 ml-2", children: isCurrent ? "\u25CF current" : "restore" })
989
1233
  ]
990
1234
  }
991
1235
  ),
992
- onToggleFavorite && /* @__PURE__ */ jsx3(
1236
+ onToggleFavorite && /* @__PURE__ */ jsx4(
993
1237
  "button",
994
1238
  {
995
1239
  type: "button",
@@ -1007,10 +1251,10 @@ function TrackDrawer({
1007
1251
  ] });
1008
1252
  }
1009
1253
  if (effectiveTab === "pick" && editorStage) {
1010
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
1254
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
1011
1255
  header,
1012
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1013
- /* @__PURE__ */ jsx3(
1256
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1257
+ /* @__PURE__ */ jsx4(
1014
1258
  "button",
1015
1259
  {
1016
1260
  onClick: () => onBackToInstruments?.(),
@@ -1018,9 +1262,9 @@ function TrackDrawer({
1018
1262
  children: "\u2190 Back"
1019
1263
  }
1020
1264
  ),
1021
- /* @__PURE__ */ jsx3("span", { className: "text-xs text-sas-muted font-medium truncate flex-1", children: selectedInstrumentName ?? "Plugin" })
1265
+ /* @__PURE__ */ jsx4("span", { className: "text-xs text-sas-muted font-medium truncate flex-1", children: selectedInstrumentName ?? "Plugin" })
1022
1266
  ] }),
1023
- /* @__PURE__ */ jsx3(
1267
+ /* @__PURE__ */ jsx4(
1024
1268
  "button",
1025
1269
  {
1026
1270
  onClick: () => onShowEditor?.(),
@@ -1032,10 +1276,10 @@ function TrackDrawer({
1032
1276
  }
1033
1277
  const isDefaultSelected = currentPluginId === null;
1034
1278
  const isSelected = (pluginId) => pluginId === currentPluginId;
1035
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
1279
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
1036
1280
  header,
1037
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1038
- /* @__PURE__ */ jsx3(
1281
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1282
+ /* @__PURE__ */ jsx4(
1039
1283
  "input",
1040
1284
  {
1041
1285
  type: "text",
@@ -1045,7 +1289,7 @@ function TrackDrawer({
1045
1289
  className: "sas-input flex-1 px-2 py-1 text-xs"
1046
1290
  }
1047
1291
  ),
1048
- /* @__PURE__ */ jsx3(
1292
+ /* @__PURE__ */ jsx4(
1049
1293
  "button",
1050
1294
  {
1051
1295
  onClick: () => onRefresh?.(),
@@ -1056,54 +1300,54 @@ function TrackDrawer({
1056
1300
  }
1057
1301
  )
1058
1302
  ] }),
1059
- isLoading && instruments.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
1060
- /* @__PURE__ */ jsxs3(
1303
+ isLoading && instruments.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
1304
+ /* @__PURE__ */ jsxs4(
1061
1305
  "button",
1062
1306
  {
1063
1307
  onClick: () => onSelect?.(SURGE_XT_DEFAULT_ID),
1064
1308
  className: `flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors ${isDefaultSelected ? "border-sas-accent bg-sas-accent/20 text-sas-accent" : "border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent"}`,
1065
1309
  title: "Surge XT \u2014 Default instrument",
1066
1310
  children: [
1067
- /* @__PURE__ */ jsxs3("span", { className: "text-xs font-medium truncate w-full", children: [
1311
+ /* @__PURE__ */ jsxs4("span", { className: "text-xs font-medium truncate w-full", children: [
1068
1312
  isDefaultSelected && "\u2713 ",
1069
1313
  "Surge XT"
1070
1314
  ] }),
1071
- /* @__PURE__ */ jsx3("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: "Default" })
1315
+ /* @__PURE__ */ jsx4("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: "Default" })
1072
1316
  ]
1073
1317
  },
1074
1318
  "__surge-xt-default__"
1075
1319
  ),
1076
1320
  filtered.map((inst) => {
1077
1321
  const selected = isSelected(inst.pluginId);
1078
- return /* @__PURE__ */ jsxs3(
1322
+ return /* @__PURE__ */ jsxs4(
1079
1323
  "button",
1080
1324
  {
1081
1325
  onClick: () => onSelect?.(inst.pluginId),
1082
1326
  className: `flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors ${selected ? "border-sas-accent bg-sas-accent/20 text-sas-accent" : inst.missing ? "border-amber-500/50 bg-amber-500/10 text-amber-400 hover:border-amber-500" : "border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent"}`,
1083
1327
  title: `${inst.name} by ${inst.manufacturer} (${inst.type.toUpperCase()})${inst.missing ? " \u2014 MISSING" : ""}`,
1084
1328
  children: [
1085
- /* @__PURE__ */ jsxs3("span", { className: "text-xs font-medium truncate w-full", children: [
1329
+ /* @__PURE__ */ jsxs4("span", { className: "text-xs font-medium truncate w-full", children: [
1086
1330
  selected && "\u2713 ",
1087
1331
  inst.name
1088
1332
  ] }),
1089
- /* @__PURE__ */ jsx3("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: inst.manufacturer || inst.type.toUpperCase() })
1333
+ /* @__PURE__ */ jsx4("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: inst.manufacturer || inst.type.toUpperCase() })
1090
1334
  ]
1091
1335
  },
1092
1336
  inst.pluginId
1093
1337
  );
1094
1338
  }),
1095
- filtered.length === 0 && /* @__PURE__ */ jsx3("div", { className: "col-span-2 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No other plugins found" })
1339
+ filtered.length === 0 && /* @__PURE__ */ jsx4("div", { className: "col-span-2 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No other plugins found" })
1096
1340
  ] })
1097
1341
  ] });
1098
1342
  }
1099
1343
 
1100
1344
  // src/components/ConfirmDialog.tsx
1101
- import { useRef as useRef2 } from "react";
1345
+ import { useRef as useRef3 } from "react";
1102
1346
 
1103
1347
  // src/components/Modal.tsx
1104
- import { useEffect } from "react";
1348
+ import { useEffect as useEffect2 } from "react";
1105
1349
  import { createPortal } from "react-dom";
1106
- import { jsx as jsx4 } from "react/jsx-runtime";
1350
+ import { jsx as jsx5 } from "react/jsx-runtime";
1107
1351
  function Modal({
1108
1352
  open,
1109
1353
  onClose,
@@ -1113,7 +1357,7 @@ function Modal({
1113
1357
  closeOnEscape = true,
1114
1358
  initialFocusRef
1115
1359
  }) {
1116
- useEffect(() => {
1360
+ useEffect2(() => {
1117
1361
  if (!open) return void 0;
1118
1362
  const onKey = (e) => {
1119
1363
  if (closeOnEscape && e.key === "Escape") {
@@ -1127,7 +1371,7 @@ function Modal({
1127
1371
  }, [open, onClose, closeOnEscape, initialFocusRef]);
1128
1372
  if (!open) return null;
1129
1373
  return createPortal(
1130
- /* @__PURE__ */ jsx4(
1374
+ /* @__PURE__ */ jsx5(
1131
1375
  "div",
1132
1376
  {
1133
1377
  className: "fixed inset-0 z-[1000] flex items-center justify-center bg-black/60",
@@ -1141,7 +1385,7 @@ function Modal({
1141
1385
  }
1142
1386
 
1143
1387
  // src/components/ConfirmDialog.tsx
1144
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1388
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1145
1389
  function ConfirmDialog({
1146
1390
  open,
1147
1391
  title,
@@ -1153,8 +1397,8 @@ function ConfirmDialog({
1153
1397
  onCancel,
1154
1398
  testIdPrefix = "confirm-dialog"
1155
1399
  }) {
1156
- const cancelRef = useRef2(null);
1157
- return /* @__PURE__ */ jsx5(Modal, { open, onClose: onCancel, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs4(
1400
+ const cancelRef = useRef3(null);
1401
+ return /* @__PURE__ */ jsx6(Modal, { open, onClose: onCancel, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs5(
1158
1402
  "div",
1159
1403
  {
1160
1404
  className: "w-[360px] max-w-[90vw] flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
@@ -1164,8 +1408,8 @@ function ConfirmDialog({
1164
1408
  "aria-label": title,
1165
1409
  "data-testid": `${testIdPrefix}-modal`,
1166
1410
  children: [
1167
- /* @__PURE__ */ jsx5("div", { className: "px-4 py-3 border-b border-sas-border", children: /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-sas-text", "data-testid": `${testIdPrefix}-title`, children: title }) }),
1168
- /* @__PURE__ */ jsx5(
1411
+ /* @__PURE__ */ jsx6("div", { className: "px-4 py-3 border-b border-sas-border", children: /* @__PURE__ */ jsx6("span", { className: "text-sm font-medium text-sas-text", "data-testid": `${testIdPrefix}-title`, children: title }) }),
1412
+ /* @__PURE__ */ jsx6(
1169
1413
  "div",
1170
1414
  {
1171
1415
  className: "px-4 py-3 text-xs text-sas-muted leading-relaxed break-words",
@@ -1173,8 +1417,8 @@ function ConfirmDialog({
1173
1417
  children: message
1174
1418
  }
1175
1419
  ),
1176
- /* @__PURE__ */ jsxs4("div", { className: "flex justify-end gap-2 px-4 py-3 border-t border-sas-border", children: [
1177
- /* @__PURE__ */ jsx5(
1420
+ /* @__PURE__ */ jsxs5("div", { className: "flex justify-end gap-2 px-4 py-3 border-t border-sas-border", children: [
1421
+ /* @__PURE__ */ jsx6(
1178
1422
  "button",
1179
1423
  {
1180
1424
  ref: cancelRef,
@@ -1185,7 +1429,7 @@ function ConfirmDialog({
1185
1429
  children: cancelLabel
1186
1430
  }
1187
1431
  ),
1188
- /* @__PURE__ */ jsx5(
1432
+ /* @__PURE__ */ jsx6(
1189
1433
  "button",
1190
1434
  {
1191
1435
  type: "button",
@@ -1202,7 +1446,7 @@ function ConfirmDialog({
1202
1446
  }
1203
1447
 
1204
1448
  // src/components/LevelMeter.tsx
1205
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1449
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1206
1450
  var COLOR_GREEN = "#2BD576";
1207
1451
  var COLOR_ORANGE = "#F5A623";
1208
1452
  var COLOR_RED = "#FF4D5E";
@@ -1230,7 +1474,7 @@ var LevelMeter = ({
1230
1474
  const widthPct = active ? dbToPct(peakDb) : 0;
1231
1475
  const showPeak = peakHoldDb != null && active && peakHoldDb > -60;
1232
1476
  const peakHoldPct = showPeak ? dbToPct(peakHoldDb) : 0;
1233
- return /* @__PURE__ */ jsxs5(
1477
+ return /* @__PURE__ */ jsxs6(
1234
1478
  "div",
1235
1479
  {
1236
1480
  className: `sas-level-meter ${className ?? ""}`,
@@ -1241,7 +1485,7 @@ var LevelMeter = ({
1241
1485
  gap: compact ? 0 : 6
1242
1486
  },
1243
1487
  children: [
1244
- /* @__PURE__ */ jsxs5(
1488
+ /* @__PURE__ */ jsxs6(
1245
1489
  "div",
1246
1490
  {
1247
1491
  style: {
@@ -1255,8 +1499,8 @@ var LevelMeter = ({
1255
1499
  minWidth: compact ? 0 : 60
1256
1500
  },
1257
1501
  children: [
1258
- /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, background: METER_GRADIENT } }),
1259
- /* @__PURE__ */ jsx6(
1502
+ /* @__PURE__ */ jsx7("div", { style: { position: "absolute", inset: 0, background: METER_GRADIENT } }),
1503
+ /* @__PURE__ */ jsx7(
1260
1504
  "div",
1261
1505
  {
1262
1506
  style: {
@@ -1270,7 +1514,7 @@ var LevelMeter = ({
1270
1514
  }
1271
1515
  }
1272
1516
  ),
1273
- /* @__PURE__ */ jsx6(
1517
+ /* @__PURE__ */ jsx7(
1274
1518
  "div",
1275
1519
  {
1276
1520
  "data-testid": `${id}-segments`,
@@ -1283,7 +1527,7 @@ var LevelMeter = ({
1283
1527
  }
1284
1528
  }
1285
1529
  ),
1286
- showPeak && /* @__PURE__ */ jsx6(
1530
+ showPeak && /* @__PURE__ */ jsx7(
1287
1531
  "div",
1288
1532
  {
1289
1533
  "data-testid": `${id}-peak`,
@@ -1304,7 +1548,7 @@ var LevelMeter = ({
1304
1548
  ]
1305
1549
  }
1306
1550
  ),
1307
- !compact && /* @__PURE__ */ jsx6(
1551
+ !compact && /* @__PURE__ */ jsx7(
1308
1552
  "span",
1309
1553
  {
1310
1554
  style: {
@@ -1317,7 +1561,7 @@ var LevelMeter = ({
1317
1561
  children: active && peakDb > -120 ? `${peakDb.toFixed(0)} dB` : "\u2014"
1318
1562
  }
1319
1563
  ),
1320
- clipped && /* @__PURE__ */ jsx6(
1564
+ clipped && /* @__PURE__ */ jsx7(
1321
1565
  "span",
1322
1566
  {
1323
1567
  "data-testid": `${id}-clip`,
@@ -1342,7 +1586,7 @@ var LevelMeter = ({
1342
1586
  };
1343
1587
 
1344
1588
  // src/hooks/useTrackLevels.ts
1345
- import { useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
1589
+ import { useEffect as useEffect3, useRef as useRef4, useState as useState5 } from "react";
1346
1590
  var meterDiagRLast = /* @__PURE__ */ new Map();
1347
1591
  var POLL_INTERVAL_MS = 33;
1348
1592
  var HIDDEN_RECHECK_MS = 250;
@@ -1353,9 +1597,9 @@ function isHidden() {
1353
1597
  return typeof document !== "undefined" && document.hidden === true;
1354
1598
  }
1355
1599
  function useTrackLevels(host, enabled = true) {
1356
- const mapRef = useRef3(/* @__PURE__ */ new Map());
1357
- const listenersRef = useRef3(/* @__PURE__ */ new Set());
1358
- const handleRef = useRef3(null);
1600
+ const mapRef = useRef4(/* @__PURE__ */ new Map());
1601
+ const listenersRef = useRef4(/* @__PURE__ */ new Set());
1602
+ const handleRef = useRef4(null);
1359
1603
  if (handleRef.current === null) {
1360
1604
  handleRef.current = {
1361
1605
  getLevel: (trackId) => mapRef.current.get(trackId) ?? null,
@@ -1367,7 +1611,7 @@ function useTrackLevels(host, enabled = true) {
1367
1611
  }
1368
1612
  };
1369
1613
  }
1370
- useEffect2(() => {
1614
+ useEffect3(() => {
1371
1615
  const notify = () => {
1372
1616
  listenersRef.current.forEach((l) => l());
1373
1617
  };
@@ -1437,8 +1681,8 @@ function sameLevel(a, b) {
1437
1681
  return a.peakDb === b.peakDb && a.clipped === b.clipped;
1438
1682
  }
1439
1683
  function useTrackLevel(handle, trackId) {
1440
- const [level, setLevel] = useState3(null);
1441
- useEffect2(() => {
1684
+ const [level, setLevel] = useState5(null);
1685
+ useEffect3(() => {
1442
1686
  if (!handle) {
1443
1687
  setLevel(null);
1444
1688
  return;
@@ -1462,11 +1706,11 @@ function sameMeter(a, b) {
1462
1706
  return a.active === b.active && a.clipped === b.clipped && a.peakDb === b.peakDb && Math.round(a.peakHoldDb * 2) === Math.round(b.peakHoldDb * 2);
1463
1707
  }
1464
1708
  function useTrackMeter(handle, trackId) {
1465
- const [view, setView] = useState3(IDLE_METER_VIEW);
1466
- const heldDbRef = useRef3(METER_FLOOR_DB);
1467
- const heldAtRef = useRef3(0);
1468
- const lastTickRef = useRef3(0);
1469
- useEffect2(() => {
1709
+ const [view, setView] = useState5(IDLE_METER_VIEW);
1710
+ const heldDbRef = useRef4(METER_FLOOR_DB);
1711
+ const heldAtRef = useRef4(0);
1712
+ const lastTickRef = useRef4(0);
1713
+ useEffect3(() => {
1470
1714
  if (!handle) {
1471
1715
  heldDbRef.current = METER_FLOOR_DB;
1472
1716
  lastTickRef.current = 0;
@@ -1509,8 +1753,8 @@ function useTrackMeter(handle, trackId) {
1509
1753
  return view;
1510
1754
  }
1511
1755
  function useTransportPlaying(host) {
1512
- const [playing, setPlaying] = useState3(false);
1513
- useEffect2(() => {
1756
+ const [playing, setPlaying] = useState5(false);
1757
+ useEffect3(() => {
1514
1758
  if (!host) {
1515
1759
  setPlaying(false);
1516
1760
  return;
@@ -1538,7 +1782,7 @@ function useTransportPlaying(host) {
1538
1782
  }
1539
1783
 
1540
1784
  // src/components/TrackMeterStrip.tsx
1541
- import { jsx as jsx7 } from "react/jsx-runtime";
1785
+ import { jsx as jsx8 } from "react/jsx-runtime";
1542
1786
  var TrackMeterStrip = ({
1543
1787
  levels,
1544
1788
  trackId,
@@ -1546,12 +1790,12 @@ var TrackMeterStrip = ({
1546
1790
  className
1547
1791
  }) => {
1548
1792
  const meter = useTrackMeter(levels, trackId);
1549
- return /* @__PURE__ */ jsx7(
1793
+ return /* @__PURE__ */ jsx8(
1550
1794
  "div",
1551
1795
  {
1552
1796
  "data-testid": "sdk-track-meter",
1553
1797
  className: `w-full px-2 py-1 bg-sas-panel-alt border border-t-0 border-sas-border ${roundBottom ? "rounded-b-sm" : ""} ${className ?? ""}`,
1554
- children: /* @__PURE__ */ jsx7(
1798
+ children: /* @__PURE__ */ jsx8(
1555
1799
  LevelMeter,
1556
1800
  {
1557
1801
  compact: true,
@@ -1567,7 +1811,7 @@ var TrackMeterStrip = ({
1567
1811
  };
1568
1812
 
1569
1813
  // src/components/VolumeSlider.tsx
1570
- import { useCallback as useCallback2, useState as useState4, useRef as useRef4, useEffect as useEffect3 } from "react";
1814
+ import { useCallback as useCallback3, useState as useState6, useRef as useRef5, useEffect as useEffect4 } from "react";
1571
1815
 
1572
1816
  // src/utils/volume-conversion.ts
1573
1817
  var SLIDER_UNITY = 0.75;
@@ -1589,7 +1833,7 @@ function dbToSlider(db) {
1589
1833
  }
1590
1834
 
1591
1835
  // src/components/VolumeSlider.tsx
1592
- import { jsx as jsx8 } from "react/jsx-runtime";
1836
+ import { jsx as jsx9 } from "react/jsx-runtime";
1593
1837
  function formatDb(value) {
1594
1838
  const db = sliderToDb(value);
1595
1839
  if (db <= -60) return "-\u221E dB";
@@ -1597,12 +1841,12 @@ function formatDb(value) {
1597
1841
  return `${sign}${db.toFixed(1)} dB`;
1598
1842
  }
1599
1843
  function useDebouncedCallback(callback, delay) {
1600
- const timeoutRef = useRef4(null);
1601
- const callbackRef = useRef4(callback);
1602
- useEffect3(() => {
1844
+ const timeoutRef = useRef5(null);
1845
+ const callbackRef = useRef5(callback);
1846
+ useEffect4(() => {
1603
1847
  callbackRef.current = callback;
1604
1848
  }, [callback]);
1605
- const debouncedCallback = useCallback2(
1849
+ const debouncedCallback = useCallback3(
1606
1850
  (...args) => {
1607
1851
  if (timeoutRef.current) {
1608
1852
  clearTimeout(timeoutRef.current);
@@ -1613,7 +1857,7 @@ function useDebouncedCallback(callback, delay) {
1613
1857
  },
1614
1858
  [delay]
1615
1859
  );
1616
- useEffect3(() => {
1860
+ useEffect4(() => {
1617
1861
  return () => {
1618
1862
  if (timeoutRef.current) {
1619
1863
  clearTimeout(timeoutRef.current);
@@ -1628,15 +1872,15 @@ var VolumeSlider = ({
1628
1872
  disabled = false,
1629
1873
  className = ""
1630
1874
  }) => {
1631
- const [localValue, setLocalValue] = useState4(value);
1632
- const [isDragging, setIsDragging] = useState4(false);
1633
- useEffect3(() => {
1875
+ const [localValue, setLocalValue] = useState6(value);
1876
+ const [isDragging, setIsDragging] = useState6(false);
1877
+ useEffect4(() => {
1634
1878
  if (!isDragging) {
1635
1879
  setLocalValue(value);
1636
1880
  }
1637
1881
  }, [value, isDragging]);
1638
1882
  const debouncedOnChange = useDebouncedCallback(onChange, 50);
1639
- const handleChange = useCallback2(
1883
+ const handleChange = useCallback3(
1640
1884
  (e) => {
1641
1885
  const newValue = parseFloat(e.target.value);
1642
1886
  setLocalValue(newValue);
@@ -1644,19 +1888,19 @@ var VolumeSlider = ({
1644
1888
  },
1645
1889
  [debouncedOnChange]
1646
1890
  );
1647
- const handleMouseDown = useCallback2(() => {
1891
+ const handleMouseDown = useCallback3(() => {
1648
1892
  setIsDragging(true);
1649
1893
  }, []);
1650
- const handleMouseUp = useCallback2(() => {
1894
+ const handleMouseUp = useCallback3(() => {
1651
1895
  setIsDragging(false);
1652
1896
  onChange(localValue);
1653
1897
  }, [localValue, onChange]);
1654
- return /* @__PURE__ */ jsx8(
1898
+ return /* @__PURE__ */ jsx9(
1655
1899
  "div",
1656
1900
  {
1657
1901
  className: `flex items-center ${className}`,
1658
1902
  title: `Volume: ${formatDb(localValue)}`,
1659
- children: /* @__PURE__ */ jsx8(
1903
+ children: /* @__PURE__ */ jsx9(
1660
1904
  "input",
1661
1905
  {
1662
1906
  type: "range",
@@ -1696,8 +1940,8 @@ var VolumeSlider = ({
1696
1940
  };
1697
1941
 
1698
1942
  // src/components/PanSlider.tsx
1699
- import { useCallback as useCallback3, useState as useState5, useRef as useRef5, useEffect as useEffect4 } from "react";
1700
- import { jsx as jsx9 } from "react/jsx-runtime";
1943
+ import { useCallback as useCallback4, useState as useState7, useRef as useRef6, useEffect as useEffect5 } from "react";
1944
+ import { jsx as jsx10 } from "react/jsx-runtime";
1701
1945
  function toPanDisplay(value) {
1702
1946
  if (Math.abs(value) < 0.02) {
1703
1947
  return "Center";
@@ -1706,12 +1950,12 @@ function toPanDisplay(value) {
1706
1950
  return value < 0 ? `L${percent}` : `R${percent}`;
1707
1951
  }
1708
1952
  function useDebouncedCallback2(callback, delay) {
1709
- const timeoutRef = useRef5(null);
1710
- const callbackRef = useRef5(callback);
1711
- useEffect4(() => {
1953
+ const timeoutRef = useRef6(null);
1954
+ const callbackRef = useRef6(callback);
1955
+ useEffect5(() => {
1712
1956
  callbackRef.current = callback;
1713
1957
  }, [callback]);
1714
- const debouncedCallback = useCallback3(
1958
+ const debouncedCallback = useCallback4(
1715
1959
  (...args) => {
1716
1960
  if (timeoutRef.current) {
1717
1961
  clearTimeout(timeoutRef.current);
@@ -1722,7 +1966,7 @@ function useDebouncedCallback2(callback, delay) {
1722
1966
  },
1723
1967
  [delay]
1724
1968
  );
1725
- useEffect4(() => {
1969
+ useEffect5(() => {
1726
1970
  return () => {
1727
1971
  if (timeoutRef.current) {
1728
1972
  clearTimeout(timeoutRef.current);
@@ -1737,15 +1981,15 @@ var PanSlider = ({
1737
1981
  disabled = false,
1738
1982
  className = ""
1739
1983
  }) => {
1740
- const [localValue, setLocalValue] = useState5(value);
1741
- const [isDragging, setIsDragging] = useState5(false);
1742
- useEffect4(() => {
1984
+ const [localValue, setLocalValue] = useState7(value);
1985
+ const [isDragging, setIsDragging] = useState7(false);
1986
+ useEffect5(() => {
1743
1987
  if (!isDragging) {
1744
1988
  setLocalValue(value);
1745
1989
  }
1746
1990
  }, [value, isDragging]);
1747
1991
  const debouncedOnChange = useDebouncedCallback2(onChange, 50);
1748
- const handleChange = useCallback3(
1992
+ const handleChange = useCallback4(
1749
1993
  (e) => {
1750
1994
  const newValue = parseFloat(e.target.value);
1751
1995
  setLocalValue(newValue);
@@ -1753,23 +1997,23 @@ var PanSlider = ({
1753
1997
  },
1754
1998
  [debouncedOnChange]
1755
1999
  );
1756
- const handleMouseDown = useCallback3(() => {
2000
+ const handleMouseDown = useCallback4(() => {
1757
2001
  setIsDragging(true);
1758
2002
  }, []);
1759
- const handleMouseUp = useCallback3(() => {
2003
+ const handleMouseUp = useCallback4(() => {
1760
2004
  setIsDragging(false);
1761
2005
  onChange(localValue);
1762
2006
  }, [localValue, onChange]);
1763
- const handleDoubleClick = useCallback3(() => {
2007
+ const handleDoubleClick = useCallback4(() => {
1764
2008
  setLocalValue(0);
1765
2009
  onChange(0);
1766
2010
  }, [onChange]);
1767
- return /* @__PURE__ */ jsx9(
2011
+ return /* @__PURE__ */ jsx10(
1768
2012
  "div",
1769
2013
  {
1770
2014
  className: `flex items-center ${className}`,
1771
2015
  title: `Pan: ${toPanDisplay(localValue)}`,
1772
- children: /* @__PURE__ */ jsx9(
2016
+ children: /* @__PURE__ */ jsx10(
1773
2017
  "input",
1774
2018
  {
1775
2019
  type: "range",
@@ -1810,8 +2054,8 @@ var PanSlider = ({
1810
2054
  };
1811
2055
 
1812
2056
  // src/components/SorceryProgressBar.tsx
1813
- import { useState as useState6, useEffect as useEffect5, useRef as useRef6 } from "react";
1814
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2057
+ import { useState as useState8, useEffect as useEffect6, useRef as useRef7 } from "react";
2058
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1815
2059
  function calculateTimeBasedTarget(elapsedMs, estimatedDurationMs) {
1816
2060
  const t = elapsedMs / estimatedDurationMs;
1817
2061
  if (t <= 0) return 0;
@@ -1856,20 +2100,20 @@ function SorceryProgressBar({
1856
2100
  onProgressChange,
1857
2101
  estimatedDurationMs
1858
2102
  }) {
1859
- const [progress, setProgress] = useState6(initialProgress);
1860
- const timerRef = useRef6(null);
1861
- const isLoadingRef = useRef6(false);
1862
- const hasStartedRef = useRef6(false);
1863
- const startTimeRef = useRef6(0);
1864
- const onProgressChangeRef = useRef6(onProgressChange);
1865
- const onCompleteRef = useRef6(onComplete);
2103
+ const [progress, setProgress] = useState8(initialProgress);
2104
+ const timerRef = useRef7(null);
2105
+ const isLoadingRef = useRef7(false);
2106
+ const hasStartedRef = useRef7(false);
2107
+ const startTimeRef = useRef7(0);
2108
+ const onProgressChangeRef = useRef7(onProgressChange);
2109
+ const onCompleteRef = useRef7(onComplete);
1866
2110
  onProgressChangeRef.current = onProgressChange;
1867
2111
  onCompleteRef.current = onComplete;
1868
- const initialProgressRef = useRef6(initialProgress);
2112
+ const initialProgressRef = useRef7(initialProgress);
1869
2113
  initialProgressRef.current = initialProgress;
1870
- const estimatedDurationMsRef = useRef6(estimatedDurationMs);
2114
+ const estimatedDurationMsRef = useRef7(estimatedDurationMs);
1871
2115
  estimatedDurationMsRef.current = estimatedDurationMs;
1872
- useEffect5(() => {
2116
+ useEffect6(() => {
1873
2117
  const wasLoading = isLoadingRef.current;
1874
2118
  isLoadingRef.current = isLoading;
1875
2119
  if (isLoading && !wasLoading) {
@@ -1931,12 +2175,12 @@ function SorceryProgressBar({
1931
2175
  const displayProgress = Math.floor(progress);
1932
2176
  const isComplete = !isLoading && progress === 100;
1933
2177
  const transitionDuration = progress < 50 ? "300ms" : progress < 80 ? "500ms" : "700ms";
1934
- return /* @__PURE__ */ jsxs6(
2178
+ return /* @__PURE__ */ jsxs7(
1935
2179
  "div",
1936
2180
  {
1937
2181
  className: `relative w-full ${heightClass} bg-sas-panel-alt border border-sas-border rounded-sm overflow-hidden shadow-inner`,
1938
2182
  children: [
1939
- /* @__PURE__ */ jsx10(
2183
+ /* @__PURE__ */ jsx11(
1940
2184
  "div",
1941
2185
  {
1942
2186
  className: `
@@ -1954,13 +2198,13 @@ function SorceryProgressBar({
1954
2198
  }
1955
2199
  }
1956
2200
  ),
1957
- /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 flex items-center justify-center", children: isLoading && progress < 100 ? /* @__PURE__ */ jsxs6("span", { className: "font-mono text-xs text-sas-accent font-bold drop-shadow-md tracking-wider", children: [
2201
+ /* @__PURE__ */ jsx11("div", { className: "absolute inset-0 flex items-center justify-center", children: isLoading && progress < 100 ? /* @__PURE__ */ jsxs7("span", { className: "font-mono text-xs text-sas-accent font-bold drop-shadow-md tracking-wider", children: [
1958
2202
  statusText,
1959
2203
  " ",
1960
2204
  displayProgress,
1961
2205
  "%"
1962
- ] }) : isComplete ? /* @__PURE__ */ jsx10("span", { className: "font-mono text-xs text-sas-text font-bold drop-shadow-md tracking-wider", children: completeText }) : null }),
1963
- /* @__PURE__ */ jsx10(
2206
+ ] }) : isComplete ? /* @__PURE__ */ jsx11("span", { className: "font-mono text-xs text-sas-text font-bold drop-shadow-md tracking-wider", children: completeText }) : null }),
2207
+ /* @__PURE__ */ jsx11(
1964
2208
  "div",
1965
2209
  {
1966
2210
  className: "absolute inset-0 pointer-events-none opacity-10",
@@ -1981,7 +2225,7 @@ function SorceryProgressBar({
1981
2225
  }
1982
2226
 
1983
2227
  // src/components/TrackRow.tsx
1984
- import { Fragment, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2228
+ import { Fragment, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1985
2229
  function TrackRow({
1986
2230
  track,
1987
2231
  prompt,
@@ -2010,6 +2254,7 @@ function TrackRow({
2010
2254
  onFxToggle,
2011
2255
  onFxPresetChange,
2012
2256
  onFxDryWetChange,
2257
+ externalFxHost,
2013
2258
  onToggleFxDrawer,
2014
2259
  onProgressChange,
2015
2260
  accentColor = "#A78BFA",
@@ -2040,7 +2285,7 @@ function TrackRow({
2040
2285
  levels
2041
2286
  }) {
2042
2287
  const { muted: isMuted, solo: isSoloed, volume: currentVolume, pan: currentPan } = runtimeState;
2043
- const [confirmDelete, setConfirmDelete] = React8.useState(false);
2288
+ const [confirmDelete, setConfirmDelete] = React9.useState(false);
2044
2289
  const needsGeneration = !!(prompt?.trim() && !hasMidi && !isGenerating);
2045
2290
  const hasFxActive = Object.values(fxDetailState).some(
2046
2291
  (d) => d.enabled
@@ -2055,8 +2300,8 @@ function TrackRow({
2055
2300
  };
2056
2301
  const borderColorStyle = needsGeneration ? void 0 : accentColor;
2057
2302
  const borderClass = needsGeneration ? "border-amber-400 animate-pulse" : "border-sas-border";
2058
- return /* @__PURE__ */ jsxs7("div", { "data-testid": "sdk-track-row-wrapper", className: "w-full", ...drag?.rowProps ?? {}, children: [
2059
- /* @__PURE__ */ jsxs7(
2303
+ return /* @__PURE__ */ jsxs8("div", { "data-testid": "sdk-track-row-wrapper", className: "w-full", ...drag?.rowProps ?? {}, children: [
2304
+ /* @__PURE__ */ jsxs8(
2060
2305
  "div",
2061
2306
  {
2062
2307
  "data-testid": "sdk-track-row",
@@ -2066,7 +2311,7 @@ function TrackRow({
2066
2311
  borderLeftWidth: "3px"
2067
2312
  },
2068
2313
  children: [
2069
- drag && /* @__PURE__ */ jsx11(
2314
+ drag && /* @__PURE__ */ jsx12(
2070
2315
  "div",
2071
2316
  {
2072
2317
  "data-testid": "sdk-drag-handle",
@@ -2074,10 +2319,10 @@ function TrackRow({
2074
2319
  className: "flex-shrink-0 self-stretch flex items-center -ml-0.5 pr-0.5 text-sas-muted/40 hover:text-sas-muted cursor-grab active:cursor-grabbing relative z-30",
2075
2320
  title: "Drag to reorder",
2076
2321
  "aria-label": "Drag to reorder track",
2077
- children: /* @__PURE__ */ jsx11(GripVertical, { className: "w-3.5 h-3.5", strokeWidth: 2 })
2322
+ children: /* @__PURE__ */ jsx12(GripVertical, { className: "w-3.5 h-3.5", strokeWidth: 2 })
2078
2323
  }
2079
2324
  ),
2080
- isGenerating && /* @__PURE__ */ jsx11("div", { className: "absolute left-0 top-0 bottom-0 right-44 z-20", children: /* @__PURE__ */ jsx11(
2325
+ isGenerating && /* @__PURE__ */ jsx12("div", { className: "absolute left-0 top-0 bottom-0 right-44 z-20", children: /* @__PURE__ */ jsx12(
2081
2326
  SorceryProgressBar,
2082
2327
  {
2083
2328
  isLoading: true,
@@ -2088,14 +2333,14 @@ function TrackRow({
2088
2333
  estimatedDurationMs: estimatedGenerationMs
2089
2334
  }
2090
2335
  ) }),
2091
- /* @__PURE__ */ jsxs7(
2336
+ /* @__PURE__ */ jsxs8(
2092
2337
  "div",
2093
2338
  {
2094
2339
  "data-testid": "sdk-track-content",
2095
2340
  className: `flex flex-col flex-1 min-w-0 relative z-10 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
2096
2341
  title: soloedOut ? "Silenced \u2014 another track is soloed" : void 0,
2097
2342
  children: [
2098
- contentSlot ? contentSlot : onPromptChange ? /* @__PURE__ */ jsx11(
2343
+ contentSlot ? contentSlot : onPromptChange ? /* @__PURE__ */ jsx12(
2099
2344
  "input",
2100
2345
  {
2101
2346
  type: "text",
@@ -2108,10 +2353,10 @@ function TrackRow({
2108
2353
  className: "sas-input w-full px-2 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed"
2109
2354
  }
2110
2355
  ) : null,
2111
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 mt-1", children: [
2112
- track.name && /* @__PURE__ */ jsx11("span", { className: "text-[10px] text-sas-muted/60 truncate pl-2 flex-shrink-0 max-w-[80px]", title: track.name, children: track.name }),
2113
- /* @__PURE__ */ jsx11("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "vol:" }),
2114
- /* @__PURE__ */ jsx11(
2356
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 mt-1", children: [
2357
+ track.name && /* @__PURE__ */ jsx12("span", { className: "text-[10px] text-sas-muted/60 truncate pl-2 flex-shrink-0 max-w-[80px]", title: track.name, children: track.name }),
2358
+ /* @__PURE__ */ jsx12("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "vol:" }),
2359
+ /* @__PURE__ */ jsx12(
2115
2360
  VolumeSlider,
2116
2361
  {
2117
2362
  value: currentVolume,
@@ -2120,8 +2365,8 @@ function TrackRow({
2120
2365
  className: "flex-1 min-w-[40px]"
2121
2366
  }
2122
2367
  ),
2123
- /* @__PURE__ */ jsx11("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "pan:" }),
2124
- /* @__PURE__ */ jsx11(
2368
+ /* @__PURE__ */ jsx12("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "pan:" }),
2369
+ /* @__PURE__ */ jsx12(
2125
2370
  PanSlider,
2126
2371
  {
2127
2372
  value: currentPan,
@@ -2134,27 +2379,27 @@ function TrackRow({
2134
2379
  ]
2135
2380
  }
2136
2381
  ),
2137
- error && /* @__PURE__ */ jsx11(
2382
+ error && /* @__PURE__ */ jsx12(
2138
2383
  "div",
2139
2384
  {
2140
2385
  "data-testid": "sdk-error-indicator",
2141
2386
  className: "flex-shrink-0 relative z-10 self-stretch flex items-center px-1 group cursor-help",
2142
2387
  title: error,
2143
- children: /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
2144
- /* @__PURE__ */ jsx11(
2388
+ children: /* @__PURE__ */ jsxs8("div", { className: "relative", children: [
2389
+ /* @__PURE__ */ jsx12(
2145
2390
  AlertCircle,
2146
2391
  {
2147
2392
  className: "w-5 h-5 text-red-500 animate-pulse",
2148
2393
  strokeWidth: 2.5
2149
2394
  }
2150
2395
  ),
2151
- /* @__PURE__ */ jsx11("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-red-900/95 text-red-100 text-xs rounded shadow-lg whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 max-w-[200px] truncate", children: error })
2396
+ /* @__PURE__ */ jsx12("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-red-900/95 text-red-100 text-xs rounded shadow-lg whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 max-w-[200px] truncate", children: error })
2152
2397
  ] })
2153
2398
  }
2154
2399
  ),
2155
- /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-0.5 flex-shrink-0 relative z-30 justify-center", children: [
2156
- /* @__PURE__ */ jsxs7("div", { className: "flex gap-1 items-center", children: [
2157
- onGenerate && /* @__PURE__ */ jsx11(
2400
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-0.5 flex-shrink-0 relative z-30 justify-center", children: [
2401
+ /* @__PURE__ */ jsxs8("div", { className: "flex gap-1 items-center", children: [
2402
+ onGenerate && /* @__PURE__ */ jsx12(
2158
2403
  "button",
2159
2404
  {
2160
2405
  "data-testid": "sdk-generate-button",
@@ -2165,7 +2410,7 @@ function TrackRow({
2165
2410
  children: "Create"
2166
2411
  }
2167
2412
  ),
2168
- onCopy && /* @__PURE__ */ jsx11(
2413
+ onCopy && /* @__PURE__ */ jsx12(
2169
2414
  "button",
2170
2415
  {
2171
2416
  "data-testid": "sdk-copy-button",
@@ -2176,7 +2421,7 @@ function TrackRow({
2176
2421
  children: "Copy"
2177
2422
  }
2178
2423
  ),
2179
- /* @__PURE__ */ jsx11(
2424
+ /* @__PURE__ */ jsx12(
2180
2425
  "button",
2181
2426
  {
2182
2427
  "data-testid": "sdk-mute-button",
@@ -2186,7 +2431,7 @@ function TrackRow({
2186
2431
  children: "M"
2187
2432
  }
2188
2433
  ),
2189
- onDelete && /* @__PURE__ */ jsx11(
2434
+ onDelete && /* @__PURE__ */ jsx12(
2190
2435
  "button",
2191
2436
  {
2192
2437
  "data-testid": "sdk-delete-button",
@@ -2197,8 +2442,8 @@ function TrackRow({
2197
2442
  }
2198
2443
  )
2199
2444
  ] }),
2200
- /* @__PURE__ */ jsxs7("div", { className: "flex gap-1 items-center", children: [
2201
- onShuffle && /* @__PURE__ */ jsx11(
2445
+ /* @__PURE__ */ jsxs8("div", { className: "flex gap-1 items-center", children: [
2446
+ onShuffle && /* @__PURE__ */ jsx12(
2202
2447
  "button",
2203
2448
  {
2204
2449
  "data-testid": "sdk-shuffle-button",
@@ -2209,7 +2454,7 @@ function TrackRow({
2209
2454
  children: "Shuffle"
2210
2455
  }
2211
2456
  ),
2212
- onToggleFxDrawer && /* @__PURE__ */ jsx11(
2457
+ onToggleFxDrawer && /* @__PURE__ */ jsx12(
2213
2458
  "button",
2214
2459
  {
2215
2460
  "data-testid": "sdk-fx-button",
@@ -2220,7 +2465,7 @@ function TrackRow({
2220
2465
  children: "FX"
2221
2466
  }
2222
2467
  ),
2223
- /* @__PURE__ */ jsx11(
2468
+ /* @__PURE__ */ jsx12(
2224
2469
  "button",
2225
2470
  {
2226
2471
  "data-testid": "sdk-solo-button",
@@ -2231,7 +2476,7 @@ function TrackRow({
2231
2476
  children: "S"
2232
2477
  }
2233
2478
  ),
2234
- onToggleDrawer && /* @__PURE__ */ jsx11(
2479
+ onToggleDrawer && /* @__PURE__ */ jsx12(
2235
2480
  "button",
2236
2481
  {
2237
2482
  "data-testid": "sdk-plugin-button",
@@ -2239,7 +2484,7 @@ function TrackRow({
2239
2484
  disabled: isGenerating,
2240
2485
  className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${isGenerating ? "bg-sas-panel text-sas-muted/50 cursor-not-allowed" : soundTabOpen ? "bg-sas-accent border-sas-accent text-sas-bg" : instrumentMissing ? "bg-amber-500/20 text-amber-400 hover:bg-amber-500/40" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"}`,
2241
2486
  title: `Sound \u2014 presets & history${instrumentMissing ? " (instrument missing)" : ""}`,
2242
- children: /* @__PURE__ */ jsx11(ChevronDown, { className: "w-3 h-3", strokeWidth: 2.5 })
2487
+ children: /* @__PURE__ */ jsx12(ChevronDown, { className: "w-3 h-3", strokeWidth: 2.5 })
2243
2488
  }
2244
2489
  )
2245
2490
  ] })
@@ -2247,13 +2492,13 @@ function TrackRow({
2247
2492
  ]
2248
2493
  }
2249
2494
  ),
2250
- levels && /* @__PURE__ */ jsx11(TrackMeterStrip, { levels, trackId: track.id, roundBottom: !drawerOpen }),
2251
- drawerOpen && /* @__PURE__ */ jsx11(
2495
+ levels && /* @__PURE__ */ jsx12(TrackMeterStrip, { levels, trackId: track.id, roundBottom: !drawerOpen }),
2496
+ drawerOpen && /* @__PURE__ */ jsx12(
2252
2497
  "div",
2253
2498
  {
2254
2499
  "data-testid": "sdk-track-drawer",
2255
2500
  className: "border border-t-0 border-sas-border bg-sas-bg rounded-b-sm px-3 py-2 max-h-[260px] overflow-y-auto",
2256
- children: /* @__PURE__ */ jsx11(
2501
+ children: /* @__PURE__ */ jsx12(
2257
2502
  TrackDrawer,
2258
2503
  {
2259
2504
  activeTab: drawerTab,
@@ -2263,6 +2508,7 @@ function TrackRow({
2263
2508
  onFxToggle,
2264
2509
  onFxPresetChange,
2265
2510
  onFxDryWetChange,
2511
+ externalFxHost,
2266
2512
  fxDisabled: isGenerating,
2267
2513
  instruments: availableInstruments,
2268
2514
  currentPluginId: currentInstrumentPluginId ?? null,
@@ -2289,13 +2535,13 @@ function TrackRow({
2289
2535
  )
2290
2536
  }
2291
2537
  ),
2292
- /* @__PURE__ */ jsx11(
2538
+ /* @__PURE__ */ jsx12(
2293
2539
  ConfirmDialog,
2294
2540
  {
2295
2541
  open: confirmDelete,
2296
2542
  title: "Delete track?",
2297
- message: /* @__PURE__ */ jsxs7(Fragment, { children: [
2298
- /* @__PURE__ */ jsx11("span", { className: "text-sas-text", children: track.name?.trim() || "This track" }),
2543
+ message: /* @__PURE__ */ jsxs8(Fragment, { children: [
2544
+ /* @__PURE__ */ jsx12("span", { className: "text-sas-text", children: track.name?.trim() || "This track" }),
2299
2545
  " will be permanently removed from this scene. This cannot be undone."
2300
2546
  ] }),
2301
2547
  confirmLabel: "Delete",
@@ -2311,13 +2557,13 @@ function TrackRow({
2311
2557
  }
2312
2558
 
2313
2559
  // src/components/CrossfadeTrackRow.tsx
2314
- import React9 from "react";
2315
- import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2560
+ import React10 from "react";
2561
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
2316
2562
  function LayerCaption({ tag, layer }) {
2317
- return /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2318
- /* @__PURE__ */ jsx12("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2319
- /* @__PURE__ */ jsx12("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2320
- layer.soundLabel && /* @__PURE__ */ jsxs8("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
2563
+ return /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2564
+ /* @__PURE__ */ jsx13("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2565
+ /* @__PURE__ */ jsx13("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2566
+ layer.soundLabel && /* @__PURE__ */ jsxs9("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
2321
2567
  "\xB7 ",
2322
2568
  layer.soundLabel
2323
2569
  ] })
@@ -2336,8 +2582,8 @@ function CrossfadeTrackRow({
2336
2582
  levels,
2337
2583
  accentColor = "#9333EA"
2338
2584
  }) {
2339
- const [confirmDelete, setConfirmDelete] = React9.useState(false);
2340
- const renderLayer = (layer, slot, tag) => /* @__PURE__ */ jsx12(
2585
+ const [confirmDelete, setConfirmDelete] = React10.useState(false);
2586
+ const renderLayer = (layer, slot, tag) => /* @__PURE__ */ jsx13(
2341
2587
  TrackRow,
2342
2588
  {
2343
2589
  track: { id: layer.trackId, name: "", role: layer.role },
@@ -2347,23 +2593,23 @@ function CrossfadeTrackRow({
2347
2593
  drawerTab: "fx",
2348
2594
  levels,
2349
2595
  accentColor,
2350
- contentSlot: /* @__PURE__ */ jsx12(LayerCaption, { tag, layer }),
2596
+ contentSlot: /* @__PURE__ */ jsx13(LayerCaption, { tag, layer }),
2351
2597
  onMuteToggle,
2352
2598
  onSoloToggle,
2353
2599
  onVolumeChange: (v) => onVolumeChange(slot, v),
2354
2600
  onPanChange: (p) => onPanChange(slot, p)
2355
2601
  }
2356
2602
  );
2357
- return /* @__PURE__ */ jsxs8(
2603
+ return /* @__PURE__ */ jsxs9(
2358
2604
  "div",
2359
2605
  {
2360
2606
  "data-testid": "crossfade-track-row",
2361
2607
  className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
2362
2608
  style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
2363
2609
  children: [
2364
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2365
- /* @__PURE__ */ jsx12("span", { className: "text-[10px] font-bold uppercase tracking-wide", style: { color: accentColor }, children: "\u21C4 Crossfade" }),
2366
- /* @__PURE__ */ jsx12(
2610
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2611
+ /* @__PURE__ */ jsx13("span", { className: "text-[10px] font-bold uppercase tracking-wide", style: { color: accentColor }, children: "\u21C4 Crossfade" }),
2612
+ /* @__PURE__ */ jsx13(
2367
2613
  "button",
2368
2614
  {
2369
2615
  "data-testid": "crossfade-delete-button",
@@ -2376,8 +2622,8 @@ function CrossfadeTrackRow({
2376
2622
  )
2377
2623
  ] }),
2378
2624
  renderLayer(origin, "origin", "Origin"),
2379
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "crossfade-slider-row", children: [
2380
- /* @__PURE__ */ jsx12(
2625
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "crossfade-slider-row", children: [
2626
+ /* @__PURE__ */ jsx13(
2381
2627
  "span",
2382
2628
  {
2383
2629
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
@@ -2385,7 +2631,7 @@ function CrossfadeTrackRow({
2385
2631
  children: origin.sourceName ?? origin.name
2386
2632
  }
2387
2633
  ),
2388
- /* @__PURE__ */ jsx12(
2634
+ /* @__PURE__ */ jsx13(
2389
2635
  "input",
2390
2636
  {
2391
2637
  type: "range",
@@ -2401,7 +2647,7 @@ function CrossfadeTrackRow({
2401
2647
  "aria-label": "Crossfade position"
2402
2648
  }
2403
2649
  ),
2404
- /* @__PURE__ */ jsx12(
2650
+ /* @__PURE__ */ jsx13(
2405
2651
  "span",
2406
2652
  {
2407
2653
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
@@ -2411,12 +2657,12 @@ function CrossfadeTrackRow({
2411
2657
  )
2412
2658
  ] }),
2413
2659
  renderLayer(target, "target", "Target"),
2414
- /* @__PURE__ */ jsx12(
2660
+ /* @__PURE__ */ jsx13(
2415
2661
  ConfirmDialog,
2416
2662
  {
2417
2663
  open: confirmDelete,
2418
2664
  title: "Delete crossfade?",
2419
- message: /* @__PURE__ */ jsx12(Fragment2, { children: "This crossfade pair (both layers) will be permanently removed from this scene. This cannot be undone." }),
2665
+ message: /* @__PURE__ */ jsx13(Fragment2, { children: "This crossfade pair (both layers) will be permanently removed from this scene. This cannot be undone." }),
2420
2666
  confirmLabel: "Delete",
2421
2667
  onConfirm: () => {
2422
2668
  setConfirmDelete(false);
@@ -2659,22 +2905,22 @@ function defaultFadeGesture(role) {
2659
2905
  }
2660
2906
 
2661
2907
  // src/components/FadeTrackRow.tsx
2662
- import React10 from "react";
2663
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
2908
+ import React11 from "react";
2909
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
2664
2910
  function FadeCaption({
2665
2911
  layer,
2666
2912
  direction,
2667
2913
  gesture
2668
2914
  }) {
2669
2915
  const tag = direction === "in" ? "Fade in" : "Fade out";
2670
- return /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2671
- /* @__PURE__ */ jsx13("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2672
- /* @__PURE__ */ jsx13("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2673
- layer.soundLabel && /* @__PURE__ */ jsxs9("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
2916
+ return /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2917
+ /* @__PURE__ */ jsx14("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2918
+ /* @__PURE__ */ jsx14("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2919
+ layer.soundLabel && /* @__PURE__ */ jsxs10("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
2674
2920
  "\xB7 ",
2675
2921
  layer.soundLabel
2676
2922
  ] }),
2677
- /* @__PURE__ */ jsxs9("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", title: `Fade gesture: ${gesture}`, children: [
2923
+ /* @__PURE__ */ jsxs10("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", title: `Fade gesture: ${gesture}`, children: [
2678
2924
  "\xB7 ",
2679
2925
  gesture
2680
2926
  ] })
@@ -2695,20 +2941,20 @@ function FadeTrackRow({
2695
2941
  levels,
2696
2942
  accentColor = "#9333EA"
2697
2943
  }) {
2698
- const [confirmDelete, setConfirmDelete] = React10.useState(false);
2944
+ const [confirmDelete, setConfirmDelete] = React11.useState(false);
2699
2945
  const leftLabel = direction === "in" ? "(silent)" : layer.sourceName ?? layer.name;
2700
2946
  const rightLabel = direction === "in" ? layer.sourceName ?? layer.name : "(silent)";
2701
2947
  const verb = effect && effect !== "fade" ? effect.charAt(0).toUpperCase() + effect.slice(1) : "Fade";
2702
2948
  const badge = direction === "in" ? `\u2197 ${verb} in` : `\u2198 ${verb} out`;
2703
- return /* @__PURE__ */ jsxs9(
2949
+ return /* @__PURE__ */ jsxs10(
2704
2950
  "div",
2705
2951
  {
2706
2952
  "data-testid": "fade-track-row",
2707
2953
  className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
2708
2954
  style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
2709
2955
  children: [
2710
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2711
- /* @__PURE__ */ jsx13(
2956
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2957
+ /* @__PURE__ */ jsx14(
2712
2958
  "span",
2713
2959
  {
2714
2960
  "data-testid": "fade-direction-badge",
@@ -2717,7 +2963,7 @@ function FadeTrackRow({
2717
2963
  children: badge
2718
2964
  }
2719
2965
  ),
2720
- /* @__PURE__ */ jsx13(
2966
+ /* @__PURE__ */ jsx14(
2721
2967
  "button",
2722
2968
  {
2723
2969
  "data-testid": "fade-delete-button",
@@ -2729,7 +2975,7 @@ function FadeTrackRow({
2729
2975
  }
2730
2976
  )
2731
2977
  ] }),
2732
- /* @__PURE__ */ jsx13(
2978
+ /* @__PURE__ */ jsx14(
2733
2979
  TrackRow,
2734
2980
  {
2735
2981
  track: { id: layer.trackId, name: "", role: layer.role },
@@ -2739,15 +2985,15 @@ function FadeTrackRow({
2739
2985
  drawerTab: "fx",
2740
2986
  levels,
2741
2987
  accentColor,
2742
- contentSlot: /* @__PURE__ */ jsx13(FadeCaption, { layer, direction, gesture }),
2988
+ contentSlot: /* @__PURE__ */ jsx14(FadeCaption, { layer, direction, gesture }),
2743
2989
  onMuteToggle,
2744
2990
  onSoloToggle,
2745
2991
  onVolumeChange,
2746
2992
  onPanChange
2747
2993
  }
2748
2994
  ),
2749
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "fade-slider-row", children: [
2750
- /* @__PURE__ */ jsx13(
2995
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "fade-slider-row", children: [
2996
+ /* @__PURE__ */ jsx14(
2751
2997
  "span",
2752
2998
  {
2753
2999
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
@@ -2755,7 +3001,7 @@ function FadeTrackRow({
2755
3001
  children: leftLabel
2756
3002
  }
2757
3003
  ),
2758
- /* @__PURE__ */ jsx13(
3004
+ /* @__PURE__ */ jsx14(
2759
3005
  "input",
2760
3006
  {
2761
3007
  type: "range",
@@ -2771,7 +3017,7 @@ function FadeTrackRow({
2771
3017
  "aria-label": "Fade position"
2772
3018
  }
2773
3019
  ),
2774
- /* @__PURE__ */ jsx13(
3020
+ /* @__PURE__ */ jsx14(
2775
3021
  "span",
2776
3022
  {
2777
3023
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
@@ -2780,12 +3026,12 @@ function FadeTrackRow({
2780
3026
  }
2781
3027
  )
2782
3028
  ] }),
2783
- /* @__PURE__ */ jsx13(
3029
+ /* @__PURE__ */ jsx14(
2784
3030
  ConfirmDialog,
2785
3031
  {
2786
3032
  open: confirmDelete,
2787
3033
  title: "Delete fade?",
2788
- message: /* @__PURE__ */ jsx13(Fragment3, { children: "This fade track will be permanently removed from this scene. This cannot be undone." }),
3034
+ message: /* @__PURE__ */ jsx14(Fragment3, { children: "This fade track will be permanently removed from this scene. This cannot be undone." }),
2789
3035
  confirmLabel: "Delete",
2790
3036
  onConfirm: () => {
2791
3037
  setConfirmDelete(false);
@@ -2801,8 +3047,8 @@ function FadeTrackRow({
2801
3047
  }
2802
3048
 
2803
3049
  // src/components/FadeModal.tsx
2804
- import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef7, useState as useState7 } from "react";
2805
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
3050
+ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef8, useState as useState9 } from "react";
3051
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
2806
3052
  function shortId(dbId) {
2807
3053
  return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
2808
3054
  }
@@ -2845,7 +3091,7 @@ function OrphanRow({
2845
3091
  }) {
2846
3092
  const primary = track.prompt?.trim() || track.name;
2847
3093
  const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(" \xB7 ");
2848
- return /* @__PURE__ */ jsxs10(
3094
+ return /* @__PURE__ */ jsxs11(
2849
3095
  "button",
2850
3096
  {
2851
3097
  type: "button",
@@ -2857,8 +3103,8 @@ function OrphanRow({
2857
3103
  disabled,
2858
3104
  className: `w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${selected ? "bg-sas-accent/15 border-sas-accent" : "bg-sas-panel border-sas-border hover:border-sas-accent/50"}`,
2859
3105
  children: [
2860
- /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
2861
- meta && /* @__PURE__ */ jsx14("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
3106
+ /* @__PURE__ */ jsx15("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3107
+ meta && /* @__PURE__ */ jsx15("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
2862
3108
  ]
2863
3109
  }
2864
3110
  );
@@ -2875,14 +3121,14 @@ function FadeModal({
2875
3121
  onCreate,
2876
3122
  testIdPrefix = "fade-modal"
2877
3123
  }) {
2878
- const [load, setLoad] = useState7({ status: "loading" });
2879
- const [selectedDbId, setSelectedDbId] = useState7("");
2880
- const [isCreating, setIsCreating] = useState7(false);
2881
- const [error, setError] = useState7(null);
2882
- const [fromName, setFromName] = useState7(null);
2883
- const [toName, setToName] = useState7(null);
2884
- const cancelRef = useRef7(null);
2885
- const refresh = useCallback4(async () => {
3124
+ const [load, setLoad] = useState9({ status: "loading" });
3125
+ const [selectedDbId, setSelectedDbId] = useState9("");
3126
+ const [isCreating, setIsCreating] = useState9(false);
3127
+ const [error, setError] = useState9(null);
3128
+ const [fromName, setFromName] = useState9(null);
3129
+ const [toName, setToName] = useState9(null);
3130
+ const cancelRef = useRef8(null);
3131
+ const refresh = useCallback5(async () => {
2886
3132
  if (!host.listSceneFamilyTracks) {
2887
3133
  setLoad({ status: "error", message: "This host does not support fades." });
2888
3134
  return;
@@ -2902,7 +3148,7 @@ function FadeModal({
2902
3148
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
2903
3149
  }
2904
3150
  }, [host, fromSceneId, toSceneId]);
2905
- useEffect6(() => {
3151
+ useEffect7(() => {
2906
3152
  if (open) {
2907
3153
  setError(null);
2908
3154
  setIsCreating(false);
@@ -2910,29 +3156,29 @@ function FadeModal({
2910
3156
  void refresh();
2911
3157
  }
2912
3158
  }, [open, refresh]);
2913
- const excludeSet = useMemo3(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
2914
- const { fadeOut, fadeIn } = useMemo3(
3159
+ const excludeSet = useMemo4(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3160
+ const { fadeOut, fadeIn } = useMemo4(
2915
3161
  () => load.status === "ready" ? computeOrphans(load.from, load.to, excludeSet) : { fadeOut: [], fadeIn: [] },
2916
3162
  [load, excludeSet]
2917
3163
  );
2918
- const allOrphans = useMemo3(
3164
+ const allOrphans = useMemo4(
2919
3165
  () => [
2920
3166
  ...fadeOut.map((t) => ({ track: t, direction: "out" })),
2921
3167
  ...fadeIn.map((t) => ({ track: t, direction: "in" }))
2922
3168
  ],
2923
3169
  [fadeOut, fadeIn]
2924
3170
  );
2925
- useEffect6(() => {
3171
+ useEffect7(() => {
2926
3172
  if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {
2927
3173
  setSelectedDbId(allOrphans[0]?.track.dbId ?? "");
2928
3174
  }
2929
3175
  }, [allOrphans, selectedDbId]);
2930
3176
  const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;
2931
3177
  const canCreate = !isCreating && !!selected;
2932
- const handleClose = useCallback4(() => {
3178
+ const handleClose = useCallback5(() => {
2933
3179
  if (!isCreating) onClose();
2934
3180
  }, [isCreating, onClose]);
2935
- const handleCreate = useCallback4(async () => {
3181
+ const handleCreate = useCallback5(async () => {
2936
3182
  if (!selected) return;
2937
3183
  setIsCreating(true);
2938
3184
  setError(null);
@@ -2953,16 +3199,16 @@ function FadeModal({
2953
3199
  if (!open) return null;
2954
3200
  const renderSection = (heading, list, section) => {
2955
3201
  if (list.length === 0) return null;
2956
- return /* @__PURE__ */ jsxs10("div", { className: "block", children: [
2957
- /* @__PURE__ */ jsx14("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
2958
- /* @__PURE__ */ jsx14(
3202
+ return /* @__PURE__ */ jsxs11("div", { className: "block", children: [
3203
+ /* @__PURE__ */ jsx15("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
3204
+ /* @__PURE__ */ jsx15(
2959
3205
  "div",
2960
3206
  {
2961
3207
  role: "radiogroup",
2962
3208
  "aria-label": heading,
2963
3209
  "data-testid": `${testIdPrefix}-${section === "out" ? "fade-out" : "fade-in"}-list`,
2964
3210
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
2965
- children: list.map((t) => /* @__PURE__ */ jsx14(
3211
+ children: list.map((t) => /* @__PURE__ */ jsx15(
2966
3212
  OrphanRow,
2967
3213
  {
2968
3214
  track: t,
@@ -2978,32 +3224,32 @@ function FadeModal({
2978
3224
  )
2979
3225
  ] });
2980
3226
  };
2981
- return /* @__PURE__ */ jsx14(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs10(
3227
+ return /* @__PURE__ */ jsx15(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs11(
2982
3228
  "div",
2983
3229
  {
2984
3230
  className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
2985
3231
  onClick: (e) => e.stopPropagation(),
2986
3232
  "data-testid": `${testIdPrefix}-box`,
2987
3233
  children: [
2988
- /* @__PURE__ */ jsx14("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
2989
- /* @__PURE__ */ jsxs10("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
3234
+ /* @__PURE__ */ jsx15("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
3235
+ /* @__PURE__ */ jsxs11("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
2990
3236
  "Tracks with no counterpart between",
2991
3237
  " ",
2992
- /* @__PURE__ */ jsx14("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
3238
+ /* @__PURE__ */ jsx15("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
2993
3239
  " and",
2994
3240
  " ",
2995
- /* @__PURE__ */ jsx14("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
3241
+ /* @__PURE__ */ jsx15("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
2996
3242
  " can gracefully fade out (leaving) or fade in (entering) across this transition."
2997
3243
  ] }),
2998
- load.status === "loading" && /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
2999
- load.status === "error" && /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3000
- load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-muted py-4 text-center", "data-testid": `${testIdPrefix}-empty`, children: "Every track has a counterpart in the other scene \u2014 nothing to fade. Use \u201C+ Crossfade\u201D to bridge matching tracks." }) : /* @__PURE__ */ jsxs10(Fragment4, { children: [
3244
+ load.status === "loading" && /* @__PURE__ */ jsx15("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3245
+ load.status === "error" && /* @__PURE__ */ jsx15("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3246
+ load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ jsx15("div", { className: "text-xs text-sas-muted py-4 text-center", "data-testid": `${testIdPrefix}-empty`, children: "Every track has a counterpart in the other scene \u2014 nothing to fade. Use \u201C+ Crossfade\u201D to bridge matching tracks." }) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
3001
3247
  renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ""}`, fadeOut, "out"),
3002
3248
  renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ""}`, fadeIn, "in")
3003
3249
  ] })),
3004
- error && /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3005
- /* @__PURE__ */ jsxs10("div", { className: "flex justify-end gap-2 pt-1", children: [
3006
- /* @__PURE__ */ jsx14(
3250
+ error && /* @__PURE__ */ jsx15("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3251
+ /* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-2 pt-1", children: [
3252
+ /* @__PURE__ */ jsx15(
3007
3253
  "button",
3008
3254
  {
3009
3255
  ref: cancelRef,
@@ -3014,7 +3260,7 @@ function FadeModal({
3014
3260
  children: "Cancel"
3015
3261
  }
3016
3262
  ),
3017
- /* @__PURE__ */ jsx14(
3263
+ /* @__PURE__ */ jsx15(
3018
3264
  "button",
3019
3265
  {
3020
3266
  "data-testid": `${testIdPrefix}-confirm`,
@@ -3031,8 +3277,8 @@ function FadeModal({
3031
3277
  }
3032
3278
 
3033
3279
  // src/components/ImportTrackModal.tsx
3034
- import { useCallback as useCallback5, useEffect as useEffect7, useState as useState8 } from "react";
3035
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
3280
+ import { useCallback as useCallback6, useEffect as useEffect8, useState as useState10 } from "react";
3281
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3036
3282
  function ImportTrackModal({
3037
3283
  host,
3038
3284
  open,
@@ -3044,10 +3290,10 @@ function ImportTrackModal({
3044
3290
  onPick,
3045
3291
  onPortTrack
3046
3292
  }) {
3047
- const [load, setLoad] = useState8({ status: "loading" });
3048
- const [selectedSceneId, setSelectedSceneId] = useState8(null);
3049
- const [importingTrackId, setImportingTrackId] = useState8(null);
3050
- const refresh = useCallback5(async () => {
3293
+ const [load, setLoad] = useState10({ status: "loading" });
3294
+ const [selectedSceneId, setSelectedSceneId] = useState10(null);
3295
+ const [importingTrackId, setImportingTrackId] = useState10(null);
3296
+ const refresh = useCallback6(async () => {
3051
3297
  if (!host.listImportableTracks) {
3052
3298
  setLoad({ status: "error", message: "This host does not support importing tracks." });
3053
3299
  return;
@@ -3063,14 +3309,14 @@ function ImportTrackModal({
3063
3309
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load scenes." });
3064
3310
  }
3065
3311
  }, [host, mode, onPortTrack]);
3066
- useEffect7(() => {
3312
+ useEffect8(() => {
3067
3313
  if (open) {
3068
3314
  setSelectedSceneId(null);
3069
3315
  setImportingTrackId(null);
3070
3316
  void refresh();
3071
3317
  }
3072
3318
  }, [open, refresh]);
3073
- const handleImport = useCallback5(
3319
+ const handleImport = useCallback6(
3074
3320
  async (track, sourceSceneId, sceneName, isSameScene) => {
3075
3321
  if (isSameScene && onPortTrack) {
3076
3322
  if (!track.importable) return;
@@ -3111,16 +3357,16 @@ function ImportTrackModal({
3111
3357
  if (!open) return null;
3112
3358
  const scenes = load.status === "ready" ? load.scenes : [];
3113
3359
  const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;
3114
- return /* @__PURE__ */ jsx15(Modal, { open, onClose, testIdPrefix, children: /* @__PURE__ */ jsxs11(
3360
+ return /* @__PURE__ */ jsx16(Modal, { open, onClose, testIdPrefix, children: /* @__PURE__ */ jsxs12(
3115
3361
  "div",
3116
3362
  {
3117
3363
  className: "w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
3118
3364
  onClick: (e) => e.stopPropagation(),
3119
3365
  "data-testid": `${testIdPrefix}-modal`,
3120
3366
  children: [
3121
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
3122
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
3123
- selectedScene && /* @__PURE__ */ jsx15(
3367
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
3368
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
3369
+ selectedScene && /* @__PURE__ */ jsx16(
3124
3370
  "button",
3125
3371
  {
3126
3372
  className: "text-sas-muted hover:text-sas-accent text-xs",
@@ -3129,9 +3375,9 @@ function ImportTrackModal({
3129
3375
  children: "\u2190"
3130
3376
  }
3131
3377
  ),
3132
- /* @__PURE__ */ jsx15("span", { className: "text-sm font-medium text-sas-text", children: selectedScene ? selectedScene.sceneName : title })
3378
+ /* @__PURE__ */ jsx16("span", { className: "text-sm font-medium text-sas-text", children: selectedScene ? selectedScene.sceneName : title })
3133
3379
  ] }),
3134
- /* @__PURE__ */ jsx15(
3380
+ /* @__PURE__ */ jsx16(
3135
3381
  "button",
3136
3382
  {
3137
3383
  className: "text-sas-muted hover:text-sas-accent text-sm",
@@ -3141,30 +3387,30 @@ function ImportTrackModal({
3141
3387
  }
3142
3388
  )
3143
3389
  ] }),
3144
- /* @__PURE__ */ jsxs11("div", { className: "overflow-y-auto p-2 flex-1", children: [
3145
- load.status === "loading" && /* @__PURE__ */ jsx15("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-loading`, children: "Loading scenes\u2026" }),
3146
- load.status === "error" && /* @__PURE__ */ jsx15("div", { className: "py-8 text-center text-xs text-red-400", "data-testid": `${testIdPrefix}-error`, children: load.message }),
3147
- load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ jsx15("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-empty`, children: mode === "sound" ? "No other scenes have a sound to import." : "No other scenes have a compatible track to import." }),
3148
- load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ jsx15("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-scene-list`, children: scenes.map((scene) => /* @__PURE__ */ jsx15("li", { children: /* @__PURE__ */ jsxs11(
3390
+ /* @__PURE__ */ jsxs12("div", { className: "overflow-y-auto p-2 flex-1", children: [
3391
+ load.status === "loading" && /* @__PURE__ */ jsx16("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-loading`, children: "Loading scenes\u2026" }),
3392
+ load.status === "error" && /* @__PURE__ */ jsx16("div", { className: "py-8 text-center text-xs text-red-400", "data-testid": `${testIdPrefix}-error`, children: load.message }),
3393
+ load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ jsx16("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-empty`, children: mode === "sound" ? "No other scenes have a sound to import." : "No other scenes have a compatible track to import." }),
3394
+ load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ jsx16("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-scene-list`, children: scenes.map((scene) => /* @__PURE__ */ jsx16("li", { children: /* @__PURE__ */ jsxs12(
3149
3395
  "button",
3150
3396
  {
3151
3397
  className: "w-full flex items-center justify-between px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt text-left text-xs text-sas-text hover:border-sas-accent hover:text-sas-accent transition-colors",
3152
3398
  onClick: () => setSelectedSceneId(scene.sceneId),
3153
3399
  "data-testid": `${testIdPrefix}-scene`,
3154
3400
  children: [
3155
- /* @__PURE__ */ jsx15("span", { className: "truncate", children: scene.sceneName }),
3156
- /* @__PURE__ */ jsxs11("span", { className: "text-sas-muted", children: [
3401
+ /* @__PURE__ */ jsx16("span", { className: "truncate", children: scene.sceneName }),
3402
+ /* @__PURE__ */ jsxs12("span", { className: "text-sas-muted", children: [
3157
3403
  scene.tracks.length,
3158
3404
  " \u2192"
3159
3405
  ] })
3160
3406
  ]
3161
3407
  }
3162
3408
  ) }, scene.sceneId)) }),
3163
- selectedScene && /* @__PURE__ */ jsx15("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-track-list`, children: selectedScene.tracks.map((track) => {
3409
+ selectedScene && /* @__PURE__ */ jsx16("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-track-list`, children: selectedScene.tracks.map((track) => {
3164
3410
  const busy = importingTrackId === track.trackId;
3165
3411
  const gated = mode === "track" && !track.importable;
3166
3412
  const disabled = gated || busy;
3167
- return /* @__PURE__ */ jsx15("li", { children: /* @__PURE__ */ jsxs11(
3413
+ return /* @__PURE__ */ jsx16("li", { children: /* @__PURE__ */ jsxs12(
3168
3414
  "button",
3169
3415
  {
3170
3416
  className: `w-full flex items-center justify-between px-2 py-1.5 rounded-sm border text-left text-xs transition-colors ${disabled ? "bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed" : "bg-sas-panel-alt border-sas-border text-sas-text hover:border-sas-accent hover:text-sas-accent"}`,
@@ -3174,14 +3420,14 @@ function ImportTrackModal({
3174
3420
  "data-testid": `${testIdPrefix}-track`,
3175
3421
  "data-importable": mode === "sound" || track.importable ? "true" : "false",
3176
3422
  children: [
3177
- /* @__PURE__ */ jsxs11("span", { className: "truncate", children: [
3423
+ /* @__PURE__ */ jsxs12("span", { className: "truncate", children: [
3178
3424
  track.name,
3179
- track.role ? /* @__PURE__ */ jsxs11("span", { className: "text-sas-muted", children: [
3425
+ track.role ? /* @__PURE__ */ jsxs12("span", { className: "text-sas-muted", children: [
3180
3426
  " \xB7 ",
3181
3427
  track.role
3182
3428
  ] }) : null
3183
3429
  ] }),
3184
- busy ? /* @__PURE__ */ jsx15("span", { className: "text-sas-muted", children: "\u2026" }) : gated ? /* @__PURE__ */ jsx15("span", { className: "text-sas-muted", children: "\u2298" }) : null
3430
+ busy ? /* @__PURE__ */ jsx16("span", { className: "text-sas-muted", children: "\u2026" }) : gated ? /* @__PURE__ */ jsx16("span", { className: "text-sas-muted", children: "\u2298" }) : null
3185
3431
  ]
3186
3432
  }
3187
3433
  ) }, track.dbId);
@@ -3193,8 +3439,8 @@ function ImportTrackModal({
3193
3439
  }
3194
3440
 
3195
3441
  // src/components/CrossfadeModal.tsx
3196
- import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef8, useState as useState9 } from "react";
3197
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3442
+ import { useCallback as useCallback7, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef9, useState as useState11 } from "react";
3443
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3198
3444
  function shortId2(dbId) {
3199
3445
  return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
3200
3446
  }
@@ -3207,7 +3453,7 @@ function CandidateRow({
3207
3453
  }) {
3208
3454
  const primary = track.prompt?.trim() || track.name;
3209
3455
  const meta = [track.role, shortId2(track.dbId)].filter(Boolean).join(" \xB7 ");
3210
- return /* @__PURE__ */ jsxs12(
3456
+ return /* @__PURE__ */ jsxs13(
3211
3457
  "button",
3212
3458
  {
3213
3459
  type: "button",
@@ -3219,8 +3465,8 @@ function CandidateRow({
3219
3465
  disabled,
3220
3466
  className: `w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${selected ? "bg-sas-accent/15 border-sas-accent" : "bg-sas-panel border-sas-border hover:border-sas-accent/50"}`,
3221
3467
  children: [
3222
- /* @__PURE__ */ jsx16("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3223
- meta && /* @__PURE__ */ jsx16("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
3468
+ /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3469
+ meta && /* @__PURE__ */ jsx17("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
3224
3470
  ]
3225
3471
  }
3226
3472
  );
@@ -3237,15 +3483,15 @@ function CrossfadeModal({
3237
3483
  onCreate,
3238
3484
  testIdPrefix = "crossfade-modal"
3239
3485
  }) {
3240
- const [load, setLoad] = useState9({ status: "loading" });
3241
- const [originDbId, setOriginDbId] = useState9("");
3242
- const [targetDbId, setTargetDbId] = useState9("");
3243
- const [isCreating, setIsCreating] = useState9(false);
3244
- const [error, setError] = useState9(null);
3245
- const [fromName, setFromName] = useState9(null);
3246
- const [toName, setToName] = useState9(null);
3247
- const cancelRef = useRef8(null);
3248
- const refresh = useCallback6(async () => {
3486
+ const [load, setLoad] = useState11({ status: "loading" });
3487
+ const [originDbId, setOriginDbId] = useState11("");
3488
+ const [targetDbId, setTargetDbId] = useState11("");
3489
+ const [isCreating, setIsCreating] = useState11(false);
3490
+ const [error, setError] = useState11(null);
3491
+ const [fromName, setFromName] = useState11(null);
3492
+ const [toName, setToName] = useState11(null);
3493
+ const cancelRef = useRef9(null);
3494
+ const refresh = useCallback7(async () => {
3249
3495
  if (!host.listSceneFamilyTracks) {
3250
3496
  setLoad({ status: "error", message: "This host does not support crossfade tracks." });
3251
3497
  return;
@@ -3265,7 +3511,7 @@ function CrossfadeModal({
3265
3511
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
3266
3512
  }
3267
3513
  }, [host, fromSceneId, toSceneId]);
3268
- useEffect8(() => {
3514
+ useEffect9(() => {
3269
3515
  if (open) {
3270
3516
  setError(null);
3271
3517
  setIsCreating(false);
@@ -3274,21 +3520,21 @@ function CrossfadeModal({
3274
3520
  void refresh();
3275
3521
  }
3276
3522
  }, [open, refresh]);
3277
- const excludeSet = useMemo4(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3278
- const originCandidates = useMemo4(
3523
+ const excludeSet = useMemo5(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3524
+ const originCandidates = useMemo5(
3279
3525
  () => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
3280
3526
  [load, excludeSet]
3281
3527
  );
3282
- const targetCandidates = useMemo4(
3528
+ const targetCandidates = useMemo5(
3283
3529
  () => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
3284
3530
  [load, excludeSet]
3285
3531
  );
3286
- useEffect8(() => {
3532
+ useEffect9(() => {
3287
3533
  if (!originCandidates.some((t) => t.dbId === originDbId)) {
3288
3534
  setOriginDbId(originCandidates[0]?.dbId ?? "");
3289
3535
  }
3290
3536
  }, [originCandidates, originDbId]);
3291
- useEffect8(() => {
3537
+ useEffect9(() => {
3292
3538
  if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
3293
3539
  setTargetDbId(targetCandidates[0]?.dbId ?? "");
3294
3540
  }
@@ -3296,10 +3542,10 @@ function CrossfadeModal({
3296
3542
  const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
3297
3543
  const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
3298
3544
  const canCreate = !isCreating && !!originTrack && !!targetTrack;
3299
- const handleClose = useCallback6(() => {
3545
+ const handleClose = useCallback7(() => {
3300
3546
  if (!isCreating) onClose();
3301
3547
  }, [isCreating, onClose]);
3302
- const handleCreate = useCallback6(async () => {
3548
+ const handleCreate = useCallback7(async () => {
3303
3549
  if (!originTrack || !targetTrack) return;
3304
3550
  setIsCreating(true);
3305
3551
  setError(null);
@@ -3317,26 +3563,26 @@ function CrossfadeModal({
3317
3563
  const fromLabel = fromName ?? fromSceneName ?? null;
3318
3564
  const toLabel = toName ?? toSceneName ?? null;
3319
3565
  if (!open) return null;
3320
- return /* @__PURE__ */ jsx16(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs12(
3566
+ return /* @__PURE__ */ jsx17(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs13(
3321
3567
  "div",
3322
3568
  {
3323
3569
  className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
3324
3570
  onClick: (e) => e.stopPropagation(),
3325
3571
  "data-testid": `${testIdPrefix}-box`,
3326
3572
  children: [
3327
- /* @__PURE__ */ jsx16("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
3328
- /* @__PURE__ */ jsxs12("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
3573
+ /* @__PURE__ */ jsx17("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
3574
+ /* @__PURE__ */ jsxs13("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
3329
3575
  "Bridge a track from",
3330
3576
  " ",
3331
- /* @__PURE__ */ jsx16("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
3577
+ /* @__PURE__ */ jsx17("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
3332
3578
  " into one from",
3333
3579
  " ",
3334
- /* @__PURE__ */ jsx16("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
3580
+ /* @__PURE__ */ jsx17("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
3335
3581
  ". Both layers share one generated part; each keeps its own preset."
3336
3582
  ] }),
3337
- load.status === "loading" && /* @__PURE__ */ jsx16("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3338
- load.status === "error" && /* @__PURE__ */ jsx16("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3339
- load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ jsxs12(
3583
+ load.status === "loading" && /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3584
+ load.status === "error" && /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3585
+ load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ jsxs13(
3340
3586
  "div",
3341
3587
  {
3342
3588
  className: "text-xs text-sas-muted py-4 text-center",
@@ -3347,20 +3593,20 @@ function CrossfadeModal({
3347
3593
  ". Add one (or free one from another crossfade) first."
3348
3594
  ]
3349
3595
  }
3350
- ) : /* @__PURE__ */ jsxs12(Fragment5, { children: [
3351
- /* @__PURE__ */ jsxs12("div", { className: "block", children: [
3352
- /* @__PURE__ */ jsxs12("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
3596
+ ) : /* @__PURE__ */ jsxs13(Fragment5, { children: [
3597
+ /* @__PURE__ */ jsxs13("div", { className: "block", children: [
3598
+ /* @__PURE__ */ jsxs13("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
3353
3599
  "Origin ",
3354
3600
  fromLabel ? `(${fromLabel})` : "(top)"
3355
3601
  ] }),
3356
- /* @__PURE__ */ jsx16(
3602
+ /* @__PURE__ */ jsx17(
3357
3603
  "div",
3358
3604
  {
3359
3605
  role: "radiogroup",
3360
3606
  "aria-label": "Origin track",
3361
3607
  "data-testid": `${testIdPrefix}-origin-list`,
3362
3608
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
3363
- children: originCandidates.map((t) => /* @__PURE__ */ jsx16(
3609
+ children: originCandidates.map((t) => /* @__PURE__ */ jsx17(
3364
3610
  CandidateRow,
3365
3611
  {
3366
3612
  track: t,
@@ -3374,23 +3620,23 @@ function CrossfadeModal({
3374
3620
  }
3375
3621
  )
3376
3622
  ] }),
3377
- /* @__PURE__ */ jsxs12("div", { className: "block", children: [
3378
- /* @__PURE__ */ jsxs12("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
3623
+ /* @__PURE__ */ jsxs13("div", { className: "block", children: [
3624
+ /* @__PURE__ */ jsxs13("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
3379
3625
  "Target ",
3380
3626
  toLabel ? `(${toLabel})` : "(bottom)"
3381
3627
  ] }),
3382
- targetCandidates.length === 0 ? /* @__PURE__ */ jsxs12("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
3628
+ targetCandidates.length === 0 ? /* @__PURE__ */ jsxs13("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
3383
3629
  "No available tracks in ",
3384
3630
  toLabel ?? "the target scene",
3385
3631
  " to crossfade into."
3386
- ] }) : /* @__PURE__ */ jsx16(
3632
+ ] }) : /* @__PURE__ */ jsx17(
3387
3633
  "div",
3388
3634
  {
3389
3635
  role: "radiogroup",
3390
3636
  "aria-label": "Target track",
3391
3637
  "data-testid": `${testIdPrefix}-target-list`,
3392
3638
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
3393
- children: targetCandidates.map((t) => /* @__PURE__ */ jsx16(
3639
+ children: targetCandidates.map((t) => /* @__PURE__ */ jsx17(
3394
3640
  CandidateRow,
3395
3641
  {
3396
3642
  track: t,
@@ -3405,9 +3651,9 @@ function CrossfadeModal({
3405
3651
  )
3406
3652
  ] })
3407
3653
  ] })),
3408
- error && /* @__PURE__ */ jsx16("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3409
- /* @__PURE__ */ jsxs12("div", { className: "flex justify-end gap-2 pt-1", children: [
3410
- /* @__PURE__ */ jsx16(
3654
+ error && /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3655
+ /* @__PURE__ */ jsxs13("div", { className: "flex justify-end gap-2 pt-1", children: [
3656
+ /* @__PURE__ */ jsx17(
3411
3657
  "button",
3412
3658
  {
3413
3659
  ref: cancelRef,
@@ -3418,7 +3664,7 @@ function CrossfadeModal({
3418
3664
  children: "Cancel"
3419
3665
  }
3420
3666
  ),
3421
- /* @__PURE__ */ jsx16(
3667
+ /* @__PURE__ */ jsx17(
3422
3668
  "button",
3423
3669
  {
3424
3670
  "data-testid": `${testIdPrefix}-confirm`,
@@ -3435,10 +3681,10 @@ function CrossfadeModal({
3435
3681
  }
3436
3682
 
3437
3683
  // src/components/TransitionDesigner.tsx
3438
- import { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef10, useState as useState11 } from "react";
3684
+ import { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo6, useRef as useRef11, useState as useState13 } from "react";
3439
3685
 
3440
3686
  // src/hooks/useTrackReorder.ts
3441
- import { useCallback as useCallback7, useRef as useRef9, useState as useState10 } from "react";
3687
+ import { useCallback as useCallback8, useRef as useRef10, useState as useState12 } from "react";
3442
3688
  function moveItem(arr, from, to) {
3443
3689
  const next = arr.slice();
3444
3690
  if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
@@ -3455,12 +3701,12 @@ function useTrackReorder({
3455
3701
  getId,
3456
3702
  onError
3457
3703
  }) {
3458
- const [draggingIndex, setDraggingIndex] = useState10(null);
3459
- const [dragOverIndex, setDragOverIndex] = useState10(null);
3460
- const fromRef = useRef9(null);
3461
- const itemsRef = useRef9(items);
3704
+ const [draggingIndex, setDraggingIndex] = useState12(null);
3705
+ const [dragOverIndex, setDragOverIndex] = useState12(null);
3706
+ const fromRef = useRef10(null);
3707
+ const itemsRef = useRef10(items);
3462
3708
  itemsRef.current = items;
3463
- const dragPropsFor = useCallback7(
3709
+ const dragPropsFor = useCallback8(
3464
3710
  (index) => ({
3465
3711
  handleProps: {
3466
3712
  draggable: true,
@@ -3642,7 +3888,7 @@ function dbIdsFromKeys(keys) {
3642
3888
  }
3643
3889
 
3644
3890
  // src/components/TransitionDesigner.tsx
3645
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3891
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
3646
3892
  var CROSSFADE_ESTIMATE_MS = 15e3;
3647
3893
  var FADE_ESTIMATE_MS = 11e3;
3648
3894
  var CREATE_ALL_CONCURRENCY = 5;
@@ -3666,44 +3912,44 @@ function TransitionDesigner({
3666
3912
  familyLabel,
3667
3913
  testIdPrefix = "transition-designer"
3668
3914
  }) {
3669
- const [load, setLoad] = useState11({ status: "loading" });
3670
- const [fromName, setFromName] = useState11(null);
3671
- const [toName, setToName] = useState11(null);
3672
- const [originSlots, setOriginSlots] = useState11([]);
3673
- const [targetSlots, setTargetSlots] = useState11([]);
3674
- const [creatingKeys, setCreatingKeys] = useState11(() => /* @__PURE__ */ new Set());
3675
- const [rowErrors, setRowErrors] = useState11({});
3676
- const [rowEffects, setRowEffects] = useState11({});
3677
- const rowEffectsRef = useRef10(rowEffects);
3915
+ const [load, setLoad] = useState13({ status: "loading" });
3916
+ const [fromName, setFromName] = useState13(null);
3917
+ const [toName, setToName] = useState13(null);
3918
+ const [originSlots, setOriginSlots] = useState13([]);
3919
+ const [targetSlots, setTargetSlots] = useState13([]);
3920
+ const [creatingKeys, setCreatingKeys] = useState13(() => /* @__PURE__ */ new Set());
3921
+ const [rowErrors, setRowErrors] = useState13({});
3922
+ const [rowEffects, setRowEffects] = useState13({});
3923
+ const rowEffectsRef = useRef11(rowEffects);
3678
3924
  rowEffectsRef.current = rowEffects;
3679
3925
  const audioEffectsEnabled = !!onCreateAudioTransition;
3680
- const excludeRef = useRef10(excludeSourceDbIds);
3926
+ const excludeRef = useRef11(excludeSourceDbIds);
3681
3927
  excludeRef.current = excludeSourceDbIds;
3682
- const originSlotsRef = useRef10(originSlots);
3928
+ const originSlotsRef = useRef11(originSlots);
3683
3929
  originSlotsRef.current = originSlots;
3684
- const targetSlotsRef = useRef10(targetSlots);
3930
+ const targetSlotsRef = useRef11(targetSlots);
3685
3931
  targetSlotsRef.current = targetSlots;
3686
- const creatingKeysRef = useRef10(creatingKeys);
3932
+ const creatingKeysRef = useRef11(creatingKeys);
3687
3933
  creatingKeysRef.current = creatingKeys;
3688
- const dragRef = useRef10(null);
3689
- const [dragging, setDragging] = useState11(null);
3690
- const [dragOver, setDragOver] = useState11(null);
3691
- const excludeSet = useMemo5(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3692
- const originPool = useMemo5(
3934
+ const dragRef = useRef11(null);
3935
+ const [dragging, setDragging] = useState13(null);
3936
+ const [dragOver, setDragOver] = useState13(null);
3937
+ const excludeSet = useMemo6(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3938
+ const originPool = useMemo6(
3693
3939
  () => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
3694
3940
  [load, excludeSet]
3695
3941
  );
3696
- const targetPool = useMemo5(
3942
+ const targetPool = useMemo6(
3697
3943
  () => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
3698
3944
  [load, excludeSet]
3699
3945
  );
3700
- const originById = useMemo5(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);
3701
- const targetById = useMemo5(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);
3702
- const originByIdRef = useRef10(originById);
3946
+ const originById = useMemo6(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);
3947
+ const targetById = useMemo6(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);
3948
+ const originByIdRef = useRef11(originById);
3703
3949
  originByIdRef.current = originById;
3704
- const targetByIdRef = useRef10(targetById);
3950
+ const targetByIdRef = useRef11(targetById);
3705
3951
  targetByIdRef.current = targetById;
3706
- const refresh = useCallback8(async () => {
3952
+ const refresh = useCallback9(async () => {
3707
3953
  if (!host.listSceneFamilyTracks) {
3708
3954
  setLoad({ status: "error", message: "This host does not support transition tracks." });
3709
3955
  return;
@@ -3738,10 +3984,10 @@ function TransitionDesigner({
3738
3984
  });
3739
3985
  }
3740
3986
  }, [host, fromSceneId, toSceneId, transitionSceneId]);
3741
- useEffect9(() => {
3987
+ useEffect10(() => {
3742
3988
  void refresh();
3743
3989
  }, [refresh]);
3744
- useEffect9(() => {
3990
+ useEffect10(() => {
3745
3991
  if (load.status !== "ready") return;
3746
3992
  const [po, pt] = padPair(
3747
3993
  reconcileSlots(originSlotsRef.current, originPool.map((t) => t.dbId)),
@@ -3750,7 +3996,7 @@ function TransitionDesigner({
3750
3996
  if (!slotsEqual(po, originSlotsRef.current)) setOriginSlots(po);
3751
3997
  if (!slotsEqual(pt, targetSlotsRef.current)) setTargetSlots(pt);
3752
3998
  }, [originPool, targetPool, load.status]);
3753
- const mutate = useCallback8(
3999
+ const mutate = useCallback9(
3754
4000
  (nextOrigin, nextTarget) => {
3755
4001
  const norm = normalizeSlots(nextOrigin, nextTarget);
3756
4002
  const [po, pt] = padPair(norm.originOrder, norm.targetOrder);
@@ -3763,7 +4009,7 @@ function TransitionDesigner({
3763
4009
  },
3764
4010
  [host, transitionSceneId]
3765
4011
  );
3766
- const setRowEffect = useCallback8(
4012
+ const setRowEffect = useCallback9(
3767
4013
  (sourceDbId, effect) => {
3768
4014
  setRowEffects((prev) => {
3769
4015
  const next = { ...prev, [sourceDbId]: effect };
@@ -3777,7 +4023,7 @@ function TransitionDesigner({
3777
4023
  },
3778
4024
  [host, transitionSceneId]
3779
4025
  );
3780
- const insertGapAbove = useCallback8(
4026
+ const insertGapAbove = useCallback9(
3781
4027
  (col, index) => {
3782
4028
  const slots = col === "origin" ? originSlots : targetSlots;
3783
4029
  const next = [...slots.slice(0, index), null, ...slots.slice(index)];
@@ -3786,7 +4032,7 @@ function TransitionDesigner({
3786
4032
  },
3787
4033
  [originSlots, targetSlots, mutate]
3788
4034
  );
3789
- const removeGap = useCallback8(
4035
+ const removeGap = useCallback9(
3790
4036
  (col, index) => {
3791
4037
  const slots = col === "origin" ? originSlots : targetSlots;
3792
4038
  const next = slots.filter((_, i) => i !== index);
@@ -3795,7 +4041,7 @@ function TransitionDesigner({
3795
4041
  },
3796
4042
  [originSlots, targetSlots, mutate]
3797
4043
  );
3798
- const handleDrop = useCallback8(
4044
+ const handleDrop = useCallback9(
3799
4045
  (col, to) => {
3800
4046
  const from = dragRef.current;
3801
4047
  dragRef.current = null;
@@ -3807,16 +4053,16 @@ function TransitionDesigner({
3807
4053
  },
3808
4054
  [originSlots, targetSlots, mutate]
3809
4055
  );
3810
- const rows = useMemo5(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);
3811
- const creatingDbIds = useMemo5(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);
3812
- const eligibleCount = useMemo5(
4056
+ const rows = useMemo6(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);
4057
+ const creatingDbIds = useMemo6(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);
4058
+ const eligibleCount = useMemo6(
3813
4059
  () => rows.filter((r) => {
3814
4060
  const k = rowKey(r);
3815
4061
  return k !== null && !creatingKeys.has(k);
3816
4062
  }).length,
3817
4063
  [rows, creatingKeys]
3818
4064
  );
3819
- const createRow = useCallback8(
4065
+ const createRow = useCallback9(
3820
4066
  async (row) => {
3821
4067
  const key = rowKey(row);
3822
4068
  if (!key || !row.type || creatingKeysRef.current.has(key)) return;
@@ -3870,7 +4116,7 @@ function TransitionDesigner({
3870
4116
  },
3871
4117
  [onCreateCrossfade, onCreateFade, onCreateAudioTransition]
3872
4118
  );
3873
- const createAll = useCallback8(async () => {
4119
+ const createAll = useCallback9(async () => {
3874
4120
  const eligible = buildRowSlots(originSlotsRef.current, targetSlotsRef.current).filter((r) => {
3875
4121
  const k = rowKey(r);
3876
4122
  return k !== null && !creatingKeysRef.current.has(k);
@@ -3938,15 +4184,15 @@ function TransitionDesigner({
3938
4184
  const base = "group relative rounded-sm border px-2 py-1.5 text-left transition-colors select-none";
3939
4185
  const tone = isDragTarget ? "border-sas-accent bg-sas-accent/10" : "border-sas-border bg-sas-panel";
3940
4186
  if (slotId === null) {
3941
- return /* @__PURE__ */ jsxs13(
4187
+ return /* @__PURE__ */ jsxs14(
3942
4188
  "div",
3943
4189
  {
3944
4190
  ...cellDragProps(col, index, false),
3945
4191
  "data-testid": `${testIdPrefix}-${col}-gap-${index}`,
3946
4192
  className: `${base} ${tone} border-dashed flex items-center justify-between ${isDragging ? "opacity-40" : "opacity-70"}`,
3947
4193
  children: [
3948
- /* @__PURE__ */ jsx17("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "\u2014 gap \u2014" }),
3949
- /* @__PURE__ */ jsx17(
4194
+ /* @__PURE__ */ jsx18("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "\u2014 gap \u2014" }),
4195
+ /* @__PURE__ */ jsx18(
3950
4196
  "button",
3951
4197
  {
3952
4198
  type: "button",
@@ -3963,7 +4209,7 @@ function TransitionDesigner({
3963
4209
  }
3964
4210
  const primary = track ? track.prompt?.trim() || track.name : slotId;
3965
4211
  const meta = track ? [track.role, shortId3(track.dbId)].filter(Boolean).join(" \xB7 ") : "missing";
3966
- return /* @__PURE__ */ jsx17(
4212
+ return /* @__PURE__ */ jsx18(
3967
4213
  "div",
3968
4214
  {
3969
4215
  ...cellDragProps(col, index, locked),
@@ -3971,13 +4217,13 @@ function TransitionDesigner({
3971
4217
  "data-value": slotId,
3972
4218
  className: `${base} ${tone} ${isDragging ? "opacity-40" : ""} ${locked ? "opacity-60" : "cursor-grab active:cursor-grabbing"}`,
3973
4219
  title: track ? track.dbId : "Track no longer available",
3974
- children: /* @__PURE__ */ jsxs13("div", { className: "flex items-start gap-1", children: [
3975
- /* @__PURE__ */ jsx17("span", { className: "text-sas-muted/60 text-xs leading-tight pt-0.5", "aria-hidden": true, children: "\u283F" }),
3976
- /* @__PURE__ */ jsxs13("div", { className: "min-w-0 flex-1", children: [
3977
- /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-text truncate", children: primary }),
3978
- meta && /* @__PURE__ */ jsx17("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: meta })
4220
+ children: /* @__PURE__ */ jsxs14("div", { className: "flex items-start gap-1", children: [
4221
+ /* @__PURE__ */ jsx18("span", { className: "text-sas-muted/60 text-xs leading-tight pt-0.5", "aria-hidden": true, children: "\u283F" }),
4222
+ /* @__PURE__ */ jsxs14("div", { className: "min-w-0 flex-1", children: [
4223
+ /* @__PURE__ */ jsx18("div", { className: "text-xs text-sas-text truncate", children: primary }),
4224
+ meta && /* @__PURE__ */ jsx18("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: meta })
3979
4225
  ] }),
3980
- /* @__PURE__ */ jsx17(
4226
+ /* @__PURE__ */ jsx18(
3981
4227
  "button",
3982
4228
  {
3983
4229
  type: "button",
@@ -3993,22 +4239,22 @@ function TransitionDesigner({
3993
4239
  }
3994
4240
  );
3995
4241
  };
3996
- return /* @__PURE__ */ jsxs13("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
3997
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
3998
- /* @__PURE__ */ jsxs13("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
3999
- /* @__PURE__ */ jsx17("span", { className: "text-sas-text", children: fromLabel }),
4242
+ return /* @__PURE__ */ jsxs14("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
4243
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
4244
+ /* @__PURE__ */ jsxs14("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
4245
+ /* @__PURE__ */ jsx18("span", { className: "text-sas-text", children: fromLabel }),
4000
4246
  " \u2192",
4001
4247
  " ",
4002
- /* @__PURE__ */ jsx17("span", { className: "text-sas-text", children: toLabel }),
4248
+ /* @__PURE__ */ jsx18("span", { className: "text-sas-text", children: toLabel }),
4003
4249
  familyLabel ? ` \xB7 ${familyLabel}` : "",
4004
4250
  " \xB7 line up a track on each side to crossfade; leave one blank (or insert a gap) to fade."
4005
4251
  ] }),
4006
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 shrink-0", children: [
4007
- creatingKeys.size > 0 && /* @__PURE__ */ jsxs13("span", { className: "text-[10px] text-sas-accent whitespace-nowrap", "data-testid": `${testIdPrefix}-creating-count`, children: [
4252
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2 shrink-0", children: [
4253
+ creatingKeys.size > 0 && /* @__PURE__ */ jsxs14("span", { className: "text-[10px] text-sas-accent whitespace-nowrap", "data-testid": `${testIdPrefix}-creating-count`, children: [
4008
4254
  creatingKeys.size,
4009
4255
  " creating\u2026"
4010
4256
  ] }),
4011
- /* @__PURE__ */ jsxs13(
4257
+ /* @__PURE__ */ jsxs14(
4012
4258
  "button",
4013
4259
  {
4014
4260
  type: "button",
@@ -4025,49 +4271,49 @@ function TransitionDesigner({
4025
4271
  )
4026
4272
  ] })
4027
4273
  ] }),
4028
- /* @__PURE__ */ jsxs13("div", { className: "grid grid-cols-[1fr_auto_1fr] gap-2", children: [
4029
- /* @__PURE__ */ jsxs13("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate", children: [
4274
+ /* @__PURE__ */ jsxs14("div", { className: "grid grid-cols-[1fr_auto_1fr] gap-2", children: [
4275
+ /* @__PURE__ */ jsxs14("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate", children: [
4030
4276
  "Origin (",
4031
4277
  fromLabel,
4032
4278
  ")"
4033
4279
  ] }),
4034
- /* @__PURE__ */ jsx17("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted text-center px-2", children: "Transition" }),
4035
- /* @__PURE__ */ jsxs13("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate text-right", children: [
4280
+ /* @__PURE__ */ jsx18("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted text-center px-2", children: "Transition" }),
4281
+ /* @__PURE__ */ jsxs14("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate text-right", children: [
4036
4282
  "Target (",
4037
4283
  toLabel,
4038
4284
  ")"
4039
4285
  ] })
4040
4286
  ] }),
4041
- load.status === "loading" && /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
4042
- load.status === "error" && /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
4043
- load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ jsxs13("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
4287
+ load.status === "loading" && /* @__PURE__ */ jsx18("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
4288
+ load.status === "error" && /* @__PURE__ */ jsx18("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
4289
+ load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ jsxs14("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
4044
4290
  "No tracks to arrange in this panel for either scene. Add tracks to ",
4045
4291
  fromLabel,
4046
4292
  " or ",
4047
4293
  toLabel,
4048
4294
  " ",
4049
4295
  "first (or free one by deleting an existing crossfade/fade)."
4050
- ] }) : /* @__PURE__ */ jsx17("div", { className: "space-y-2", children: rows.map((row, i) => {
4296
+ ] }) : /* @__PURE__ */ jsx18("div", { className: "space-y-2", children: rows.map((row, i) => {
4051
4297
  const key = rowKey(row);
4052
4298
  const isCreatingThis = key !== null && creatingKeys.has(key);
4053
4299
  const errMsg = key !== null ? rowErrors[key] : void 0;
4054
- return /* @__PURE__ */ jsxs13(
4300
+ return /* @__PURE__ */ jsxs14(
4055
4301
  "div",
4056
4302
  {
4057
4303
  "data-testid": `${testIdPrefix}-row-${i}`,
4058
4304
  className: "grid grid-cols-[1fr_auto_1fr] gap-2 items-center",
4059
4305
  children: [
4060
4306
  renderCell("origin", i, row.originId),
4061
- /* @__PURE__ */ jsxs13("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
4062
- !row.type ? /* @__PURE__ */ jsx17("span", { className: "text-[10px] text-sas-muted/50", children: "\u2014" }) : row.type === "crossfade" ? /* @__PURE__ */ jsx17(
4307
+ /* @__PURE__ */ jsxs14("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
4308
+ !row.type ? /* @__PURE__ */ jsx18("span", { className: "text-[10px] text-sas-muted/50", children: "\u2014" }) : row.type === "crossfade" ? /* @__PURE__ */ jsx18(
4063
4309
  "span",
4064
4310
  {
4065
4311
  "data-testid": `${testIdPrefix}-type-${i}`,
4066
4312
  className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-accent/50 text-sas-accent",
4067
4313
  children: TYPE_LABEL[row.type]
4068
4314
  }
4069
- ) : audioEffectsEnabled ? /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1", "data-testid": `${testIdPrefix}-type-${i}`, children: [
4070
- /* @__PURE__ */ jsx17(
4315
+ ) : audioEffectsEnabled ? /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1", "data-testid": `${testIdPrefix}-type-${i}`, children: [
4316
+ /* @__PURE__ */ jsx18(
4071
4317
  "select",
4072
4318
  {
4073
4319
  "data-testid": `${testIdPrefix}-effect-${i}`,
@@ -4077,11 +4323,11 @@ function TransitionDesigner({
4077
4323
  if (id) setRowEffect(id, e.target.value);
4078
4324
  },
4079
4325
  className: "text-[10px] bg-sas-panel border border-sas-border rounded-sm px-1 py-0.5 text-sas-text",
4080
- children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ jsx17("option", { value: eff, children: AUDIO_EFFECT_LABEL[eff] }, eff))
4326
+ children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ jsx18("option", { value: eff, children: AUDIO_EFFECT_LABEL[eff] }, eff))
4081
4327
  }
4082
4328
  ),
4083
- /* @__PURE__ */ jsx17("span", { className: "text-[9px] text-sas-muted", children: row.type === "fade-out" ? "out" : "in" })
4084
- ] }) : /* @__PURE__ */ jsx17(
4329
+ /* @__PURE__ */ jsx18("span", { className: "text-[9px] text-sas-muted", children: row.type === "fade-out" ? "out" : "in" })
4330
+ ] }) : /* @__PURE__ */ jsx18(
4085
4331
  "span",
4086
4332
  {
4087
4333
  "data-testid": `${testIdPrefix}-type-${i}`,
@@ -4089,7 +4335,7 @@ function TransitionDesigner({
4089
4335
  children: TYPE_LABEL[row.type]
4090
4336
  }
4091
4337
  ),
4092
- isCreatingThis ? /* @__PURE__ */ jsx17("div", { className: "w-full", children: /* @__PURE__ */ jsx17(
4338
+ isCreatingThis ? /* @__PURE__ */ jsx18("div", { className: "w-full", children: /* @__PURE__ */ jsx18(
4093
4339
  SorceryProgressBar,
4094
4340
  {
4095
4341
  isLoading: true,
@@ -4097,7 +4343,7 @@ function TransitionDesigner({
4097
4343
  statusText: "CREATING",
4098
4344
  estimatedDurationMs: row.type === "crossfade" ? CROSSFADE_ESTIMATE_MS : FADE_ESTIMATE_MS
4099
4345
  }
4100
- ) }) : /* @__PURE__ */ jsx17(
4346
+ ) }) : /* @__PURE__ */ jsx18(
4101
4347
  "button",
4102
4348
  {
4103
4349
  type: "button",
@@ -4108,7 +4354,7 @@ function TransitionDesigner({
4108
4354
  children: "Create"
4109
4355
  }
4110
4356
  ),
4111
- errMsg && /* @__PURE__ */ jsx17(
4357
+ errMsg && /* @__PURE__ */ jsx18(
4112
4358
  "span",
4113
4359
  {
4114
4360
  "data-testid": `${testIdPrefix}-row-error-${i}`,
@@ -4127,10 +4373,11 @@ function TransitionDesigner({
4127
4373
  }
4128
4374
 
4129
4375
  // src/components/PanelMasterStrip.tsx
4130
- import { useMemo as useMemo6, useState as useState12 } from "react";
4131
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
4376
+ import { useMemo as useMemo7, useState as useState14 } from "react";
4377
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
4132
4378
  function PanelMasterStrip({
4133
4379
  bus,
4380
+ levels = null,
4134
4381
  availableFx = [],
4135
4382
  fxLoading = false,
4136
4383
  soloedOut = false,
@@ -4146,22 +4393,22 @@ function PanelMasterStrip({
4146
4393
  onToggleFxEnabled,
4147
4394
  onShowFxEditor
4148
4395
  }) {
4149
- const [search, setSearch] = useState12("");
4150
- const filtered = useMemo6(() => {
4396
+ const [search, setSearch] = useState14("");
4397
+ const filtered = useMemo7(() => {
4151
4398
  const q = search.trim().toLowerCase();
4152
4399
  if (!q) return availableFx;
4153
4400
  return availableFx.filter(
4154
4401
  (fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
4155
4402
  );
4156
4403
  }, [availableFx, search]);
4157
- return /* @__PURE__ */ jsxs14(
4404
+ return /* @__PURE__ */ jsxs15(
4158
4405
  "div",
4159
4406
  {
4160
4407
  "data-testid": "panel-master-strip",
4161
- className: `flex flex-col gap-1 px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt/50 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
4408
+ className: `flex flex-col gap-1 px-2 py-1.5 rounded-sm border border-sas-border border-l-2 border-l-sas-accent/50 bg-sas-accent/5 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
4162
4409
  children: [
4163
- /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
4164
- /* @__PURE__ */ jsx18(
4410
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", children: [
4411
+ /* @__PURE__ */ jsx19(
4165
4412
  "span",
4166
4413
  {
4167
4414
  className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
@@ -4169,15 +4416,38 @@ function PanelMasterStrip({
4169
4416
  children: "BUS"
4170
4417
  }
4171
4418
  ),
4172
- /* @__PURE__ */ jsx18("div", { className: "w-24", children: /* @__PURE__ */ jsx18(
4173
- VolumeSlider,
4174
- {
4175
- value: dbToSlider(bus.volume),
4176
- onChange: (sliderValue) => onVolumeChange(sliderToDb(sliderValue)),
4177
- disabled
4178
- }
4179
- ) }),
4180
- /* @__PURE__ */ jsx18(
4419
+ /* @__PURE__ */ jsxs15("div", { className: "flex-1 min-w-[8rem] flex flex-col gap-0.5", children: [
4420
+ /* @__PURE__ */ jsx19(
4421
+ VolumeSlider,
4422
+ {
4423
+ value: dbToSlider(bus.volume),
4424
+ onChange: (sliderValue) => onVolumeChange(sliderToDb(sliderValue)),
4425
+ disabled
4426
+ }
4427
+ ),
4428
+ /* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-px", "data-testid": "bus-meter", children: [
4429
+ /* @__PURE__ */ jsx19(
4430
+ LevelMeter,
4431
+ {
4432
+ peakDb: levels?.leftDb ?? -120,
4433
+ active: levels != null,
4434
+ clipped: levels?.clipped,
4435
+ compact: true,
4436
+ "data-testid": "bus-meter-left"
4437
+ }
4438
+ ),
4439
+ /* @__PURE__ */ jsx19(
4440
+ LevelMeter,
4441
+ {
4442
+ peakDb: levels?.rightDb ?? -120,
4443
+ active: levels != null,
4444
+ compact: true,
4445
+ "data-testid": "bus-meter-right"
4446
+ }
4447
+ )
4448
+ ] })
4449
+ ] }),
4450
+ /* @__PURE__ */ jsx19(
4181
4451
  "button",
4182
4452
  {
4183
4453
  "data-testid": "bus-mute-button",
@@ -4188,7 +4458,7 @@ function PanelMasterStrip({
4188
4458
  children: "M"
4189
4459
  }
4190
4460
  ),
4191
- /* @__PURE__ */ jsx18(
4461
+ /* @__PURE__ */ jsx19(
4192
4462
  "button",
4193
4463
  {
4194
4464
  "data-testid": "bus-solo-button",
@@ -4199,14 +4469,14 @@ function PanelMasterStrip({
4199
4469
  children: "S"
4200
4470
  }
4201
4471
  ),
4202
- /* @__PURE__ */ jsx18("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ jsxs14(
4472
+ /* @__PURE__ */ jsx19("div", { className: "flex items-center gap-1 max-w-[45%] min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ jsxs15(
4203
4473
  "span",
4204
4474
  {
4205
4475
  "data-testid": `bus-fx-chip-${fx.index}`,
4206
4476
  className: `flex items-center gap-1 px-1.5 py-0.5 rounded-sm border text-[10px] whitespace-nowrap ${fx.enabled ? "border-sas-accent/60 text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted/50 bg-sas-panel"}`,
4207
4477
  title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
4208
4478
  children: [
4209
- /* @__PURE__ */ jsx18(
4479
+ /* @__PURE__ */ jsx19(
4210
4480
  "button",
4211
4481
  {
4212
4482
  "data-testid": `bus-fx-toggle-${fx.index}`,
@@ -4217,7 +4487,7 @@ function PanelMasterStrip({
4217
4487
  children: fx.enabled ? "\u25CF" : "\u25CB"
4218
4488
  }
4219
4489
  ),
4220
- onShowFxEditor ? /* @__PURE__ */ jsx18(
4490
+ onShowFxEditor ? /* @__PURE__ */ jsx19(
4221
4491
  "button",
4222
4492
  {
4223
4493
  "data-testid": `bus-fx-edit-${fx.index}`,
@@ -4227,8 +4497,8 @@ function PanelMasterStrip({
4227
4497
  title: `Open ${fx.name} editor`,
4228
4498
  children: fx.name
4229
4499
  }
4230
- ) : /* @__PURE__ */ jsx18("span", { className: "max-w-[80px] truncate", children: fx.name }),
4231
- /* @__PURE__ */ jsx18(
4500
+ ) : /* @__PURE__ */ jsx19("span", { className: "max-w-[80px] truncate", children: fx.name }),
4501
+ /* @__PURE__ */ jsx19(
4232
4502
  "button",
4233
4503
  {
4234
4504
  "data-testid": `bus-fx-remove-${fx.index}`,
@@ -4243,21 +4513,21 @@ function PanelMasterStrip({
4243
4513
  },
4244
4514
  `${fx.index}:${fx.pluginId}`
4245
4515
  )) }),
4246
- /* @__PURE__ */ jsx18(
4516
+ /* @__PURE__ */ jsx19(
4247
4517
  "button",
4248
4518
  {
4249
4519
  "data-testid": "bus-fx-add-button",
4250
4520
  onClick: () => onToggleFxPicker(!fxPickerOpen),
4251
4521
  disabled,
4252
4522
  className: `px-1.5 py-0.5 rounded-sm border text-xs whitespace-nowrap transition-colors ${fxPickerOpen ? "border-sas-accent text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent"} disabled:opacity-50`,
4253
- title: "Add an FX plugin to the panel bus",
4254
- children: "FX +"
4523
+ title: fxPickerOpen ? "Close the FX picker" : "Add an FX plugin to the panel bus",
4524
+ children: fxPickerOpen ? "FX \u25B4" : "FX +"
4255
4525
  }
4256
4526
  )
4257
4527
  ] }),
4258
- fxPickerOpen && /* @__PURE__ */ jsxs14("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
4259
- /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
4260
- /* @__PURE__ */ jsx18(
4528
+ fxPickerOpen && /* @__PURE__ */ jsxs15("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
4529
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", children: [
4530
+ /* @__PURE__ */ jsx19(
4261
4531
  "input",
4262
4532
  {
4263
4533
  type: "text",
@@ -4267,7 +4537,7 @@ function PanelMasterStrip({
4267
4537
  className: "sas-input flex-1 px-2 py-1 text-xs"
4268
4538
  }
4269
4539
  ),
4270
- onRefreshFx && /* @__PURE__ */ jsx18(
4540
+ onRefreshFx && /* @__PURE__ */ jsx19(
4271
4541
  "button",
4272
4542
  {
4273
4543
  onClick: () => onRefreshFx(),
@@ -4278,8 +4548,8 @@ function PanelMasterStrip({
4278
4548
  }
4279
4549
  )
4280
4550
  ] }),
4281
- fxLoading && availableFx.length === 0 ? /* @__PURE__ */ jsx18("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ jsxs14("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
4282
- filtered.map((fx) => /* @__PURE__ */ jsxs14(
4551
+ fxLoading && availableFx.length === 0 ? /* @__PURE__ */ jsx19("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ jsxs15("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
4552
+ filtered.map((fx) => /* @__PURE__ */ jsxs15(
4283
4553
  "button",
4284
4554
  {
4285
4555
  "data-testid": `bus-fx-pick-${fx.pluginId}`,
@@ -4287,13 +4557,13 @@ function PanelMasterStrip({
4287
4557
  className: "flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent",
4288
4558
  title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
4289
4559
  children: [
4290
- /* @__PURE__ */ jsx18("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
4291
- /* @__PURE__ */ jsx18("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
4560
+ /* @__PURE__ */ jsx19("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
4561
+ /* @__PURE__ */ jsx19("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
4292
4562
  ]
4293
4563
  },
4294
4564
  fx.pluginId
4295
4565
  )),
4296
- filtered.length === 0 && /* @__PURE__ */ jsx18("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
4566
+ filtered.length === 0 && /* @__PURE__ */ jsx19("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
4297
4567
  ] })
4298
4568
  ] })
4299
4569
  ]
@@ -4302,16 +4572,18 @@ function PanelMasterStrip({
4302
4572
  }
4303
4573
 
4304
4574
  // src/hooks/usePanelBus.ts
4305
- import { useCallback as useCallback9, useEffect as useEffect10, useRef as useRef11, useState as useState13 } from "react";
4575
+ import { useCallback as useCallback10, useEffect as useEffect11, useRef as useRef12, useState as useState15 } from "react";
4576
+ var LEVELS_POLL_MS = 66;
4306
4577
  function usePanelBus(host, activeSceneId) {
4307
4578
  const supported = typeof host.getPanelBusState === "function";
4308
- const [bus, setBus] = useState13(null);
4309
- const [availableFx, setAvailableFx] = useState13([]);
4310
- const [fxLoading, setFxLoading] = useState13(false);
4311
- const [fxPickerOpen, setFxPickerOpen] = useState13(false);
4312
- const fxLoadedRef = useRef11(false);
4313
- const loadSeqRef = useRef11(0);
4314
- const reload = useCallback9(async () => {
4579
+ const [bus, setBus] = useState15(null);
4580
+ const [levels, setLevels] = useState15(null);
4581
+ const [availableFx, setAvailableFx] = useState15([]);
4582
+ const [fxLoading, setFxLoading] = useState15(false);
4583
+ const [fxPickerOpen, setFxPickerOpen] = useState15(false);
4584
+ const fxLoadedRef = useRef12(false);
4585
+ const loadSeqRef = useRef12(0);
4586
+ const reload = useCallback10(async () => {
4315
4587
  if (!supported || !activeSceneId || !host.getPanelBusState) {
4316
4588
  setBus(null);
4317
4589
  return;
@@ -4323,12 +4595,34 @@ function usePanelBus(host, activeSceneId) {
4323
4595
  } catch {
4324
4596
  }
4325
4597
  }, [host, activeSceneId, supported]);
4326
- useEffect10(() => {
4598
+ useEffect11(() => {
4327
4599
  setBus(null);
4328
4600
  setFxPickerOpen(false);
4329
4601
  void reload();
4330
4602
  }, [reload]);
4331
- const loadFxList = useCallback9(
4603
+ useEffect11(() => {
4604
+ if (!supported || !activeSceneId || !bus?.engaged || !host.getPanelBusLevels) {
4605
+ setLevels(null);
4606
+ return;
4607
+ }
4608
+ let cancelled = false;
4609
+ const tick = async () => {
4610
+ if (typeof document !== "undefined" && document.hidden) return;
4611
+ try {
4612
+ const next = await host.getPanelBusLevels(activeSceneId);
4613
+ if (!cancelled) setLevels(next);
4614
+ } catch {
4615
+ if (!cancelled) setLevels(null);
4616
+ }
4617
+ };
4618
+ void tick();
4619
+ const id = setInterval(() => void tick(), LEVELS_POLL_MS);
4620
+ return () => {
4621
+ cancelled = true;
4622
+ clearInterval(id);
4623
+ };
4624
+ }, [supported, activeSceneId, bus?.engaged, host]);
4625
+ const loadFxList = useCallback10(
4332
4626
  async (force) => {
4333
4627
  if (!supported || !host.getAvailableFx) return;
4334
4628
  if (fxLoadedRef.current && !force) return;
@@ -4344,14 +4638,14 @@ function usePanelBus(host, activeSceneId) {
4344
4638
  },
4345
4639
  [host, supported]
4346
4640
  );
4347
- const openPicker = useCallback9(
4641
+ const openPicker = useCallback10(
4348
4642
  (open) => {
4349
4643
  setFxPickerOpen(open);
4350
4644
  if (open) void loadFxList(false);
4351
4645
  },
4352
4646
  [loadFxList]
4353
4647
  );
4354
- const mutate = useCallback9(
4648
+ const mutate = useCallback10(
4355
4649
  (fn) => {
4356
4650
  if (!fn || !activeSceneId) return;
4357
4651
  void (async () => {
@@ -4367,6 +4661,7 @@ function usePanelBus(host, activeSceneId) {
4367
4661
  return {
4368
4662
  supported,
4369
4663
  bus,
4664
+ levels,
4370
4665
  availableFx,
4371
4666
  fxLoading,
4372
4667
  fxPickerOpen,
@@ -4394,8 +4689,8 @@ function usePanelBus(host, activeSceneId) {
4394
4689
  }
4395
4690
 
4396
4691
  // src/components/DownloadPackButton.tsx
4397
- import { useCallback as useCallback10, useEffect as useEffect11, useState as useState14 } from "react";
4398
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
4692
+ import { useCallback as useCallback11, useEffect as useEffect12, useState as useState16 } from "react";
4693
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
4399
4694
  function formatSize(bytes) {
4400
4695
  if (!bytes || bytes <= 0) return "";
4401
4696
  const gb = bytes / 1024 ** 3;
@@ -4411,10 +4706,10 @@ var DownloadPackButton = ({
4411
4706
  variant = "compact",
4412
4707
  onDownloadComplete
4413
4708
  }) => {
4414
- const [status, setStatus] = useState14("idle");
4415
- const [progress, setProgress] = useState14(0);
4416
- const [errorMessage, setErrorMessage] = useState14(null);
4417
- useEffect11(() => {
4709
+ const [status, setStatus] = useState16("idle");
4710
+ const [progress, setProgress] = useState16(0);
4711
+ const [errorMessage, setErrorMessage] = useState16(null);
4712
+ useEffect12(() => {
4418
4713
  const unsub = host.onSamplePackProgress(packId, (p) => {
4419
4714
  setStatus(p.status);
4420
4715
  setProgress(p.progress);
@@ -4429,7 +4724,7 @@ var DownloadPackButton = ({
4429
4724
  });
4430
4725
  return unsub;
4431
4726
  }, [host, packId, onDownloadComplete]);
4432
- const handleClick = useCallback10(async () => {
4727
+ const handleClick = useCallback11(async () => {
4433
4728
  if (status !== "idle" && status !== "error") return;
4434
4729
  try {
4435
4730
  setStatus("downloading");
@@ -4483,8 +4778,8 @@ var DownloadPackButton = ({
4483
4778
  } else {
4484
4779
  className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
4485
4780
  }
4486
- return /* @__PURE__ */ jsxs15("div", { children: [
4487
- /* @__PURE__ */ jsx19(
4781
+ return /* @__PURE__ */ jsxs16("div", { children: [
4782
+ /* @__PURE__ */ jsx20(
4488
4783
  "button",
4489
4784
  {
4490
4785
  "data-testid": `download-pack-button-${packId}`,
@@ -4495,12 +4790,12 @@ var DownloadPackButton = ({
4495
4790
  children: buttonLabel
4496
4791
  }
4497
4792
  ),
4498
- variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ jsx19("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
4793
+ variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ jsx20("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
4499
4794
  ] });
4500
4795
  };
4501
4796
 
4502
4797
  // src/components/SamplePackCTACard.tsx
4503
- import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
4798
+ import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
4504
4799
  var SamplePackCTACard = ({
4505
4800
  host,
4506
4801
  pack,
@@ -4508,7 +4803,7 @@ var SamplePackCTACard = ({
4508
4803
  onDownloadComplete
4509
4804
  }) => {
4510
4805
  if (status === "checking") {
4511
- return /* @__PURE__ */ jsx20(
4806
+ return /* @__PURE__ */ jsx21(
4512
4807
  "div",
4513
4808
  {
4514
4809
  "data-testid": `sample-pack-cta-checking-${pack.packId}`,
@@ -4519,16 +4814,16 @@ var SamplePackCTACard = ({
4519
4814
  }
4520
4815
  const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
4521
4816
  const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
4522
- return /* @__PURE__ */ jsxs16(
4817
+ return /* @__PURE__ */ jsxs17(
4523
4818
  "div",
4524
4819
  {
4525
4820
  "data-testid": `sample-pack-cta-${pack.packId}`,
4526
4821
  className: "flex flex-col items-center justify-center py-12 px-6 text-center",
4527
4822
  children: [
4528
- /* @__PURE__ */ jsx20("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
4529
- /* @__PURE__ */ jsx20("div", { className: "text-base text-sas-text mb-1", children: headline }),
4530
- /* @__PURE__ */ jsx20("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
4531
- /* @__PURE__ */ jsx20(
4823
+ /* @__PURE__ */ jsx21("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
4824
+ /* @__PURE__ */ jsx21("div", { className: "text-base text-sas-text mb-1", children: headline }),
4825
+ /* @__PURE__ */ jsx21("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
4826
+ /* @__PURE__ */ jsx21(
4532
4827
  DownloadPackButton,
4533
4828
  {
4534
4829
  host,
@@ -4545,7 +4840,7 @@ var SamplePackCTACard = ({
4545
4840
  };
4546
4841
 
4547
4842
  // src/components/WaveformView.tsx
4548
- import { useEffect as useEffect12, useRef as useRef12, useState as useState15 } from "react";
4843
+ import { useEffect as useEffect13, useRef as useRef13, useState as useState17 } from "react";
4549
4844
 
4550
4845
  // src/components/waveform.ts
4551
4846
  function computePeaks(audioBuffer, bins, targetSamples) {
@@ -4608,7 +4903,7 @@ function drawWaveform(canvas, peaks, options = {}) {
4608
4903
  }
4609
4904
 
4610
4905
  // src/components/WaveformView.tsx
4611
- import { jsx as jsx21 } from "react/jsx-runtime";
4906
+ import { jsx as jsx22 } from "react/jsx-runtime";
4612
4907
  var WaveformView = ({
4613
4908
  host,
4614
4909
  filePath,
@@ -4617,9 +4912,9 @@ var WaveformView = ({
4617
4912
  fillStyle,
4618
4913
  targetSamples
4619
4914
  }) => {
4620
- const canvasRef = useRef12(null);
4621
- const [peaks, setPeaks] = useState15(null);
4622
- useEffect12(() => {
4915
+ const canvasRef = useRef13(null);
4916
+ const [peaks, setPeaks] = useState17(null);
4917
+ useEffect13(() => {
4623
4918
  let cancelled = false;
4624
4919
  let audioContext = null;
4625
4920
  (async () => {
@@ -4645,7 +4940,7 @@ var WaveformView = ({
4645
4940
  cancelled = true;
4646
4941
  };
4647
4942
  }, [host, filePath, bins, targetSamples]);
4648
- useEffect12(() => {
4943
+ useEffect13(() => {
4649
4944
  if (!peaks) return;
4650
4945
  const canvas = canvasRef.current;
4651
4946
  if (!canvas) return;
@@ -4656,7 +4951,7 @@ var WaveformView = ({
4656
4951
  observer.observe(canvas);
4657
4952
  return () => observer.disconnect();
4658
4953
  }, [peaks, fillStyle]);
4659
- return /* @__PURE__ */ jsx21(
4954
+ return /* @__PURE__ */ jsx22(
4660
4955
  "canvas",
4661
4956
  {
4662
4957
  ref: canvasRef,
@@ -4667,8 +4962,8 @@ var WaveformView = ({
4667
4962
  };
4668
4963
 
4669
4964
  // src/components/ScrollingWaveform.tsx
4670
- import { useEffect as useEffect13, useRef as useRef13 } from "react";
4671
- import { jsx as jsx22 } from "react/jsx-runtime";
4965
+ import { useEffect as useEffect14, useRef as useRef14 } from "react";
4966
+ import { jsx as jsx23 } from "react/jsx-runtime";
4672
4967
  var ScrollingWaveform = ({
4673
4968
  getPeakDb,
4674
4969
  active,
@@ -4676,11 +4971,11 @@ var ScrollingWaveform = ({
4676
4971
  className,
4677
4972
  fillStyle
4678
4973
  }) => {
4679
- const canvasRef = useRef13(null);
4680
- const ringRef = useRef13(new Float32Array(columns));
4681
- const writeIdxRef = useRef13(0);
4682
- const rafRef = useRef13(null);
4683
- useEffect13(() => {
4974
+ const canvasRef = useRef14(null);
4975
+ const ringRef = useRef14(new Float32Array(columns));
4976
+ const writeIdxRef = useRef14(0);
4977
+ const rafRef = useRef14(null);
4978
+ useEffect14(() => {
4684
4979
  if (ringRef.current.length !== columns) {
4685
4980
  const next = new Float32Array(columns);
4686
4981
  const prev = ringRef.current;
@@ -4692,7 +4987,7 @@ var ScrollingWaveform = ({
4692
4987
  writeIdxRef.current = writeIdxRef.current % columns;
4693
4988
  }
4694
4989
  }, [columns]);
4695
- useEffect13(() => {
4990
+ useEffect14(() => {
4696
4991
  if (!active) {
4697
4992
  if (rafRef.current !== null) {
4698
4993
  cancelAnimationFrame(rafRef.current);
@@ -4744,7 +5039,7 @@ var ScrollingWaveform = ({
4744
5039
  }
4745
5040
  };
4746
5041
  }, [active, getPeakDb, fillStyle]);
4747
- return /* @__PURE__ */ jsx22(
5042
+ return /* @__PURE__ */ jsx23(
4748
5043
  "canvas",
4749
5044
  {
4750
5045
  ref: canvasRef,
@@ -4755,8 +5050,8 @@ var ScrollingWaveform = ({
4755
5050
  };
4756
5051
 
4757
5052
  // src/components/OffsetScrubber.tsx
4758
- import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo7, useRef as useRef14, useState as useState16 } from "react";
4759
- import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
5053
+ import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useMemo8, useRef as useRef15, useState as useState18 } from "react";
5054
+ import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
4760
5055
  var SLIDER_HEIGHT_PX = 28;
4761
5056
  var TICK_HEIGHT_PX = 14;
4762
5057
  var DOWNBEAT_TICK_HEIGHT_PX = 22;
@@ -4769,40 +5064,40 @@ function OffsetScrubber({
4769
5064
  onChange,
4770
5065
  disabled = false
4771
5066
  }) {
4772
- const trackRef = useRef14(null);
4773
- const [draftOffset, setDraftOffset] = useState16(offsetSamples);
4774
- const [isDragging, setIsDragging] = useState16(false);
4775
- useEffect14(() => {
5067
+ const trackRef = useRef15(null);
5068
+ const [draftOffset, setDraftOffset] = useState18(offsetSamples);
5069
+ const [isDragging, setIsDragging] = useState18(false);
5070
+ useEffect15(() => {
4776
5071
  if (!isDragging) setDraftOffset(offsetSamples);
4777
5072
  }, [offsetSamples, isDragging]);
4778
5073
  const sampleRate = cuePoints?.sample_rate ?? 44100;
4779
5074
  const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
4780
- const beatsForRange = useMemo7(() => {
5075
+ const beatsForRange = useMemo8(() => {
4781
5076
  return Math.round(60 / projectBpm * sampleRate);
4782
5077
  }, [projectBpm, sampleRate]);
4783
5078
  const rangeSamples = beatsForRange * meter;
4784
- const sampleToFraction = useCallback11(
5079
+ const sampleToFraction = useCallback12(
4785
5080
  (sample) => {
4786
5081
  const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
4787
5082
  return (clamped + rangeSamples) / (2 * rangeSamples);
4788
5083
  },
4789
5084
  [rangeSamples]
4790
5085
  );
4791
- const fractionToSample = useCallback11(
5086
+ const fractionToSample = useCallback12(
4792
5087
  (fraction) => {
4793
5088
  const clamped = Math.max(0, Math.min(1, fraction));
4794
5089
  return Math.round(clamped * 2 * rangeSamples - rangeSamples);
4795
5090
  },
4796
5091
  [rangeSamples]
4797
5092
  );
4798
- const snapTargets = useMemo7(() => {
5093
+ const snapTargets = useMemo8(() => {
4799
5094
  if (!cuePoints || cuePoints.beats.length === 0) return [];
4800
5095
  const downbeat = cuePoints.beats[0];
4801
5096
  const positives = cuePoints.beats.map((b) => b - downbeat);
4802
5097
  const negatives = positives.slice(1).map((p) => -p);
4803
5098
  return [...negatives, ...positives].sort((a, b) => a - b);
4804
5099
  }, [cuePoints]);
4805
- const snapToBeat = useCallback11(
5100
+ const snapToBeat = useCallback12(
4806
5101
  (sample) => {
4807
5102
  if (snapTargets.length === 0) return sample;
4808
5103
  let best = snapTargets[0];
@@ -4818,7 +5113,7 @@ function OffsetScrubber({
4818
5113
  },
4819
5114
  [snapTargets]
4820
5115
  );
4821
- const handlePointerDown = useCallback11(
5116
+ const handlePointerDown = useCallback12(
4822
5117
  (e) => {
4823
5118
  if (disabled || !cuePoints) return;
4824
5119
  e.preventDefault();
@@ -4852,7 +5147,7 @@ function OffsetScrubber({
4852
5147
  },
4853
5148
  [disabled, cuePoints, fractionToSample, onChange, snapToBeat]
4854
5149
  );
4855
- const handleResetToZero = useCallback11(() => {
5150
+ const handleResetToZero = useCallback12(() => {
4856
5151
  if (disabled) return;
4857
5152
  setDraftOffset(0);
4858
5153
  onChange(0);
@@ -4860,7 +5155,7 @@ function OffsetScrubber({
4860
5155
  const thumbFraction = sampleToFraction(draftOffset);
4861
5156
  const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
4862
5157
  const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
4863
- const ticks = useMemo7(() => {
5158
+ const ticks = useMemo8(() => {
4864
5159
  if (!cuePoints) return [];
4865
5160
  const downbeat = cuePoints.beats[0] ?? 0;
4866
5161
  return cuePoints.beats.map((b, i) => {
@@ -4871,9 +5166,9 @@ function OffsetScrubber({
4871
5166
  });
4872
5167
  }, [cuePoints, sampleToFraction]);
4873
5168
  const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
4874
- return /* @__PURE__ */ jsxs17("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
4875
- /* @__PURE__ */ jsx23("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
4876
- /* @__PURE__ */ jsxs17(
5169
+ return /* @__PURE__ */ jsxs18("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
5170
+ /* @__PURE__ */ jsx24("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
5171
+ /* @__PURE__ */ jsxs18(
4877
5172
  "div",
4878
5173
  {
4879
5174
  ref: trackRef,
@@ -4889,7 +5184,7 @@ function OffsetScrubber({
4889
5184
  "aria-valuenow": draftOffset,
4890
5185
  "aria-disabled": isDisabled,
4891
5186
  children: [
4892
- /* @__PURE__ */ jsx23(
5187
+ /* @__PURE__ */ jsx24(
4893
5188
  "div",
4894
5189
  {
4895
5190
  "aria-hidden": "true",
@@ -4897,7 +5192,7 @@ function OffsetScrubber({
4897
5192
  style: { left: "50%" }
4898
5193
  }
4899
5194
  ),
4900
- ticks.map((t) => /* @__PURE__ */ jsx23(
5195
+ ticks.map((t) => /* @__PURE__ */ jsx24(
4901
5196
  "div",
4902
5197
  {
4903
5198
  "data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
@@ -4912,7 +5207,7 @@ function OffsetScrubber({
4912
5207
  },
4913
5208
  t.i
4914
5209
  )),
4915
- /* @__PURE__ */ jsx23(
5210
+ /* @__PURE__ */ jsx24(
4916
5211
  "div",
4917
5212
  {
4918
5213
  "data-testid": "offset-scrubber-thumb",
@@ -4929,7 +5224,7 @@ function OffsetScrubber({
4929
5224
  ]
4930
5225
  }
4931
5226
  ),
4932
- /* @__PURE__ */ jsx23(
5227
+ /* @__PURE__ */ jsx24(
4933
5228
  "span",
4934
5229
  {
4935
5230
  "data-testid": "offset-scrubber-readout",
@@ -4937,7 +5232,7 @@ function OffsetScrubber({
4937
5232
  children: formatOffset(draftOffset, sampleRate)
4938
5233
  }
4939
5234
  ),
4940
- /* @__PURE__ */ jsx23(
5235
+ /* @__PURE__ */ jsx24(
4941
5236
  "button",
4942
5237
  {
4943
5238
  type: "button",
@@ -4949,7 +5244,7 @@ function OffsetScrubber({
4949
5244
  children: "\u2316"
4950
5245
  }
4951
5246
  ),
4952
- bpmMismatch && /* @__PURE__ */ jsx23(
5247
+ bpmMismatch && /* @__PURE__ */ jsx24(
4953
5248
  "span",
4954
5249
  {
4955
5250
  "data-testid": "offset-bpm-mismatch",
@@ -5021,16 +5316,16 @@ function synthesizeCuePoints({
5021
5316
  }
5022
5317
 
5023
5318
  // src/panel-core/useGeneratorPanelCore.tsx
5024
- import { useState as useState21, useEffect as useEffect17, useCallback as useCallback15, useRef as useRef18, useMemo as useMemo9 } from "react";
5319
+ import { useState as useState23, useEffect as useEffect18, useCallback as useCallback16, useRef as useRef19, useMemo as useMemo10 } from "react";
5025
5320
 
5026
5321
  // src/hooks/useSceneState.ts
5027
- import { useState as useState17, useCallback as useCallback12, useRef as useRef15 } from "react";
5322
+ import { useState as useState19, useCallback as useCallback13, useRef as useRef16 } from "react";
5028
5323
  function useSceneState(activeSceneId, initialValue) {
5029
- const [stateMap, setStateMap] = useState17(() => /* @__PURE__ */ new Map());
5030
- const activeSceneIdRef = useRef15(activeSceneId);
5324
+ const [stateMap, setStateMap] = useState19(() => /* @__PURE__ */ new Map());
5325
+ const activeSceneIdRef = useRef16(activeSceneId);
5031
5326
  activeSceneIdRef.current = activeSceneId;
5032
5327
  const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
5033
- const setForCurrentScene = useCallback12((value) => {
5328
+ const setForCurrentScene = useCallback13((value) => {
5034
5329
  const sid = activeSceneIdRef.current;
5035
5330
  if (sid === null) return;
5036
5331
  setStateMap((prev) => {
@@ -5041,7 +5336,7 @@ function useSceneState(activeSceneId, initialValue) {
5041
5336
  return newMap;
5042
5337
  });
5043
5338
  }, [initialValue]);
5044
- const setForScene = useCallback12((sceneId, value) => {
5339
+ const setForScene = useCallback13((sceneId, value) => {
5045
5340
  setStateMap((prev) => {
5046
5341
  const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
5047
5342
  const next = typeof value === "function" ? value(current) : value;
@@ -5054,10 +5349,10 @@ function useSceneState(activeSceneId, initialValue) {
5054
5349
  }
5055
5350
 
5056
5351
  // src/hooks/useAnySolo.ts
5057
- import { useEffect as useEffect15, useState as useState18 } from "react";
5352
+ import { useEffect as useEffect16, useState as useState20 } from "react";
5058
5353
  function useAnySolo(host) {
5059
- const [anySolo, setAnySolo] = useState18(false);
5060
- useEffect15(() => {
5354
+ const [anySolo, setAnySolo] = useState20(false);
5355
+ useEffect16(() => {
5061
5356
  let active = true;
5062
5357
  const refresh = () => {
5063
5358
  host.isAnySoloActive().then((v) => {
@@ -5076,7 +5371,7 @@ function useAnySolo(host) {
5076
5371
  }
5077
5372
 
5078
5373
  // src/hooks/useSoundHistory.ts
5079
- import { useCallback as useCallback13, useMemo as useMemo8, useRef as useRef16, useState as useState19 } from "react";
5374
+ import { useCallback as useCallback14, useMemo as useMemo9, useRef as useRef17, useState as useState21 } from "react";
5080
5375
  var EMPTY = { entries: [], cursor: -1 };
5081
5376
  function sameDescriptor(a, b) {
5082
5377
  if (a === b) return true;
@@ -5088,14 +5383,14 @@ function sameDescriptor(a, b) {
5088
5383
  }
5089
5384
  function useSoundHistory(applySound, opts = {}) {
5090
5385
  const max = Math.max(2, opts.max ?? 24);
5091
- const applyRef = useRef16(applySound);
5386
+ const applyRef = useRef17(applySound);
5092
5387
  applyRef.current = applySound;
5093
- const onChangeRef = useRef16(opts.onChange);
5388
+ const onChangeRef = useRef17(opts.onChange);
5094
5389
  onChangeRef.current = opts.onChange;
5095
- const dataRef = useRef16({});
5096
- const [, setVersion] = useState19(0);
5097
- const bump = useCallback13(() => setVersion((v) => v + 1), []);
5098
- const commit = useCallback13(
5390
+ const dataRef = useRef17({});
5391
+ const [, setVersion] = useState21(0);
5392
+ const bump = useCallback14(() => setVersion((v) => v + 1), []);
5393
+ const commit = useCallback14(
5099
5394
  (trackId, next, notify) => {
5100
5395
  dataRef.current = { ...dataRef.current, [trackId]: next };
5101
5396
  bump();
@@ -5103,7 +5398,7 @@ function useSoundHistory(applySound, opts = {}) {
5103
5398
  },
5104
5399
  [bump]
5105
5400
  );
5106
- const record = useCallback13(
5401
+ const record = useCallback14(
5107
5402
  (trackId, descriptor, label) => {
5108
5403
  const h = dataRef.current[trackId];
5109
5404
  const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
@@ -5118,7 +5413,7 @@ function useSoundHistory(applySound, opts = {}) {
5118
5413
  },
5119
5414
  [max, commit]
5120
5415
  );
5121
- const restoreTo = useCallback13(
5416
+ const restoreTo = useCallback14(
5122
5417
  async (trackId, index) => {
5123
5418
  const h = dataRef.current[trackId];
5124
5419
  if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
@@ -5128,7 +5423,7 @@ function useSoundHistory(applySound, opts = {}) {
5128
5423
  },
5129
5424
  [commit]
5130
5425
  );
5131
- const undo = useCallback13(
5426
+ const undo = useCallback14(
5132
5427
  (trackId) => {
5133
5428
  const h = dataRef.current[trackId];
5134
5429
  if (!h || h.cursor <= 0) return Promise.resolve(false);
@@ -5136,7 +5431,7 @@ function useSoundHistory(applySound, opts = {}) {
5136
5431
  },
5137
5432
  [restoreTo]
5138
5433
  );
5139
- const toggleFavorite = useCallback13(
5434
+ const toggleFavorite = useCallback14(
5140
5435
  (trackId, index) => {
5141
5436
  const h = dataRef.current[trackId];
5142
5437
  if (!h || index < 0 || index >= h.entries.length) return;
@@ -5145,7 +5440,7 @@ function useSoundHistory(applySound, opts = {}) {
5145
5440
  },
5146
5441
  [commit]
5147
5442
  );
5148
- const restore = useCallback13(
5443
+ const restore = useCallback14(
5149
5444
  (trackId, state) => {
5150
5445
  const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
5151
5446
  const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
@@ -5154,15 +5449,15 @@ function useSoundHistory(applySound, opts = {}) {
5154
5449
  },
5155
5450
  [commit]
5156
5451
  );
5157
- const list = useCallback13(
5452
+ const list = useCallback14(
5158
5453
  (trackId) => dataRef.current[trackId] ?? EMPTY,
5159
5454
  []
5160
5455
  );
5161
- const canUndo = useCallback13((trackId) => {
5456
+ const canUndo = useCallback14((trackId) => {
5162
5457
  const h = dataRef.current[trackId];
5163
5458
  return !!h && h.cursor > 0;
5164
5459
  }, []);
5165
- const clear = useCallback13(
5460
+ const clear = useCallback14(
5166
5461
  (trackId) => {
5167
5462
  if (dataRef.current[trackId]) {
5168
5463
  const next = { ...dataRef.current };
@@ -5174,11 +5469,11 @@ function useSoundHistory(applySound, opts = {}) {
5174
5469
  },
5175
5470
  [bump]
5176
5471
  );
5177
- const reset = useCallback13(() => {
5472
+ const reset = useCallback14(() => {
5178
5473
  dataRef.current = {};
5179
5474
  bump();
5180
5475
  }, [bump]);
5181
- return useMemo8(
5476
+ return useMemo9(
5182
5477
  () => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
5183
5478
  [record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
5184
5479
  );
@@ -5311,7 +5606,7 @@ function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
5311
5606
  }
5312
5607
 
5313
5608
  // src/panel-core/useTransitionOps.ts
5314
- import { useCallback as useCallback14, useEffect as useEffect16, useRef as useRef17, useState as useState20 } from "react";
5609
+ import { useCallback as useCallback15, useEffect as useEffect17, useRef as useRef18, useState as useState22 } from "react";
5315
5610
  function useTransitionOps({
5316
5611
  host,
5317
5612
  adapter,
@@ -5328,8 +5623,8 @@ function useTransitionOps({
5328
5623
  resolvedFades
5329
5624
  }) {
5330
5625
  const { identity } = adapter;
5331
- const appliedFadeAutomationRef = useRef17(/* @__PURE__ */ new Set());
5332
- const applyCrossfadeAutomation = useCallback14(
5626
+ const appliedFadeAutomationRef = useRef18(/* @__PURE__ */ new Set());
5627
+ const applyCrossfadeAutomation = useCallback15(
5333
5628
  async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
5334
5629
  if (host.setTrackVolumeAutomation) {
5335
5630
  const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
@@ -5346,7 +5641,7 @@ function useTransitionOps({
5346
5641
  },
5347
5642
  [host]
5348
5643
  );
5349
- const applyFadeAutomation = useCallback14(
5644
+ const applyFadeAutomation = useCallback15(
5350
5645
  async (trackId, direction, bars, bpm, sliderPos, gesture) => {
5351
5646
  if (!host.setTrackVolumeAutomation) return;
5352
5647
  const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
@@ -5355,8 +5650,8 @@ function useTransitionOps({
5355
5650
  },
5356
5651
  [host]
5357
5652
  );
5358
- const [isCreatingCrossfade, setIsCreatingCrossfade] = useState20(false);
5359
- const handleCreateCrossfade = useCallback14(
5653
+ const [isCreatingCrossfade, setIsCreatingCrossfade] = useState22(false);
5654
+ const handleCreateCrossfade = useCallback15(
5360
5655
  async (origin, target) => {
5361
5656
  const scene = activeSceneId;
5362
5657
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5484,8 +5779,8 @@ function useTransitionOps({
5484
5779
  loadTracks
5485
5780
  ]
5486
5781
  );
5487
- const [isCreatingFade, setIsCreatingFade] = useState20(false);
5488
- const handleCreateFade = useCallback14(
5782
+ const [isCreatingFade, setIsCreatingFade] = useState22(false);
5783
+ const handleCreateFade = useCallback15(
5489
5784
  async (selection, direction, gesture) => {
5490
5785
  const scene = activeSceneId;
5491
5786
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5595,7 +5890,7 @@ function useTransitionOps({
5595
5890
  loadTracks
5596
5891
  ]
5597
5892
  );
5598
- const handleCrossfadeMute = useCallback14(
5893
+ const handleCrossfadeMute = useCallback15(
5599
5894
  (pair) => {
5600
5895
  const newMuted = !pair.origin.runtimeState.muted;
5601
5896
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5610,7 +5905,7 @@ function useTransitionOps({
5610
5905
  },
5611
5906
  [host, setTracks]
5612
5907
  );
5613
- const handleCrossfadeSolo = useCallback14(
5908
+ const handleCrossfadeSolo = useCallback15(
5614
5909
  (pair) => {
5615
5910
  const newSolo = !pair.origin.runtimeState.solo;
5616
5911
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5625,7 +5920,7 @@ function useTransitionOps({
5625
5920
  },
5626
5921
  [host, setTracks]
5627
5922
  );
5628
- const handleCrossfadeDelete = useCallback14(
5923
+ const handleCrossfadeDelete = useCallback15(
5629
5924
  async (pair) => {
5630
5925
  try {
5631
5926
  for (const member of [pair.origin, pair.target]) {
@@ -5651,8 +5946,8 @@ function useTransitionOps({
5651
5946
  },
5652
5947
  [host, activeSceneId, setCrossfadePairsMeta, setTracks]
5653
5948
  );
5654
- const crossfadeSliderTimers = useRef17({});
5655
- const handleCrossfadeSlider = useCallback14(
5949
+ const crossfadeSliderTimers = useRef18({});
5950
+ const handleCrossfadeSlider = useCallback15(
5656
5951
  (pair, pos) => {
5657
5952
  setCrossfadePairsMeta(
5658
5953
  (prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
@@ -5685,7 +5980,7 @@ function useTransitionOps({
5685
5980
  },
5686
5981
  [host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
5687
5982
  );
5688
- const handleFadeDelete = useCallback14(
5983
+ const handleFadeDelete = useCallback15(
5689
5984
  async (fade) => {
5690
5985
  try {
5691
5986
  await host.deleteTrack(fade.track.handle.id);
@@ -5705,8 +6000,8 @@ function useTransitionOps({
5705
6000
  },
5706
6001
  [host, activeSceneId, setFadesMeta, setTracks]
5707
6002
  );
5708
- const fadeSliderTimers = useRef17({});
5709
- const handleFadeSlider = useCallback14(
6003
+ const fadeSliderTimers = useRef18({});
6004
+ const handleFadeSlider = useCallback15(
5710
6005
  (fade, pos) => {
5711
6006
  setFadesMeta(
5712
6007
  (prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
@@ -5736,8 +6031,8 @@ function useTransitionOps({
5736
6031
  },
5737
6032
  [host, activeSceneId, applyFadeAutomation, setFadesMeta]
5738
6033
  );
5739
- const lastResyncKeyRef = useRef17("");
5740
- useEffect16(() => {
6034
+ const lastResyncKeyRef = useRef18("");
6035
+ useEffect17(() => {
5741
6036
  if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
5742
6037
  return;
5743
6038
  }
@@ -5778,7 +6073,7 @@ function useTransitionOps({
5778
6073
  cancelled = true;
5779
6074
  };
5780
6075
  }, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
5781
- useEffect16(() => {
6076
+ useEffect17(() => {
5782
6077
  if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
5783
6078
  void (async () => {
5784
6079
  const mc = await host.getMusicalContext();
@@ -5812,7 +6107,7 @@ function useTransitionOps({
5812
6107
  }
5813
6108
 
5814
6109
  // src/panel-core/useGeneratorPanelCore.tsx
5815
- import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
6110
+ import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
5816
6111
  var EMPTY_PLACEHOLDERS = [];
5817
6112
  function useGeneratorPanelCore({
5818
6113
  ui,
@@ -5832,8 +6127,8 @@ function useGeneratorPanelCore({
5832
6127
  } = ui;
5833
6128
  const { identity, features } = adapter;
5834
6129
  const logTag = identity.logTag;
5835
- const adapterRef = useRef18(adapter);
5836
- useEffect17(() => {
6130
+ const adapterRef = useRef19(adapter);
6131
+ useEffect18(() => {
5837
6132
  if (adapterRef.current !== adapter) {
5838
6133
  adapterRef.current = adapter;
5839
6134
  console.warn(
@@ -5843,27 +6138,27 @@ function useGeneratorPanelCore({
5843
6138
  }, [adapter, logTag]);
5844
6139
  const supportsMeters = typeof host.getTrackLevels === "function";
5845
6140
  const trackLevels = useTrackLevels(host, isExpanded);
5846
- const [tracks, setTracks] = useState21([]);
5847
- const [isLoadingTracks, setIsLoadingTracks] = useState21(false);
5848
- const [importOpen, setImportOpen] = useState21(false);
5849
- const [soundImportTarget, setSoundImportTarget] = useState21(null);
5850
- const [designerView, setDesignerView] = useState21(false);
5851
- const [transitionSourceTotal, setTransitionSourceTotal] = useState21(0);
5852
- const [crossfadePairsMeta, setCrossfadePairsMeta] = useState21([]);
5853
- const [fadesMeta, setFadesMeta] = useState21([]);
5854
- const [genericGroupMetas, setGenericGroupMetas] = useState21({});
6141
+ const [tracks, setTracks] = useState23([]);
6142
+ const [isLoadingTracks, setIsLoadingTracks] = useState23(false);
6143
+ const [importOpen, setImportOpen] = useState23(false);
6144
+ const [soundImportTarget, setSoundImportTarget] = useState23(null);
6145
+ const [designerView, setDesignerView] = useState23(false);
6146
+ const [transitionSourceTotal, setTransitionSourceTotal] = useState23(0);
6147
+ const [crossfadePairsMeta, setCrossfadePairsMeta] = useState23([]);
6148
+ const [fadesMeta, setFadesMeta] = useState23([]);
6149
+ const [genericGroupMetas, setGenericGroupMetas] = useState23({});
5855
6150
  const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
5856
6151
  const [placeholders, , setPlaceholdersForScene] = useSceneState(
5857
6152
  activeSceneId,
5858
6153
  EMPTY_PLACEHOLDERS
5859
6154
  );
5860
- const saveTimeoutRefs = useRef18({});
5861
- const editLoadStartedRef = useRef18(/* @__PURE__ */ new Set());
5862
- const [availableInstruments, setAvailableInstruments] = useState21([]);
5863
- const [instrumentsLoading, setInstrumentsLoading] = useState21(false);
5864
- const engineToDbIdRef = useRef18(/* @__PURE__ */ new Map());
5865
- const tracksLoadedForSceneRef = useRef18(null);
5866
- const persistSoundHistory = useCallback15(
6155
+ const saveTimeoutRefs = useRef19({});
6156
+ const editLoadStartedRef = useRef19(/* @__PURE__ */ new Set());
6157
+ const [availableInstruments, setAvailableInstruments] = useState23([]);
6158
+ const [instrumentsLoading, setInstrumentsLoading] = useState23(false);
6159
+ const engineToDbIdRef = useRef19(/* @__PURE__ */ new Map());
6160
+ const tracksLoadedForSceneRef = useRef19(null);
6161
+ const persistSoundHistory = useCallback16(
5867
6162
  (trackId, state) => {
5868
6163
  if (!activeSceneId) return;
5869
6164
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -5883,7 +6178,7 @@ function useGeneratorPanelCore({
5883
6178
  setItems: setTracks,
5884
6179
  getId: (t) => t.handle.dbId
5885
6180
  });
5886
- const loadTracks = useCallback15(
6181
+ const loadTracks = useCallback16(
5887
6182
  async (incremental = false) => {
5888
6183
  const sceneAtStart = activeSceneId;
5889
6184
  if (!sceneAtStart) {
@@ -6008,18 +6303,18 @@ function useGeneratorPanelCore({
6008
6303
  },
6009
6304
  [host, activeSceneId, soundHistory, adapter, logTag]
6010
6305
  );
6011
- useEffect17(() => {
6306
+ useEffect18(() => {
6012
6307
  loadTracks();
6013
6308
  }, [loadTracks]);
6014
- useEffect17(() => {
6309
+ useEffect18(() => {
6015
6310
  const map = /* @__PURE__ */ new Map();
6016
6311
  for (const t of tracks) {
6017
6312
  map.set(t.handle.id, t.handle.dbId);
6018
6313
  }
6019
6314
  engineToDbIdRef.current = map;
6020
6315
  }, [tracks]);
6021
- const loadedCompletedIdsRef = useRef18(/* @__PURE__ */ new Set());
6022
- useEffect17(() => {
6316
+ const loadedCompletedIdsRef = useRef19(/* @__PURE__ */ new Set());
6317
+ useEffect18(() => {
6023
6318
  if (placeholders.length === 0) {
6024
6319
  loadedCompletedIdsRef.current.clear();
6025
6320
  return;
@@ -6038,16 +6333,16 @@ function useGeneratorPanelCore({
6038
6333
  loadTracks(true);
6039
6334
  }
6040
6335
  }, [placeholders, loadTracks, logTag]);
6041
- const adoptAndLoad = useCallback15(() => {
6336
+ const adoptAndLoad = useCallback16(() => {
6042
6337
  loadTracks(true);
6043
6338
  }, [loadTracks]);
6044
- useEffect17(() => {
6339
+ useEffect18(() => {
6045
6340
  const unsub = host.onEngineReady(() => {
6046
6341
  adoptAndLoad();
6047
6342
  });
6048
6343
  return unsub;
6049
6344
  }, [host, adoptAndLoad]);
6050
- useEffect17(() => {
6345
+ useEffect18(() => {
6051
6346
  if (typeof host.onAfterAgentMutation !== "function") return;
6052
6347
  let timer = null;
6053
6348
  const unsub = host.onAfterAgentMutation(() => {
@@ -6062,13 +6357,13 @@ function useGeneratorPanelCore({
6062
6357
  if (timer) clearTimeout(timer);
6063
6358
  };
6064
6359
  }, [host, loadTracks]);
6065
- useEffect17(() => {
6360
+ useEffect18(() => {
6066
6361
  const unsub = host.onTrackStateChange((trackId, state) => {
6067
6362
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
6068
6363
  });
6069
6364
  return unsub;
6070
6365
  }, [host]);
6071
- useEffect17(() => {
6366
+ useEffect18(() => {
6072
6367
  if (!features.bulkComposePlaceholders) return;
6073
6368
  console.log(`[${logTag}] Subscribing to composeProgress`);
6074
6369
  const unsub = host.onComposeProgress((event) => {
@@ -6102,7 +6397,7 @@ function useGeneratorPanelCore({
6102
6397
  });
6103
6398
  return unsub;
6104
6399
  }, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
6105
- useEffect17(() => {
6400
+ useEffect18(() => {
6106
6401
  const refs = saveTimeoutRefs;
6107
6402
  return () => {
6108
6403
  for (const timeout of Object.values(refs.current)) {
@@ -6110,9 +6405,9 @@ function useGeneratorPanelCore({
6110
6405
  }
6111
6406
  };
6112
6407
  }, []);
6113
- const isAddingTrackRef = useRef18(false);
6114
- const [isAddingTrack, setIsAddingTrack] = useState21(false);
6115
- const handleAddTrack = useCallback15(async () => {
6408
+ const isAddingTrackRef = useRef19(false);
6409
+ const [isAddingTrack, setIsAddingTrack] = useState23(false);
6410
+ const handleAddTrack = useCallback16(async () => {
6116
6411
  if (isAddingTrackRef.current) return;
6117
6412
  if (!activeSceneId) {
6118
6413
  host.showToast("warning", "Select SCENE");
@@ -6152,7 +6447,7 @@ function useGeneratorPanelCore({
6152
6447
  setIsAddingTrack(false);
6153
6448
  }
6154
6449
  }, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
6155
- const handlePortTrack = useCallback15(
6450
+ const handlePortTrack = useCallback16(
6156
6451
  async (sel) => {
6157
6452
  if (!activeSceneId) {
6158
6453
  host.showToast("warning", "Select SCENE");
@@ -6209,7 +6504,7 @@ function useGeneratorPanelCore({
6209
6504
  },
6210
6505
  [host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
6211
6506
  );
6212
- const handleSoundImportPick = useCallback15(
6507
+ const handleSoundImportPick = useCallback16(
6213
6508
  async (sel) => {
6214
6509
  const target = soundImportTarget;
6215
6510
  if (!target || !host.getTrackSound) {
@@ -6240,8 +6535,8 @@ function useGeneratorPanelCore({
6240
6535
  },
6241
6536
  [soundImportTarget, host, adapter, identity.familyKey, soundHistory]
6242
6537
  );
6243
- const [isExportingMidi, setIsExportingMidi] = useState21(false);
6244
- const handleExportMidi = useCallback15(async () => {
6538
+ const [isExportingMidi, setIsExportingMidi] = useState23(false);
6539
+ const handleExportMidi = useCallback16(async () => {
6245
6540
  if (isExportingMidi) return;
6246
6541
  setIsExportingMidi(true);
6247
6542
  try {
@@ -6272,10 +6567,10 @@ function useGeneratorPanelCore({
6272
6567
  const xfFromId = sceneContext?.transitionFromSceneId ?? null;
6273
6568
  const xfToId = sceneContext?.transitionToSceneId ?? null;
6274
6569
  const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
6275
- useEffect17(() => {
6570
+ useEffect18(() => {
6276
6571
  if (!canCrossfade) setDesignerView(false);
6277
6572
  }, [canCrossfade]);
6278
- useEffect17(() => {
6573
+ useEffect18(() => {
6279
6574
  if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
6280
6575
  setTransitionSourceTotal(0);
6281
6576
  return;
@@ -6291,12 +6586,12 @@ function useGeneratorPanelCore({
6291
6586
  };
6292
6587
  }, [canCrossfade, xfFromId, xfToId, host]);
6293
6588
  const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
6294
- useEffect17(() => {
6589
+ useEffect18(() => {
6295
6590
  if (!onHeaderContent) return;
6296
6591
  const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
6297
6592
  onHeaderContent(
6298
- /* @__PURE__ */ jsxs18("div", { className: "flex gap-1 items-center", children: [
6299
- features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ jsx24(
6593
+ /* @__PURE__ */ jsxs19("div", { className: "flex gap-1 items-center", children: [
6594
+ features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ jsx25(
6300
6595
  "button",
6301
6596
  {
6302
6597
  "data-testid": `import-from-scene-${identity.familyKey}-button`,
@@ -6310,7 +6605,7 @@ function useGeneratorPanelCore({
6310
6605
  children: identity.importTrackLabel ?? "Import Track"
6311
6606
  }
6312
6607
  ),
6313
- (!canCrossfade || !designerView) && /* @__PURE__ */ jsx24(
6608
+ (!canCrossfade || !designerView) && /* @__PURE__ */ jsx25(
6314
6609
  "button",
6315
6610
  {
6316
6611
  "data-testid": `add-${identity.familyKey}-track-button`,
@@ -6326,7 +6621,7 @@ function useGeneratorPanelCore({
6326
6621
  children: identity.addTrackLabel ?? "Add Track"
6327
6622
  }
6328
6623
  ),
6329
- canCrossfade && /* @__PURE__ */ jsxs18(
6624
+ canCrossfade && /* @__PURE__ */ jsxs19(
6330
6625
  "button",
6331
6626
  {
6332
6627
  "data-testid": `${identity.familyKey}-view-toggle`,
@@ -6345,7 +6640,7 @@ function useGeneratorPanelCore({
6345
6640
  title: designerView ? "Back to the track list" : "Open the transition designer",
6346
6641
  className: "relative overflow-hidden px-2 py-0.5 text-[10px] font-medium rounded-sm border border-sas-accent/40 text-sas-accent transition-colors hover:border-sas-accent disabled:opacity-50",
6347
6642
  children: [
6348
- transitionSourceTotal > 0 && /* @__PURE__ */ jsx24(
6643
+ transitionSourceTotal > 0 && /* @__PURE__ */ jsx25(
6349
6644
  "span",
6350
6645
  {
6351
6646
  className: "absolute inset-y-0 left-0 bg-sas-accent/25",
@@ -6353,7 +6648,7 @@ function useGeneratorPanelCore({
6353
6648
  "aria-hidden": true
6354
6649
  }
6355
6650
  ),
6356
- /* @__PURE__ */ jsxs18("span", { className: "relative", children: [
6651
+ /* @__PURE__ */ jsxs19("span", { className: "relative", children: [
6357
6652
  "\u21C4 ",
6358
6653
  designerView ? "Transition" : "Tracks",
6359
6654
  transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
@@ -6384,7 +6679,7 @@ function useGeneratorPanelCore({
6384
6679
  identity,
6385
6680
  features.importTracks
6386
6681
  ]);
6387
- useEffect17(() => {
6682
+ useEffect18(() => {
6388
6683
  if (!onLoading) return;
6389
6684
  const anyGenerating = tracks.some((t) => t.isGenerating);
6390
6685
  onLoading(isLoadingTracks || anyGenerating || isBulkActive);
@@ -6392,7 +6687,7 @@ function useGeneratorPanelCore({
6392
6687
  onLoading(false);
6393
6688
  };
6394
6689
  }, [onLoading, isLoadingTracks, tracks, isBulkActive]);
6395
- const handleDeleteTrack = useCallback15(
6690
+ const handleDeleteTrack = useCallback16(
6396
6691
  async (trackId) => {
6397
6692
  try {
6398
6693
  await host.deleteTrack(trackId);
@@ -6408,7 +6703,7 @@ function useGeneratorPanelCore({
6408
6703
  },
6409
6704
  [host, activeSceneId]
6410
6705
  );
6411
- const handlePromptChange = useCallback15(
6706
+ const handlePromptChange = useCallback16(
6412
6707
  (trackId, prompt) => {
6413
6708
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
6414
6709
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -6424,7 +6719,7 @@ function useGeneratorPanelCore({
6424
6719
  },
6425
6720
  [host, activeSceneId]
6426
6721
  );
6427
- const resolvedGenericGroups = useMemo9(() => {
6722
+ const resolvedGenericGroups = useMemo10(() => {
6428
6723
  const out = {};
6429
6724
  for (const ext of adapter.groupExtensions ?? []) {
6430
6725
  out[ext.metaKey] = resolveTrackGroups(
@@ -6438,18 +6733,18 @@ function useGeneratorPanelCore({
6438
6733
  }
6439
6734
  return out;
6440
6735
  }, [adapter, genericGroupMetas, tracks]);
6441
- const genericGroupMemberDbIds = useMemo9(() => {
6736
+ const genericGroupMemberDbIds = useMemo10(() => {
6442
6737
  const s = /* @__PURE__ */ new Set();
6443
6738
  for (const r of Object.values(resolvedGenericGroups)) {
6444
6739
  for (const dbId of r.memberDbIds) s.add(dbId);
6445
6740
  }
6446
6741
  return s;
6447
6742
  }, [resolvedGenericGroups]);
6448
- const engineToDbId = useCallback15(
6743
+ const engineToDbId = useCallback16(
6449
6744
  (trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
6450
6745
  []
6451
6746
  );
6452
- const updateTrack = useCallback15(
6747
+ const updateTrack = useCallback16(
6453
6748
  (trackId, patch) => {
6454
6749
  setTracks(
6455
6750
  (prev) => prev.map(
@@ -6459,18 +6754,18 @@ function useGeneratorPanelCore({
6459
6754
  },
6460
6755
  []
6461
6756
  );
6462
- const markEditLoaded = useCallback15((trackId) => {
6757
+ const markEditLoaded = useCallback16((trackId) => {
6463
6758
  editLoadStartedRef.current.add(trackId);
6464
6759
  }, []);
6465
- const tracksRef = useRef18(tracks);
6466
- useEffect17(() => {
6760
+ const tracksRef = useRef19(tracks);
6761
+ useEffect18(() => {
6467
6762
  tracksRef.current = tracks;
6468
6763
  }, [tracks]);
6469
- const resolvedGenericGroupsRef = useRef18(resolvedGenericGroups);
6470
- useEffect17(() => {
6764
+ const resolvedGenericGroupsRef = useRef19(resolvedGenericGroups);
6765
+ useEffect18(() => {
6471
6766
  resolvedGenericGroupsRef.current = resolvedGenericGroups;
6472
6767
  }, [resolvedGenericGroups]);
6473
- const makeServices = useCallback15(() => {
6768
+ const makeServices = useCallback16(() => {
6474
6769
  return {
6475
6770
  host,
6476
6771
  activeSceneId,
@@ -6489,7 +6784,7 @@ function useGeneratorPanelCore({
6489
6784
  resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
6490
6785
  };
6491
6786
  }, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
6492
- const handleGenerate = useCallback15(
6787
+ const handleGenerate = useCallback16(
6493
6788
  async (trackId) => {
6494
6789
  const track = tracks.find((t) => t.handle.id === trackId);
6495
6790
  if (!track || !track.prompt.trim()) return;
@@ -6516,7 +6811,7 @@ function useGeneratorPanelCore({
6516
6811
  },
6517
6812
  [host, adapter, tracks, isAuthenticated, makeServices]
6518
6813
  );
6519
- const handleMuteToggle = useCallback15(
6814
+ const handleMuteToggle = useCallback16(
6520
6815
  (trackId) => {
6521
6816
  const track = tracks.find((t) => t.handle.id === trackId);
6522
6817
  if (!track) return;
@@ -6536,7 +6831,7 @@ function useGeneratorPanelCore({
6536
6831
  },
6537
6832
  [host, tracks]
6538
6833
  );
6539
- const handleSoloToggle = useCallback15(
6834
+ const handleSoloToggle = useCallback16(
6540
6835
  (trackId) => {
6541
6836
  const track = tracks.find((t) => t.handle.id === trackId);
6542
6837
  if (!track) return;
@@ -6556,7 +6851,7 @@ function useGeneratorPanelCore({
6556
6851
  },
6557
6852
  [host, tracks]
6558
6853
  );
6559
- const handleVolumeChange = useCallback15(
6854
+ const handleVolumeChange = useCallback16(
6560
6855
  (trackId, volume) => {
6561
6856
  setTracks(
6562
6857
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
@@ -6566,7 +6861,7 @@ function useGeneratorPanelCore({
6566
6861
  },
6567
6862
  [host]
6568
6863
  );
6569
- const handlePanChange = useCallback15(
6864
+ const handlePanChange = useCallback16(
6570
6865
  (trackId, pan) => {
6571
6866
  setTracks(
6572
6867
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
@@ -6576,7 +6871,7 @@ function useGeneratorPanelCore({
6576
6871
  },
6577
6872
  [host]
6578
6873
  );
6579
- const handleShuffle = useCallback15(
6874
+ const handleShuffle = useCallback16(
6580
6875
  async (trackId) => {
6581
6876
  const track = tracks.find((t) => t.handle.id === trackId);
6582
6877
  if (!track) return;
@@ -6618,7 +6913,7 @@ function useGeneratorPanelCore({
6618
6913
  },
6619
6914
  [host, adapter, tracks, soundHistory, logTag]
6620
6915
  );
6621
- const handleCopy = useCallback15(
6916
+ const handleCopy = useCallback16(
6622
6917
  async (trackId) => {
6623
6918
  try {
6624
6919
  const newHandle = await host.duplicateTrack(trackId);
@@ -6631,7 +6926,7 @@ function useGeneratorPanelCore({
6631
6926
  },
6632
6927
  [host, loadTracks]
6633
6928
  );
6634
- const handleFxToggle = useCallback15(
6929
+ const handleFxToggle = useCallback16(
6635
6930
  (trackId, category, enabled) => {
6636
6931
  setTracks(
6637
6932
  (prev) => prev.map(
@@ -6654,7 +6949,7 @@ function useGeneratorPanelCore({
6654
6949
  },
6655
6950
  [host]
6656
6951
  );
6657
- const handleFxPresetChange = useCallback15(
6952
+ const handleFxPresetChange = useCallback16(
6658
6953
  (trackId, category, presetIndex) => {
6659
6954
  setTracks(
6660
6955
  (prev) => prev.map(
@@ -6680,7 +6975,7 @@ function useGeneratorPanelCore({
6680
6975
  },
6681
6976
  [host]
6682
6977
  );
6683
- const handleFxDryWetChange = useCallback15(
6978
+ const handleFxDryWetChange = useCallback16(
6684
6979
  (trackId, category, value) => {
6685
6980
  setTracks(
6686
6981
  (prev) => prev.map(
@@ -6692,7 +6987,7 @@ function useGeneratorPanelCore({
6692
6987
  },
6693
6988
  [host]
6694
6989
  );
6695
- const toggleFxDrawer = useCallback15(
6990
+ const toggleFxDrawer = useCallback16(
6696
6991
  (trackId) => {
6697
6992
  setTracks(
6698
6993
  (prev) => prev.map((t) => {
@@ -6714,7 +7009,7 @@ function useGeneratorPanelCore({
6714
7009
  },
6715
7010
  [host, tracks]
6716
7011
  );
6717
- const loadEditNotes = useCallback15(
7012
+ const loadEditNotes = useCallback16(
6718
7013
  async (trackId) => {
6719
7014
  try {
6720
7015
  const mc = await host.getMusicalContext();
@@ -6732,7 +7027,7 @@ function useGeneratorPanelCore({
6732
7027
  },
6733
7028
  [host, logTag]
6734
7029
  );
6735
- const handleNotesChange = useCallback15(
7030
+ const handleNotesChange = useCallback16(
6736
7031
  (trackId, notes) => {
6737
7032
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
6738
7033
  const key = `edit:${trackId}`;
@@ -6762,7 +7057,7 @@ function useGeneratorPanelCore({
6762
7057
  },
6763
7058
  [host]
6764
7059
  );
6765
- const handleTabChange = useCallback15(
7060
+ const handleTabChange = useCallback16(
6766
7061
  (trackId, tab) => {
6767
7062
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
6768
7063
  if (tab === "fx") {
@@ -6787,10 +7082,10 @@ function useGeneratorPanelCore({
6787
7082
  },
6788
7083
  [host, availableInstruments.length, instrumentsLoading, loadEditNotes]
6789
7084
  );
6790
- const handleProgressChange = useCallback15((trackId, pct) => {
7085
+ const handleProgressChange = useCallback16((trackId, pct) => {
6791
7086
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
6792
7087
  }, []);
6793
- const handleToggleDrawer = useCallback15((trackId) => {
7088
+ const handleToggleDrawer = useCallback16((trackId) => {
6794
7089
  setTracks(
6795
7090
  (prev) => prev.map((t) => {
6796
7091
  if (t.handle.id !== trackId) return t;
@@ -6799,7 +7094,7 @@ function useGeneratorPanelCore({
6799
7094
  })
6800
7095
  );
6801
7096
  }, []);
6802
- const handleInstrumentSelect = useCallback15(
7097
+ const handleInstrumentSelect = useCallback16(
6803
7098
  async (trackId, pluginId) => {
6804
7099
  const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
6805
7100
  if (isDefaultInstrument) {
@@ -6852,7 +7147,7 @@ function useGeneratorPanelCore({
6852
7147
  },
6853
7148
  [host, identity.defaultInstrumentPluginId, logTag]
6854
7149
  );
6855
- const handleShowEditor = useCallback15(
7150
+ const handleShowEditor = useCallback16(
6856
7151
  async (trackId) => {
6857
7152
  try {
6858
7153
  await host.showInstrumentEditor(trackId);
@@ -6863,12 +7158,12 @@ function useGeneratorPanelCore({
6863
7158
  },
6864
7159
  [host]
6865
7160
  );
6866
- const handleBackToInstruments = useCallback15((trackId) => {
7161
+ const handleBackToInstruments = useCallback16((trackId) => {
6867
7162
  setTracks(
6868
7163
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
6869
7164
  );
6870
7165
  }, []);
6871
- const handleRefreshInstruments = useCallback15(() => {
7166
+ const handleRefreshInstruments = useCallback16(() => {
6872
7167
  setInstrumentsLoading(true);
6873
7168
  host.getAvailableInstruments().then((instruments) => {
6874
7169
  setAvailableInstruments(instruments);
@@ -6877,13 +7172,13 @@ function useGeneratorPanelCore({
6877
7172
  setInstrumentsLoading(false);
6878
7173
  });
6879
7174
  }, [host]);
6880
- const onAuditionNote = useCallback15(
7175
+ const onAuditionNote = useCallback16(
6881
7176
  (trackId, pitch, velocity, ms) => {
6882
7177
  void host.auditionNote(trackId, pitch, velocity, ms);
6883
7178
  },
6884
7179
  [host]
6885
7180
  );
6886
- const { resolvedCrossfadePairs, crossfadeMemberDbIds } = useMemo9(() => {
7181
+ const { resolvedCrossfadePairs, crossfadeMemberDbIds } = useMemo10(() => {
6887
7182
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
6888
7183
  const pairs = [];
6889
7184
  const members = /* @__PURE__ */ new Set();
@@ -6898,7 +7193,7 @@ function useGeneratorPanelCore({
6898
7193
  }
6899
7194
  return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
6900
7195
  }, [tracks, crossfadePairsMeta]);
6901
- const { resolvedFades, fadeMemberDbIds } = useMemo9(() => {
7196
+ const { resolvedFades, fadeMemberDbIds } = useMemo10(() => {
6902
7197
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
6903
7198
  const list = [];
6904
7199
  const members = /* @__PURE__ */ new Set();
@@ -6926,7 +7221,7 @@ function useGeneratorPanelCore({
6926
7221
  resolvedCrossfadePairs,
6927
7222
  resolvedFades
6928
7223
  });
6929
- const setGroupMute = useCallback15(
7224
+ const setGroupMute = useCallback16(
6930
7225
  (trackIds, muted) => {
6931
7226
  for (const id of trackIds) {
6932
7227
  setTracks(
@@ -6938,7 +7233,7 @@ function useGeneratorPanelCore({
6938
7233
  },
6939
7234
  [host]
6940
7235
  );
6941
- const setGroupSolo = useCallback15(
7236
+ const setGroupSolo = useCallback16(
6942
7237
  (trackIds, solo) => {
6943
7238
  for (const id of trackIds) {
6944
7239
  setTracks(
@@ -6950,7 +7245,7 @@ function useGeneratorPanelCore({
6950
7245
  },
6951
7246
  [host]
6952
7247
  );
6953
- const deleteGroup = useCallback15(
7248
+ const deleteGroup = useCallback16(
6954
7249
  async (members, cleanupKeySuffixes) => {
6955
7250
  for (const member of members) {
6956
7251
  try {
@@ -6970,7 +7265,7 @@ function useGeneratorPanelCore({
6970
7265
  },
6971
7266
  [host, activeSceneId, loadTracks]
6972
7267
  );
6973
- const handlers = useMemo9(
7268
+ const handlers = useMemo10(
6974
7269
  () => ({
6975
7270
  promptChange: handlePromptChange,
6976
7271
  generate: (trackId) => {
@@ -7084,8 +7379,8 @@ function useGeneratorPanelCore({
7084
7379
  }
7085
7380
 
7086
7381
  // src/panel-core/GeneratorPanelShell.tsx
7087
- import React21, { useCallback as useCallback16 } from "react";
7088
- import { Fragment as Fragment6, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
7382
+ import React22, { useCallback as useCallback17 } from "react";
7383
+ import { Fragment as Fragment6, jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
7089
7384
  function GeneratorPanelShell({ core, slots }) {
7090
7385
  const {
7091
7386
  ui,
@@ -7140,7 +7435,7 @@ function GeneratorPanelShell({ core, slots }) {
7140
7435
  const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
7141
7436
  const panelBus = usePanelBus(host, activeSceneId);
7142
7437
  const { identity, features } = adapter;
7143
- const buildRowProps = useCallback16(
7438
+ const buildRowProps = useCallback17(
7144
7439
  (track, drag) => {
7145
7440
  const id = track.handle.id;
7146
7441
  const pickerProps = features.instrumentPicker ? {
@@ -7192,6 +7487,7 @@ function GeneratorPanelShell({ core, slots }) {
7192
7487
  onVolumeChange: (vol) => handlers.volumeChange(id, vol),
7193
7488
  onPanChange: (pan) => handlers.panChange(id, pan),
7194
7489
  onFxToggle: (cat, enabled) => handleFxToggle(id, cat, enabled),
7490
+ externalFxHost: host,
7195
7491
  onFxPresetChange: (cat, idx) => handleFxPresetChange(id, cat, idx),
7196
7492
  onFxDryWetChange: (cat, val) => handleFxDryWetChange(id, cat, val),
7197
7493
  onToggleFxDrawer: () => handlers.toggleFxDrawer(id),
@@ -7239,12 +7535,12 @@ function GeneratorPanelShell({ core, slots }) {
7239
7535
  ]
7240
7536
  );
7241
7537
  if (!activeSceneId) {
7242
- return /* @__PURE__ */ jsx25(
7538
+ return /* @__PURE__ */ jsx26(
7243
7539
  "div",
7244
7540
  {
7245
7541
  "data-testid": `no-scene-placeholder-${identity.familyKey}`,
7246
7542
  className: "flex items-center justify-center py-8",
7247
- children: /* @__PURE__ */ jsx25(
7543
+ children: /* @__PURE__ */ jsx26(
7248
7544
  "button",
7249
7545
  {
7250
7546
  onClick: () => onSelectScene?.(),
@@ -7256,12 +7552,12 @@ function GeneratorPanelShell({ core, slots }) {
7256
7552
  );
7257
7553
  }
7258
7554
  if (!sceneContext?.hasContract) {
7259
- return /* @__PURE__ */ jsx25(
7555
+ return /* @__PURE__ */ jsx26(
7260
7556
  "div",
7261
7557
  {
7262
7558
  "data-testid": `no-contract-placeholder-${identity.familyKey}`,
7263
7559
  className: "flex items-center justify-center py-8",
7264
- children: /* @__PURE__ */ jsx25(
7560
+ children: /* @__PURE__ */ jsx26(
7265
7561
  "button",
7266
7562
  {
7267
7563
  onClick: () => onOpenContract?.(),
@@ -7273,7 +7569,7 @@ function GeneratorPanelShell({ core, slots }) {
7273
7569
  );
7274
7570
  }
7275
7571
  if (features.bulkComposePlaceholders && isComposing) {
7276
- return /* @__PURE__ */ jsx25("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ jsx25(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
7572
+ return /* @__PURE__ */ jsx26("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ jsx26(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
7277
7573
  }
7278
7574
  const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
7279
7575
  if (activePlaceholders.length > 0) {
@@ -7284,18 +7580,18 @@ function GeneratorPanelShell({ core, slots }) {
7284
7580
  tracksByDbId.set(t.handle.id, t);
7285
7581
  }
7286
7582
  }
7287
- return /* @__PURE__ */ jsx25("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
7583
+ return /* @__PURE__ */ jsx26("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
7288
7584
  const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
7289
7585
  if (loadedTrack) {
7290
- return /* @__PURE__ */ jsx25(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7586
+ return /* @__PURE__ */ jsx26(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7291
7587
  }
7292
- return /* @__PURE__ */ jsx25(
7588
+ return /* @__PURE__ */ jsx26(
7293
7589
  "div",
7294
7590
  {
7295
7591
  "data-testid": "bulk-placeholder-track",
7296
7592
  className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
7297
7593
  style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
7298
- children: /* @__PURE__ */ jsx25(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
7594
+ children: /* @__PURE__ */ jsx26(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
7299
7595
  },
7300
7596
  ph.id
7301
7597
  );
@@ -7307,13 +7603,13 @@ function GeneratorPanelShell({ core, slots }) {
7307
7603
  supportsMeters,
7308
7604
  levels: supportsMeters ? trackLevels : void 0,
7309
7605
  handlers,
7310
- renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ jsx25(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
7606
+ renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ jsx26(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
7311
7607
  setGroupMute,
7312
7608
  setGroupSolo,
7313
7609
  deleteGroup
7314
7610
  };
7315
- return /* @__PURE__ */ jsxs19("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
7316
- features.importTracks && host.listImportableTracks && /* @__PURE__ */ jsx25(
7611
+ return /* @__PURE__ */ jsxs20("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
7612
+ features.importTracks && host.listImportableTracks && /* @__PURE__ */ jsx26(
7317
7613
  ImportTrackModal,
7318
7614
  {
7319
7615
  host,
@@ -7326,7 +7622,7 @@ function GeneratorPanelShell({ core, slots }) {
7326
7622
  testIdPrefix: `${identity.familyKey}-import`
7327
7623
  }
7328
7624
  ),
7329
- features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ jsx25(
7625
+ features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ jsx26(
7330
7626
  ImportTrackModal,
7331
7627
  {
7332
7628
  host,
@@ -7341,7 +7637,7 @@ function GeneratorPanelShell({ core, slots }) {
7341
7637
  }
7342
7638
  ),
7343
7639
  slots?.modals,
7344
- canCrossfade && xfFromId && xfToId && /* @__PURE__ */ jsx25("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ jsx25(
7640
+ canCrossfade && xfFromId && xfToId && /* @__PURE__ */ jsx26("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ jsx26(
7345
7641
  TransitionDesigner,
7346
7642
  {
7347
7643
  host,
@@ -7358,11 +7654,12 @@ function GeneratorPanelShell({ core, slots }) {
7358
7654
  testIdPrefix: `${identity.familyKey}-transition-designer`
7359
7655
  }
7360
7656
  ) }),
7361
- !(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ jsx25("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ jsxs19(Fragment6, { children: [
7362
- panelBus.supported && panelBus.bus && /* @__PURE__ */ jsx25(
7657
+ !(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ jsx26("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ jsxs20(Fragment6, { children: [
7658
+ panelBus.supported && panelBus.bus && /* @__PURE__ */ jsx26(
7363
7659
  PanelMasterStrip,
7364
7660
  {
7365
7661
  bus: panelBus.bus,
7662
+ levels: panelBus.levels,
7366
7663
  availableFx: panelBus.availableFx,
7367
7664
  fxLoading: panelBus.fxLoading,
7368
7665
  soloedOut: anySolo && !panelBus.bus.soloed,
@@ -7379,7 +7676,7 @@ function GeneratorPanelShell({ core, slots }) {
7379
7676
  }
7380
7677
  ),
7381
7678
  slots?.beforeRows,
7382
- resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ jsx25(
7679
+ resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ jsx26(
7383
7680
  CrossfadeTrackRow,
7384
7681
  {
7385
7682
  accentColor: identity.transitionAccentColor,
@@ -7416,7 +7713,7 @@ function GeneratorPanelShell({ core, slots }) {
7416
7713
  },
7417
7714
  pair.groupId
7418
7715
  )),
7419
- resolvedFades.map((fade) => /* @__PURE__ */ jsx25(
7716
+ resolvedFades.map((fade) => /* @__PURE__ */ jsx26(
7420
7717
  FadeTrackRow,
7421
7718
  {
7422
7719
  accentColor: identity.transitionAccentColor,
@@ -7442,20 +7739,20 @@ function GeneratorPanelShell({ core, slots }) {
7442
7739
  fade.dbId
7443
7740
  )),
7444
7741
  (adapter.groupExtensions ?? []).flatMap(
7445
- (ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ jsx25(React21.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
7742
+ (ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ jsx26(React22.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
7446
7743
  ),
7447
7744
  tracks.map((track, index) => {
7448
7745
  if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
7449
7746
  return null;
7450
7747
  }
7451
- return /* @__PURE__ */ jsx25(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
7748
+ return /* @__PURE__ */ jsx26(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
7452
7749
  }),
7453
7750
  slots?.afterRows
7454
7751
  ] })),
7455
7752
  features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
7456
7753
  const hasAnyMidi = tracks.some((t) => t.hasMidi);
7457
7754
  const exportDisabled = isExportingMidi || !hasAnyMidi;
7458
- return /* @__PURE__ */ jsx25("div", { className: "pt-2", children: /* @__PURE__ */ jsx25(
7755
+ return /* @__PURE__ */ jsx26("div", { className: "pt-2", children: /* @__PURE__ */ jsx26(
7459
7756
  "button",
7460
7757
  {
7461
7758
  "data-testid": "export-midi-tracks-button",
@@ -7523,7 +7820,7 @@ function createSurgeSoundAdapter(host, overrides = {}) {
7523
7820
  }
7524
7821
 
7525
7822
  // src/constants/sdk-version.ts
7526
- var PLUGIN_SDK_VERSION = "2.37.0";
7823
+ var PLUGIN_SDK_VERSION = "2.39.0";
7527
7824
 
7528
7825
  // src/utils/format-concurrent-tracks.ts
7529
7826
  function formatConcurrentTracks(ctx) {
@@ -7710,6 +8007,7 @@ export {
7710
8007
  TEXTURAL_ROLES,
7711
8008
  TRANSITION_DESIGNER_DRAFT_KEY,
7712
8009
  TrackDrawer,
8010
+ TrackExternalFxSection,
7713
8011
  TrackMeterStrip,
7714
8012
  TrackRow,
7715
8013
  TransitionDesigner,
@@ -7766,6 +8064,7 @@ export {
7766
8064
  usePanelBus,
7767
8065
  useSceneState,
7768
8066
  useSoundHistory,
8067
+ useTrackExternalFx,
7769
8068
  useTrackLevel,
7770
8069
  useTrackLevels,
7771
8070
  useTrackMeter,