@signalsandsorcery/plugin-sdk 2.35.1 → 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.js CHANGED
@@ -62,6 +62,7 @@ __export(index_exports, {
62
62
  PLUGIN_SDK_VERSION: () => PLUGIN_SDK_VERSION,
63
63
  PX_PER_BEAT: () => PX_PER_BEAT,
64
64
  PanSlider: () => PanSlider,
65
+ PanelMasterStrip: () => PanelMasterStrip,
65
66
  PianoRollEditor: () => PianoRollEditor,
66
67
  PluginError: () => PluginError,
67
68
  RESIZE_HANDLE_PX: () => RESIZE_HANDLE_PX,
@@ -73,6 +74,7 @@ __export(index_exports, {
73
74
  TEXTURAL_ROLES: () => TEXTURAL_ROLES,
74
75
  TRANSITION_DESIGNER_DRAFT_KEY: () => TRANSITION_DESIGNER_DRAFT_KEY,
75
76
  TrackDrawer: () => TrackDrawer,
77
+ TrackExternalFxSection: () => TrackExternalFxSection,
76
78
  TrackMeterStrip: () => TrackMeterStrip,
77
79
  TrackRow: () => TrackRow,
78
80
  TransitionDesigner: () => TransitionDesigner,
@@ -126,8 +128,10 @@ __export(index_exports, {
126
128
  transposeNotes: () => transposeNotes,
127
129
  useAnySolo: () => useAnySolo,
128
130
  useGeneratorPanelCore: () => useGeneratorPanelCore,
131
+ usePanelBus: () => usePanelBus,
129
132
  useSceneState: () => useSceneState,
130
133
  useSoundHistory: () => useSoundHistory,
134
+ useTrackExternalFx: () => useTrackExternalFx,
131
135
  useTrackLevel: () => useTrackLevel,
132
136
  useTrackLevels: () => useTrackLevels,
133
137
  useTrackMeter: () => useTrackMeter,
@@ -204,11 +208,11 @@ var EMPTY_FX_DETAIL_STATE = {
204
208
  };
205
209
 
206
210
  // src/components/TrackRow.tsx
207
- var import_react9 = __toESM(require("react"));
211
+ var import_react11 = __toESM(require("react"));
208
212
  var import_lucide_react = require("lucide-react");
209
213
 
210
214
  // src/components/TrackDrawer.tsx
211
- var import_react2 = require("react");
215
+ var import_react4 = require("react");
212
216
 
213
217
  // src/constants/fx-presets.ts
214
218
  var EQ_PRESETS = {
@@ -928,8 +932,250 @@ function PianoRollEditor({
928
932
  ] });
929
933
  }
930
934
 
931
- // src/components/TrackDrawer.tsx
935
+ // src/components/TrackExternalFxSection.tsx
936
+ var import_react3 = require("react");
937
+
938
+ // src/hooks/useTrackExternalFx.ts
939
+ var import_react2 = require("react");
940
+ function useTrackExternalFx(host, trackId) {
941
+ const supported = typeof host.getTrackExternalFx === "function";
942
+ const [fx, setFx] = (0, import_react2.useState)(null);
943
+ const [availableFx, setAvailableFx] = (0, import_react2.useState)([]);
944
+ const [fxLoading, setFxLoading] = (0, import_react2.useState)(false);
945
+ const [pickerOpen, setPickerOpen] = (0, import_react2.useState)(false);
946
+ const fxLoadedRef = (0, import_react2.useRef)(false);
947
+ const loadSeqRef = (0, import_react2.useRef)(0);
948
+ const reload = (0, import_react2.useCallback)(async () => {
949
+ if (!supported || !trackId || !host.getTrackExternalFx) {
950
+ setFx(null);
951
+ return;
952
+ }
953
+ const seq = ++loadSeqRef.current;
954
+ try {
955
+ const list = await host.getTrackExternalFx(trackId);
956
+ if (loadSeqRef.current === seq) setFx(list);
957
+ } catch {
958
+ }
959
+ }, [host, trackId, supported]);
960
+ (0, import_react2.useEffect)(() => {
961
+ setFx(null);
962
+ setPickerOpen(false);
963
+ void reload();
964
+ }, [reload]);
965
+ const loadFxList = (0, import_react2.useCallback)(
966
+ async (force) => {
967
+ if (!supported || !host.getAvailableFx) return;
968
+ if (fxLoadedRef.current && !force) return;
969
+ setFxLoading(true);
970
+ try {
971
+ const list = await host.getAvailableFx();
972
+ setAvailableFx(list);
973
+ fxLoadedRef.current = true;
974
+ } catch {
975
+ } finally {
976
+ setFxLoading(false);
977
+ }
978
+ },
979
+ [host, supported]
980
+ );
981
+ const openPicker = (0, import_react2.useCallback)(
982
+ (open) => {
983
+ setPickerOpen(open);
984
+ if (open) void loadFxList(false);
985
+ },
986
+ [loadFxList]
987
+ );
988
+ const mutate = (0, import_react2.useCallback)(
989
+ (fn) => {
990
+ if (!fn || !trackId) return;
991
+ void (async () => {
992
+ try {
993
+ await fn();
994
+ } catch {
995
+ }
996
+ await reload();
997
+ })();
998
+ },
999
+ [trackId, reload]
1000
+ );
1001
+ return {
1002
+ supported,
1003
+ fx,
1004
+ availableFx,
1005
+ fxLoading,
1006
+ pickerOpen,
1007
+ setPickerOpen: openPicker,
1008
+ refreshFx: () => void loadFxList(true),
1009
+ reload,
1010
+ onAddFx: (pluginId) => mutate(host.loadTrackExternalFx && (async () => {
1011
+ await host.loadTrackExternalFx(trackId, pluginId);
1012
+ })),
1013
+ onRemoveFx: (fxIndex) => mutate(host.removeTrackExternalFx && (() => host.removeTrackExternalFx(trackId, fxIndex))),
1014
+ onToggleFxEnabled: (fxIndex, enabled) => mutate(
1015
+ host.setTrackExternalFxEnabled && (() => host.setTrackExternalFxEnabled(trackId, fxIndex, enabled))
1016
+ ),
1017
+ onShowFxEditor: (fxIndex) => mutate(
1018
+ host.showTrackExternalFxEditor && (() => host.showTrackExternalFxEditor(trackId, fxIndex))
1019
+ )
1020
+ };
1021
+ }
1022
+
1023
+ // src/components/TrackExternalFxSection.tsx
932
1024
  var import_jsx_runtime3 = require("react/jsx-runtime");
1025
+ function TrackExternalFxSection({
1026
+ host,
1027
+ trackId,
1028
+ disabled = false
1029
+ }) {
1030
+ const {
1031
+ supported,
1032
+ fx,
1033
+ availableFx,
1034
+ fxLoading,
1035
+ pickerOpen,
1036
+ setPickerOpen,
1037
+ refreshFx,
1038
+ onAddFx,
1039
+ onRemoveFx,
1040
+ onToggleFxEnabled,
1041
+ onShowFxEditor
1042
+ } = useTrackExternalFx(host, trackId);
1043
+ const [search, setSearch] = (0, import_react3.useState)("");
1044
+ const filtered = (0, import_react3.useMemo)(() => {
1045
+ const q = search.trim().toLowerCase();
1046
+ if (!q) return availableFx;
1047
+ return availableFx.filter(
1048
+ (candidate) => candidate.name.toLowerCase().includes(q) || candidate.manufacturer.toLowerCase().includes(q)
1049
+ );
1050
+ }, [availableFx, search]);
1051
+ if (!supported) return null;
1052
+ const entries = fx ?? [];
1053
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1054
+ "div",
1055
+ {
1056
+ "data-testid": "track-external-fx-section",
1057
+ className: "flex flex-col gap-1.5 pt-2 mt-1 border-t border-sas-border/60",
1058
+ children: [
1059
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
1060
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1061
+ "span",
1062
+ {
1063
+ className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
1064
+ title: "Third-party FX inserts (VST3/AU) on this track, before its fader. Settings persist with the project.",
1065
+ children: "3RD-PARTY FX"
1066
+ }
1067
+ ),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto", children: [
1069
+ entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1070
+ "span",
1071
+ {
1072
+ "data-testid": `track-fx-chip-${entry.index}`,
1073
+ 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"}`,
1074
+ title: `${entry.name}${entry.enabled ? "" : " (bypassed)"}`,
1075
+ children: [
1076
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1077
+ "button",
1078
+ {
1079
+ "data-testid": `track-fx-toggle-${entry.index}`,
1080
+ onClick: () => onToggleFxEnabled(entry.index, !entry.enabled),
1081
+ disabled,
1082
+ className: "hover:opacity-70 disabled:opacity-50",
1083
+ title: entry.enabled ? `Bypass ${entry.name}` : `Enable ${entry.name}`,
1084
+ children: entry.enabled ? "\u25CF" : "\u25CB"
1085
+ }
1086
+ ),
1087
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1088
+ "button",
1089
+ {
1090
+ "data-testid": `track-fx-edit-${entry.index}`,
1091
+ onClick: () => onShowFxEditor(entry.index),
1092
+ disabled,
1093
+ className: "max-w-[110px] truncate hover:underline disabled:opacity-50",
1094
+ title: `Open ${entry.name} editor`,
1095
+ children: entry.name
1096
+ }
1097
+ ),
1098
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1099
+ "button",
1100
+ {
1101
+ "data-testid": `track-fx-remove-${entry.index}`,
1102
+ onClick: () => onRemoveFx(entry.index),
1103
+ disabled,
1104
+ className: "text-sas-muted/60 hover:text-sas-danger disabled:opacity-50",
1105
+ title: `Remove ${entry.name} from this track`,
1106
+ children: "\u2715"
1107
+ }
1108
+ )
1109
+ ]
1110
+ },
1111
+ `${entry.index}:${entry.pluginId}`
1112
+ )),
1113
+ entries.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] text-sas-muted/40 select-none", children: "none" })
1114
+ ] }),
1115
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1116
+ "button",
1117
+ {
1118
+ "data-testid": "track-fx-add-button",
1119
+ onClick: () => setPickerOpen(!pickerOpen),
1120
+ disabled,
1121
+ 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`,
1122
+ title: pickerOpen ? "Close the FX picker" : "Add a VST3/AU FX plugin to this track",
1123
+ children: pickerOpen ? "FX \u25B4" : "FX +"
1124
+ }
1125
+ )
1126
+ ] }),
1127
+ pickerOpen && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { "data-testid": "track-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
1128
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
1129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1130
+ "input",
1131
+ {
1132
+ type: "text",
1133
+ value: search,
1134
+ onChange: (e) => setSearch(e.target.value),
1135
+ placeholder: "Search FX...",
1136
+ className: "sas-input flex-1 px-2 py-1 text-xs"
1137
+ }
1138
+ ),
1139
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1140
+ "button",
1141
+ {
1142
+ onClick: () => refreshFx(),
1143
+ disabled: fxLoading,
1144
+ 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",
1145
+ title: "Re-scan plugins",
1146
+ children: fxLoading ? "..." : "Refresh"
1147
+ }
1148
+ )
1149
+ ] }),
1150
+ fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
1151
+ filtered.map((candidate) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1152
+ "button",
1153
+ {
1154
+ "data-testid": `track-fx-pick-${candidate.pluginId}`,
1155
+ onClick: () => {
1156
+ onAddFx(candidate.pluginId);
1157
+ setPickerOpen(false);
1158
+ },
1159
+ disabled,
1160
+ 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",
1161
+ title: `${candidate.name} by ${candidate.manufacturer} (${candidate.type.toUpperCase()})`,
1162
+ children: [
1163
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs font-medium truncate w-full", children: candidate.name }),
1164
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: candidate.manufacturer || candidate.type.toUpperCase() })
1165
+ ]
1166
+ },
1167
+ candidate.pluginId
1168
+ )),
1169
+ filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
1170
+ ] })
1171
+ ] })
1172
+ ]
1173
+ }
1174
+ );
1175
+ }
1176
+
1177
+ // src/components/TrackDrawer.tsx
1178
+ var import_jsx_runtime4 = require("react/jsx-runtime");
933
1179
  var TAB_LABELS = {
934
1180
  fx: "FX",
935
1181
  pick: "Pick",
@@ -946,6 +1192,7 @@ function TrackDrawer({
946
1192
  onFxPresetChange,
947
1193
  onFxDryWetChange,
948
1194
  fxDisabled = false,
1195
+ externalFxHost,
949
1196
  instruments = [],
950
1197
  currentPluginId = null,
951
1198
  isLoading = false,
@@ -968,13 +1215,13 @@ function TrackDrawer({
968
1215
  editSnap,
969
1216
  onAuditionNote
970
1217
  }) {
971
- const [search, setSearch] = (0, import_react2.useState)("");
1218
+ const [search, setSearch] = (0, import_react4.useState)("");
972
1219
  const fxEnabled = !!onFxToggle;
973
1220
  const pickEnabled = !!onSelect;
974
1221
  const historyEnabled = !!onRestoreSound;
975
1222
  const importEnabled = !!onImportSound;
976
1223
  const editEnabled = !!onNotesChange;
977
- const enabledTabs = (0, import_react2.useMemo)(() => {
1224
+ const enabledTabs = (0, import_react4.useMemo)(() => {
978
1225
  const tabs = [];
979
1226
  if (fxEnabled) tabs.push("fx");
980
1227
  if (pickEnabled) tabs.push("pick");
@@ -984,7 +1231,7 @@ function TrackDrawer({
984
1231
  return tabs;
985
1232
  }, [fxEnabled, pickEnabled, historyEnabled, importEnabled, editEnabled]);
986
1233
  const SURGE_XT_DEFAULT_ID = "Surge XT";
987
- const filtered = (0, import_react2.useMemo)(() => {
1234
+ const filtered = (0, import_react4.useMemo)(() => {
988
1235
  let all = instruments.filter((i) => i.name !== "Surge XT");
989
1236
  if (search.trim()) {
990
1237
  const q = search.toLowerCase();
@@ -1004,12 +1251,12 @@ function TrackDrawer({
1004
1251
  const history = soundHistory ?? [];
1005
1252
  const effectiveTab = enabledTabs.includes(activeTab) ? activeTab : enabledTabs[0] ?? "fx";
1006
1253
  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"}`;
1007
- const strip = enabledTabs.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1254
+ const strip = enabledTabs.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1008
1255
  "div",
1009
1256
  {
1010
1257
  className: "flex items-center gap-1 border-b border-sas-border pb-1",
1011
1258
  "data-testid": "sdk-drawer-tabs",
1012
- children: enabledTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1259
+ children: enabledTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1013
1260
  "button",
1014
1261
  {
1015
1262
  type: "button",
@@ -1023,9 +1270,9 @@ function TrackDrawer({
1023
1270
  }
1024
1271
  ) : null;
1025
1272
  const currentSound = soundHistoryCursor >= 0 && soundHistoryCursor < history.length ? history[soundHistoryCursor].label : null;
1026
- const header = strip || currentSound ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-1", "data-testid": "sdk-drawer-header", children: [
1273
+ const header = strip || currentSound ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1", "data-testid": "sdk-drawer-header", children: [
1027
1274
  strip,
1028
- currentSound && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1275
+ currentSound && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1029
1276
  "span",
1030
1277
  {
1031
1278
  className: "text-[10px] text-sas-muted/60 truncate px-0.5",
@@ -1035,9 +1282,9 @@ function TrackDrawer({
1035
1282
  )
1036
1283
  ] }) : null;
1037
1284
  if (effectiveTab === "edit") {
1038
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-edit", children: [
1285
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-edit", children: [
1039
1286
  header,
1040
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1287
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1041
1288
  PianoRollEditor,
1042
1289
  {
1043
1290
  notes: editNotes ?? [],
@@ -1052,9 +1299,9 @@ function TrackDrawer({
1052
1299
  ] });
1053
1300
  }
1054
1301
  if (effectiveTab === "fx") {
1055
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-fx", children: [
1302
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-fx", children: [
1056
1303
  header,
1057
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1304
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1058
1305
  FxToggleBar,
1059
1306
  {
1060
1307
  trackId,
@@ -1064,20 +1311,21 @@ function TrackDrawer({
1064
1311
  onDryWetChange: (_t, category, value) => onFxDryWetChange?.(category, value),
1065
1312
  disabled: fxDisabled
1066
1313
  }
1067
- )
1314
+ ),
1315
+ externalFxHost && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TrackExternalFxSection, { host: externalFxHost, trackId, disabled: fxDisabled })
1068
1316
  ] });
1069
1317
  }
1070
1318
  if (effectiveTab === "import") {
1071
1319
  const soundNoun = /preset/i.test(importSoundLabel ?? "") ? "preset" : /sample/i.test(importSoundLabel ?? "") ? "sample" : "sound";
1072
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-import", children: [
1320
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-2", "data-testid": "sdk-drawer-import", children: [
1073
1321
  header,
1074
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-[11px] text-sas-muted/70 leading-snug", children: [
1322
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-[11px] text-sas-muted/70 leading-snug", children: [
1075
1323
  "Copy the sound from a matching track in another scene \u2014 your MIDI stays, only the",
1076
1324
  " ",
1077
1325
  soundNoun,
1078
1326
  " changes."
1079
1327
  ] }),
1080
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1328
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1081
1329
  "button",
1082
1330
  {
1083
1331
  type: "button",
@@ -1095,16 +1343,16 @@ function TrackDrawer({
1095
1343
  }
1096
1344
  if (effectiveTab === "history") {
1097
1345
  const order = history.map((_, i) => i).reverse();
1098
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", children: [
1346
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-2", children: [
1099
1347
  header,
1100
- history.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1348
+ history.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1101
1349
  "div",
1102
1350
  {
1103
1351
  className: "text-xs text-sas-muted/60 text-center py-3",
1104
1352
  "data-testid": "sdk-history-empty",
1105
1353
  children: "No sounds yet \u2014 shuffle to build history."
1106
1354
  }
1107
- ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1355
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1108
1356
  "ul",
1109
1357
  {
1110
1358
  className: "flex flex-col gap-1 max-h-[160px] overflow-y-auto",
@@ -1112,8 +1360,8 @@ function TrackDrawer({
1112
1360
  children: order.map((i) => {
1113
1361
  const entry = history[i];
1114
1362
  const isCurrent = i === soundHistoryCursor;
1115
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "flex items-center gap-1", children: [
1116
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1363
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "flex items-center gap-1", children: [
1364
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1117
1365
  "button",
1118
1366
  {
1119
1367
  type: "button",
@@ -1123,12 +1371,12 @@ function TrackDrawer({
1123
1371
  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"}`,
1124
1372
  title: isCurrent ? "Current sound" : `Restore: ${entry.label}`,
1125
1373
  children: [
1126
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "truncate", children: entry.label }),
1127
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] text-sas-muted/60 flex-shrink-0 ml-2", children: isCurrent ? "\u25CF current" : "restore" })
1374
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate", children: entry.label }),
1375
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[10px] text-sas-muted/60 flex-shrink-0 ml-2", children: isCurrent ? "\u25CF current" : "restore" })
1128
1376
  ]
1129
1377
  }
1130
1378
  ),
1131
- onToggleFavorite && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1379
+ onToggleFavorite && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1132
1380
  "button",
1133
1381
  {
1134
1382
  type: "button",
@@ -1146,10 +1394,10 @@ function TrackDrawer({
1146
1394
  ] });
1147
1395
  }
1148
1396
  if (effectiveTab === "pick" && editorStage) {
1149
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", children: [
1397
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-2", children: [
1150
1398
  header,
1151
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
1152
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1399
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
1400
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1153
1401
  "button",
1154
1402
  {
1155
1403
  onClick: () => onBackToInstruments?.(),
@@ -1157,9 +1405,9 @@ function TrackDrawer({
1157
1405
  children: "\u2190 Back"
1158
1406
  }
1159
1407
  ),
1160
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-sas-muted font-medium truncate flex-1", children: selectedInstrumentName ?? "Plugin" })
1408
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs text-sas-muted font-medium truncate flex-1", children: selectedInstrumentName ?? "Plugin" })
1161
1409
  ] }),
1162
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1410
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1163
1411
  "button",
1164
1412
  {
1165
1413
  onClick: () => onShowEditor?.(),
@@ -1171,10 +1419,10 @@ function TrackDrawer({
1171
1419
  }
1172
1420
  const isDefaultSelected = currentPluginId === null;
1173
1421
  const isSelected = (pluginId) => pluginId === currentPluginId;
1174
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", children: [
1422
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-2", children: [
1175
1423
  header,
1176
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
1177
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1424
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
1425
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1178
1426
  "input",
1179
1427
  {
1180
1428
  type: "text",
@@ -1184,7 +1432,7 @@ function TrackDrawer({
1184
1432
  className: "sas-input flex-1 px-2 py-1 text-xs"
1185
1433
  }
1186
1434
  ),
1187
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1435
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1188
1436
  "button",
1189
1437
  {
1190
1438
  onClick: () => onRefresh?.(),
@@ -1195,54 +1443,54 @@ function TrackDrawer({
1195
1443
  }
1196
1444
  )
1197
1445
  ] }),
1198
- isLoading && instruments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
1199
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1446
+ isLoading && instruments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
1447
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1200
1448
  "button",
1201
1449
  {
1202
1450
  onClick: () => onSelect?.(SURGE_XT_DEFAULT_ID),
1203
1451
  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"}`,
1204
1452
  title: "Surge XT \u2014 Default instrument",
1205
1453
  children: [
1206
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-xs font-medium truncate w-full", children: [
1454
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "text-xs font-medium truncate w-full", children: [
1207
1455
  isDefaultSelected && "\u2713 ",
1208
1456
  "Surge XT"
1209
1457
  ] }),
1210
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: "Default" })
1458
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: "Default" })
1211
1459
  ]
1212
1460
  },
1213
1461
  "__surge-xt-default__"
1214
1462
  ),
1215
1463
  filtered.map((inst) => {
1216
1464
  const selected = isSelected(inst.pluginId);
1217
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1465
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1218
1466
  "button",
1219
1467
  {
1220
1468
  onClick: () => onSelect?.(inst.pluginId),
1221
1469
  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"}`,
1222
1470
  title: `${inst.name} by ${inst.manufacturer} (${inst.type.toUpperCase()})${inst.missing ? " \u2014 MISSING" : ""}`,
1223
1471
  children: [
1224
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-xs font-medium truncate w-full", children: [
1472
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "text-xs font-medium truncate w-full", children: [
1225
1473
  selected && "\u2713 ",
1226
1474
  inst.name
1227
1475
  ] }),
1228
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: inst.manufacturer || inst.type.toUpperCase() })
1476
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: inst.manufacturer || inst.type.toUpperCase() })
1229
1477
  ]
1230
1478
  },
1231
1479
  inst.pluginId
1232
1480
  );
1233
1481
  }),
1234
- filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "col-span-2 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No other plugins found" })
1482
+ filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "col-span-2 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No other plugins found" })
1235
1483
  ] })
1236
1484
  ] });
1237
1485
  }
1238
1486
 
1239
1487
  // src/components/ConfirmDialog.tsx
1240
- var import_react4 = require("react");
1488
+ var import_react6 = require("react");
1241
1489
 
1242
1490
  // src/components/Modal.tsx
1243
- var import_react3 = require("react");
1491
+ var import_react5 = require("react");
1244
1492
  var import_react_dom = require("react-dom");
1245
- var import_jsx_runtime4 = require("react/jsx-runtime");
1493
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1246
1494
  function Modal({
1247
1495
  open,
1248
1496
  onClose,
@@ -1252,7 +1500,7 @@ function Modal({
1252
1500
  closeOnEscape = true,
1253
1501
  initialFocusRef
1254
1502
  }) {
1255
- (0, import_react3.useEffect)(() => {
1503
+ (0, import_react5.useEffect)(() => {
1256
1504
  if (!open) return void 0;
1257
1505
  const onKey = (e) => {
1258
1506
  if (closeOnEscape && e.key === "Escape") {
@@ -1266,7 +1514,7 @@ function Modal({
1266
1514
  }, [open, onClose, closeOnEscape, initialFocusRef]);
1267
1515
  if (!open) return null;
1268
1516
  return (0, import_react_dom.createPortal)(
1269
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1517
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1270
1518
  "div",
1271
1519
  {
1272
1520
  className: "fixed inset-0 z-[1000] flex items-center justify-center bg-black/60",
@@ -1280,7 +1528,7 @@ function Modal({
1280
1528
  }
1281
1529
 
1282
1530
  // src/components/ConfirmDialog.tsx
1283
- var import_jsx_runtime5 = require("react/jsx-runtime");
1531
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1284
1532
  function ConfirmDialog({
1285
1533
  open,
1286
1534
  title,
@@ -1292,8 +1540,8 @@ function ConfirmDialog({
1292
1540
  onCancel,
1293
1541
  testIdPrefix = "confirm-dialog"
1294
1542
  }) {
1295
- const cancelRef = (0, import_react4.useRef)(null);
1296
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Modal, { open, onClose: onCancel, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1543
+ const cancelRef = (0, import_react6.useRef)(null);
1544
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Modal, { open, onClose: onCancel, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1297
1545
  "div",
1298
1546
  {
1299
1547
  className: "w-[360px] max-w-[90vw] flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
@@ -1303,8 +1551,8 @@ function ConfirmDialog({
1303
1551
  "aria-label": title,
1304
1552
  "data-testid": `${testIdPrefix}-modal`,
1305
1553
  children: [
1306
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "px-4 py-3 border-b border-sas-border", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-sm font-medium text-sas-text", "data-testid": `${testIdPrefix}-title`, children: title }) }),
1307
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1554
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "px-4 py-3 border-b border-sas-border", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-sm font-medium text-sas-text", "data-testid": `${testIdPrefix}-title`, children: title }) }),
1555
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1308
1556
  "div",
1309
1557
  {
1310
1558
  className: "px-4 py-3 text-xs text-sas-muted leading-relaxed break-words",
@@ -1312,8 +1560,8 @@ function ConfirmDialog({
1312
1560
  children: message
1313
1561
  }
1314
1562
  ),
1315
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex justify-end gap-2 px-4 py-3 border-t border-sas-border", children: [
1316
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1563
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex justify-end gap-2 px-4 py-3 border-t border-sas-border", children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1317
1565
  "button",
1318
1566
  {
1319
1567
  ref: cancelRef,
@@ -1324,7 +1572,7 @@ function ConfirmDialog({
1324
1572
  children: cancelLabel
1325
1573
  }
1326
1574
  ),
1327
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1575
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1328
1576
  "button",
1329
1577
  {
1330
1578
  type: "button",
@@ -1341,7 +1589,7 @@ function ConfirmDialog({
1341
1589
  }
1342
1590
 
1343
1591
  // src/components/LevelMeter.tsx
1344
- var import_jsx_runtime6 = require("react/jsx-runtime");
1592
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1345
1593
  var COLOR_GREEN = "#2BD576";
1346
1594
  var COLOR_ORANGE = "#F5A623";
1347
1595
  var COLOR_RED = "#FF4D5E";
@@ -1369,7 +1617,7 @@ var LevelMeter = ({
1369
1617
  const widthPct = active ? dbToPct(peakDb) : 0;
1370
1618
  const showPeak = peakHoldDb != null && active && peakHoldDb > -60;
1371
1619
  const peakHoldPct = showPeak ? dbToPct(peakHoldDb) : 0;
1372
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1620
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1373
1621
  "div",
1374
1622
  {
1375
1623
  className: `sas-level-meter ${className ?? ""}`,
@@ -1380,7 +1628,7 @@ var LevelMeter = ({
1380
1628
  gap: compact ? 0 : 6
1381
1629
  },
1382
1630
  children: [
1383
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1631
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1384
1632
  "div",
1385
1633
  {
1386
1634
  style: {
@@ -1394,8 +1642,8 @@ var LevelMeter = ({
1394
1642
  minWidth: compact ? 0 : 60
1395
1643
  },
1396
1644
  children: [
1397
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { position: "absolute", inset: 0, background: METER_GRADIENT } }),
1398
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1645
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { position: "absolute", inset: 0, background: METER_GRADIENT } }),
1646
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1399
1647
  "div",
1400
1648
  {
1401
1649
  style: {
@@ -1409,7 +1657,7 @@ var LevelMeter = ({
1409
1657
  }
1410
1658
  }
1411
1659
  ),
1412
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1660
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1413
1661
  "div",
1414
1662
  {
1415
1663
  "data-testid": `${id}-segments`,
@@ -1422,7 +1670,7 @@ var LevelMeter = ({
1422
1670
  }
1423
1671
  }
1424
1672
  ),
1425
- showPeak && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1673
+ showPeak && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1426
1674
  "div",
1427
1675
  {
1428
1676
  "data-testid": `${id}-peak`,
@@ -1443,7 +1691,7 @@ var LevelMeter = ({
1443
1691
  ]
1444
1692
  }
1445
1693
  ),
1446
- !compact && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1694
+ !compact && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1447
1695
  "span",
1448
1696
  {
1449
1697
  style: {
@@ -1456,7 +1704,7 @@ var LevelMeter = ({
1456
1704
  children: active && peakDb > -120 ? `${peakDb.toFixed(0)} dB` : "\u2014"
1457
1705
  }
1458
1706
  ),
1459
- clipped && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1707
+ clipped && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1460
1708
  "span",
1461
1709
  {
1462
1710
  "data-testid": `${id}-clip`,
@@ -1481,7 +1729,7 @@ var LevelMeter = ({
1481
1729
  };
1482
1730
 
1483
1731
  // src/hooks/useTrackLevels.ts
1484
- var import_react5 = require("react");
1732
+ var import_react7 = require("react");
1485
1733
  var meterDiagRLast = /* @__PURE__ */ new Map();
1486
1734
  var POLL_INTERVAL_MS = 33;
1487
1735
  var HIDDEN_RECHECK_MS = 250;
@@ -1492,9 +1740,9 @@ function isHidden() {
1492
1740
  return typeof document !== "undefined" && document.hidden === true;
1493
1741
  }
1494
1742
  function useTrackLevels(host, enabled = true) {
1495
- const mapRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1496
- const listenersRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
1497
- const handleRef = (0, import_react5.useRef)(null);
1743
+ const mapRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
1744
+ const listenersRef = (0, import_react7.useRef)(/* @__PURE__ */ new Set());
1745
+ const handleRef = (0, import_react7.useRef)(null);
1498
1746
  if (handleRef.current === null) {
1499
1747
  handleRef.current = {
1500
1748
  getLevel: (trackId) => mapRef.current.get(trackId) ?? null,
@@ -1506,7 +1754,7 @@ function useTrackLevels(host, enabled = true) {
1506
1754
  }
1507
1755
  };
1508
1756
  }
1509
- (0, import_react5.useEffect)(() => {
1757
+ (0, import_react7.useEffect)(() => {
1510
1758
  const notify = () => {
1511
1759
  listenersRef.current.forEach((l) => l());
1512
1760
  };
@@ -1576,8 +1824,8 @@ function sameLevel(a, b) {
1576
1824
  return a.peakDb === b.peakDb && a.clipped === b.clipped;
1577
1825
  }
1578
1826
  function useTrackLevel(handle, trackId) {
1579
- const [level, setLevel] = (0, import_react5.useState)(null);
1580
- (0, import_react5.useEffect)(() => {
1827
+ const [level, setLevel] = (0, import_react7.useState)(null);
1828
+ (0, import_react7.useEffect)(() => {
1581
1829
  if (!handle) {
1582
1830
  setLevel(null);
1583
1831
  return;
@@ -1601,11 +1849,11 @@ function sameMeter(a, b) {
1601
1849
  return a.active === b.active && a.clipped === b.clipped && a.peakDb === b.peakDb && Math.round(a.peakHoldDb * 2) === Math.round(b.peakHoldDb * 2);
1602
1850
  }
1603
1851
  function useTrackMeter(handle, trackId) {
1604
- const [view, setView] = (0, import_react5.useState)(IDLE_METER_VIEW);
1605
- const heldDbRef = (0, import_react5.useRef)(METER_FLOOR_DB);
1606
- const heldAtRef = (0, import_react5.useRef)(0);
1607
- const lastTickRef = (0, import_react5.useRef)(0);
1608
- (0, import_react5.useEffect)(() => {
1852
+ const [view, setView] = (0, import_react7.useState)(IDLE_METER_VIEW);
1853
+ const heldDbRef = (0, import_react7.useRef)(METER_FLOOR_DB);
1854
+ const heldAtRef = (0, import_react7.useRef)(0);
1855
+ const lastTickRef = (0, import_react7.useRef)(0);
1856
+ (0, import_react7.useEffect)(() => {
1609
1857
  if (!handle) {
1610
1858
  heldDbRef.current = METER_FLOOR_DB;
1611
1859
  lastTickRef.current = 0;
@@ -1648,8 +1896,8 @@ function useTrackMeter(handle, trackId) {
1648
1896
  return view;
1649
1897
  }
1650
1898
  function useTransportPlaying(host) {
1651
- const [playing, setPlaying] = (0, import_react5.useState)(false);
1652
- (0, import_react5.useEffect)(() => {
1899
+ const [playing, setPlaying] = (0, import_react7.useState)(false);
1900
+ (0, import_react7.useEffect)(() => {
1653
1901
  if (!host) {
1654
1902
  setPlaying(false);
1655
1903
  return;
@@ -1677,7 +1925,7 @@ function useTransportPlaying(host) {
1677
1925
  }
1678
1926
 
1679
1927
  // src/components/TrackMeterStrip.tsx
1680
- var import_jsx_runtime7 = require("react/jsx-runtime");
1928
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1681
1929
  var TrackMeterStrip = ({
1682
1930
  levels,
1683
1931
  trackId,
@@ -1685,12 +1933,12 @@ var TrackMeterStrip = ({
1685
1933
  className
1686
1934
  }) => {
1687
1935
  const meter = useTrackMeter(levels, trackId);
1688
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1936
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1689
1937
  "div",
1690
1938
  {
1691
1939
  "data-testid": "sdk-track-meter",
1692
1940
  className: `w-full px-2 py-1 bg-sas-panel-alt border border-t-0 border-sas-border ${roundBottom ? "rounded-b-sm" : ""} ${className ?? ""}`,
1693
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1941
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1694
1942
  LevelMeter,
1695
1943
  {
1696
1944
  compact: true,
@@ -1706,7 +1954,7 @@ var TrackMeterStrip = ({
1706
1954
  };
1707
1955
 
1708
1956
  // src/components/VolumeSlider.tsx
1709
- var import_react6 = require("react");
1957
+ var import_react8 = require("react");
1710
1958
 
1711
1959
  // src/utils/volume-conversion.ts
1712
1960
  var SLIDER_UNITY = 0.75;
@@ -1728,7 +1976,7 @@ function dbToSlider(db) {
1728
1976
  }
1729
1977
 
1730
1978
  // src/components/VolumeSlider.tsx
1731
- var import_jsx_runtime8 = require("react/jsx-runtime");
1979
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1732
1980
  function formatDb(value) {
1733
1981
  const db = sliderToDb(value);
1734
1982
  if (db <= -60) return "-\u221E dB";
@@ -1736,12 +1984,12 @@ function formatDb(value) {
1736
1984
  return `${sign}${db.toFixed(1)} dB`;
1737
1985
  }
1738
1986
  function useDebouncedCallback(callback, delay) {
1739
- const timeoutRef = (0, import_react6.useRef)(null);
1740
- const callbackRef = (0, import_react6.useRef)(callback);
1741
- (0, import_react6.useEffect)(() => {
1987
+ const timeoutRef = (0, import_react8.useRef)(null);
1988
+ const callbackRef = (0, import_react8.useRef)(callback);
1989
+ (0, import_react8.useEffect)(() => {
1742
1990
  callbackRef.current = callback;
1743
1991
  }, [callback]);
1744
- const debouncedCallback = (0, import_react6.useCallback)(
1992
+ const debouncedCallback = (0, import_react8.useCallback)(
1745
1993
  (...args) => {
1746
1994
  if (timeoutRef.current) {
1747
1995
  clearTimeout(timeoutRef.current);
@@ -1752,7 +2000,7 @@ function useDebouncedCallback(callback, delay) {
1752
2000
  },
1753
2001
  [delay]
1754
2002
  );
1755
- (0, import_react6.useEffect)(() => {
2003
+ (0, import_react8.useEffect)(() => {
1756
2004
  return () => {
1757
2005
  if (timeoutRef.current) {
1758
2006
  clearTimeout(timeoutRef.current);
@@ -1767,15 +2015,15 @@ var VolumeSlider = ({
1767
2015
  disabled = false,
1768
2016
  className = ""
1769
2017
  }) => {
1770
- const [localValue, setLocalValue] = (0, import_react6.useState)(value);
1771
- const [isDragging, setIsDragging] = (0, import_react6.useState)(false);
1772
- (0, import_react6.useEffect)(() => {
2018
+ const [localValue, setLocalValue] = (0, import_react8.useState)(value);
2019
+ const [isDragging, setIsDragging] = (0, import_react8.useState)(false);
2020
+ (0, import_react8.useEffect)(() => {
1773
2021
  if (!isDragging) {
1774
2022
  setLocalValue(value);
1775
2023
  }
1776
2024
  }, [value, isDragging]);
1777
2025
  const debouncedOnChange = useDebouncedCallback(onChange, 50);
1778
- const handleChange = (0, import_react6.useCallback)(
2026
+ const handleChange = (0, import_react8.useCallback)(
1779
2027
  (e) => {
1780
2028
  const newValue = parseFloat(e.target.value);
1781
2029
  setLocalValue(newValue);
@@ -1783,19 +2031,19 @@ var VolumeSlider = ({
1783
2031
  },
1784
2032
  [debouncedOnChange]
1785
2033
  );
1786
- const handleMouseDown = (0, import_react6.useCallback)(() => {
2034
+ const handleMouseDown = (0, import_react8.useCallback)(() => {
1787
2035
  setIsDragging(true);
1788
2036
  }, []);
1789
- const handleMouseUp = (0, import_react6.useCallback)(() => {
2037
+ const handleMouseUp = (0, import_react8.useCallback)(() => {
1790
2038
  setIsDragging(false);
1791
2039
  onChange(localValue);
1792
2040
  }, [localValue, onChange]);
1793
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2041
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1794
2042
  "div",
1795
2043
  {
1796
2044
  className: `flex items-center ${className}`,
1797
2045
  title: `Volume: ${formatDb(localValue)}`,
1798
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2046
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1799
2047
  "input",
1800
2048
  {
1801
2049
  type: "range",
@@ -1835,8 +2083,8 @@ var VolumeSlider = ({
1835
2083
  };
1836
2084
 
1837
2085
  // src/components/PanSlider.tsx
1838
- var import_react7 = require("react");
1839
- var import_jsx_runtime9 = require("react/jsx-runtime");
2086
+ var import_react9 = require("react");
2087
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1840
2088
  function toPanDisplay(value) {
1841
2089
  if (Math.abs(value) < 0.02) {
1842
2090
  return "Center";
@@ -1845,12 +2093,12 @@ function toPanDisplay(value) {
1845
2093
  return value < 0 ? `L${percent}` : `R${percent}`;
1846
2094
  }
1847
2095
  function useDebouncedCallback2(callback, delay) {
1848
- const timeoutRef = (0, import_react7.useRef)(null);
1849
- const callbackRef = (0, import_react7.useRef)(callback);
1850
- (0, import_react7.useEffect)(() => {
2096
+ const timeoutRef = (0, import_react9.useRef)(null);
2097
+ const callbackRef = (0, import_react9.useRef)(callback);
2098
+ (0, import_react9.useEffect)(() => {
1851
2099
  callbackRef.current = callback;
1852
2100
  }, [callback]);
1853
- const debouncedCallback = (0, import_react7.useCallback)(
2101
+ const debouncedCallback = (0, import_react9.useCallback)(
1854
2102
  (...args) => {
1855
2103
  if (timeoutRef.current) {
1856
2104
  clearTimeout(timeoutRef.current);
@@ -1861,7 +2109,7 @@ function useDebouncedCallback2(callback, delay) {
1861
2109
  },
1862
2110
  [delay]
1863
2111
  );
1864
- (0, import_react7.useEffect)(() => {
2112
+ (0, import_react9.useEffect)(() => {
1865
2113
  return () => {
1866
2114
  if (timeoutRef.current) {
1867
2115
  clearTimeout(timeoutRef.current);
@@ -1876,15 +2124,15 @@ var PanSlider = ({
1876
2124
  disabled = false,
1877
2125
  className = ""
1878
2126
  }) => {
1879
- const [localValue, setLocalValue] = (0, import_react7.useState)(value);
1880
- const [isDragging, setIsDragging] = (0, import_react7.useState)(false);
1881
- (0, import_react7.useEffect)(() => {
2127
+ const [localValue, setLocalValue] = (0, import_react9.useState)(value);
2128
+ const [isDragging, setIsDragging] = (0, import_react9.useState)(false);
2129
+ (0, import_react9.useEffect)(() => {
1882
2130
  if (!isDragging) {
1883
2131
  setLocalValue(value);
1884
2132
  }
1885
2133
  }, [value, isDragging]);
1886
2134
  const debouncedOnChange = useDebouncedCallback2(onChange, 50);
1887
- const handleChange = (0, import_react7.useCallback)(
2135
+ const handleChange = (0, import_react9.useCallback)(
1888
2136
  (e) => {
1889
2137
  const newValue = parseFloat(e.target.value);
1890
2138
  setLocalValue(newValue);
@@ -1892,23 +2140,23 @@ var PanSlider = ({
1892
2140
  },
1893
2141
  [debouncedOnChange]
1894
2142
  );
1895
- const handleMouseDown = (0, import_react7.useCallback)(() => {
2143
+ const handleMouseDown = (0, import_react9.useCallback)(() => {
1896
2144
  setIsDragging(true);
1897
2145
  }, []);
1898
- const handleMouseUp = (0, import_react7.useCallback)(() => {
2146
+ const handleMouseUp = (0, import_react9.useCallback)(() => {
1899
2147
  setIsDragging(false);
1900
2148
  onChange(localValue);
1901
2149
  }, [localValue, onChange]);
1902
- const handleDoubleClick = (0, import_react7.useCallback)(() => {
2150
+ const handleDoubleClick = (0, import_react9.useCallback)(() => {
1903
2151
  setLocalValue(0);
1904
2152
  onChange(0);
1905
2153
  }, [onChange]);
1906
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2154
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1907
2155
  "div",
1908
2156
  {
1909
2157
  className: `flex items-center ${className}`,
1910
2158
  title: `Pan: ${toPanDisplay(localValue)}`,
1911
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2159
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1912
2160
  "input",
1913
2161
  {
1914
2162
  type: "range",
@@ -1949,8 +2197,8 @@ var PanSlider = ({
1949
2197
  };
1950
2198
 
1951
2199
  // src/components/SorceryProgressBar.tsx
1952
- var import_react8 = require("react");
1953
- var import_jsx_runtime10 = require("react/jsx-runtime");
2200
+ var import_react10 = require("react");
2201
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1954
2202
  function calculateTimeBasedTarget(elapsedMs, estimatedDurationMs) {
1955
2203
  const t = elapsedMs / estimatedDurationMs;
1956
2204
  if (t <= 0) return 0;
@@ -1995,20 +2243,20 @@ function SorceryProgressBar({
1995
2243
  onProgressChange,
1996
2244
  estimatedDurationMs
1997
2245
  }) {
1998
- const [progress, setProgress] = (0, import_react8.useState)(initialProgress);
1999
- const timerRef = (0, import_react8.useRef)(null);
2000
- const isLoadingRef = (0, import_react8.useRef)(false);
2001
- const hasStartedRef = (0, import_react8.useRef)(false);
2002
- const startTimeRef = (0, import_react8.useRef)(0);
2003
- const onProgressChangeRef = (0, import_react8.useRef)(onProgressChange);
2004
- const onCompleteRef = (0, import_react8.useRef)(onComplete);
2246
+ const [progress, setProgress] = (0, import_react10.useState)(initialProgress);
2247
+ const timerRef = (0, import_react10.useRef)(null);
2248
+ const isLoadingRef = (0, import_react10.useRef)(false);
2249
+ const hasStartedRef = (0, import_react10.useRef)(false);
2250
+ const startTimeRef = (0, import_react10.useRef)(0);
2251
+ const onProgressChangeRef = (0, import_react10.useRef)(onProgressChange);
2252
+ const onCompleteRef = (0, import_react10.useRef)(onComplete);
2005
2253
  onProgressChangeRef.current = onProgressChange;
2006
2254
  onCompleteRef.current = onComplete;
2007
- const initialProgressRef = (0, import_react8.useRef)(initialProgress);
2255
+ const initialProgressRef = (0, import_react10.useRef)(initialProgress);
2008
2256
  initialProgressRef.current = initialProgress;
2009
- const estimatedDurationMsRef = (0, import_react8.useRef)(estimatedDurationMs);
2257
+ const estimatedDurationMsRef = (0, import_react10.useRef)(estimatedDurationMs);
2010
2258
  estimatedDurationMsRef.current = estimatedDurationMs;
2011
- (0, import_react8.useEffect)(() => {
2259
+ (0, import_react10.useEffect)(() => {
2012
2260
  const wasLoading = isLoadingRef.current;
2013
2261
  isLoadingRef.current = isLoading;
2014
2262
  if (isLoading && !wasLoading) {
@@ -2070,12 +2318,12 @@ function SorceryProgressBar({
2070
2318
  const displayProgress = Math.floor(progress);
2071
2319
  const isComplete = !isLoading && progress === 100;
2072
2320
  const transitionDuration = progress < 50 ? "300ms" : progress < 80 ? "500ms" : "700ms";
2073
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2321
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2074
2322
  "div",
2075
2323
  {
2076
2324
  className: `relative w-full ${heightClass} bg-sas-panel-alt border border-sas-border rounded-sm overflow-hidden shadow-inner`,
2077
2325
  children: [
2078
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2326
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2079
2327
  "div",
2080
2328
  {
2081
2329
  className: `
@@ -2093,13 +2341,13 @@ function SorceryProgressBar({
2093
2341
  }
2094
2342
  }
2095
2343
  ),
2096
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "absolute inset-0 flex items-center justify-center", children: isLoading && progress < 100 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-mono text-xs text-sas-accent font-bold drop-shadow-md tracking-wider", children: [
2344
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "absolute inset-0 flex items-center justify-center", children: isLoading && progress < 100 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "font-mono text-xs text-sas-accent font-bold drop-shadow-md tracking-wider", children: [
2097
2345
  statusText,
2098
2346
  " ",
2099
2347
  displayProgress,
2100
2348
  "%"
2101
- ] }) : isComplete ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono text-xs text-sas-text font-bold drop-shadow-md tracking-wider", children: completeText }) : null }),
2102
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2349
+ ] }) : isComplete ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-mono text-xs text-sas-text font-bold drop-shadow-md tracking-wider", children: completeText }) : null }),
2350
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2103
2351
  "div",
2104
2352
  {
2105
2353
  className: "absolute inset-0 pointer-events-none opacity-10",
@@ -2120,7 +2368,7 @@ function SorceryProgressBar({
2120
2368
  }
2121
2369
 
2122
2370
  // src/components/TrackRow.tsx
2123
- var import_jsx_runtime11 = require("react/jsx-runtime");
2371
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2124
2372
  function TrackRow({
2125
2373
  track,
2126
2374
  prompt,
@@ -2149,6 +2397,7 @@ function TrackRow({
2149
2397
  onFxToggle,
2150
2398
  onFxPresetChange,
2151
2399
  onFxDryWetChange,
2400
+ externalFxHost,
2152
2401
  onToggleFxDrawer,
2153
2402
  onProgressChange,
2154
2403
  accentColor = "#A78BFA",
@@ -2179,7 +2428,7 @@ function TrackRow({
2179
2428
  levels
2180
2429
  }) {
2181
2430
  const { muted: isMuted, solo: isSoloed, volume: currentVolume, pan: currentPan } = runtimeState;
2182
- const [confirmDelete, setConfirmDelete] = import_react9.default.useState(false);
2431
+ const [confirmDelete, setConfirmDelete] = import_react11.default.useState(false);
2183
2432
  const needsGeneration = !!(prompt?.trim() && !hasMidi && !isGenerating);
2184
2433
  const hasFxActive = Object.values(fxDetailState).some(
2185
2434
  (d) => d.enabled
@@ -2194,8 +2443,8 @@ function TrackRow({
2194
2443
  };
2195
2444
  const borderColorStyle = needsGeneration ? void 0 : accentColor;
2196
2445
  const borderClass = needsGeneration ? "border-amber-400 animate-pulse" : "border-sas-border";
2197
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-testid": "sdk-track-row-wrapper", className: "w-full", ...drag?.rowProps ?? {}, children: [
2198
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2446
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-testid": "sdk-track-row-wrapper", className: "w-full", ...drag?.rowProps ?? {}, children: [
2447
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2199
2448
  "div",
2200
2449
  {
2201
2450
  "data-testid": "sdk-track-row",
@@ -2205,7 +2454,7 @@ function TrackRow({
2205
2454
  borderLeftWidth: "3px"
2206
2455
  },
2207
2456
  children: [
2208
- drag && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2457
+ drag && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2209
2458
  "div",
2210
2459
  {
2211
2460
  "data-testid": "sdk-drag-handle",
@@ -2213,10 +2462,10 @@ function TrackRow({
2213
2462
  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",
2214
2463
  title: "Drag to reorder",
2215
2464
  "aria-label": "Drag to reorder track",
2216
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react.GripVertical, { className: "w-3.5 h-3.5", strokeWidth: 2 })
2465
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react.GripVertical, { className: "w-3.5 h-3.5", strokeWidth: 2 })
2217
2466
  }
2218
2467
  ),
2219
- isGenerating && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "absolute left-0 top-0 bottom-0 right-44 z-20", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2468
+ isGenerating && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "absolute left-0 top-0 bottom-0 right-44 z-20", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2220
2469
  SorceryProgressBar,
2221
2470
  {
2222
2471
  isLoading: true,
@@ -2227,14 +2476,14 @@ function TrackRow({
2227
2476
  estimatedDurationMs: estimatedGenerationMs
2228
2477
  }
2229
2478
  ) }),
2230
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2479
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2231
2480
  "div",
2232
2481
  {
2233
2482
  "data-testid": "sdk-track-content",
2234
2483
  className: `flex flex-col flex-1 min-w-0 relative z-10 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
2235
2484
  title: soloedOut ? "Silenced \u2014 another track is soloed" : void 0,
2236
2485
  children: [
2237
- contentSlot ? contentSlot : onPromptChange ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2486
+ contentSlot ? contentSlot : onPromptChange ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2238
2487
  "input",
2239
2488
  {
2240
2489
  type: "text",
@@ -2247,10 +2496,10 @@ function TrackRow({
2247
2496
  className: "sas-input w-full px-2 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed"
2248
2497
  }
2249
2498
  ) : null,
2250
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-2 mt-1", children: [
2251
- track.name && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-[10px] text-sas-muted/60 truncate pl-2 flex-shrink-0 max-w-[80px]", title: track.name, children: track.name }),
2252
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "vol:" }),
2253
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2499
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2 mt-1", children: [
2500
+ track.name && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-sas-muted/60 truncate pl-2 flex-shrink-0 max-w-[80px]", title: track.name, children: track.name }),
2501
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "vol:" }),
2502
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2254
2503
  VolumeSlider,
2255
2504
  {
2256
2505
  value: currentVolume,
@@ -2259,8 +2508,8 @@ function TrackRow({
2259
2508
  className: "flex-1 min-w-[40px]"
2260
2509
  }
2261
2510
  ),
2262
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "pan:" }),
2263
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2511
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "pan:" }),
2512
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2264
2513
  PanSlider,
2265
2514
  {
2266
2515
  value: currentPan,
@@ -2273,27 +2522,27 @@ function TrackRow({
2273
2522
  ]
2274
2523
  }
2275
2524
  ),
2276
- error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2525
+ error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2277
2526
  "div",
2278
2527
  {
2279
2528
  "data-testid": "sdk-error-indicator",
2280
2529
  className: "flex-shrink-0 relative z-10 self-stretch flex items-center px-1 group cursor-help",
2281
2530
  title: error,
2282
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative", children: [
2283
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2531
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "relative", children: [
2532
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2284
2533
  import_lucide_react.AlertCircle,
2285
2534
  {
2286
2535
  className: "w-5 h-5 text-red-500 animate-pulse",
2287
2536
  strokeWidth: 2.5
2288
2537
  }
2289
2538
  ),
2290
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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 })
2539
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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 })
2291
2540
  ] })
2292
2541
  }
2293
2542
  ),
2294
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-0.5 flex-shrink-0 relative z-30 justify-center", children: [
2295
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-1 items-center", children: [
2296
- onGenerate && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2543
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-0.5 flex-shrink-0 relative z-30 justify-center", children: [
2544
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-1 items-center", children: [
2545
+ onGenerate && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2297
2546
  "button",
2298
2547
  {
2299
2548
  "data-testid": "sdk-generate-button",
@@ -2304,7 +2553,7 @@ function TrackRow({
2304
2553
  children: "Create"
2305
2554
  }
2306
2555
  ),
2307
- onCopy && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2556
+ onCopy && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2308
2557
  "button",
2309
2558
  {
2310
2559
  "data-testid": "sdk-copy-button",
@@ -2315,7 +2564,7 @@ function TrackRow({
2315
2564
  children: "Copy"
2316
2565
  }
2317
2566
  ),
2318
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2567
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2319
2568
  "button",
2320
2569
  {
2321
2570
  "data-testid": "sdk-mute-button",
@@ -2325,7 +2574,7 @@ function TrackRow({
2325
2574
  children: "M"
2326
2575
  }
2327
2576
  ),
2328
- onDelete && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2577
+ onDelete && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2329
2578
  "button",
2330
2579
  {
2331
2580
  "data-testid": "sdk-delete-button",
@@ -2336,8 +2585,8 @@ function TrackRow({
2336
2585
  }
2337
2586
  )
2338
2587
  ] }),
2339
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-1 items-center", children: [
2340
- onShuffle && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2588
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-1 items-center", children: [
2589
+ onShuffle && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2341
2590
  "button",
2342
2591
  {
2343
2592
  "data-testid": "sdk-shuffle-button",
@@ -2348,7 +2597,7 @@ function TrackRow({
2348
2597
  children: "Shuffle"
2349
2598
  }
2350
2599
  ),
2351
- onToggleFxDrawer && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2600
+ onToggleFxDrawer && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2352
2601
  "button",
2353
2602
  {
2354
2603
  "data-testid": "sdk-fx-button",
@@ -2359,7 +2608,7 @@ function TrackRow({
2359
2608
  children: "FX"
2360
2609
  }
2361
2610
  ),
2362
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2611
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2363
2612
  "button",
2364
2613
  {
2365
2614
  "data-testid": "sdk-solo-button",
@@ -2370,7 +2619,7 @@ function TrackRow({
2370
2619
  children: "S"
2371
2620
  }
2372
2621
  ),
2373
- onToggleDrawer && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2622
+ onToggleDrawer && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2374
2623
  "button",
2375
2624
  {
2376
2625
  "data-testid": "sdk-plugin-button",
@@ -2378,7 +2627,7 @@ function TrackRow({
2378
2627
  disabled: isGenerating,
2379
2628
  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"}`,
2380
2629
  title: `Sound \u2014 presets & history${instrumentMissing ? " (instrument missing)" : ""}`,
2381
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react.ChevronDown, { className: "w-3 h-3", strokeWidth: 2.5 })
2630
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react.ChevronDown, { className: "w-3 h-3", strokeWidth: 2.5 })
2382
2631
  }
2383
2632
  )
2384
2633
  ] })
@@ -2386,13 +2635,13 @@ function TrackRow({
2386
2635
  ]
2387
2636
  }
2388
2637
  ),
2389
- levels && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TrackMeterStrip, { levels, trackId: track.id, roundBottom: !drawerOpen }),
2390
- drawerOpen && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2638
+ levels && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TrackMeterStrip, { levels, trackId: track.id, roundBottom: !drawerOpen }),
2639
+ drawerOpen && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2391
2640
  "div",
2392
2641
  {
2393
2642
  "data-testid": "sdk-track-drawer",
2394
2643
  className: "border border-t-0 border-sas-border bg-sas-bg rounded-b-sm px-3 py-2 max-h-[260px] overflow-y-auto",
2395
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2644
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2396
2645
  TrackDrawer,
2397
2646
  {
2398
2647
  activeTab: drawerTab,
@@ -2402,6 +2651,7 @@ function TrackRow({
2402
2651
  onFxToggle,
2403
2652
  onFxPresetChange,
2404
2653
  onFxDryWetChange,
2654
+ externalFxHost,
2405
2655
  fxDisabled: isGenerating,
2406
2656
  instruments: availableInstruments,
2407
2657
  currentPluginId: currentInstrumentPluginId ?? null,
@@ -2428,13 +2678,13 @@ function TrackRow({
2428
2678
  )
2429
2679
  }
2430
2680
  ),
2431
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2681
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2432
2682
  ConfirmDialog,
2433
2683
  {
2434
2684
  open: confirmDelete,
2435
2685
  title: "Delete track?",
2436
- message: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2437
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-sas-text", children: track.name?.trim() || "This track" }),
2686
+ message: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2687
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-sas-text", children: track.name?.trim() || "This track" }),
2438
2688
  " will be permanently removed from this scene. This cannot be undone."
2439
2689
  ] }),
2440
2690
  confirmLabel: "Delete",
@@ -2450,13 +2700,13 @@ function TrackRow({
2450
2700
  }
2451
2701
 
2452
2702
  // src/components/CrossfadeTrackRow.tsx
2453
- var import_react10 = __toESM(require("react"));
2454
- var import_jsx_runtime12 = require("react/jsx-runtime");
2703
+ var import_react12 = __toESM(require("react"));
2704
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2455
2705
  function LayerCaption({ tag, layer }) {
2456
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2457
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2458
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2459
- layer.soundLabel && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
2706
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2707
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2708
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2709
+ layer.soundLabel && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
2460
2710
  "\xB7 ",
2461
2711
  layer.soundLabel
2462
2712
  ] })
@@ -2475,8 +2725,8 @@ function CrossfadeTrackRow({
2475
2725
  levels,
2476
2726
  accentColor = "#9333EA"
2477
2727
  }) {
2478
- const [confirmDelete, setConfirmDelete] = import_react10.default.useState(false);
2479
- const renderLayer = (layer, slot, tag) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2728
+ const [confirmDelete, setConfirmDelete] = import_react12.default.useState(false);
2729
+ const renderLayer = (layer, slot, tag) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2480
2730
  TrackRow,
2481
2731
  {
2482
2732
  track: { id: layer.trackId, name: "", role: layer.role },
@@ -2486,23 +2736,23 @@ function CrossfadeTrackRow({
2486
2736
  drawerTab: "fx",
2487
2737
  levels,
2488
2738
  accentColor,
2489
- contentSlot: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LayerCaption, { tag, layer }),
2739
+ contentSlot: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(LayerCaption, { tag, layer }),
2490
2740
  onMuteToggle,
2491
2741
  onSoloToggle,
2492
2742
  onVolumeChange: (v) => onVolumeChange(slot, v),
2493
2743
  onPanChange: (p) => onPanChange(slot, p)
2494
2744
  }
2495
2745
  );
2496
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2746
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2497
2747
  "div",
2498
2748
  {
2499
2749
  "data-testid": "crossfade-track-row",
2500
2750
  className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
2501
2751
  style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
2502
2752
  children: [
2503
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2504
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] font-bold uppercase tracking-wide", style: { color: accentColor }, children: "\u21C4 Crossfade" }),
2505
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2753
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2754
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[10px] font-bold uppercase tracking-wide", style: { color: accentColor }, children: "\u21C4 Crossfade" }),
2755
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2506
2756
  "button",
2507
2757
  {
2508
2758
  "data-testid": "crossfade-delete-button",
@@ -2515,8 +2765,8 @@ function CrossfadeTrackRow({
2515
2765
  )
2516
2766
  ] }),
2517
2767
  renderLayer(origin, "origin", "Origin"),
2518
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "crossfade-slider-row", children: [
2519
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2768
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "crossfade-slider-row", children: [
2769
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2520
2770
  "span",
2521
2771
  {
2522
2772
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
@@ -2524,7 +2774,7 @@ function CrossfadeTrackRow({
2524
2774
  children: origin.sourceName ?? origin.name
2525
2775
  }
2526
2776
  ),
2527
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2777
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2528
2778
  "input",
2529
2779
  {
2530
2780
  type: "range",
@@ -2540,7 +2790,7 @@ function CrossfadeTrackRow({
2540
2790
  "aria-label": "Crossfade position"
2541
2791
  }
2542
2792
  ),
2543
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2793
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2544
2794
  "span",
2545
2795
  {
2546
2796
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
@@ -2550,12 +2800,12 @@ function CrossfadeTrackRow({
2550
2800
  )
2551
2801
  ] }),
2552
2802
  renderLayer(target, "target", "Target"),
2553
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2803
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2554
2804
  ConfirmDialog,
2555
2805
  {
2556
2806
  open: confirmDelete,
2557
2807
  title: "Delete crossfade?",
2558
- message: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_jsx_runtime12.Fragment, { children: "This crossfade pair (both layers) will be permanently removed from this scene. This cannot be undone." }),
2808
+ message: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: "This crossfade pair (both layers) will be permanently removed from this scene. This cannot be undone." }),
2559
2809
  confirmLabel: "Delete",
2560
2810
  onConfirm: () => {
2561
2811
  setConfirmDelete(false);
@@ -2798,22 +3048,22 @@ function defaultFadeGesture(role) {
2798
3048
  }
2799
3049
 
2800
3050
  // src/components/FadeTrackRow.tsx
2801
- var import_react11 = __toESM(require("react"));
2802
- var import_jsx_runtime13 = require("react/jsx-runtime");
3051
+ var import_react13 = __toESM(require("react"));
3052
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2803
3053
  function FadeCaption({
2804
3054
  layer,
2805
3055
  direction,
2806
3056
  gesture
2807
3057
  }) {
2808
3058
  const tag = direction === "in" ? "Fade in" : "Fade out";
2809
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2810
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2811
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2812
- layer.soundLabel && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
3059
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
3060
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
3061
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
3062
+ layer.soundLabel && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
2813
3063
  "\xB7 ",
2814
3064
  layer.soundLabel
2815
3065
  ] }),
2816
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", title: `Fade gesture: ${gesture}`, children: [
3066
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", title: `Fade gesture: ${gesture}`, children: [
2817
3067
  "\xB7 ",
2818
3068
  gesture
2819
3069
  ] })
@@ -2834,20 +3084,20 @@ function FadeTrackRow({
2834
3084
  levels,
2835
3085
  accentColor = "#9333EA"
2836
3086
  }) {
2837
- const [confirmDelete, setConfirmDelete] = import_react11.default.useState(false);
3087
+ const [confirmDelete, setConfirmDelete] = import_react13.default.useState(false);
2838
3088
  const leftLabel = direction === "in" ? "(silent)" : layer.sourceName ?? layer.name;
2839
3089
  const rightLabel = direction === "in" ? layer.sourceName ?? layer.name : "(silent)";
2840
3090
  const verb = effect && effect !== "fade" ? effect.charAt(0).toUpperCase() + effect.slice(1) : "Fade";
2841
3091
  const badge = direction === "in" ? `\u2197 ${verb} in` : `\u2198 ${verb} out`;
2842
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3092
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2843
3093
  "div",
2844
3094
  {
2845
3095
  "data-testid": "fade-track-row",
2846
3096
  className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
2847
3097
  style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
2848
3098
  children: [
2849
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2850
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3099
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
3100
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2851
3101
  "span",
2852
3102
  {
2853
3103
  "data-testid": "fade-direction-badge",
@@ -2856,7 +3106,7 @@ function FadeTrackRow({
2856
3106
  children: badge
2857
3107
  }
2858
3108
  ),
2859
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3109
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2860
3110
  "button",
2861
3111
  {
2862
3112
  "data-testid": "fade-delete-button",
@@ -2868,7 +3118,7 @@ function FadeTrackRow({
2868
3118
  }
2869
3119
  )
2870
3120
  ] }),
2871
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3121
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2872
3122
  TrackRow,
2873
3123
  {
2874
3124
  track: { id: layer.trackId, name: "", role: layer.role },
@@ -2878,15 +3128,15 @@ function FadeTrackRow({
2878
3128
  drawerTab: "fx",
2879
3129
  levels,
2880
3130
  accentColor,
2881
- contentSlot: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FadeCaption, { layer, direction, gesture }),
3131
+ contentSlot: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(FadeCaption, { layer, direction, gesture }),
2882
3132
  onMuteToggle,
2883
3133
  onSoloToggle,
2884
3134
  onVolumeChange,
2885
3135
  onPanChange
2886
3136
  }
2887
3137
  ),
2888
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "fade-slider-row", children: [
2889
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3138
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "fade-slider-row", children: [
3139
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2890
3140
  "span",
2891
3141
  {
2892
3142
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
@@ -2894,7 +3144,7 @@ function FadeTrackRow({
2894
3144
  children: leftLabel
2895
3145
  }
2896
3146
  ),
2897
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3147
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2898
3148
  "input",
2899
3149
  {
2900
3150
  type: "range",
@@ -2910,7 +3160,7 @@ function FadeTrackRow({
2910
3160
  "aria-label": "Fade position"
2911
3161
  }
2912
3162
  ),
2913
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3163
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2914
3164
  "span",
2915
3165
  {
2916
3166
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
@@ -2919,12 +3169,12 @@ function FadeTrackRow({
2919
3169
  }
2920
3170
  )
2921
3171
  ] }),
2922
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3172
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2923
3173
  ConfirmDialog,
2924
3174
  {
2925
3175
  open: confirmDelete,
2926
3176
  title: "Delete fade?",
2927
- message: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: "This fade track will be permanently removed from this scene. This cannot be undone." }),
3177
+ message: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children: "This fade track will be permanently removed from this scene. This cannot be undone." }),
2928
3178
  confirmLabel: "Delete",
2929
3179
  onConfirm: () => {
2930
3180
  setConfirmDelete(false);
@@ -2940,8 +3190,8 @@ function FadeTrackRow({
2940
3190
  }
2941
3191
 
2942
3192
  // src/components/FadeModal.tsx
2943
- var import_react12 = require("react");
2944
- var import_jsx_runtime14 = require("react/jsx-runtime");
3193
+ var import_react14 = require("react");
3194
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2945
3195
  function shortId(dbId) {
2946
3196
  return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
2947
3197
  }
@@ -2984,7 +3234,7 @@ function OrphanRow({
2984
3234
  }) {
2985
3235
  const primary = track.prompt?.trim() || track.name;
2986
3236
  const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(" \xB7 ");
2987
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3237
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2988
3238
  "button",
2989
3239
  {
2990
3240
  type: "button",
@@ -2996,8 +3246,8 @@ function OrphanRow({
2996
3246
  disabled,
2997
3247
  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"}`,
2998
3248
  children: [
2999
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3000
- meta && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
3249
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3250
+ meta && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
3001
3251
  ]
3002
3252
  }
3003
3253
  );
@@ -3014,14 +3264,14 @@ function FadeModal({
3014
3264
  onCreate,
3015
3265
  testIdPrefix = "fade-modal"
3016
3266
  }) {
3017
- const [load, setLoad] = (0, import_react12.useState)({ status: "loading" });
3018
- const [selectedDbId, setSelectedDbId] = (0, import_react12.useState)("");
3019
- const [isCreating, setIsCreating] = (0, import_react12.useState)(false);
3020
- const [error, setError] = (0, import_react12.useState)(null);
3021
- const [fromName, setFromName] = (0, import_react12.useState)(null);
3022
- const [toName, setToName] = (0, import_react12.useState)(null);
3023
- const cancelRef = (0, import_react12.useRef)(null);
3024
- const refresh = (0, import_react12.useCallback)(async () => {
3267
+ const [load, setLoad] = (0, import_react14.useState)({ status: "loading" });
3268
+ const [selectedDbId, setSelectedDbId] = (0, import_react14.useState)("");
3269
+ const [isCreating, setIsCreating] = (0, import_react14.useState)(false);
3270
+ const [error, setError] = (0, import_react14.useState)(null);
3271
+ const [fromName, setFromName] = (0, import_react14.useState)(null);
3272
+ const [toName, setToName] = (0, import_react14.useState)(null);
3273
+ const cancelRef = (0, import_react14.useRef)(null);
3274
+ const refresh = (0, import_react14.useCallback)(async () => {
3025
3275
  if (!host.listSceneFamilyTracks) {
3026
3276
  setLoad({ status: "error", message: "This host does not support fades." });
3027
3277
  return;
@@ -3041,7 +3291,7 @@ function FadeModal({
3041
3291
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
3042
3292
  }
3043
3293
  }, [host, fromSceneId, toSceneId]);
3044
- (0, import_react12.useEffect)(() => {
3294
+ (0, import_react14.useEffect)(() => {
3045
3295
  if (open) {
3046
3296
  setError(null);
3047
3297
  setIsCreating(false);
@@ -3049,29 +3299,29 @@ function FadeModal({
3049
3299
  void refresh();
3050
3300
  }
3051
3301
  }, [open, refresh]);
3052
- const excludeSet = (0, import_react12.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3053
- const { fadeOut, fadeIn } = (0, import_react12.useMemo)(
3302
+ const excludeSet = (0, import_react14.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3303
+ const { fadeOut, fadeIn } = (0, import_react14.useMemo)(
3054
3304
  () => load.status === "ready" ? computeOrphans(load.from, load.to, excludeSet) : { fadeOut: [], fadeIn: [] },
3055
3305
  [load, excludeSet]
3056
3306
  );
3057
- const allOrphans = (0, import_react12.useMemo)(
3307
+ const allOrphans = (0, import_react14.useMemo)(
3058
3308
  () => [
3059
3309
  ...fadeOut.map((t) => ({ track: t, direction: "out" })),
3060
3310
  ...fadeIn.map((t) => ({ track: t, direction: "in" }))
3061
3311
  ],
3062
3312
  [fadeOut, fadeIn]
3063
3313
  );
3064
- (0, import_react12.useEffect)(() => {
3314
+ (0, import_react14.useEffect)(() => {
3065
3315
  if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {
3066
3316
  setSelectedDbId(allOrphans[0]?.track.dbId ?? "");
3067
3317
  }
3068
3318
  }, [allOrphans, selectedDbId]);
3069
3319
  const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;
3070
3320
  const canCreate = !isCreating && !!selected;
3071
- const handleClose = (0, import_react12.useCallback)(() => {
3321
+ const handleClose = (0, import_react14.useCallback)(() => {
3072
3322
  if (!isCreating) onClose();
3073
3323
  }, [isCreating, onClose]);
3074
- const handleCreate = (0, import_react12.useCallback)(async () => {
3324
+ const handleCreate = (0, import_react14.useCallback)(async () => {
3075
3325
  if (!selected) return;
3076
3326
  setIsCreating(true);
3077
3327
  setError(null);
@@ -3092,16 +3342,16 @@ function FadeModal({
3092
3342
  if (!open) return null;
3093
3343
  const renderSection = (heading, list, section) => {
3094
3344
  if (list.length === 0) return null;
3095
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "block", children: [
3096
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
3097
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3345
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "block", children: [
3346
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
3347
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3098
3348
  "div",
3099
3349
  {
3100
3350
  role: "radiogroup",
3101
3351
  "aria-label": heading,
3102
3352
  "data-testid": `${testIdPrefix}-${section === "out" ? "fade-out" : "fade-in"}-list`,
3103
3353
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
3104
- children: list.map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3354
+ children: list.map((t) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3105
3355
  OrphanRow,
3106
3356
  {
3107
3357
  track: t,
@@ -3117,32 +3367,32 @@ function FadeModal({
3117
3367
  )
3118
3368
  ] });
3119
3369
  };
3120
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3370
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3121
3371
  "div",
3122
3372
  {
3123
3373
  className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
3124
3374
  onClick: (e) => e.stopPropagation(),
3125
3375
  "data-testid": `${testIdPrefix}-box`,
3126
3376
  children: [
3127
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
3128
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
3377
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
3378
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
3129
3379
  "Tracks with no counterpart between",
3130
3380
  " ",
3131
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
3381
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
3132
3382
  " and",
3133
3383
  " ",
3134
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
3384
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
3135
3385
  " can gracefully fade out (leaving) or fade in (entering) across this transition."
3136
3386
  ] }),
3137
- load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3138
- load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3139
- load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("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__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
3387
+ load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3388
+ load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3389
+ load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
3140
3390
  renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ""}`, fadeOut, "out"),
3141
3391
  renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ""}`, fadeIn, "in")
3142
3392
  ] })),
3143
- error && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3144
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
3145
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3393
+ error && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3394
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
3395
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3146
3396
  "button",
3147
3397
  {
3148
3398
  ref: cancelRef,
@@ -3153,7 +3403,7 @@ function FadeModal({
3153
3403
  children: "Cancel"
3154
3404
  }
3155
3405
  ),
3156
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3406
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3157
3407
  "button",
3158
3408
  {
3159
3409
  "data-testid": `${testIdPrefix}-confirm`,
@@ -3170,8 +3420,8 @@ function FadeModal({
3170
3420
  }
3171
3421
 
3172
3422
  // src/components/ImportTrackModal.tsx
3173
- var import_react13 = require("react");
3174
- var import_jsx_runtime15 = require("react/jsx-runtime");
3423
+ var import_react15 = require("react");
3424
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3175
3425
  function ImportTrackModal({
3176
3426
  host,
3177
3427
  open,
@@ -3183,10 +3433,10 @@ function ImportTrackModal({
3183
3433
  onPick,
3184
3434
  onPortTrack
3185
3435
  }) {
3186
- const [load, setLoad] = (0, import_react13.useState)({ status: "loading" });
3187
- const [selectedSceneId, setSelectedSceneId] = (0, import_react13.useState)(null);
3188
- const [importingTrackId, setImportingTrackId] = (0, import_react13.useState)(null);
3189
- const refresh = (0, import_react13.useCallback)(async () => {
3436
+ const [load, setLoad] = (0, import_react15.useState)({ status: "loading" });
3437
+ const [selectedSceneId, setSelectedSceneId] = (0, import_react15.useState)(null);
3438
+ const [importingTrackId, setImportingTrackId] = (0, import_react15.useState)(null);
3439
+ const refresh = (0, import_react15.useCallback)(async () => {
3190
3440
  if (!host.listImportableTracks) {
3191
3441
  setLoad({ status: "error", message: "This host does not support importing tracks." });
3192
3442
  return;
@@ -3202,14 +3452,14 @@ function ImportTrackModal({
3202
3452
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load scenes." });
3203
3453
  }
3204
3454
  }, [host, mode, onPortTrack]);
3205
- (0, import_react13.useEffect)(() => {
3455
+ (0, import_react15.useEffect)(() => {
3206
3456
  if (open) {
3207
3457
  setSelectedSceneId(null);
3208
3458
  setImportingTrackId(null);
3209
3459
  void refresh();
3210
3460
  }
3211
3461
  }, [open, refresh]);
3212
- const handleImport = (0, import_react13.useCallback)(
3462
+ const handleImport = (0, import_react15.useCallback)(
3213
3463
  async (track, sourceSceneId, sceneName, isSameScene) => {
3214
3464
  if (isSameScene && onPortTrack) {
3215
3465
  if (!track.importable) return;
@@ -3250,16 +3500,16 @@ function ImportTrackModal({
3250
3500
  if (!open) return null;
3251
3501
  const scenes = load.status === "ready" ? load.scenes : [];
3252
3502
  const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;
3253
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Modal, { open, onClose, testIdPrefix, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3503
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Modal, { open, onClose, testIdPrefix, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3254
3504
  "div",
3255
3505
  {
3256
3506
  className: "w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
3257
3507
  onClick: (e) => e.stopPropagation(),
3258
3508
  "data-testid": `${testIdPrefix}-modal`,
3259
3509
  children: [
3260
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
3261
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", children: [
3262
- selectedScene && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3510
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
3511
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
3512
+ selectedScene && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3263
3513
  "button",
3264
3514
  {
3265
3515
  className: "text-sas-muted hover:text-sas-accent text-xs",
@@ -3268,9 +3518,9 @@ function ImportTrackModal({
3268
3518
  children: "\u2190"
3269
3519
  }
3270
3520
  ),
3271
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sm font-medium text-sas-text", children: selectedScene ? selectedScene.sceneName : title })
3521
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sm font-medium text-sas-text", children: selectedScene ? selectedScene.sceneName : title })
3272
3522
  ] }),
3273
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3523
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3274
3524
  "button",
3275
3525
  {
3276
3526
  className: "text-sas-muted hover:text-sas-accent text-sm",
@@ -3280,30 +3530,30 @@ function ImportTrackModal({
3280
3530
  }
3281
3531
  )
3282
3532
  ] }),
3283
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "overflow-y-auto p-2 flex-1", children: [
3284
- load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-loading`, children: "Loading scenes\u2026" }),
3285
- load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "py-8 text-center text-xs text-red-400", "data-testid": `${testIdPrefix}-error`, children: load.message }),
3286
- load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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." }),
3287
- load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-scene-list`, children: scenes.map((scene) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3533
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "overflow-y-auto p-2 flex-1", children: [
3534
+ load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-loading`, children: "Loading scenes\u2026" }),
3535
+ load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "py-8 text-center text-xs text-red-400", "data-testid": `${testIdPrefix}-error`, children: load.message }),
3536
+ load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("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." }),
3537
+ load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-scene-list`, children: scenes.map((scene) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3288
3538
  "button",
3289
3539
  {
3290
3540
  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",
3291
3541
  onClick: () => setSelectedSceneId(scene.sceneId),
3292
3542
  "data-testid": `${testIdPrefix}-scene`,
3293
3543
  children: [
3294
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate", children: scene.sceneName }),
3295
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-sas-muted", children: [
3544
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate", children: scene.sceneName }),
3545
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-sas-muted", children: [
3296
3546
  scene.tracks.length,
3297
3547
  " \u2192"
3298
3548
  ] })
3299
3549
  ]
3300
3550
  }
3301
3551
  ) }, scene.sceneId)) }),
3302
- selectedScene && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-track-list`, children: selectedScene.tracks.map((track) => {
3552
+ selectedScene && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-track-list`, children: selectedScene.tracks.map((track) => {
3303
3553
  const busy = importingTrackId === track.trackId;
3304
3554
  const gated = mode === "track" && !track.importable;
3305
3555
  const disabled = gated || busy;
3306
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3556
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3307
3557
  "button",
3308
3558
  {
3309
3559
  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"}`,
@@ -3313,14 +3563,14 @@ function ImportTrackModal({
3313
3563
  "data-testid": `${testIdPrefix}-track`,
3314
3564
  "data-importable": mode === "sound" || track.importable ? "true" : "false",
3315
3565
  children: [
3316
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "truncate", children: [
3566
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "truncate", children: [
3317
3567
  track.name,
3318
- track.role ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-sas-muted", children: [
3568
+ track.role ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-sas-muted", children: [
3319
3569
  " \xB7 ",
3320
3570
  track.role
3321
3571
  ] }) : null
3322
3572
  ] }),
3323
- busy ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sas-muted", children: "\u2026" }) : gated ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sas-muted", children: "\u2298" }) : null
3573
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-muted", children: "\u2026" }) : gated ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-muted", children: "\u2298" }) : null
3324
3574
  ]
3325
3575
  }
3326
3576
  ) }, track.dbId);
@@ -3332,8 +3582,8 @@ function ImportTrackModal({
3332
3582
  }
3333
3583
 
3334
3584
  // src/components/CrossfadeModal.tsx
3335
- var import_react14 = require("react");
3336
- var import_jsx_runtime16 = require("react/jsx-runtime");
3585
+ var import_react16 = require("react");
3586
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3337
3587
  function shortId2(dbId) {
3338
3588
  return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
3339
3589
  }
@@ -3346,7 +3596,7 @@ function CandidateRow({
3346
3596
  }) {
3347
3597
  const primary = track.prompt?.trim() || track.name;
3348
3598
  const meta = [track.role, shortId2(track.dbId)].filter(Boolean).join(" \xB7 ");
3349
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3599
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3350
3600
  "button",
3351
3601
  {
3352
3602
  type: "button",
@@ -3358,8 +3608,8 @@ function CandidateRow({
3358
3608
  disabled,
3359
3609
  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"}`,
3360
3610
  children: [
3361
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3362
- meta && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
3611
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3612
+ meta && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
3363
3613
  ]
3364
3614
  }
3365
3615
  );
@@ -3376,15 +3626,15 @@ function CrossfadeModal({
3376
3626
  onCreate,
3377
3627
  testIdPrefix = "crossfade-modal"
3378
3628
  }) {
3379
- const [load, setLoad] = (0, import_react14.useState)({ status: "loading" });
3380
- const [originDbId, setOriginDbId] = (0, import_react14.useState)("");
3381
- const [targetDbId, setTargetDbId] = (0, import_react14.useState)("");
3382
- const [isCreating, setIsCreating] = (0, import_react14.useState)(false);
3383
- const [error, setError] = (0, import_react14.useState)(null);
3384
- const [fromName, setFromName] = (0, import_react14.useState)(null);
3385
- const [toName, setToName] = (0, import_react14.useState)(null);
3386
- const cancelRef = (0, import_react14.useRef)(null);
3387
- const refresh = (0, import_react14.useCallback)(async () => {
3629
+ const [load, setLoad] = (0, import_react16.useState)({ status: "loading" });
3630
+ const [originDbId, setOriginDbId] = (0, import_react16.useState)("");
3631
+ const [targetDbId, setTargetDbId] = (0, import_react16.useState)("");
3632
+ const [isCreating, setIsCreating] = (0, import_react16.useState)(false);
3633
+ const [error, setError] = (0, import_react16.useState)(null);
3634
+ const [fromName, setFromName] = (0, import_react16.useState)(null);
3635
+ const [toName, setToName] = (0, import_react16.useState)(null);
3636
+ const cancelRef = (0, import_react16.useRef)(null);
3637
+ const refresh = (0, import_react16.useCallback)(async () => {
3388
3638
  if (!host.listSceneFamilyTracks) {
3389
3639
  setLoad({ status: "error", message: "This host does not support crossfade tracks." });
3390
3640
  return;
@@ -3404,7 +3654,7 @@ function CrossfadeModal({
3404
3654
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
3405
3655
  }
3406
3656
  }, [host, fromSceneId, toSceneId]);
3407
- (0, import_react14.useEffect)(() => {
3657
+ (0, import_react16.useEffect)(() => {
3408
3658
  if (open) {
3409
3659
  setError(null);
3410
3660
  setIsCreating(false);
@@ -3413,21 +3663,21 @@ function CrossfadeModal({
3413
3663
  void refresh();
3414
3664
  }
3415
3665
  }, [open, refresh]);
3416
- const excludeSet = (0, import_react14.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3417
- const originCandidates = (0, import_react14.useMemo)(
3666
+ const excludeSet = (0, import_react16.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3667
+ const originCandidates = (0, import_react16.useMemo)(
3418
3668
  () => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
3419
3669
  [load, excludeSet]
3420
3670
  );
3421
- const targetCandidates = (0, import_react14.useMemo)(
3671
+ const targetCandidates = (0, import_react16.useMemo)(
3422
3672
  () => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
3423
3673
  [load, excludeSet]
3424
3674
  );
3425
- (0, import_react14.useEffect)(() => {
3675
+ (0, import_react16.useEffect)(() => {
3426
3676
  if (!originCandidates.some((t) => t.dbId === originDbId)) {
3427
3677
  setOriginDbId(originCandidates[0]?.dbId ?? "");
3428
3678
  }
3429
3679
  }, [originCandidates, originDbId]);
3430
- (0, import_react14.useEffect)(() => {
3680
+ (0, import_react16.useEffect)(() => {
3431
3681
  if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
3432
3682
  setTargetDbId(targetCandidates[0]?.dbId ?? "");
3433
3683
  }
@@ -3435,10 +3685,10 @@ function CrossfadeModal({
3435
3685
  const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
3436
3686
  const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
3437
3687
  const canCreate = !isCreating && !!originTrack && !!targetTrack;
3438
- const handleClose = (0, import_react14.useCallback)(() => {
3688
+ const handleClose = (0, import_react16.useCallback)(() => {
3439
3689
  if (!isCreating) onClose();
3440
3690
  }, [isCreating, onClose]);
3441
- const handleCreate = (0, import_react14.useCallback)(async () => {
3691
+ const handleCreate = (0, import_react16.useCallback)(async () => {
3442
3692
  if (!originTrack || !targetTrack) return;
3443
3693
  setIsCreating(true);
3444
3694
  setError(null);
@@ -3456,26 +3706,26 @@ function CrossfadeModal({
3456
3706
  const fromLabel = fromName ?? fromSceneName ?? null;
3457
3707
  const toLabel = toName ?? toSceneName ?? null;
3458
3708
  if (!open) return null;
3459
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3709
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3460
3710
  "div",
3461
3711
  {
3462
3712
  className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
3463
3713
  onClick: (e) => e.stopPropagation(),
3464
3714
  "data-testid": `${testIdPrefix}-box`,
3465
3715
  children: [
3466
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
3467
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
3716
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
3717
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
3468
3718
  "Bridge a track from",
3469
3719
  " ",
3470
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
3720
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
3471
3721
  " into one from",
3472
3722
  " ",
3473
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
3723
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
3474
3724
  ". Both layers share one generated part; each keeps its own preset."
3475
3725
  ] }),
3476
- load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3477
- load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3478
- load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3726
+ load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3727
+ load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3728
+ load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3479
3729
  "div",
3480
3730
  {
3481
3731
  className: "text-xs text-sas-muted py-4 text-center",
@@ -3486,20 +3736,20 @@ function CrossfadeModal({
3486
3736
  ". Add one (or free one from another crossfade) first."
3487
3737
  ]
3488
3738
  }
3489
- ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3490
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "block", children: [
3491
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
3739
+ ) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
3740
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "block", children: [
3741
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
3492
3742
  "Origin ",
3493
3743
  fromLabel ? `(${fromLabel})` : "(top)"
3494
3744
  ] }),
3495
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3745
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3496
3746
  "div",
3497
3747
  {
3498
3748
  role: "radiogroup",
3499
3749
  "aria-label": "Origin track",
3500
3750
  "data-testid": `${testIdPrefix}-origin-list`,
3501
3751
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
3502
- children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3752
+ children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3503
3753
  CandidateRow,
3504
3754
  {
3505
3755
  track: t,
@@ -3513,23 +3763,23 @@ function CrossfadeModal({
3513
3763
  }
3514
3764
  )
3515
3765
  ] }),
3516
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "block", children: [
3517
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
3766
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "block", children: [
3767
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
3518
3768
  "Target ",
3519
3769
  toLabel ? `(${toLabel})` : "(bottom)"
3520
3770
  ] }),
3521
- targetCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
3771
+ targetCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
3522
3772
  "No available tracks in ",
3523
3773
  toLabel ?? "the target scene",
3524
3774
  " to crossfade into."
3525
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3775
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3526
3776
  "div",
3527
3777
  {
3528
3778
  role: "radiogroup",
3529
3779
  "aria-label": "Target track",
3530
3780
  "data-testid": `${testIdPrefix}-target-list`,
3531
3781
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
3532
- children: targetCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3782
+ children: targetCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3533
3783
  CandidateRow,
3534
3784
  {
3535
3785
  track: t,
@@ -3544,9 +3794,9 @@ function CrossfadeModal({
3544
3794
  )
3545
3795
  ] })
3546
3796
  ] })),
3547
- error && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3548
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
3549
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3797
+ error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3798
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
3799
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3550
3800
  "button",
3551
3801
  {
3552
3802
  ref: cancelRef,
@@ -3557,7 +3807,7 @@ function CrossfadeModal({
3557
3807
  children: "Cancel"
3558
3808
  }
3559
3809
  ),
3560
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3810
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3561
3811
  "button",
3562
3812
  {
3563
3813
  "data-testid": `${testIdPrefix}-confirm`,
@@ -3574,10 +3824,10 @@ function CrossfadeModal({
3574
3824
  }
3575
3825
 
3576
3826
  // src/components/TransitionDesigner.tsx
3577
- var import_react16 = require("react");
3827
+ var import_react18 = require("react");
3578
3828
 
3579
3829
  // src/hooks/useTrackReorder.ts
3580
- var import_react15 = require("react");
3830
+ var import_react17 = require("react");
3581
3831
  function moveItem(arr, from, to) {
3582
3832
  const next = arr.slice();
3583
3833
  if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
@@ -3594,12 +3844,12 @@ function useTrackReorder({
3594
3844
  getId,
3595
3845
  onError
3596
3846
  }) {
3597
- const [draggingIndex, setDraggingIndex] = (0, import_react15.useState)(null);
3598
- const [dragOverIndex, setDragOverIndex] = (0, import_react15.useState)(null);
3599
- const fromRef = (0, import_react15.useRef)(null);
3600
- const itemsRef = (0, import_react15.useRef)(items);
3847
+ const [draggingIndex, setDraggingIndex] = (0, import_react17.useState)(null);
3848
+ const [dragOverIndex, setDragOverIndex] = (0, import_react17.useState)(null);
3849
+ const fromRef = (0, import_react17.useRef)(null);
3850
+ const itemsRef = (0, import_react17.useRef)(items);
3601
3851
  itemsRef.current = items;
3602
- const dragPropsFor = (0, import_react15.useCallback)(
3852
+ const dragPropsFor = (0, import_react17.useCallback)(
3603
3853
  (index) => ({
3604
3854
  handleProps: {
3605
3855
  draggable: true,
@@ -3781,7 +4031,7 @@ function dbIdsFromKeys(keys) {
3781
4031
  }
3782
4032
 
3783
4033
  // src/components/TransitionDesigner.tsx
3784
- var import_jsx_runtime17 = require("react/jsx-runtime");
4034
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3785
4035
  var CROSSFADE_ESTIMATE_MS = 15e3;
3786
4036
  var FADE_ESTIMATE_MS = 11e3;
3787
4037
  var CREATE_ALL_CONCURRENCY = 5;
@@ -3805,44 +4055,44 @@ function TransitionDesigner({
3805
4055
  familyLabel,
3806
4056
  testIdPrefix = "transition-designer"
3807
4057
  }) {
3808
- const [load, setLoad] = (0, import_react16.useState)({ status: "loading" });
3809
- const [fromName, setFromName] = (0, import_react16.useState)(null);
3810
- const [toName, setToName] = (0, import_react16.useState)(null);
3811
- const [originSlots, setOriginSlots] = (0, import_react16.useState)([]);
3812
- const [targetSlots, setTargetSlots] = (0, import_react16.useState)([]);
3813
- const [creatingKeys, setCreatingKeys] = (0, import_react16.useState)(() => /* @__PURE__ */ new Set());
3814
- const [rowErrors, setRowErrors] = (0, import_react16.useState)({});
3815
- const [rowEffects, setRowEffects] = (0, import_react16.useState)({});
3816
- const rowEffectsRef = (0, import_react16.useRef)(rowEffects);
4058
+ const [load, setLoad] = (0, import_react18.useState)({ status: "loading" });
4059
+ const [fromName, setFromName] = (0, import_react18.useState)(null);
4060
+ const [toName, setToName] = (0, import_react18.useState)(null);
4061
+ const [originSlots, setOriginSlots] = (0, import_react18.useState)([]);
4062
+ const [targetSlots, setTargetSlots] = (0, import_react18.useState)([]);
4063
+ const [creatingKeys, setCreatingKeys] = (0, import_react18.useState)(() => /* @__PURE__ */ new Set());
4064
+ const [rowErrors, setRowErrors] = (0, import_react18.useState)({});
4065
+ const [rowEffects, setRowEffects] = (0, import_react18.useState)({});
4066
+ const rowEffectsRef = (0, import_react18.useRef)(rowEffects);
3817
4067
  rowEffectsRef.current = rowEffects;
3818
4068
  const audioEffectsEnabled = !!onCreateAudioTransition;
3819
- const excludeRef = (0, import_react16.useRef)(excludeSourceDbIds);
4069
+ const excludeRef = (0, import_react18.useRef)(excludeSourceDbIds);
3820
4070
  excludeRef.current = excludeSourceDbIds;
3821
- const originSlotsRef = (0, import_react16.useRef)(originSlots);
4071
+ const originSlotsRef = (0, import_react18.useRef)(originSlots);
3822
4072
  originSlotsRef.current = originSlots;
3823
- const targetSlotsRef = (0, import_react16.useRef)(targetSlots);
4073
+ const targetSlotsRef = (0, import_react18.useRef)(targetSlots);
3824
4074
  targetSlotsRef.current = targetSlots;
3825
- const creatingKeysRef = (0, import_react16.useRef)(creatingKeys);
4075
+ const creatingKeysRef = (0, import_react18.useRef)(creatingKeys);
3826
4076
  creatingKeysRef.current = creatingKeys;
3827
- const dragRef = (0, import_react16.useRef)(null);
3828
- const [dragging, setDragging] = (0, import_react16.useState)(null);
3829
- const [dragOver, setDragOver] = (0, import_react16.useState)(null);
3830
- const excludeSet = (0, import_react16.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3831
- const originPool = (0, import_react16.useMemo)(
4077
+ const dragRef = (0, import_react18.useRef)(null);
4078
+ const [dragging, setDragging] = (0, import_react18.useState)(null);
4079
+ const [dragOver, setDragOver] = (0, import_react18.useState)(null);
4080
+ const excludeSet = (0, import_react18.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
4081
+ const originPool = (0, import_react18.useMemo)(
3832
4082
  () => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
3833
4083
  [load, excludeSet]
3834
4084
  );
3835
- const targetPool = (0, import_react16.useMemo)(
4085
+ const targetPool = (0, import_react18.useMemo)(
3836
4086
  () => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
3837
4087
  [load, excludeSet]
3838
4088
  );
3839
- const originById = (0, import_react16.useMemo)(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);
3840
- const targetById = (0, import_react16.useMemo)(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);
3841
- const originByIdRef = (0, import_react16.useRef)(originById);
4089
+ const originById = (0, import_react18.useMemo)(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);
4090
+ const targetById = (0, import_react18.useMemo)(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);
4091
+ const originByIdRef = (0, import_react18.useRef)(originById);
3842
4092
  originByIdRef.current = originById;
3843
- const targetByIdRef = (0, import_react16.useRef)(targetById);
4093
+ const targetByIdRef = (0, import_react18.useRef)(targetById);
3844
4094
  targetByIdRef.current = targetById;
3845
- const refresh = (0, import_react16.useCallback)(async () => {
4095
+ const refresh = (0, import_react18.useCallback)(async () => {
3846
4096
  if (!host.listSceneFamilyTracks) {
3847
4097
  setLoad({ status: "error", message: "This host does not support transition tracks." });
3848
4098
  return;
@@ -3877,10 +4127,10 @@ function TransitionDesigner({
3877
4127
  });
3878
4128
  }
3879
4129
  }, [host, fromSceneId, toSceneId, transitionSceneId]);
3880
- (0, import_react16.useEffect)(() => {
4130
+ (0, import_react18.useEffect)(() => {
3881
4131
  void refresh();
3882
4132
  }, [refresh]);
3883
- (0, import_react16.useEffect)(() => {
4133
+ (0, import_react18.useEffect)(() => {
3884
4134
  if (load.status !== "ready") return;
3885
4135
  const [po, pt] = padPair(
3886
4136
  reconcileSlots(originSlotsRef.current, originPool.map((t) => t.dbId)),
@@ -3889,7 +4139,7 @@ function TransitionDesigner({
3889
4139
  if (!slotsEqual(po, originSlotsRef.current)) setOriginSlots(po);
3890
4140
  if (!slotsEqual(pt, targetSlotsRef.current)) setTargetSlots(pt);
3891
4141
  }, [originPool, targetPool, load.status]);
3892
- const mutate = (0, import_react16.useCallback)(
4142
+ const mutate = (0, import_react18.useCallback)(
3893
4143
  (nextOrigin, nextTarget) => {
3894
4144
  const norm = normalizeSlots(nextOrigin, nextTarget);
3895
4145
  const [po, pt] = padPair(norm.originOrder, norm.targetOrder);
@@ -3902,7 +4152,7 @@ function TransitionDesigner({
3902
4152
  },
3903
4153
  [host, transitionSceneId]
3904
4154
  );
3905
- const setRowEffect = (0, import_react16.useCallback)(
4155
+ const setRowEffect = (0, import_react18.useCallback)(
3906
4156
  (sourceDbId, effect) => {
3907
4157
  setRowEffects((prev) => {
3908
4158
  const next = { ...prev, [sourceDbId]: effect };
@@ -3916,7 +4166,7 @@ function TransitionDesigner({
3916
4166
  },
3917
4167
  [host, transitionSceneId]
3918
4168
  );
3919
- const insertGapAbove = (0, import_react16.useCallback)(
4169
+ const insertGapAbove = (0, import_react18.useCallback)(
3920
4170
  (col, index) => {
3921
4171
  const slots = col === "origin" ? originSlots : targetSlots;
3922
4172
  const next = [...slots.slice(0, index), null, ...slots.slice(index)];
@@ -3925,7 +4175,7 @@ function TransitionDesigner({
3925
4175
  },
3926
4176
  [originSlots, targetSlots, mutate]
3927
4177
  );
3928
- const removeGap = (0, import_react16.useCallback)(
4178
+ const removeGap = (0, import_react18.useCallback)(
3929
4179
  (col, index) => {
3930
4180
  const slots = col === "origin" ? originSlots : targetSlots;
3931
4181
  const next = slots.filter((_, i) => i !== index);
@@ -3934,7 +4184,7 @@ function TransitionDesigner({
3934
4184
  },
3935
4185
  [originSlots, targetSlots, mutate]
3936
4186
  );
3937
- const handleDrop = (0, import_react16.useCallback)(
4187
+ const handleDrop = (0, import_react18.useCallback)(
3938
4188
  (col, to) => {
3939
4189
  const from = dragRef.current;
3940
4190
  dragRef.current = null;
@@ -3946,16 +4196,16 @@ function TransitionDesigner({
3946
4196
  },
3947
4197
  [originSlots, targetSlots, mutate]
3948
4198
  );
3949
- const rows = (0, import_react16.useMemo)(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);
3950
- const creatingDbIds = (0, import_react16.useMemo)(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);
3951
- const eligibleCount = (0, import_react16.useMemo)(
4199
+ const rows = (0, import_react18.useMemo)(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);
4200
+ const creatingDbIds = (0, import_react18.useMemo)(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);
4201
+ const eligibleCount = (0, import_react18.useMemo)(
3952
4202
  () => rows.filter((r) => {
3953
4203
  const k = rowKey(r);
3954
4204
  return k !== null && !creatingKeys.has(k);
3955
4205
  }).length,
3956
4206
  [rows, creatingKeys]
3957
4207
  );
3958
- const createRow = (0, import_react16.useCallback)(
4208
+ const createRow = (0, import_react18.useCallback)(
3959
4209
  async (row) => {
3960
4210
  const key = rowKey(row);
3961
4211
  if (!key || !row.type || creatingKeysRef.current.has(key)) return;
@@ -4009,7 +4259,7 @@ function TransitionDesigner({
4009
4259
  },
4010
4260
  [onCreateCrossfade, onCreateFade, onCreateAudioTransition]
4011
4261
  );
4012
- const createAll = (0, import_react16.useCallback)(async () => {
4262
+ const createAll = (0, import_react18.useCallback)(async () => {
4013
4263
  const eligible = buildRowSlots(originSlotsRef.current, targetSlotsRef.current).filter((r) => {
4014
4264
  const k = rowKey(r);
4015
4265
  return k !== null && !creatingKeysRef.current.has(k);
@@ -4077,15 +4327,15 @@ function TransitionDesigner({
4077
4327
  const base = "group relative rounded-sm border px-2 py-1.5 text-left transition-colors select-none";
4078
4328
  const tone = isDragTarget ? "border-sas-accent bg-sas-accent/10" : "border-sas-border bg-sas-panel";
4079
4329
  if (slotId === null) {
4080
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4330
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4081
4331
  "div",
4082
4332
  {
4083
4333
  ...cellDragProps(col, index, false),
4084
4334
  "data-testid": `${testIdPrefix}-${col}-gap-${index}`,
4085
4335
  className: `${base} ${tone} border-dashed flex items-center justify-between ${isDragging ? "opacity-40" : "opacity-70"}`,
4086
4336
  children: [
4087
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "\u2014 gap \u2014" }),
4088
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4337
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "\u2014 gap \u2014" }),
4338
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4089
4339
  "button",
4090
4340
  {
4091
4341
  type: "button",
@@ -4102,7 +4352,7 @@ function TransitionDesigner({
4102
4352
  }
4103
4353
  const primary = track ? track.prompt?.trim() || track.name : slotId;
4104
4354
  const meta = track ? [track.role, shortId3(track.dbId)].filter(Boolean).join(" \xB7 ") : "missing";
4105
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4355
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4106
4356
  "div",
4107
4357
  {
4108
4358
  ...cellDragProps(col, index, locked),
@@ -4110,13 +4360,13 @@ function TransitionDesigner({
4110
4360
  "data-value": slotId,
4111
4361
  className: `${base} ${tone} ${isDragging ? "opacity-40" : ""} ${locked ? "opacity-60" : "cursor-grab active:cursor-grabbing"}`,
4112
4362
  title: track ? track.dbId : "Track no longer available",
4113
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-start gap-1", children: [
4114
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-muted/60 text-xs leading-tight pt-0.5", "aria-hidden": true, children: "\u283F" }),
4115
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "min-w-0 flex-1", children: [
4116
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-text truncate", children: primary }),
4117
- meta && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: meta })
4363
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-start gap-1", children: [
4364
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-sas-muted/60 text-xs leading-tight pt-0.5", "aria-hidden": true, children: "\u283F" }),
4365
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "min-w-0 flex-1", children: [
4366
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-text truncate", children: primary }),
4367
+ meta && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: meta })
4118
4368
  ] }),
4119
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4369
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4120
4370
  "button",
4121
4371
  {
4122
4372
  type: "button",
@@ -4132,22 +4382,22 @@ function TransitionDesigner({
4132
4382
  }
4133
4383
  );
4134
4384
  };
4135
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
4136
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
4137
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
4138
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-text", children: fromLabel }),
4385
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
4386
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
4387
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
4388
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-sas-text", children: fromLabel }),
4139
4389
  " \u2192",
4140
4390
  " ",
4141
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-text", children: toLabel }),
4391
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-sas-text", children: toLabel }),
4142
4392
  familyLabel ? ` \xB7 ${familyLabel}` : "",
4143
4393
  " \xB7 line up a track on each side to crossfade; leave one blank (or insert a gap) to fade."
4144
4394
  ] }),
4145
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
4146
- creatingKeys.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-[10px] text-sas-accent whitespace-nowrap", "data-testid": `${testIdPrefix}-creating-count`, children: [
4395
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
4396
+ creatingKeys.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-[10px] text-sas-accent whitespace-nowrap", "data-testid": `${testIdPrefix}-creating-count`, children: [
4147
4397
  creatingKeys.size,
4148
4398
  " creating\u2026"
4149
4399
  ] }),
4150
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4400
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4151
4401
  "button",
4152
4402
  {
4153
4403
  type: "button",
@@ -4164,49 +4414,49 @@ function TransitionDesigner({
4164
4414
  )
4165
4415
  ] })
4166
4416
  ] }),
4167
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "grid grid-cols-[1fr_auto_1fr] gap-2", children: [
4168
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate", children: [
4417
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "grid grid-cols-[1fr_auto_1fr] gap-2", children: [
4418
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate", children: [
4169
4419
  "Origin (",
4170
4420
  fromLabel,
4171
4421
  ")"
4172
4422
  ] }),
4173
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted text-center px-2", children: "Transition" }),
4174
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate text-right", children: [
4423
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted text-center px-2", children: "Transition" }),
4424
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate text-right", children: [
4175
4425
  "Target (",
4176
4426
  toLabel,
4177
4427
  ")"
4178
4428
  ] })
4179
4429
  ] }),
4180
- load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
4181
- load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
4182
- load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
4430
+ load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
4431
+ load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
4432
+ load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
4183
4433
  "No tracks to arrange in this panel for either scene. Add tracks to ",
4184
4434
  fromLabel,
4185
4435
  " or ",
4186
4436
  toLabel,
4187
4437
  " ",
4188
4438
  "first (or free one by deleting an existing crossfade/fade)."
4189
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "space-y-2", children: rows.map((row, i) => {
4439
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "space-y-2", children: rows.map((row, i) => {
4190
4440
  const key = rowKey(row);
4191
4441
  const isCreatingThis = key !== null && creatingKeys.has(key);
4192
4442
  const errMsg = key !== null ? rowErrors[key] : void 0;
4193
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4443
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4194
4444
  "div",
4195
4445
  {
4196
4446
  "data-testid": `${testIdPrefix}-row-${i}`,
4197
4447
  className: "grid grid-cols-[1fr_auto_1fr] gap-2 items-center",
4198
4448
  children: [
4199
4449
  renderCell("origin", i, row.originId),
4200
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
4201
- !row.type ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[10px] text-sas-muted/50", children: "\u2014" }) : row.type === "crossfade" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4450
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
4451
+ !row.type ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[10px] text-sas-muted/50", children: "\u2014" }) : row.type === "crossfade" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4202
4452
  "span",
4203
4453
  {
4204
4454
  "data-testid": `${testIdPrefix}-type-${i}`,
4205
4455
  className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-accent/50 text-sas-accent",
4206
4456
  children: TYPE_LABEL[row.type]
4207
4457
  }
4208
- ) : audioEffectsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-1", "data-testid": `${testIdPrefix}-type-${i}`, children: [
4209
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4458
+ ) : audioEffectsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1", "data-testid": `${testIdPrefix}-type-${i}`, children: [
4459
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4210
4460
  "select",
4211
4461
  {
4212
4462
  "data-testid": `${testIdPrefix}-effect-${i}`,
@@ -4216,11 +4466,11 @@ function TransitionDesigner({
4216
4466
  if (id) setRowEffect(id, e.target.value);
4217
4467
  },
4218
4468
  className: "text-[10px] bg-sas-panel border border-sas-border rounded-sm px-1 py-0.5 text-sas-text",
4219
- children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: eff, children: AUDIO_EFFECT_LABEL[eff] }, eff))
4469
+ children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: eff, children: AUDIO_EFFECT_LABEL[eff] }, eff))
4220
4470
  }
4221
4471
  ),
4222
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[9px] text-sas-muted", children: row.type === "fade-out" ? "out" : "in" })
4223
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4472
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[9px] text-sas-muted", children: row.type === "fade-out" ? "out" : "in" })
4473
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4224
4474
  "span",
4225
4475
  {
4226
4476
  "data-testid": `${testIdPrefix}-type-${i}`,
@@ -4228,7 +4478,7 @@ function TransitionDesigner({
4228
4478
  children: TYPE_LABEL[row.type]
4229
4479
  }
4230
4480
  ),
4231
- isCreatingThis ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4481
+ isCreatingThis ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4232
4482
  SorceryProgressBar,
4233
4483
  {
4234
4484
  isLoading: true,
@@ -4236,7 +4486,7 @@ function TransitionDesigner({
4236
4486
  statusText: "CREATING",
4237
4487
  estimatedDurationMs: row.type === "crossfade" ? CROSSFADE_ESTIMATE_MS : FADE_ESTIMATE_MS
4238
4488
  }
4239
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4489
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4240
4490
  "button",
4241
4491
  {
4242
4492
  type: "button",
@@ -4247,7 +4497,7 @@ function TransitionDesigner({
4247
4497
  children: "Create"
4248
4498
  }
4249
4499
  ),
4250
- errMsg && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4500
+ errMsg && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4251
4501
  "span",
4252
4502
  {
4253
4503
  "data-testid": `${testIdPrefix}-row-error-${i}`,
@@ -4265,9 +4515,325 @@ function TransitionDesigner({
4265
4515
  ] });
4266
4516
  }
4267
4517
 
4518
+ // src/components/PanelMasterStrip.tsx
4519
+ var import_react19 = require("react");
4520
+ var import_jsx_runtime19 = require("react/jsx-runtime");
4521
+ function PanelMasterStrip({
4522
+ bus,
4523
+ levels = null,
4524
+ availableFx = [],
4525
+ fxLoading = false,
4526
+ soloedOut = false,
4527
+ disabled = false,
4528
+ fxPickerOpen,
4529
+ onToggleFxPicker,
4530
+ onRefreshFx,
4531
+ onVolumeChange,
4532
+ onMuteToggle,
4533
+ onSoloToggle,
4534
+ onAddFx,
4535
+ onRemoveFx,
4536
+ onToggleFxEnabled,
4537
+ onShowFxEditor
4538
+ }) {
4539
+ const [search, setSearch] = (0, import_react19.useState)("");
4540
+ const filtered = (0, import_react19.useMemo)(() => {
4541
+ const q = search.trim().toLowerCase();
4542
+ if (!q) return availableFx;
4543
+ return availableFx.filter(
4544
+ (fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
4545
+ );
4546
+ }, [availableFx, search]);
4547
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4548
+ "div",
4549
+ {
4550
+ "data-testid": "panel-master-strip",
4551
+ 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" : ""}`,
4552
+ children: [
4553
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2", children: [
4554
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4555
+ "span",
4556
+ {
4557
+ className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
4558
+ title: "Panel mix bus \u2014 volume, mute/solo and FX applied to this panel's summed output",
4559
+ children: "BUS"
4560
+ }
4561
+ ),
4562
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex-1 min-w-[8rem] flex flex-col gap-0.5", children: [
4563
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4564
+ VolumeSlider,
4565
+ {
4566
+ value: dbToSlider(bus.volume),
4567
+ onChange: (sliderValue) => onVolumeChange(sliderToDb(sliderValue)),
4568
+ disabled
4569
+ }
4570
+ ),
4571
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col gap-px", "data-testid": "bus-meter", children: [
4572
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4573
+ LevelMeter,
4574
+ {
4575
+ peakDb: levels?.leftDb ?? -120,
4576
+ active: levels != null,
4577
+ clipped: levels?.clipped,
4578
+ compact: true,
4579
+ "data-testid": "bus-meter-left"
4580
+ }
4581
+ ),
4582
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4583
+ LevelMeter,
4584
+ {
4585
+ peakDb: levels?.rightDb ?? -120,
4586
+ active: levels != null,
4587
+ compact: true,
4588
+ "data-testid": "bus-meter-right"
4589
+ }
4590
+ )
4591
+ ] })
4592
+ ] }),
4593
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4594
+ "button",
4595
+ {
4596
+ "data-testid": "bus-mute-button",
4597
+ onClick: onMuteToggle,
4598
+ disabled,
4599
+ className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${bus.muted ? "bg-red-600 text-white" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"} disabled:opacity-50`,
4600
+ title: bus.muted ? "Unmute panel bus" : "Mute panel bus (silences the whole panel)",
4601
+ children: "M"
4602
+ }
4603
+ ),
4604
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4605
+ "button",
4606
+ {
4607
+ "data-testid": "bus-solo-button",
4608
+ onClick: onSoloToggle,
4609
+ disabled,
4610
+ className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${bus.soloed ? "bg-amber-500 text-black" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"} disabled:opacity-50`,
4611
+ title: bus.soloed ? "Unsolo panel bus" : "Solo this panel (silences other panels/tracks in scope)",
4612
+ children: "S"
4613
+ }
4614
+ ),
4615
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "flex items-center gap-1 max-w-[45%] min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4616
+ "span",
4617
+ {
4618
+ "data-testid": `bus-fx-chip-${fx.index}`,
4619
+ 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"}`,
4620
+ title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
4621
+ children: [
4622
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4623
+ "button",
4624
+ {
4625
+ "data-testid": `bus-fx-toggle-${fx.index}`,
4626
+ onClick: () => onToggleFxEnabled(fx.index, !fx.enabled),
4627
+ disabled,
4628
+ className: "hover:opacity-70 disabled:opacity-50",
4629
+ title: fx.enabled ? `Bypass ${fx.name}` : `Enable ${fx.name}`,
4630
+ children: fx.enabled ? "\u25CF" : "\u25CB"
4631
+ }
4632
+ ),
4633
+ onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4634
+ "button",
4635
+ {
4636
+ "data-testid": `bus-fx-edit-${fx.index}`,
4637
+ onClick: () => onShowFxEditor(fx.index),
4638
+ disabled,
4639
+ className: "max-w-[80px] truncate hover:underline disabled:opacity-50",
4640
+ title: `Open ${fx.name} editor`,
4641
+ children: fx.name
4642
+ }
4643
+ ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
4644
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4645
+ "button",
4646
+ {
4647
+ "data-testid": `bus-fx-remove-${fx.index}`,
4648
+ onClick: () => onRemoveFx(fx.index),
4649
+ disabled,
4650
+ className: "text-sas-muted/60 hover:text-sas-danger disabled:opacity-50",
4651
+ title: `Remove ${fx.name} from the bus`,
4652
+ children: "\u2715"
4653
+ }
4654
+ )
4655
+ ]
4656
+ },
4657
+ `${fx.index}:${fx.pluginId}`
4658
+ )) }),
4659
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4660
+ "button",
4661
+ {
4662
+ "data-testid": "bus-fx-add-button",
4663
+ onClick: () => onToggleFxPicker(!fxPickerOpen),
4664
+ disabled,
4665
+ 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`,
4666
+ title: fxPickerOpen ? "Close the FX picker" : "Add an FX plugin to the panel bus",
4667
+ children: fxPickerOpen ? "FX \u25B4" : "FX +"
4668
+ }
4669
+ )
4670
+ ] }),
4671
+ fxPickerOpen && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
4672
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2", children: [
4673
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4674
+ "input",
4675
+ {
4676
+ type: "text",
4677
+ value: search,
4678
+ onChange: (e) => setSearch(e.target.value),
4679
+ placeholder: "Search FX...",
4680
+ className: "sas-input flex-1 px-2 py-1 text-xs"
4681
+ }
4682
+ ),
4683
+ onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4684
+ "button",
4685
+ {
4686
+ onClick: () => onRefreshFx(),
4687
+ disabled: fxLoading,
4688
+ 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",
4689
+ title: "Re-scan plugins",
4690
+ children: fxLoading ? "..." : "Refresh"
4691
+ }
4692
+ )
4693
+ ] }),
4694
+ fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
4695
+ filtered.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4696
+ "button",
4697
+ {
4698
+ "data-testid": `bus-fx-pick-${fx.pluginId}`,
4699
+ onClick: () => onAddFx(fx.pluginId),
4700
+ 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",
4701
+ title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
4702
+ children: [
4703
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
4704
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
4705
+ ]
4706
+ },
4707
+ fx.pluginId
4708
+ )),
4709
+ filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
4710
+ ] })
4711
+ ] })
4712
+ ]
4713
+ }
4714
+ );
4715
+ }
4716
+
4717
+ // src/hooks/usePanelBus.ts
4718
+ var import_react20 = require("react");
4719
+ var LEVELS_POLL_MS = 66;
4720
+ function usePanelBus(host, activeSceneId) {
4721
+ const supported = typeof host.getPanelBusState === "function";
4722
+ const [bus, setBus] = (0, import_react20.useState)(null);
4723
+ const [levels, setLevels] = (0, import_react20.useState)(null);
4724
+ const [availableFx, setAvailableFx] = (0, import_react20.useState)([]);
4725
+ const [fxLoading, setFxLoading] = (0, import_react20.useState)(false);
4726
+ const [fxPickerOpen, setFxPickerOpen] = (0, import_react20.useState)(false);
4727
+ const fxLoadedRef = (0, import_react20.useRef)(false);
4728
+ const loadSeqRef = (0, import_react20.useRef)(0);
4729
+ const reload = (0, import_react20.useCallback)(async () => {
4730
+ if (!supported || !activeSceneId || !host.getPanelBusState) {
4731
+ setBus(null);
4732
+ return;
4733
+ }
4734
+ const seq = ++loadSeqRef.current;
4735
+ try {
4736
+ const state = await host.getPanelBusState(activeSceneId);
4737
+ if (loadSeqRef.current === seq) setBus(state);
4738
+ } catch {
4739
+ }
4740
+ }, [host, activeSceneId, supported]);
4741
+ (0, import_react20.useEffect)(() => {
4742
+ setBus(null);
4743
+ setFxPickerOpen(false);
4744
+ void reload();
4745
+ }, [reload]);
4746
+ (0, import_react20.useEffect)(() => {
4747
+ if (!supported || !activeSceneId || !bus?.engaged || !host.getPanelBusLevels) {
4748
+ setLevels(null);
4749
+ return;
4750
+ }
4751
+ let cancelled = false;
4752
+ const tick = async () => {
4753
+ if (typeof document !== "undefined" && document.hidden) return;
4754
+ try {
4755
+ const next = await host.getPanelBusLevels(activeSceneId);
4756
+ if (!cancelled) setLevels(next);
4757
+ } catch {
4758
+ if (!cancelled) setLevels(null);
4759
+ }
4760
+ };
4761
+ void tick();
4762
+ const id = setInterval(() => void tick(), LEVELS_POLL_MS);
4763
+ return () => {
4764
+ cancelled = true;
4765
+ clearInterval(id);
4766
+ };
4767
+ }, [supported, activeSceneId, bus?.engaged, host]);
4768
+ const loadFxList = (0, import_react20.useCallback)(
4769
+ async (force) => {
4770
+ if (!supported || !host.getAvailableFx) return;
4771
+ if (fxLoadedRef.current && !force) return;
4772
+ setFxLoading(true);
4773
+ try {
4774
+ const list = await host.getAvailableFx();
4775
+ setAvailableFx(list);
4776
+ fxLoadedRef.current = true;
4777
+ } catch {
4778
+ } finally {
4779
+ setFxLoading(false);
4780
+ }
4781
+ },
4782
+ [host, supported]
4783
+ );
4784
+ const openPicker = (0, import_react20.useCallback)(
4785
+ (open) => {
4786
+ setFxPickerOpen(open);
4787
+ if (open) void loadFxList(false);
4788
+ },
4789
+ [loadFxList]
4790
+ );
4791
+ const mutate = (0, import_react20.useCallback)(
4792
+ (fn) => {
4793
+ if (!fn || !activeSceneId) return;
4794
+ void (async () => {
4795
+ try {
4796
+ await fn();
4797
+ } catch {
4798
+ }
4799
+ await reload();
4800
+ })();
4801
+ },
4802
+ [activeSceneId, reload]
4803
+ );
4804
+ return {
4805
+ supported,
4806
+ bus,
4807
+ levels,
4808
+ availableFx,
4809
+ fxLoading,
4810
+ fxPickerOpen,
4811
+ setFxPickerOpen: openPicker,
4812
+ refreshFx: () => void loadFxList(true),
4813
+ reload,
4814
+ onVolumeChange: (volumeDb) => mutate(host.setPanelBusVolume && (() => host.setPanelBusVolume(activeSceneId, volumeDb))),
4815
+ onMuteToggle: () => mutate(
4816
+ host.setPanelBusMute && (() => host.setPanelBusMute(activeSceneId, !(bus?.muted ?? false)))
4817
+ ),
4818
+ onSoloToggle: () => mutate(
4819
+ host.setPanelBusSolo && (() => host.setPanelBusSolo(activeSceneId, !(bus?.soloed ?? false)))
4820
+ ),
4821
+ onAddFx: (pluginId) => mutate(host.loadPanelBusFx && (async () => {
4822
+ await host.loadPanelBusFx(activeSceneId, pluginId);
4823
+ })),
4824
+ onRemoveFx: (fxIndex) => mutate(host.removePanelBusFx && (() => host.removePanelBusFx(activeSceneId, fxIndex))),
4825
+ onToggleFxEnabled: (fxIndex, enabled) => mutate(
4826
+ host.setPanelBusFxEnabled && (() => host.setPanelBusFxEnabled(activeSceneId, fxIndex, enabled))
4827
+ ),
4828
+ onShowFxEditor: (fxIndex) => mutate(
4829
+ host.showPanelBusFxEditor && (() => host.showPanelBusFxEditor(activeSceneId, fxIndex))
4830
+ )
4831
+ };
4832
+ }
4833
+
4268
4834
  // src/components/DownloadPackButton.tsx
4269
- var import_react17 = require("react");
4270
- var import_jsx_runtime18 = require("react/jsx-runtime");
4835
+ var import_react21 = require("react");
4836
+ var import_jsx_runtime20 = require("react/jsx-runtime");
4271
4837
  function formatSize(bytes) {
4272
4838
  if (!bytes || bytes <= 0) return "";
4273
4839
  const gb = bytes / 1024 ** 3;
@@ -4283,10 +4849,10 @@ var DownloadPackButton = ({
4283
4849
  variant = "compact",
4284
4850
  onDownloadComplete
4285
4851
  }) => {
4286
- const [status, setStatus] = (0, import_react17.useState)("idle");
4287
- const [progress, setProgress] = (0, import_react17.useState)(0);
4288
- const [errorMessage, setErrorMessage] = (0, import_react17.useState)(null);
4289
- (0, import_react17.useEffect)(() => {
4852
+ const [status, setStatus] = (0, import_react21.useState)("idle");
4853
+ const [progress, setProgress] = (0, import_react21.useState)(0);
4854
+ const [errorMessage, setErrorMessage] = (0, import_react21.useState)(null);
4855
+ (0, import_react21.useEffect)(() => {
4290
4856
  const unsub = host.onSamplePackProgress(packId, (p) => {
4291
4857
  setStatus(p.status);
4292
4858
  setProgress(p.progress);
@@ -4301,7 +4867,7 @@ var DownloadPackButton = ({
4301
4867
  });
4302
4868
  return unsub;
4303
4869
  }, [host, packId, onDownloadComplete]);
4304
- const handleClick = (0, import_react17.useCallback)(async () => {
4870
+ const handleClick = (0, import_react21.useCallback)(async () => {
4305
4871
  if (status !== "idle" && status !== "error") return;
4306
4872
  try {
4307
4873
  setStatus("downloading");
@@ -4355,8 +4921,8 @@ var DownloadPackButton = ({
4355
4921
  } else {
4356
4922
  className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
4357
4923
  }
4358
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4359
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4924
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { children: [
4925
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4360
4926
  "button",
4361
4927
  {
4362
4928
  "data-testid": `download-pack-button-${packId}`,
@@ -4367,12 +4933,12 @@ var DownloadPackButton = ({
4367
4933
  children: buttonLabel
4368
4934
  }
4369
4935
  ),
4370
- variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
4936
+ variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
4371
4937
  ] });
4372
4938
  };
4373
4939
 
4374
4940
  // src/components/SamplePackCTACard.tsx
4375
- var import_jsx_runtime19 = require("react/jsx-runtime");
4941
+ var import_jsx_runtime21 = require("react/jsx-runtime");
4376
4942
  var SamplePackCTACard = ({
4377
4943
  host,
4378
4944
  pack,
@@ -4380,7 +4946,7 @@ var SamplePackCTACard = ({
4380
4946
  onDownloadComplete
4381
4947
  }) => {
4382
4948
  if (status === "checking") {
4383
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4949
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4384
4950
  "div",
4385
4951
  {
4386
4952
  "data-testid": `sample-pack-cta-checking-${pack.packId}`,
@@ -4391,16 +4957,16 @@ var SamplePackCTACard = ({
4391
4957
  }
4392
4958
  const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
4393
4959
  const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
4394
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4960
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4395
4961
  "div",
4396
4962
  {
4397
4963
  "data-testid": `sample-pack-cta-${pack.packId}`,
4398
4964
  className: "flex flex-col items-center justify-center py-12 px-6 text-center",
4399
4965
  children: [
4400
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
4401
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
4402
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
4403
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4966
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
4967
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
4968
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
4969
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4404
4970
  DownloadPackButton,
4405
4971
  {
4406
4972
  host,
@@ -4417,7 +4983,7 @@ var SamplePackCTACard = ({
4417
4983
  };
4418
4984
 
4419
4985
  // src/components/WaveformView.tsx
4420
- var import_react18 = require("react");
4986
+ var import_react22 = require("react");
4421
4987
 
4422
4988
  // src/components/waveform.ts
4423
4989
  function computePeaks(audioBuffer, bins, targetSamples) {
@@ -4480,7 +5046,7 @@ function drawWaveform(canvas, peaks, options = {}) {
4480
5046
  }
4481
5047
 
4482
5048
  // src/components/WaveformView.tsx
4483
- var import_jsx_runtime20 = require("react/jsx-runtime");
5049
+ var import_jsx_runtime22 = require("react/jsx-runtime");
4484
5050
  var WaveformView = ({
4485
5051
  host,
4486
5052
  filePath,
@@ -4489,9 +5055,9 @@ var WaveformView = ({
4489
5055
  fillStyle,
4490
5056
  targetSamples
4491
5057
  }) => {
4492
- const canvasRef = (0, import_react18.useRef)(null);
4493
- const [peaks, setPeaks] = (0, import_react18.useState)(null);
4494
- (0, import_react18.useEffect)(() => {
5058
+ const canvasRef = (0, import_react22.useRef)(null);
5059
+ const [peaks, setPeaks] = (0, import_react22.useState)(null);
5060
+ (0, import_react22.useEffect)(() => {
4495
5061
  let cancelled = false;
4496
5062
  let audioContext = null;
4497
5063
  (async () => {
@@ -4517,7 +5083,7 @@ var WaveformView = ({
4517
5083
  cancelled = true;
4518
5084
  };
4519
5085
  }, [host, filePath, bins, targetSamples]);
4520
- (0, import_react18.useEffect)(() => {
5086
+ (0, import_react22.useEffect)(() => {
4521
5087
  if (!peaks) return;
4522
5088
  const canvas = canvasRef.current;
4523
5089
  if (!canvas) return;
@@ -4528,7 +5094,7 @@ var WaveformView = ({
4528
5094
  observer.observe(canvas);
4529
5095
  return () => observer.disconnect();
4530
5096
  }, [peaks, fillStyle]);
4531
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5097
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4532
5098
  "canvas",
4533
5099
  {
4534
5100
  ref: canvasRef,
@@ -4539,8 +5105,8 @@ var WaveformView = ({
4539
5105
  };
4540
5106
 
4541
5107
  // src/components/ScrollingWaveform.tsx
4542
- var import_react19 = require("react");
4543
- var import_jsx_runtime21 = require("react/jsx-runtime");
5108
+ var import_react23 = require("react");
5109
+ var import_jsx_runtime23 = require("react/jsx-runtime");
4544
5110
  var ScrollingWaveform = ({
4545
5111
  getPeakDb,
4546
5112
  active,
@@ -4548,11 +5114,11 @@ var ScrollingWaveform = ({
4548
5114
  className,
4549
5115
  fillStyle
4550
5116
  }) => {
4551
- const canvasRef = (0, import_react19.useRef)(null);
4552
- const ringRef = (0, import_react19.useRef)(new Float32Array(columns));
4553
- const writeIdxRef = (0, import_react19.useRef)(0);
4554
- const rafRef = (0, import_react19.useRef)(null);
4555
- (0, import_react19.useEffect)(() => {
5117
+ const canvasRef = (0, import_react23.useRef)(null);
5118
+ const ringRef = (0, import_react23.useRef)(new Float32Array(columns));
5119
+ const writeIdxRef = (0, import_react23.useRef)(0);
5120
+ const rafRef = (0, import_react23.useRef)(null);
5121
+ (0, import_react23.useEffect)(() => {
4556
5122
  if (ringRef.current.length !== columns) {
4557
5123
  const next = new Float32Array(columns);
4558
5124
  const prev = ringRef.current;
@@ -4564,7 +5130,7 @@ var ScrollingWaveform = ({
4564
5130
  writeIdxRef.current = writeIdxRef.current % columns;
4565
5131
  }
4566
5132
  }, [columns]);
4567
- (0, import_react19.useEffect)(() => {
5133
+ (0, import_react23.useEffect)(() => {
4568
5134
  if (!active) {
4569
5135
  if (rafRef.current !== null) {
4570
5136
  cancelAnimationFrame(rafRef.current);
@@ -4616,7 +5182,7 @@ var ScrollingWaveform = ({
4616
5182
  }
4617
5183
  };
4618
5184
  }, [active, getPeakDb, fillStyle]);
4619
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
5185
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4620
5186
  "canvas",
4621
5187
  {
4622
5188
  ref: canvasRef,
@@ -4627,8 +5193,8 @@ var ScrollingWaveform = ({
4627
5193
  };
4628
5194
 
4629
5195
  // src/components/OffsetScrubber.tsx
4630
- var import_react20 = require("react");
4631
- var import_jsx_runtime22 = require("react/jsx-runtime");
5196
+ var import_react24 = require("react");
5197
+ var import_jsx_runtime24 = require("react/jsx-runtime");
4632
5198
  var SLIDER_HEIGHT_PX = 28;
4633
5199
  var TICK_HEIGHT_PX = 14;
4634
5200
  var DOWNBEAT_TICK_HEIGHT_PX = 22;
@@ -4641,40 +5207,40 @@ function OffsetScrubber({
4641
5207
  onChange,
4642
5208
  disabled = false
4643
5209
  }) {
4644
- const trackRef = (0, import_react20.useRef)(null);
4645
- const [draftOffset, setDraftOffset] = (0, import_react20.useState)(offsetSamples);
4646
- const [isDragging, setIsDragging] = (0, import_react20.useState)(false);
4647
- (0, import_react20.useEffect)(() => {
5210
+ const trackRef = (0, import_react24.useRef)(null);
5211
+ const [draftOffset, setDraftOffset] = (0, import_react24.useState)(offsetSamples);
5212
+ const [isDragging, setIsDragging] = (0, import_react24.useState)(false);
5213
+ (0, import_react24.useEffect)(() => {
4648
5214
  if (!isDragging) setDraftOffset(offsetSamples);
4649
5215
  }, [offsetSamples, isDragging]);
4650
5216
  const sampleRate = cuePoints?.sample_rate ?? 44100;
4651
5217
  const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
4652
- const beatsForRange = (0, import_react20.useMemo)(() => {
5218
+ const beatsForRange = (0, import_react24.useMemo)(() => {
4653
5219
  return Math.round(60 / projectBpm * sampleRate);
4654
5220
  }, [projectBpm, sampleRate]);
4655
5221
  const rangeSamples = beatsForRange * meter;
4656
- const sampleToFraction = (0, import_react20.useCallback)(
5222
+ const sampleToFraction = (0, import_react24.useCallback)(
4657
5223
  (sample) => {
4658
5224
  const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
4659
5225
  return (clamped + rangeSamples) / (2 * rangeSamples);
4660
5226
  },
4661
5227
  [rangeSamples]
4662
5228
  );
4663
- const fractionToSample = (0, import_react20.useCallback)(
5229
+ const fractionToSample = (0, import_react24.useCallback)(
4664
5230
  (fraction) => {
4665
5231
  const clamped = Math.max(0, Math.min(1, fraction));
4666
5232
  return Math.round(clamped * 2 * rangeSamples - rangeSamples);
4667
5233
  },
4668
5234
  [rangeSamples]
4669
5235
  );
4670
- const snapTargets = (0, import_react20.useMemo)(() => {
5236
+ const snapTargets = (0, import_react24.useMemo)(() => {
4671
5237
  if (!cuePoints || cuePoints.beats.length === 0) return [];
4672
5238
  const downbeat = cuePoints.beats[0];
4673
5239
  const positives = cuePoints.beats.map((b) => b - downbeat);
4674
5240
  const negatives = positives.slice(1).map((p) => -p);
4675
5241
  return [...negatives, ...positives].sort((a, b) => a - b);
4676
5242
  }, [cuePoints]);
4677
- const snapToBeat = (0, import_react20.useCallback)(
5243
+ const snapToBeat = (0, import_react24.useCallback)(
4678
5244
  (sample) => {
4679
5245
  if (snapTargets.length === 0) return sample;
4680
5246
  let best = snapTargets[0];
@@ -4690,7 +5256,7 @@ function OffsetScrubber({
4690
5256
  },
4691
5257
  [snapTargets]
4692
5258
  );
4693
- const handlePointerDown = (0, import_react20.useCallback)(
5259
+ const handlePointerDown = (0, import_react24.useCallback)(
4694
5260
  (e) => {
4695
5261
  if (disabled || !cuePoints) return;
4696
5262
  e.preventDefault();
@@ -4724,7 +5290,7 @@ function OffsetScrubber({
4724
5290
  },
4725
5291
  [disabled, cuePoints, fractionToSample, onChange, snapToBeat]
4726
5292
  );
4727
- const handleResetToZero = (0, import_react20.useCallback)(() => {
5293
+ const handleResetToZero = (0, import_react24.useCallback)(() => {
4728
5294
  if (disabled) return;
4729
5295
  setDraftOffset(0);
4730
5296
  onChange(0);
@@ -4732,7 +5298,7 @@ function OffsetScrubber({
4732
5298
  const thumbFraction = sampleToFraction(draftOffset);
4733
5299
  const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
4734
5300
  const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
4735
- const ticks = (0, import_react20.useMemo)(() => {
5301
+ const ticks = (0, import_react24.useMemo)(() => {
4736
5302
  if (!cuePoints) return [];
4737
5303
  const downbeat = cuePoints.beats[0] ?? 0;
4738
5304
  return cuePoints.beats.map((b, i) => {
@@ -4743,9 +5309,9 @@ function OffsetScrubber({
4743
5309
  });
4744
5310
  }, [cuePoints, sampleToFraction]);
4745
5311
  const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
4746
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
4747
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
4748
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
5312
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
5313
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
5314
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
4749
5315
  "div",
4750
5316
  {
4751
5317
  ref: trackRef,
@@ -4761,7 +5327,7 @@ function OffsetScrubber({
4761
5327
  "aria-valuenow": draftOffset,
4762
5328
  "aria-disabled": isDisabled,
4763
5329
  children: [
4764
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5330
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4765
5331
  "div",
4766
5332
  {
4767
5333
  "aria-hidden": "true",
@@ -4769,7 +5335,7 @@ function OffsetScrubber({
4769
5335
  style: { left: "50%" }
4770
5336
  }
4771
5337
  ),
4772
- ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5338
+ ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4773
5339
  "div",
4774
5340
  {
4775
5341
  "data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
@@ -4784,7 +5350,7 @@ function OffsetScrubber({
4784
5350
  },
4785
5351
  t.i
4786
5352
  )),
4787
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5353
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4788
5354
  "div",
4789
5355
  {
4790
5356
  "data-testid": "offset-scrubber-thumb",
@@ -4801,7 +5367,7 @@ function OffsetScrubber({
4801
5367
  ]
4802
5368
  }
4803
5369
  ),
4804
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5370
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4805
5371
  "span",
4806
5372
  {
4807
5373
  "data-testid": "offset-scrubber-readout",
@@ -4809,7 +5375,7 @@ function OffsetScrubber({
4809
5375
  children: formatOffset(draftOffset, sampleRate)
4810
5376
  }
4811
5377
  ),
4812
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5378
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4813
5379
  "button",
4814
5380
  {
4815
5381
  type: "button",
@@ -4821,7 +5387,7 @@ function OffsetScrubber({
4821
5387
  children: "\u2316"
4822
5388
  }
4823
5389
  ),
4824
- bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5390
+ bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4825
5391
  "span",
4826
5392
  {
4827
5393
  "data-testid": "offset-bpm-mismatch",
@@ -4893,16 +5459,16 @@ function synthesizeCuePoints({
4893
5459
  }
4894
5460
 
4895
5461
  // src/panel-core/useGeneratorPanelCore.tsx
4896
- var import_react25 = require("react");
5462
+ var import_react29 = require("react");
4897
5463
 
4898
5464
  // src/hooks/useSceneState.ts
4899
- var import_react21 = require("react");
5465
+ var import_react25 = require("react");
4900
5466
  function useSceneState(activeSceneId, initialValue) {
4901
- const [stateMap, setStateMap] = (0, import_react21.useState)(() => /* @__PURE__ */ new Map());
4902
- const activeSceneIdRef = (0, import_react21.useRef)(activeSceneId);
5467
+ const [stateMap, setStateMap] = (0, import_react25.useState)(() => /* @__PURE__ */ new Map());
5468
+ const activeSceneIdRef = (0, import_react25.useRef)(activeSceneId);
4903
5469
  activeSceneIdRef.current = activeSceneId;
4904
5470
  const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
4905
- const setForCurrentScene = (0, import_react21.useCallback)((value) => {
5471
+ const setForCurrentScene = (0, import_react25.useCallback)((value) => {
4906
5472
  const sid = activeSceneIdRef.current;
4907
5473
  if (sid === null) return;
4908
5474
  setStateMap((prev) => {
@@ -4913,7 +5479,7 @@ function useSceneState(activeSceneId, initialValue) {
4913
5479
  return newMap;
4914
5480
  });
4915
5481
  }, [initialValue]);
4916
- const setForScene = (0, import_react21.useCallback)((sceneId, value) => {
5482
+ const setForScene = (0, import_react25.useCallback)((sceneId, value) => {
4917
5483
  setStateMap((prev) => {
4918
5484
  const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
4919
5485
  const next = typeof value === "function" ? value(current) : value;
@@ -4926,10 +5492,10 @@ function useSceneState(activeSceneId, initialValue) {
4926
5492
  }
4927
5493
 
4928
5494
  // src/hooks/useAnySolo.ts
4929
- var import_react22 = require("react");
5495
+ var import_react26 = require("react");
4930
5496
  function useAnySolo(host) {
4931
- const [anySolo, setAnySolo] = (0, import_react22.useState)(false);
4932
- (0, import_react22.useEffect)(() => {
5497
+ const [anySolo, setAnySolo] = (0, import_react26.useState)(false);
5498
+ (0, import_react26.useEffect)(() => {
4933
5499
  let active = true;
4934
5500
  const refresh = () => {
4935
5501
  host.isAnySoloActive().then((v) => {
@@ -4948,7 +5514,7 @@ function useAnySolo(host) {
4948
5514
  }
4949
5515
 
4950
5516
  // src/hooks/useSoundHistory.ts
4951
- var import_react23 = require("react");
5517
+ var import_react27 = require("react");
4952
5518
  var EMPTY = { entries: [], cursor: -1 };
4953
5519
  function sameDescriptor(a, b) {
4954
5520
  if (a === b) return true;
@@ -4960,14 +5526,14 @@ function sameDescriptor(a, b) {
4960
5526
  }
4961
5527
  function useSoundHistory(applySound, opts = {}) {
4962
5528
  const max = Math.max(2, opts.max ?? 24);
4963
- const applyRef = (0, import_react23.useRef)(applySound);
5529
+ const applyRef = (0, import_react27.useRef)(applySound);
4964
5530
  applyRef.current = applySound;
4965
- const onChangeRef = (0, import_react23.useRef)(opts.onChange);
5531
+ const onChangeRef = (0, import_react27.useRef)(opts.onChange);
4966
5532
  onChangeRef.current = opts.onChange;
4967
- const dataRef = (0, import_react23.useRef)({});
4968
- const [, setVersion] = (0, import_react23.useState)(0);
4969
- const bump = (0, import_react23.useCallback)(() => setVersion((v) => v + 1), []);
4970
- const commit = (0, import_react23.useCallback)(
5533
+ const dataRef = (0, import_react27.useRef)({});
5534
+ const [, setVersion] = (0, import_react27.useState)(0);
5535
+ const bump = (0, import_react27.useCallback)(() => setVersion((v) => v + 1), []);
5536
+ const commit = (0, import_react27.useCallback)(
4971
5537
  (trackId, next, notify) => {
4972
5538
  dataRef.current = { ...dataRef.current, [trackId]: next };
4973
5539
  bump();
@@ -4975,7 +5541,7 @@ function useSoundHistory(applySound, opts = {}) {
4975
5541
  },
4976
5542
  [bump]
4977
5543
  );
4978
- const record = (0, import_react23.useCallback)(
5544
+ const record = (0, import_react27.useCallback)(
4979
5545
  (trackId, descriptor, label) => {
4980
5546
  const h = dataRef.current[trackId];
4981
5547
  const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
@@ -4990,7 +5556,7 @@ function useSoundHistory(applySound, opts = {}) {
4990
5556
  },
4991
5557
  [max, commit]
4992
5558
  );
4993
- const restoreTo = (0, import_react23.useCallback)(
5559
+ const restoreTo = (0, import_react27.useCallback)(
4994
5560
  async (trackId, index) => {
4995
5561
  const h = dataRef.current[trackId];
4996
5562
  if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
@@ -5000,7 +5566,7 @@ function useSoundHistory(applySound, opts = {}) {
5000
5566
  },
5001
5567
  [commit]
5002
5568
  );
5003
- const undo = (0, import_react23.useCallback)(
5569
+ const undo = (0, import_react27.useCallback)(
5004
5570
  (trackId) => {
5005
5571
  const h = dataRef.current[trackId];
5006
5572
  if (!h || h.cursor <= 0) return Promise.resolve(false);
@@ -5008,7 +5574,7 @@ function useSoundHistory(applySound, opts = {}) {
5008
5574
  },
5009
5575
  [restoreTo]
5010
5576
  );
5011
- const toggleFavorite = (0, import_react23.useCallback)(
5577
+ const toggleFavorite = (0, import_react27.useCallback)(
5012
5578
  (trackId, index) => {
5013
5579
  const h = dataRef.current[trackId];
5014
5580
  if (!h || index < 0 || index >= h.entries.length) return;
@@ -5017,7 +5583,7 @@ function useSoundHistory(applySound, opts = {}) {
5017
5583
  },
5018
5584
  [commit]
5019
5585
  );
5020
- const restore = (0, import_react23.useCallback)(
5586
+ const restore = (0, import_react27.useCallback)(
5021
5587
  (trackId, state) => {
5022
5588
  const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
5023
5589
  const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
@@ -5026,15 +5592,15 @@ function useSoundHistory(applySound, opts = {}) {
5026
5592
  },
5027
5593
  [commit]
5028
5594
  );
5029
- const list = (0, import_react23.useCallback)(
5595
+ const list = (0, import_react27.useCallback)(
5030
5596
  (trackId) => dataRef.current[trackId] ?? EMPTY,
5031
5597
  []
5032
5598
  );
5033
- const canUndo = (0, import_react23.useCallback)((trackId) => {
5599
+ const canUndo = (0, import_react27.useCallback)((trackId) => {
5034
5600
  const h = dataRef.current[trackId];
5035
5601
  return !!h && h.cursor > 0;
5036
5602
  }, []);
5037
- const clear = (0, import_react23.useCallback)(
5603
+ const clear = (0, import_react27.useCallback)(
5038
5604
  (trackId) => {
5039
5605
  if (dataRef.current[trackId]) {
5040
5606
  const next = { ...dataRef.current };
@@ -5046,11 +5612,11 @@ function useSoundHistory(applySound, opts = {}) {
5046
5612
  },
5047
5613
  [bump]
5048
5614
  );
5049
- const reset = (0, import_react23.useCallback)(() => {
5615
+ const reset = (0, import_react27.useCallback)(() => {
5050
5616
  dataRef.current = {};
5051
5617
  bump();
5052
5618
  }, [bump]);
5053
- return (0, import_react23.useMemo)(
5619
+ return (0, import_react27.useMemo)(
5054
5620
  () => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
5055
5621
  [record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
5056
5622
  );
@@ -5183,7 +5749,7 @@ function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
5183
5749
  }
5184
5750
 
5185
5751
  // src/panel-core/useTransitionOps.ts
5186
- var import_react24 = require("react");
5752
+ var import_react28 = require("react");
5187
5753
  function useTransitionOps({
5188
5754
  host,
5189
5755
  adapter,
@@ -5200,8 +5766,8 @@ function useTransitionOps({
5200
5766
  resolvedFades
5201
5767
  }) {
5202
5768
  const { identity } = adapter;
5203
- const appliedFadeAutomationRef = (0, import_react24.useRef)(/* @__PURE__ */ new Set());
5204
- const applyCrossfadeAutomation = (0, import_react24.useCallback)(
5769
+ const appliedFadeAutomationRef = (0, import_react28.useRef)(/* @__PURE__ */ new Set());
5770
+ const applyCrossfadeAutomation = (0, import_react28.useCallback)(
5205
5771
  async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
5206
5772
  if (host.setTrackVolumeAutomation) {
5207
5773
  const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
@@ -5218,7 +5784,7 @@ function useTransitionOps({
5218
5784
  },
5219
5785
  [host]
5220
5786
  );
5221
- const applyFadeAutomation = (0, import_react24.useCallback)(
5787
+ const applyFadeAutomation = (0, import_react28.useCallback)(
5222
5788
  async (trackId, direction, bars, bpm, sliderPos, gesture) => {
5223
5789
  if (!host.setTrackVolumeAutomation) return;
5224
5790
  const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
@@ -5227,8 +5793,8 @@ function useTransitionOps({
5227
5793
  },
5228
5794
  [host]
5229
5795
  );
5230
- const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react24.useState)(false);
5231
- const handleCreateCrossfade = (0, import_react24.useCallback)(
5796
+ const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react28.useState)(false);
5797
+ const handleCreateCrossfade = (0, import_react28.useCallback)(
5232
5798
  async (origin, target) => {
5233
5799
  const scene = activeSceneId;
5234
5800
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5356,8 +5922,8 @@ function useTransitionOps({
5356
5922
  loadTracks
5357
5923
  ]
5358
5924
  );
5359
- const [isCreatingFade, setIsCreatingFade] = (0, import_react24.useState)(false);
5360
- const handleCreateFade = (0, import_react24.useCallback)(
5925
+ const [isCreatingFade, setIsCreatingFade] = (0, import_react28.useState)(false);
5926
+ const handleCreateFade = (0, import_react28.useCallback)(
5361
5927
  async (selection, direction, gesture) => {
5362
5928
  const scene = activeSceneId;
5363
5929
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5467,7 +6033,7 @@ function useTransitionOps({
5467
6033
  loadTracks
5468
6034
  ]
5469
6035
  );
5470
- const handleCrossfadeMute = (0, import_react24.useCallback)(
6036
+ const handleCrossfadeMute = (0, import_react28.useCallback)(
5471
6037
  (pair) => {
5472
6038
  const newMuted = !pair.origin.runtimeState.muted;
5473
6039
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5482,7 +6048,7 @@ function useTransitionOps({
5482
6048
  },
5483
6049
  [host, setTracks]
5484
6050
  );
5485
- const handleCrossfadeSolo = (0, import_react24.useCallback)(
6051
+ const handleCrossfadeSolo = (0, import_react28.useCallback)(
5486
6052
  (pair) => {
5487
6053
  const newSolo = !pair.origin.runtimeState.solo;
5488
6054
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5497,7 +6063,7 @@ function useTransitionOps({
5497
6063
  },
5498
6064
  [host, setTracks]
5499
6065
  );
5500
- const handleCrossfadeDelete = (0, import_react24.useCallback)(
6066
+ const handleCrossfadeDelete = (0, import_react28.useCallback)(
5501
6067
  async (pair) => {
5502
6068
  try {
5503
6069
  for (const member of [pair.origin, pair.target]) {
@@ -5523,8 +6089,8 @@ function useTransitionOps({
5523
6089
  },
5524
6090
  [host, activeSceneId, setCrossfadePairsMeta, setTracks]
5525
6091
  );
5526
- const crossfadeSliderTimers = (0, import_react24.useRef)({});
5527
- const handleCrossfadeSlider = (0, import_react24.useCallback)(
6092
+ const crossfadeSliderTimers = (0, import_react28.useRef)({});
6093
+ const handleCrossfadeSlider = (0, import_react28.useCallback)(
5528
6094
  (pair, pos) => {
5529
6095
  setCrossfadePairsMeta(
5530
6096
  (prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
@@ -5557,7 +6123,7 @@ function useTransitionOps({
5557
6123
  },
5558
6124
  [host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
5559
6125
  );
5560
- const handleFadeDelete = (0, import_react24.useCallback)(
6126
+ const handleFadeDelete = (0, import_react28.useCallback)(
5561
6127
  async (fade) => {
5562
6128
  try {
5563
6129
  await host.deleteTrack(fade.track.handle.id);
@@ -5577,8 +6143,8 @@ function useTransitionOps({
5577
6143
  },
5578
6144
  [host, activeSceneId, setFadesMeta, setTracks]
5579
6145
  );
5580
- const fadeSliderTimers = (0, import_react24.useRef)({});
5581
- const handleFadeSlider = (0, import_react24.useCallback)(
6146
+ const fadeSliderTimers = (0, import_react28.useRef)({});
6147
+ const handleFadeSlider = (0, import_react28.useCallback)(
5582
6148
  (fade, pos) => {
5583
6149
  setFadesMeta(
5584
6150
  (prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
@@ -5608,8 +6174,8 @@ function useTransitionOps({
5608
6174
  },
5609
6175
  [host, activeSceneId, applyFadeAutomation, setFadesMeta]
5610
6176
  );
5611
- const lastResyncKeyRef = (0, import_react24.useRef)("");
5612
- (0, import_react24.useEffect)(() => {
6177
+ const lastResyncKeyRef = (0, import_react28.useRef)("");
6178
+ (0, import_react28.useEffect)(() => {
5613
6179
  if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
5614
6180
  return;
5615
6181
  }
@@ -5650,7 +6216,7 @@ function useTransitionOps({
5650
6216
  cancelled = true;
5651
6217
  };
5652
6218
  }, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
5653
- (0, import_react24.useEffect)(() => {
6219
+ (0, import_react28.useEffect)(() => {
5654
6220
  if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
5655
6221
  void (async () => {
5656
6222
  const mc = await host.getMusicalContext();
@@ -5684,7 +6250,7 @@ function useTransitionOps({
5684
6250
  }
5685
6251
 
5686
6252
  // src/panel-core/useGeneratorPanelCore.tsx
5687
- var import_jsx_runtime23 = require("react/jsx-runtime");
6253
+ var import_jsx_runtime25 = require("react/jsx-runtime");
5688
6254
  var EMPTY_PLACEHOLDERS = [];
5689
6255
  function useGeneratorPanelCore({
5690
6256
  ui,
@@ -5704,8 +6270,8 @@ function useGeneratorPanelCore({
5704
6270
  } = ui;
5705
6271
  const { identity, features } = adapter;
5706
6272
  const logTag = identity.logTag;
5707
- const adapterRef = (0, import_react25.useRef)(adapter);
5708
- (0, import_react25.useEffect)(() => {
6273
+ const adapterRef = (0, import_react29.useRef)(adapter);
6274
+ (0, import_react29.useEffect)(() => {
5709
6275
  if (adapterRef.current !== adapter) {
5710
6276
  adapterRef.current = adapter;
5711
6277
  console.warn(
@@ -5715,27 +6281,27 @@ function useGeneratorPanelCore({
5715
6281
  }, [adapter, logTag]);
5716
6282
  const supportsMeters = typeof host.getTrackLevels === "function";
5717
6283
  const trackLevels = useTrackLevels(host, isExpanded);
5718
- const [tracks, setTracks] = (0, import_react25.useState)([]);
5719
- const [isLoadingTracks, setIsLoadingTracks] = (0, import_react25.useState)(false);
5720
- const [importOpen, setImportOpen] = (0, import_react25.useState)(false);
5721
- const [soundImportTarget, setSoundImportTarget] = (0, import_react25.useState)(null);
5722
- const [designerView, setDesignerView] = (0, import_react25.useState)(false);
5723
- const [transitionSourceTotal, setTransitionSourceTotal] = (0, import_react25.useState)(0);
5724
- const [crossfadePairsMeta, setCrossfadePairsMeta] = (0, import_react25.useState)([]);
5725
- const [fadesMeta, setFadesMeta] = (0, import_react25.useState)([]);
5726
- const [genericGroupMetas, setGenericGroupMetas] = (0, import_react25.useState)({});
6284
+ const [tracks, setTracks] = (0, import_react29.useState)([]);
6285
+ const [isLoadingTracks, setIsLoadingTracks] = (0, import_react29.useState)(false);
6286
+ const [importOpen, setImportOpen] = (0, import_react29.useState)(false);
6287
+ const [soundImportTarget, setSoundImportTarget] = (0, import_react29.useState)(null);
6288
+ const [designerView, setDesignerView] = (0, import_react29.useState)(false);
6289
+ const [transitionSourceTotal, setTransitionSourceTotal] = (0, import_react29.useState)(0);
6290
+ const [crossfadePairsMeta, setCrossfadePairsMeta] = (0, import_react29.useState)([]);
6291
+ const [fadesMeta, setFadesMeta] = (0, import_react29.useState)([]);
6292
+ const [genericGroupMetas, setGenericGroupMetas] = (0, import_react29.useState)({});
5727
6293
  const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
5728
6294
  const [placeholders, , setPlaceholdersForScene] = useSceneState(
5729
6295
  activeSceneId,
5730
6296
  EMPTY_PLACEHOLDERS
5731
6297
  );
5732
- const saveTimeoutRefs = (0, import_react25.useRef)({});
5733
- const editLoadStartedRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
5734
- const [availableInstruments, setAvailableInstruments] = (0, import_react25.useState)([]);
5735
- const [instrumentsLoading, setInstrumentsLoading] = (0, import_react25.useState)(false);
5736
- const engineToDbIdRef = (0, import_react25.useRef)(/* @__PURE__ */ new Map());
5737
- const tracksLoadedForSceneRef = (0, import_react25.useRef)(null);
5738
- const persistSoundHistory = (0, import_react25.useCallback)(
6298
+ const saveTimeoutRefs = (0, import_react29.useRef)({});
6299
+ const editLoadStartedRef = (0, import_react29.useRef)(/* @__PURE__ */ new Set());
6300
+ const [availableInstruments, setAvailableInstruments] = (0, import_react29.useState)([]);
6301
+ const [instrumentsLoading, setInstrumentsLoading] = (0, import_react29.useState)(false);
6302
+ const engineToDbIdRef = (0, import_react29.useRef)(/* @__PURE__ */ new Map());
6303
+ const tracksLoadedForSceneRef = (0, import_react29.useRef)(null);
6304
+ const persistSoundHistory = (0, import_react29.useCallback)(
5739
6305
  (trackId, state) => {
5740
6306
  if (!activeSceneId) return;
5741
6307
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -5755,7 +6321,7 @@ function useGeneratorPanelCore({
5755
6321
  setItems: setTracks,
5756
6322
  getId: (t) => t.handle.dbId
5757
6323
  });
5758
- const loadTracks = (0, import_react25.useCallback)(
6324
+ const loadTracks = (0, import_react29.useCallback)(
5759
6325
  async (incremental = false) => {
5760
6326
  const sceneAtStart = activeSceneId;
5761
6327
  if (!sceneAtStart) {
@@ -5880,18 +6446,18 @@ function useGeneratorPanelCore({
5880
6446
  },
5881
6447
  [host, activeSceneId, soundHistory, adapter, logTag]
5882
6448
  );
5883
- (0, import_react25.useEffect)(() => {
6449
+ (0, import_react29.useEffect)(() => {
5884
6450
  loadTracks();
5885
6451
  }, [loadTracks]);
5886
- (0, import_react25.useEffect)(() => {
6452
+ (0, import_react29.useEffect)(() => {
5887
6453
  const map = /* @__PURE__ */ new Map();
5888
6454
  for (const t of tracks) {
5889
6455
  map.set(t.handle.id, t.handle.dbId);
5890
6456
  }
5891
6457
  engineToDbIdRef.current = map;
5892
6458
  }, [tracks]);
5893
- const loadedCompletedIdsRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
5894
- (0, import_react25.useEffect)(() => {
6459
+ const loadedCompletedIdsRef = (0, import_react29.useRef)(/* @__PURE__ */ new Set());
6460
+ (0, import_react29.useEffect)(() => {
5895
6461
  if (placeholders.length === 0) {
5896
6462
  loadedCompletedIdsRef.current.clear();
5897
6463
  return;
@@ -5910,16 +6476,16 @@ function useGeneratorPanelCore({
5910
6476
  loadTracks(true);
5911
6477
  }
5912
6478
  }, [placeholders, loadTracks, logTag]);
5913
- const adoptAndLoad = (0, import_react25.useCallback)(() => {
6479
+ const adoptAndLoad = (0, import_react29.useCallback)(() => {
5914
6480
  loadTracks(true);
5915
6481
  }, [loadTracks]);
5916
- (0, import_react25.useEffect)(() => {
6482
+ (0, import_react29.useEffect)(() => {
5917
6483
  const unsub = host.onEngineReady(() => {
5918
6484
  adoptAndLoad();
5919
6485
  });
5920
6486
  return unsub;
5921
6487
  }, [host, adoptAndLoad]);
5922
- (0, import_react25.useEffect)(() => {
6488
+ (0, import_react29.useEffect)(() => {
5923
6489
  if (typeof host.onAfterAgentMutation !== "function") return;
5924
6490
  let timer = null;
5925
6491
  const unsub = host.onAfterAgentMutation(() => {
@@ -5934,13 +6500,13 @@ function useGeneratorPanelCore({
5934
6500
  if (timer) clearTimeout(timer);
5935
6501
  };
5936
6502
  }, [host, loadTracks]);
5937
- (0, import_react25.useEffect)(() => {
6503
+ (0, import_react29.useEffect)(() => {
5938
6504
  const unsub = host.onTrackStateChange((trackId, state) => {
5939
6505
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
5940
6506
  });
5941
6507
  return unsub;
5942
6508
  }, [host]);
5943
- (0, import_react25.useEffect)(() => {
6509
+ (0, import_react29.useEffect)(() => {
5944
6510
  if (!features.bulkComposePlaceholders) return;
5945
6511
  console.log(`[${logTag}] Subscribing to composeProgress`);
5946
6512
  const unsub = host.onComposeProgress((event) => {
@@ -5974,7 +6540,7 @@ function useGeneratorPanelCore({
5974
6540
  });
5975
6541
  return unsub;
5976
6542
  }, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
5977
- (0, import_react25.useEffect)(() => {
6543
+ (0, import_react29.useEffect)(() => {
5978
6544
  const refs = saveTimeoutRefs;
5979
6545
  return () => {
5980
6546
  for (const timeout of Object.values(refs.current)) {
@@ -5982,9 +6548,9 @@ function useGeneratorPanelCore({
5982
6548
  }
5983
6549
  };
5984
6550
  }, []);
5985
- const isAddingTrackRef = (0, import_react25.useRef)(false);
5986
- const [isAddingTrack, setIsAddingTrack] = (0, import_react25.useState)(false);
5987
- const handleAddTrack = (0, import_react25.useCallback)(async () => {
6551
+ const isAddingTrackRef = (0, import_react29.useRef)(false);
6552
+ const [isAddingTrack, setIsAddingTrack] = (0, import_react29.useState)(false);
6553
+ const handleAddTrack = (0, import_react29.useCallback)(async () => {
5988
6554
  if (isAddingTrackRef.current) return;
5989
6555
  if (!activeSceneId) {
5990
6556
  host.showToast("warning", "Select SCENE");
@@ -6024,7 +6590,7 @@ function useGeneratorPanelCore({
6024
6590
  setIsAddingTrack(false);
6025
6591
  }
6026
6592
  }, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
6027
- const handlePortTrack = (0, import_react25.useCallback)(
6593
+ const handlePortTrack = (0, import_react29.useCallback)(
6028
6594
  async (sel) => {
6029
6595
  if (!activeSceneId) {
6030
6596
  host.showToast("warning", "Select SCENE");
@@ -6081,7 +6647,7 @@ function useGeneratorPanelCore({
6081
6647
  },
6082
6648
  [host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
6083
6649
  );
6084
- const handleSoundImportPick = (0, import_react25.useCallback)(
6650
+ const handleSoundImportPick = (0, import_react29.useCallback)(
6085
6651
  async (sel) => {
6086
6652
  const target = soundImportTarget;
6087
6653
  if (!target || !host.getTrackSound) {
@@ -6112,8 +6678,8 @@ function useGeneratorPanelCore({
6112
6678
  },
6113
6679
  [soundImportTarget, host, adapter, identity.familyKey, soundHistory]
6114
6680
  );
6115
- const [isExportingMidi, setIsExportingMidi] = (0, import_react25.useState)(false);
6116
- const handleExportMidi = (0, import_react25.useCallback)(async () => {
6681
+ const [isExportingMidi, setIsExportingMidi] = (0, import_react29.useState)(false);
6682
+ const handleExportMidi = (0, import_react29.useCallback)(async () => {
6117
6683
  if (isExportingMidi) return;
6118
6684
  setIsExportingMidi(true);
6119
6685
  try {
@@ -6144,10 +6710,10 @@ function useGeneratorPanelCore({
6144
6710
  const xfFromId = sceneContext?.transitionFromSceneId ?? null;
6145
6711
  const xfToId = sceneContext?.transitionToSceneId ?? null;
6146
6712
  const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
6147
- (0, import_react25.useEffect)(() => {
6713
+ (0, import_react29.useEffect)(() => {
6148
6714
  if (!canCrossfade) setDesignerView(false);
6149
6715
  }, [canCrossfade]);
6150
- (0, import_react25.useEffect)(() => {
6716
+ (0, import_react29.useEffect)(() => {
6151
6717
  if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
6152
6718
  setTransitionSourceTotal(0);
6153
6719
  return;
@@ -6163,12 +6729,12 @@ function useGeneratorPanelCore({
6163
6729
  };
6164
6730
  }, [canCrossfade, xfFromId, xfToId, host]);
6165
6731
  const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
6166
- (0, import_react25.useEffect)(() => {
6732
+ (0, import_react29.useEffect)(() => {
6167
6733
  if (!onHeaderContent) return;
6168
6734
  const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
6169
6735
  onHeaderContent(
6170
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex gap-1 items-center", children: [
6171
- features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6736
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex gap-1 items-center", children: [
6737
+ features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6172
6738
  "button",
6173
6739
  {
6174
6740
  "data-testid": `import-from-scene-${identity.familyKey}-button`,
@@ -6182,7 +6748,7 @@ function useGeneratorPanelCore({
6182
6748
  children: identity.importTrackLabel ?? "Import Track"
6183
6749
  }
6184
6750
  ),
6185
- (!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6751
+ (!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6186
6752
  "button",
6187
6753
  {
6188
6754
  "data-testid": `add-${identity.familyKey}-track-button`,
@@ -6198,7 +6764,7 @@ function useGeneratorPanelCore({
6198
6764
  children: identity.addTrackLabel ?? "Add Track"
6199
6765
  }
6200
6766
  ),
6201
- canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
6767
+ canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
6202
6768
  "button",
6203
6769
  {
6204
6770
  "data-testid": `${identity.familyKey}-view-toggle`,
@@ -6217,7 +6783,7 @@ function useGeneratorPanelCore({
6217
6783
  title: designerView ? "Back to the track list" : "Open the transition designer",
6218
6784
  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",
6219
6785
  children: [
6220
- transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6786
+ transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6221
6787
  "span",
6222
6788
  {
6223
6789
  className: "absolute inset-y-0 left-0 bg-sas-accent/25",
@@ -6225,7 +6791,7 @@ function useGeneratorPanelCore({
6225
6791
  "aria-hidden": true
6226
6792
  }
6227
6793
  ),
6228
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "relative", children: [
6794
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "relative", children: [
6229
6795
  "\u21C4 ",
6230
6796
  designerView ? "Transition" : "Tracks",
6231
6797
  transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
@@ -6256,7 +6822,7 @@ function useGeneratorPanelCore({
6256
6822
  identity,
6257
6823
  features.importTracks
6258
6824
  ]);
6259
- (0, import_react25.useEffect)(() => {
6825
+ (0, import_react29.useEffect)(() => {
6260
6826
  if (!onLoading) return;
6261
6827
  const anyGenerating = tracks.some((t) => t.isGenerating);
6262
6828
  onLoading(isLoadingTracks || anyGenerating || isBulkActive);
@@ -6264,7 +6830,7 @@ function useGeneratorPanelCore({
6264
6830
  onLoading(false);
6265
6831
  };
6266
6832
  }, [onLoading, isLoadingTracks, tracks, isBulkActive]);
6267
- const handleDeleteTrack = (0, import_react25.useCallback)(
6833
+ const handleDeleteTrack = (0, import_react29.useCallback)(
6268
6834
  async (trackId) => {
6269
6835
  try {
6270
6836
  await host.deleteTrack(trackId);
@@ -6280,7 +6846,7 @@ function useGeneratorPanelCore({
6280
6846
  },
6281
6847
  [host, activeSceneId]
6282
6848
  );
6283
- const handlePromptChange = (0, import_react25.useCallback)(
6849
+ const handlePromptChange = (0, import_react29.useCallback)(
6284
6850
  (trackId, prompt) => {
6285
6851
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
6286
6852
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -6296,7 +6862,7 @@ function useGeneratorPanelCore({
6296
6862
  },
6297
6863
  [host, activeSceneId]
6298
6864
  );
6299
- const resolvedGenericGroups = (0, import_react25.useMemo)(() => {
6865
+ const resolvedGenericGroups = (0, import_react29.useMemo)(() => {
6300
6866
  const out = {};
6301
6867
  for (const ext of adapter.groupExtensions ?? []) {
6302
6868
  out[ext.metaKey] = resolveTrackGroups(
@@ -6310,18 +6876,18 @@ function useGeneratorPanelCore({
6310
6876
  }
6311
6877
  return out;
6312
6878
  }, [adapter, genericGroupMetas, tracks]);
6313
- const genericGroupMemberDbIds = (0, import_react25.useMemo)(() => {
6879
+ const genericGroupMemberDbIds = (0, import_react29.useMemo)(() => {
6314
6880
  const s = /* @__PURE__ */ new Set();
6315
6881
  for (const r of Object.values(resolvedGenericGroups)) {
6316
6882
  for (const dbId of r.memberDbIds) s.add(dbId);
6317
6883
  }
6318
6884
  return s;
6319
6885
  }, [resolvedGenericGroups]);
6320
- const engineToDbId = (0, import_react25.useCallback)(
6886
+ const engineToDbId = (0, import_react29.useCallback)(
6321
6887
  (trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
6322
6888
  []
6323
6889
  );
6324
- const updateTrack = (0, import_react25.useCallback)(
6890
+ const updateTrack = (0, import_react29.useCallback)(
6325
6891
  (trackId, patch) => {
6326
6892
  setTracks(
6327
6893
  (prev) => prev.map(
@@ -6331,18 +6897,18 @@ function useGeneratorPanelCore({
6331
6897
  },
6332
6898
  []
6333
6899
  );
6334
- const markEditLoaded = (0, import_react25.useCallback)((trackId) => {
6900
+ const markEditLoaded = (0, import_react29.useCallback)((trackId) => {
6335
6901
  editLoadStartedRef.current.add(trackId);
6336
6902
  }, []);
6337
- const tracksRef = (0, import_react25.useRef)(tracks);
6338
- (0, import_react25.useEffect)(() => {
6903
+ const tracksRef = (0, import_react29.useRef)(tracks);
6904
+ (0, import_react29.useEffect)(() => {
6339
6905
  tracksRef.current = tracks;
6340
6906
  }, [tracks]);
6341
- const resolvedGenericGroupsRef = (0, import_react25.useRef)(resolvedGenericGroups);
6342
- (0, import_react25.useEffect)(() => {
6907
+ const resolvedGenericGroupsRef = (0, import_react29.useRef)(resolvedGenericGroups);
6908
+ (0, import_react29.useEffect)(() => {
6343
6909
  resolvedGenericGroupsRef.current = resolvedGenericGroups;
6344
6910
  }, [resolvedGenericGroups]);
6345
- const makeServices = (0, import_react25.useCallback)(() => {
6911
+ const makeServices = (0, import_react29.useCallback)(() => {
6346
6912
  return {
6347
6913
  host,
6348
6914
  activeSceneId,
@@ -6361,7 +6927,7 @@ function useGeneratorPanelCore({
6361
6927
  resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
6362
6928
  };
6363
6929
  }, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
6364
- const handleGenerate = (0, import_react25.useCallback)(
6930
+ const handleGenerate = (0, import_react29.useCallback)(
6365
6931
  async (trackId) => {
6366
6932
  const track = tracks.find((t) => t.handle.id === trackId);
6367
6933
  if (!track || !track.prompt.trim()) return;
@@ -6388,7 +6954,7 @@ function useGeneratorPanelCore({
6388
6954
  },
6389
6955
  [host, adapter, tracks, isAuthenticated, makeServices]
6390
6956
  );
6391
- const handleMuteToggle = (0, import_react25.useCallback)(
6957
+ const handleMuteToggle = (0, import_react29.useCallback)(
6392
6958
  (trackId) => {
6393
6959
  const track = tracks.find((t) => t.handle.id === trackId);
6394
6960
  if (!track) return;
@@ -6408,7 +6974,7 @@ function useGeneratorPanelCore({
6408
6974
  },
6409
6975
  [host, tracks]
6410
6976
  );
6411
- const handleSoloToggle = (0, import_react25.useCallback)(
6977
+ const handleSoloToggle = (0, import_react29.useCallback)(
6412
6978
  (trackId) => {
6413
6979
  const track = tracks.find((t) => t.handle.id === trackId);
6414
6980
  if (!track) return;
@@ -6428,7 +6994,7 @@ function useGeneratorPanelCore({
6428
6994
  },
6429
6995
  [host, tracks]
6430
6996
  );
6431
- const handleVolumeChange = (0, import_react25.useCallback)(
6997
+ const handleVolumeChange = (0, import_react29.useCallback)(
6432
6998
  (trackId, volume) => {
6433
6999
  setTracks(
6434
7000
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
@@ -6438,7 +7004,7 @@ function useGeneratorPanelCore({
6438
7004
  },
6439
7005
  [host]
6440
7006
  );
6441
- const handlePanChange = (0, import_react25.useCallback)(
7007
+ const handlePanChange = (0, import_react29.useCallback)(
6442
7008
  (trackId, pan) => {
6443
7009
  setTracks(
6444
7010
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
@@ -6448,7 +7014,7 @@ function useGeneratorPanelCore({
6448
7014
  },
6449
7015
  [host]
6450
7016
  );
6451
- const handleShuffle = (0, import_react25.useCallback)(
7017
+ const handleShuffle = (0, import_react29.useCallback)(
6452
7018
  async (trackId) => {
6453
7019
  const track = tracks.find((t) => t.handle.id === trackId);
6454
7020
  if (!track) return;
@@ -6490,7 +7056,7 @@ function useGeneratorPanelCore({
6490
7056
  },
6491
7057
  [host, adapter, tracks, soundHistory, logTag]
6492
7058
  );
6493
- const handleCopy = (0, import_react25.useCallback)(
7059
+ const handleCopy = (0, import_react29.useCallback)(
6494
7060
  async (trackId) => {
6495
7061
  try {
6496
7062
  const newHandle = await host.duplicateTrack(trackId);
@@ -6503,7 +7069,7 @@ function useGeneratorPanelCore({
6503
7069
  },
6504
7070
  [host, loadTracks]
6505
7071
  );
6506
- const handleFxToggle = (0, import_react25.useCallback)(
7072
+ const handleFxToggle = (0, import_react29.useCallback)(
6507
7073
  (trackId, category, enabled) => {
6508
7074
  setTracks(
6509
7075
  (prev) => prev.map(
@@ -6526,7 +7092,7 @@ function useGeneratorPanelCore({
6526
7092
  },
6527
7093
  [host]
6528
7094
  );
6529
- const handleFxPresetChange = (0, import_react25.useCallback)(
7095
+ const handleFxPresetChange = (0, import_react29.useCallback)(
6530
7096
  (trackId, category, presetIndex) => {
6531
7097
  setTracks(
6532
7098
  (prev) => prev.map(
@@ -6552,7 +7118,7 @@ function useGeneratorPanelCore({
6552
7118
  },
6553
7119
  [host]
6554
7120
  );
6555
- const handleFxDryWetChange = (0, import_react25.useCallback)(
7121
+ const handleFxDryWetChange = (0, import_react29.useCallback)(
6556
7122
  (trackId, category, value) => {
6557
7123
  setTracks(
6558
7124
  (prev) => prev.map(
@@ -6564,7 +7130,7 @@ function useGeneratorPanelCore({
6564
7130
  },
6565
7131
  [host]
6566
7132
  );
6567
- const toggleFxDrawer = (0, import_react25.useCallback)(
7133
+ const toggleFxDrawer = (0, import_react29.useCallback)(
6568
7134
  (trackId) => {
6569
7135
  setTracks(
6570
7136
  (prev) => prev.map((t) => {
@@ -6586,7 +7152,7 @@ function useGeneratorPanelCore({
6586
7152
  },
6587
7153
  [host, tracks]
6588
7154
  );
6589
- const loadEditNotes = (0, import_react25.useCallback)(
7155
+ const loadEditNotes = (0, import_react29.useCallback)(
6590
7156
  async (trackId) => {
6591
7157
  try {
6592
7158
  const mc = await host.getMusicalContext();
@@ -6604,7 +7170,7 @@ function useGeneratorPanelCore({
6604
7170
  },
6605
7171
  [host, logTag]
6606
7172
  );
6607
- const handleNotesChange = (0, import_react25.useCallback)(
7173
+ const handleNotesChange = (0, import_react29.useCallback)(
6608
7174
  (trackId, notes) => {
6609
7175
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
6610
7176
  const key = `edit:${trackId}`;
@@ -6634,7 +7200,7 @@ function useGeneratorPanelCore({
6634
7200
  },
6635
7201
  [host]
6636
7202
  );
6637
- const handleTabChange = (0, import_react25.useCallback)(
7203
+ const handleTabChange = (0, import_react29.useCallback)(
6638
7204
  (trackId, tab) => {
6639
7205
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
6640
7206
  if (tab === "fx") {
@@ -6659,10 +7225,10 @@ function useGeneratorPanelCore({
6659
7225
  },
6660
7226
  [host, availableInstruments.length, instrumentsLoading, loadEditNotes]
6661
7227
  );
6662
- const handleProgressChange = (0, import_react25.useCallback)((trackId, pct) => {
7228
+ const handleProgressChange = (0, import_react29.useCallback)((trackId, pct) => {
6663
7229
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
6664
7230
  }, []);
6665
- const handleToggleDrawer = (0, import_react25.useCallback)((trackId) => {
7231
+ const handleToggleDrawer = (0, import_react29.useCallback)((trackId) => {
6666
7232
  setTracks(
6667
7233
  (prev) => prev.map((t) => {
6668
7234
  if (t.handle.id !== trackId) return t;
@@ -6671,7 +7237,7 @@ function useGeneratorPanelCore({
6671
7237
  })
6672
7238
  );
6673
7239
  }, []);
6674
- const handleInstrumentSelect = (0, import_react25.useCallback)(
7240
+ const handleInstrumentSelect = (0, import_react29.useCallback)(
6675
7241
  async (trackId, pluginId) => {
6676
7242
  const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
6677
7243
  if (isDefaultInstrument) {
@@ -6724,7 +7290,7 @@ function useGeneratorPanelCore({
6724
7290
  },
6725
7291
  [host, identity.defaultInstrumentPluginId, logTag]
6726
7292
  );
6727
- const handleShowEditor = (0, import_react25.useCallback)(
7293
+ const handleShowEditor = (0, import_react29.useCallback)(
6728
7294
  async (trackId) => {
6729
7295
  try {
6730
7296
  await host.showInstrumentEditor(trackId);
@@ -6735,12 +7301,12 @@ function useGeneratorPanelCore({
6735
7301
  },
6736
7302
  [host]
6737
7303
  );
6738
- const handleBackToInstruments = (0, import_react25.useCallback)((trackId) => {
7304
+ const handleBackToInstruments = (0, import_react29.useCallback)((trackId) => {
6739
7305
  setTracks(
6740
7306
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
6741
7307
  );
6742
7308
  }, []);
6743
- const handleRefreshInstruments = (0, import_react25.useCallback)(() => {
7309
+ const handleRefreshInstruments = (0, import_react29.useCallback)(() => {
6744
7310
  setInstrumentsLoading(true);
6745
7311
  host.getAvailableInstruments().then((instruments) => {
6746
7312
  setAvailableInstruments(instruments);
@@ -6749,13 +7315,13 @@ function useGeneratorPanelCore({
6749
7315
  setInstrumentsLoading(false);
6750
7316
  });
6751
7317
  }, [host]);
6752
- const onAuditionNote = (0, import_react25.useCallback)(
7318
+ const onAuditionNote = (0, import_react29.useCallback)(
6753
7319
  (trackId, pitch, velocity, ms) => {
6754
7320
  void host.auditionNote(trackId, pitch, velocity, ms);
6755
7321
  },
6756
7322
  [host]
6757
7323
  );
6758
- const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react25.useMemo)(() => {
7324
+ const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react29.useMemo)(() => {
6759
7325
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
6760
7326
  const pairs = [];
6761
7327
  const members = /* @__PURE__ */ new Set();
@@ -6770,7 +7336,7 @@ function useGeneratorPanelCore({
6770
7336
  }
6771
7337
  return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
6772
7338
  }, [tracks, crossfadePairsMeta]);
6773
- const { resolvedFades, fadeMemberDbIds } = (0, import_react25.useMemo)(() => {
7339
+ const { resolvedFades, fadeMemberDbIds } = (0, import_react29.useMemo)(() => {
6774
7340
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
6775
7341
  const list = [];
6776
7342
  const members = /* @__PURE__ */ new Set();
@@ -6798,7 +7364,7 @@ function useGeneratorPanelCore({
6798
7364
  resolvedCrossfadePairs,
6799
7365
  resolvedFades
6800
7366
  });
6801
- const setGroupMute = (0, import_react25.useCallback)(
7367
+ const setGroupMute = (0, import_react29.useCallback)(
6802
7368
  (trackIds, muted) => {
6803
7369
  for (const id of trackIds) {
6804
7370
  setTracks(
@@ -6810,7 +7376,7 @@ function useGeneratorPanelCore({
6810
7376
  },
6811
7377
  [host]
6812
7378
  );
6813
- const setGroupSolo = (0, import_react25.useCallback)(
7379
+ const setGroupSolo = (0, import_react29.useCallback)(
6814
7380
  (trackIds, solo) => {
6815
7381
  for (const id of trackIds) {
6816
7382
  setTracks(
@@ -6822,7 +7388,7 @@ function useGeneratorPanelCore({
6822
7388
  },
6823
7389
  [host]
6824
7390
  );
6825
- const deleteGroup = (0, import_react25.useCallback)(
7391
+ const deleteGroup = (0, import_react29.useCallback)(
6826
7392
  async (members, cleanupKeySuffixes) => {
6827
7393
  for (const member of members) {
6828
7394
  try {
@@ -6842,7 +7408,7 @@ function useGeneratorPanelCore({
6842
7408
  },
6843
7409
  [host, activeSceneId, loadTracks]
6844
7410
  );
6845
- const handlers = (0, import_react25.useMemo)(
7411
+ const handlers = (0, import_react29.useMemo)(
6846
7412
  () => ({
6847
7413
  promptChange: handlePromptChange,
6848
7414
  generate: (trackId) => {
@@ -6956,8 +7522,8 @@ function useGeneratorPanelCore({
6956
7522
  }
6957
7523
 
6958
7524
  // src/panel-core/GeneratorPanelShell.tsx
6959
- var import_react26 = __toESM(require("react"));
6960
- var import_jsx_runtime24 = require("react/jsx-runtime");
7525
+ var import_react30 = __toESM(require("react"));
7526
+ var import_jsx_runtime26 = require("react/jsx-runtime");
6961
7527
  function GeneratorPanelShell({ core, slots }) {
6962
7528
  const {
6963
7529
  ui,
@@ -7010,8 +7576,9 @@ function GeneratorPanelShell({ core, slots }) {
7010
7576
  deleteGroup
7011
7577
  } = core;
7012
7578
  const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
7579
+ const panelBus = usePanelBus(host, activeSceneId);
7013
7580
  const { identity, features } = adapter;
7014
- const buildRowProps = (0, import_react26.useCallback)(
7581
+ const buildRowProps = (0, import_react30.useCallback)(
7015
7582
  (track, drag) => {
7016
7583
  const id = track.handle.id;
7017
7584
  const pickerProps = features.instrumentPicker ? {
@@ -7063,6 +7630,7 @@ function GeneratorPanelShell({ core, slots }) {
7063
7630
  onVolumeChange: (vol) => handlers.volumeChange(id, vol),
7064
7631
  onPanChange: (pan) => handlers.panChange(id, pan),
7065
7632
  onFxToggle: (cat, enabled) => handleFxToggle(id, cat, enabled),
7633
+ externalFxHost: host,
7066
7634
  onFxPresetChange: (cat, idx) => handleFxPresetChange(id, cat, idx),
7067
7635
  onFxDryWetChange: (cat, val) => handleFxDryWetChange(id, cat, val),
7068
7636
  onToggleFxDrawer: () => handlers.toggleFxDrawer(id),
@@ -7110,12 +7678,12 @@ function GeneratorPanelShell({ core, slots }) {
7110
7678
  ]
7111
7679
  );
7112
7680
  if (!activeSceneId) {
7113
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7681
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7114
7682
  "div",
7115
7683
  {
7116
7684
  "data-testid": `no-scene-placeholder-${identity.familyKey}`,
7117
7685
  className: "flex items-center justify-center py-8",
7118
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7686
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7119
7687
  "button",
7120
7688
  {
7121
7689
  onClick: () => onSelectScene?.(),
@@ -7127,12 +7695,12 @@ function GeneratorPanelShell({ core, slots }) {
7127
7695
  );
7128
7696
  }
7129
7697
  if (!sceneContext?.hasContract) {
7130
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7698
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7131
7699
  "div",
7132
7700
  {
7133
7701
  "data-testid": `no-contract-placeholder-${identity.familyKey}`,
7134
7702
  className: "flex items-center justify-center py-8",
7135
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7703
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7136
7704
  "button",
7137
7705
  {
7138
7706
  onClick: () => onOpenContract?.(),
@@ -7144,7 +7712,7 @@ function GeneratorPanelShell({ core, slots }) {
7144
7712
  );
7145
7713
  }
7146
7714
  if (features.bulkComposePlaceholders && isComposing) {
7147
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
7715
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
7148
7716
  }
7149
7717
  const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
7150
7718
  if (activePlaceholders.length > 0) {
@@ -7155,18 +7723,18 @@ function GeneratorPanelShell({ core, slots }) {
7155
7723
  tracksByDbId.set(t.handle.id, t);
7156
7724
  }
7157
7725
  }
7158
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
7726
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
7159
7727
  const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
7160
7728
  if (loadedTrack) {
7161
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7729
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7162
7730
  }
7163
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7731
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7164
7732
  "div",
7165
7733
  {
7166
7734
  "data-testid": "bulk-placeholder-track",
7167
7735
  className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
7168
7736
  style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
7169
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
7737
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
7170
7738
  },
7171
7739
  ph.id
7172
7740
  );
@@ -7178,13 +7746,13 @@ function GeneratorPanelShell({ core, slots }) {
7178
7746
  supportsMeters,
7179
7747
  levels: supportsMeters ? trackLevels : void 0,
7180
7748
  handlers,
7181
- renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
7749
+ renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
7182
7750
  setGroupMute,
7183
7751
  setGroupSolo,
7184
7752
  deleteGroup
7185
7753
  };
7186
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
7187
- features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7754
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
7755
+ features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7188
7756
  ImportTrackModal,
7189
7757
  {
7190
7758
  host,
@@ -7197,7 +7765,7 @@ function GeneratorPanelShell({ core, slots }) {
7197
7765
  testIdPrefix: `${identity.familyKey}-import`
7198
7766
  }
7199
7767
  ),
7200
- features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7768
+ features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7201
7769
  ImportTrackModal,
7202
7770
  {
7203
7771
  host,
@@ -7212,7 +7780,7 @@ function GeneratorPanelShell({ core, slots }) {
7212
7780
  }
7213
7781
  ),
7214
7782
  slots?.modals,
7215
- canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7783
+ canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7216
7784
  TransitionDesigner,
7217
7785
  {
7218
7786
  host,
@@ -7229,9 +7797,29 @@ function GeneratorPanelShell({ core, slots }) {
7229
7797
  testIdPrefix: `${identity.familyKey}-transition-designer`
7230
7798
  }
7231
7799
  ) }),
7232
- !(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
7800
+ !(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
7801
+ panelBus.supported && panelBus.bus && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7802
+ PanelMasterStrip,
7803
+ {
7804
+ bus: panelBus.bus,
7805
+ levels: panelBus.levels,
7806
+ availableFx: panelBus.availableFx,
7807
+ fxLoading: panelBus.fxLoading,
7808
+ soloedOut: anySolo && !panelBus.bus.soloed,
7809
+ fxPickerOpen: panelBus.fxPickerOpen,
7810
+ onToggleFxPicker: panelBus.setFxPickerOpen,
7811
+ onRefreshFx: panelBus.refreshFx,
7812
+ onVolumeChange: panelBus.onVolumeChange,
7813
+ onMuteToggle: panelBus.onMuteToggle,
7814
+ onSoloToggle: panelBus.onSoloToggle,
7815
+ onAddFx: panelBus.onAddFx,
7816
+ onRemoveFx: panelBus.onRemoveFx,
7817
+ onToggleFxEnabled: panelBus.onToggleFxEnabled,
7818
+ onShowFxEditor: panelBus.onShowFxEditor
7819
+ }
7820
+ ),
7233
7821
  slots?.beforeRows,
7234
- resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7822
+ resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7235
7823
  CrossfadeTrackRow,
7236
7824
  {
7237
7825
  accentColor: identity.transitionAccentColor,
@@ -7268,7 +7856,7 @@ function GeneratorPanelShell({ core, slots }) {
7268
7856
  },
7269
7857
  pair.groupId
7270
7858
  )),
7271
- resolvedFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7859
+ resolvedFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7272
7860
  FadeTrackRow,
7273
7861
  {
7274
7862
  accentColor: identity.transitionAccentColor,
@@ -7294,20 +7882,20 @@ function GeneratorPanelShell({ core, slots }) {
7294
7882
  fade.dbId
7295
7883
  )),
7296
7884
  (adapter.groupExtensions ?? []).flatMap(
7297
- (ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react26.default.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
7885
+ (ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react30.default.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
7298
7886
  ),
7299
7887
  tracks.map((track, index) => {
7300
7888
  if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
7301
7889
  return null;
7302
7890
  }
7303
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
7891
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
7304
7892
  }),
7305
7893
  slots?.afterRows
7306
7894
  ] })),
7307
7895
  features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
7308
7896
  const hasAnyMidi = tracks.some((t) => t.hasMidi);
7309
7897
  const exportDisabled = isExportingMidi || !hasAnyMidi;
7310
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7898
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7311
7899
  "button",
7312
7900
  {
7313
7901
  "data-testid": "export-midi-tracks-button",
@@ -7375,7 +7963,7 @@ function createSurgeSoundAdapter(host, overrides = {}) {
7375
7963
  }
7376
7964
 
7377
7965
  // src/constants/sdk-version.ts
7378
- var PLUGIN_SDK_VERSION = "2.35.0";
7966
+ var PLUGIN_SDK_VERSION = "2.39.0";
7379
7967
 
7380
7968
  // src/utils/format-concurrent-tracks.ts
7381
7969
  function formatConcurrentTracks(ctx) {
@@ -7551,6 +8139,7 @@ function pickTopKWeighted(scored, options = {}) {
7551
8139
  PLUGIN_SDK_VERSION,
7552
8140
  PX_PER_BEAT,
7553
8141
  PanSlider,
8142
+ PanelMasterStrip,
7554
8143
  PianoRollEditor,
7555
8144
  PluginError,
7556
8145
  RESIZE_HANDLE_PX,
@@ -7562,6 +8151,7 @@ function pickTopKWeighted(scored, options = {}) {
7562
8151
  TEXTURAL_ROLES,
7563
8152
  TRANSITION_DESIGNER_DRAFT_KEY,
7564
8153
  TrackDrawer,
8154
+ TrackExternalFxSection,
7565
8155
  TrackMeterStrip,
7566
8156
  TrackRow,
7567
8157
  TransitionDesigner,
@@ -7615,8 +8205,10 @@ function pickTopKWeighted(scored, options = {}) {
7615
8205
  transposeNotes,
7616
8206
  useAnySolo,
7617
8207
  useGeneratorPanelCore,
8208
+ usePanelBus,
7618
8209
  useSceneState,
7619
8210
  useSoundHistory,
8211
+ useTrackExternalFx,
7620
8212
  useTrackLevel,
7621
8213
  useTrackLevels,
7622
8214
  useTrackMeter,