@signalsandsorcery/plugin-sdk 2.35.2 → 2.35.4

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
@@ -74,6 +74,7 @@ __export(index_exports, {
74
74
  TEXTURAL_ROLES: () => TEXTURAL_ROLES,
75
75
  TRANSITION_DESIGNER_DRAFT_KEY: () => TRANSITION_DESIGNER_DRAFT_KEY,
76
76
  TrackDrawer: () => TrackDrawer,
77
+ TrackExternalFxSection: () => TrackExternalFxSection,
77
78
  TrackMeterStrip: () => TrackMeterStrip,
78
79
  TrackRow: () => TrackRow,
79
80
  TransitionDesigner: () => TransitionDesigner,
@@ -130,6 +131,7 @@ __export(index_exports, {
130
131
  usePanelBus: () => usePanelBus,
131
132
  useSceneState: () => useSceneState,
132
133
  useSoundHistory: () => useSoundHistory,
134
+ useTrackExternalFx: () => useTrackExternalFx,
133
135
  useTrackLevel: () => useTrackLevel,
134
136
  useTrackLevels: () => useTrackLevels,
135
137
  useTrackMeter: () => useTrackMeter,
@@ -206,11 +208,11 @@ var EMPTY_FX_DETAIL_STATE = {
206
208
  };
207
209
 
208
210
  // src/components/TrackRow.tsx
209
- var import_react9 = __toESM(require("react"));
211
+ var import_react11 = __toESM(require("react"));
210
212
  var import_lucide_react = require("lucide-react");
211
213
 
212
214
  // src/components/TrackDrawer.tsx
213
- var import_react2 = require("react");
215
+ var import_react4 = require("react");
214
216
 
215
217
  // src/constants/fx-presets.ts
216
218
  var EQ_PRESETS = {
@@ -930,8 +932,250 @@ function PianoRollEditor({
930
932
  ] });
931
933
  }
932
934
 
933
- // 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 (opts) => {
967
+ if (!supported || !host.getAvailableFx) return;
968
+ if (fxLoadedRef.current && !opts.force && !opts.rescan) return;
969
+ setFxLoading(true);
970
+ try {
971
+ const list = opts.rescan && host.rescanAvailableFx ? await host.rescanAvailableFx() : 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({});
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({ rescan: 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
934
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 \u2014 picks up newly installed FX and retries any that failed a previous scan",
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");
935
1179
  var TAB_LABELS = {
936
1180
  fx: "FX",
937
1181
  pick: "Pick",
@@ -948,6 +1192,7 @@ function TrackDrawer({
948
1192
  onFxPresetChange,
949
1193
  onFxDryWetChange,
950
1194
  fxDisabled = false,
1195
+ externalFxHost,
951
1196
  instruments = [],
952
1197
  currentPluginId = null,
953
1198
  isLoading = false,
@@ -970,13 +1215,13 @@ function TrackDrawer({
970
1215
  editSnap,
971
1216
  onAuditionNote
972
1217
  }) {
973
- const [search, setSearch] = (0, import_react2.useState)("");
1218
+ const [search, setSearch] = (0, import_react4.useState)("");
974
1219
  const fxEnabled = !!onFxToggle;
975
1220
  const pickEnabled = !!onSelect;
976
1221
  const historyEnabled = !!onRestoreSound;
977
1222
  const importEnabled = !!onImportSound;
978
1223
  const editEnabled = !!onNotesChange;
979
- const enabledTabs = (0, import_react2.useMemo)(() => {
1224
+ const enabledTabs = (0, import_react4.useMemo)(() => {
980
1225
  const tabs = [];
981
1226
  if (fxEnabled) tabs.push("fx");
982
1227
  if (pickEnabled) tabs.push("pick");
@@ -986,7 +1231,7 @@ function TrackDrawer({
986
1231
  return tabs;
987
1232
  }, [fxEnabled, pickEnabled, historyEnabled, importEnabled, editEnabled]);
988
1233
  const SURGE_XT_DEFAULT_ID = "Surge XT";
989
- const filtered = (0, import_react2.useMemo)(() => {
1234
+ const filtered = (0, import_react4.useMemo)(() => {
990
1235
  let all = instruments.filter((i) => i.name !== "Surge XT");
991
1236
  if (search.trim()) {
992
1237
  const q = search.toLowerCase();
@@ -1006,12 +1251,12 @@ function TrackDrawer({
1006
1251
  const history = soundHistory ?? [];
1007
1252
  const effectiveTab = enabledTabs.includes(activeTab) ? activeTab : enabledTabs[0] ?? "fx";
1008
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"}`;
1009
- const strip = enabledTabs.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1254
+ const strip = enabledTabs.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1010
1255
  "div",
1011
1256
  {
1012
1257
  className: "flex items-center gap-1 border-b border-sas-border pb-1",
1013
1258
  "data-testid": "sdk-drawer-tabs",
1014
- children: enabledTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1259
+ children: enabledTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1015
1260
  "button",
1016
1261
  {
1017
1262
  type: "button",
@@ -1025,9 +1270,9 @@ function TrackDrawer({
1025
1270
  }
1026
1271
  ) : null;
1027
1272
  const currentSound = soundHistoryCursor >= 0 && soundHistoryCursor < history.length ? history[soundHistoryCursor].label : null;
1028
- 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: [
1029
1274
  strip,
1030
- currentSound && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1275
+ currentSound && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1031
1276
  "span",
1032
1277
  {
1033
1278
  className: "text-[10px] text-sas-muted/60 truncate px-0.5",
@@ -1037,9 +1282,9 @@ function TrackDrawer({
1037
1282
  )
1038
1283
  ] }) : null;
1039
1284
  if (effectiveTab === "edit") {
1040
- 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: [
1041
1286
  header,
1042
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1287
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1043
1288
  PianoRollEditor,
1044
1289
  {
1045
1290
  notes: editNotes ?? [],
@@ -1054,9 +1299,9 @@ function TrackDrawer({
1054
1299
  ] });
1055
1300
  }
1056
1301
  if (effectiveTab === "fx") {
1057
- 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: [
1058
1303
  header,
1059
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1304
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1060
1305
  FxToggleBar,
1061
1306
  {
1062
1307
  trackId,
@@ -1066,20 +1311,21 @@ function TrackDrawer({
1066
1311
  onDryWetChange: (_t, category, value) => onFxDryWetChange?.(category, value),
1067
1312
  disabled: fxDisabled
1068
1313
  }
1069
- )
1314
+ ),
1315
+ externalFxHost && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TrackExternalFxSection, { host: externalFxHost, trackId, disabled: fxDisabled })
1070
1316
  ] });
1071
1317
  }
1072
1318
  if (effectiveTab === "import") {
1073
1319
  const soundNoun = /preset/i.test(importSoundLabel ?? "") ? "preset" : /sample/i.test(importSoundLabel ?? "") ? "sample" : "sound";
1074
- 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: [
1075
1321
  header,
1076
- /* @__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: [
1077
1323
  "Copy the sound from a matching track in another scene \u2014 your MIDI stays, only the",
1078
1324
  " ",
1079
1325
  soundNoun,
1080
1326
  " changes."
1081
1327
  ] }),
1082
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1328
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1083
1329
  "button",
1084
1330
  {
1085
1331
  type: "button",
@@ -1097,16 +1343,16 @@ function TrackDrawer({
1097
1343
  }
1098
1344
  if (effectiveTab === "history") {
1099
1345
  const order = history.map((_, i) => i).reverse();
1100
- 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: [
1101
1347
  header,
1102
- history.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1348
+ history.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1103
1349
  "div",
1104
1350
  {
1105
1351
  className: "text-xs text-sas-muted/60 text-center py-3",
1106
1352
  "data-testid": "sdk-history-empty",
1107
1353
  children: "No sounds yet \u2014 shuffle to build history."
1108
1354
  }
1109
- ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1355
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1110
1356
  "ul",
1111
1357
  {
1112
1358
  className: "flex flex-col gap-1 max-h-[160px] overflow-y-auto",
@@ -1114,8 +1360,8 @@ function TrackDrawer({
1114
1360
  children: order.map((i) => {
1115
1361
  const entry = history[i];
1116
1362
  const isCurrent = i === soundHistoryCursor;
1117
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "flex items-center gap-1", children: [
1118
- /* @__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)(
1119
1365
  "button",
1120
1366
  {
1121
1367
  type: "button",
@@ -1125,12 +1371,12 @@ function TrackDrawer({
1125
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"}`,
1126
1372
  title: isCurrent ? "Current sound" : `Restore: ${entry.label}`,
1127
1373
  children: [
1128
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "truncate", children: entry.label }),
1129
- /* @__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" })
1130
1376
  ]
1131
1377
  }
1132
1378
  ),
1133
- onToggleFavorite && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1379
+ onToggleFavorite && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1134
1380
  "button",
1135
1381
  {
1136
1382
  type: "button",
@@ -1148,10 +1394,10 @@ function TrackDrawer({
1148
1394
  ] });
1149
1395
  }
1150
1396
  if (effectiveTab === "pick" && editorStage) {
1151
- 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: [
1152
1398
  header,
1153
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
1154
- /* @__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)(
1155
1401
  "button",
1156
1402
  {
1157
1403
  onClick: () => onBackToInstruments?.(),
@@ -1159,9 +1405,9 @@ function TrackDrawer({
1159
1405
  children: "\u2190 Back"
1160
1406
  }
1161
1407
  ),
1162
- /* @__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" })
1163
1409
  ] }),
1164
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1410
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1165
1411
  "button",
1166
1412
  {
1167
1413
  onClick: () => onShowEditor?.(),
@@ -1173,10 +1419,10 @@ function TrackDrawer({
1173
1419
  }
1174
1420
  const isDefaultSelected = currentPluginId === null;
1175
1421
  const isSelected = (pluginId) => pluginId === currentPluginId;
1176
- 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: [
1177
1423
  header,
1178
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
1179
- /* @__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)(
1180
1426
  "input",
1181
1427
  {
1182
1428
  type: "text",
@@ -1186,7 +1432,7 @@ function TrackDrawer({
1186
1432
  className: "sas-input flex-1 px-2 py-1 text-xs"
1187
1433
  }
1188
1434
  ),
1189
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1435
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1190
1436
  "button",
1191
1437
  {
1192
1438
  onClick: () => onRefresh?.(),
@@ -1197,54 +1443,54 @@ function TrackDrawer({
1197
1443
  }
1198
1444
  )
1199
1445
  ] }),
1200
- 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: [
1201
- /* @__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)(
1202
1448
  "button",
1203
1449
  {
1204
1450
  onClick: () => onSelect?.(SURGE_XT_DEFAULT_ID),
1205
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"}`,
1206
1452
  title: "Surge XT \u2014 Default instrument",
1207
1453
  children: [
1208
- /* @__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: [
1209
1455
  isDefaultSelected && "\u2713 ",
1210
1456
  "Surge XT"
1211
1457
  ] }),
1212
- /* @__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" })
1213
1459
  ]
1214
1460
  },
1215
1461
  "__surge-xt-default__"
1216
1462
  ),
1217
1463
  filtered.map((inst) => {
1218
1464
  const selected = isSelected(inst.pluginId);
1219
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1465
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1220
1466
  "button",
1221
1467
  {
1222
1468
  onClick: () => onSelect?.(inst.pluginId),
1223
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"}`,
1224
1470
  title: `${inst.name} by ${inst.manufacturer} (${inst.type.toUpperCase()})${inst.missing ? " \u2014 MISSING" : ""}`,
1225
1471
  children: [
1226
- /* @__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: [
1227
1473
  selected && "\u2713 ",
1228
1474
  inst.name
1229
1475
  ] }),
1230
- /* @__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() })
1231
1477
  ]
1232
1478
  },
1233
1479
  inst.pluginId
1234
1480
  );
1235
1481
  }),
1236
- 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" })
1237
1483
  ] })
1238
1484
  ] });
1239
1485
  }
1240
1486
 
1241
1487
  // src/components/ConfirmDialog.tsx
1242
- var import_react4 = require("react");
1488
+ var import_react6 = require("react");
1243
1489
 
1244
1490
  // src/components/Modal.tsx
1245
- var import_react3 = require("react");
1491
+ var import_react5 = require("react");
1246
1492
  var import_react_dom = require("react-dom");
1247
- var import_jsx_runtime4 = require("react/jsx-runtime");
1493
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1248
1494
  function Modal({
1249
1495
  open,
1250
1496
  onClose,
@@ -1254,7 +1500,7 @@ function Modal({
1254
1500
  closeOnEscape = true,
1255
1501
  initialFocusRef
1256
1502
  }) {
1257
- (0, import_react3.useEffect)(() => {
1503
+ (0, import_react5.useEffect)(() => {
1258
1504
  if (!open) return void 0;
1259
1505
  const onKey = (e) => {
1260
1506
  if (closeOnEscape && e.key === "Escape") {
@@ -1268,7 +1514,7 @@ function Modal({
1268
1514
  }, [open, onClose, closeOnEscape, initialFocusRef]);
1269
1515
  if (!open) return null;
1270
1516
  return (0, import_react_dom.createPortal)(
1271
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1517
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1272
1518
  "div",
1273
1519
  {
1274
1520
  className: "fixed inset-0 z-[1000] flex items-center justify-center bg-black/60",
@@ -1282,7 +1528,7 @@ function Modal({
1282
1528
  }
1283
1529
 
1284
1530
  // src/components/ConfirmDialog.tsx
1285
- var import_jsx_runtime5 = require("react/jsx-runtime");
1531
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1286
1532
  function ConfirmDialog({
1287
1533
  open,
1288
1534
  title,
@@ -1294,8 +1540,8 @@ function ConfirmDialog({
1294
1540
  onCancel,
1295
1541
  testIdPrefix = "confirm-dialog"
1296
1542
  }) {
1297
- const cancelRef = (0, import_react4.useRef)(null);
1298
- 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)(
1299
1545
  "div",
1300
1546
  {
1301
1547
  className: "w-[360px] max-w-[90vw] flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
@@ -1305,8 +1551,8 @@ function ConfirmDialog({
1305
1551
  "aria-label": title,
1306
1552
  "data-testid": `${testIdPrefix}-modal`,
1307
1553
  children: [
1308
- /* @__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 }) }),
1309
- /* @__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)(
1310
1556
  "div",
1311
1557
  {
1312
1558
  className: "px-4 py-3 text-xs text-sas-muted leading-relaxed break-words",
@@ -1314,8 +1560,8 @@ function ConfirmDialog({
1314
1560
  children: message
1315
1561
  }
1316
1562
  ),
1317
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex justify-end gap-2 px-4 py-3 border-t border-sas-border", children: [
1318
- /* @__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)(
1319
1565
  "button",
1320
1566
  {
1321
1567
  ref: cancelRef,
@@ -1326,7 +1572,7 @@ function ConfirmDialog({
1326
1572
  children: cancelLabel
1327
1573
  }
1328
1574
  ),
1329
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1575
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1330
1576
  "button",
1331
1577
  {
1332
1578
  type: "button",
@@ -1343,7 +1589,7 @@ function ConfirmDialog({
1343
1589
  }
1344
1590
 
1345
1591
  // src/components/LevelMeter.tsx
1346
- var import_jsx_runtime6 = require("react/jsx-runtime");
1592
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1347
1593
  var COLOR_GREEN = "#2BD576";
1348
1594
  var COLOR_ORANGE = "#F5A623";
1349
1595
  var COLOR_RED = "#FF4D5E";
@@ -1371,7 +1617,7 @@ var LevelMeter = ({
1371
1617
  const widthPct = active ? dbToPct(peakDb) : 0;
1372
1618
  const showPeak = peakHoldDb != null && active && peakHoldDb > -60;
1373
1619
  const peakHoldPct = showPeak ? dbToPct(peakHoldDb) : 0;
1374
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1620
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1375
1621
  "div",
1376
1622
  {
1377
1623
  className: `sas-level-meter ${className ?? ""}`,
@@ -1382,7 +1628,7 @@ var LevelMeter = ({
1382
1628
  gap: compact ? 0 : 6
1383
1629
  },
1384
1630
  children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1631
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1386
1632
  "div",
1387
1633
  {
1388
1634
  style: {
@@ -1396,8 +1642,8 @@ var LevelMeter = ({
1396
1642
  minWidth: compact ? 0 : 60
1397
1643
  },
1398
1644
  children: [
1399
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { position: "absolute", inset: 0, background: METER_GRADIENT } }),
1400
- /* @__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)(
1401
1647
  "div",
1402
1648
  {
1403
1649
  style: {
@@ -1411,7 +1657,7 @@ var LevelMeter = ({
1411
1657
  }
1412
1658
  }
1413
1659
  ),
1414
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1660
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1415
1661
  "div",
1416
1662
  {
1417
1663
  "data-testid": `${id}-segments`,
@@ -1424,7 +1670,7 @@ var LevelMeter = ({
1424
1670
  }
1425
1671
  }
1426
1672
  ),
1427
- showPeak && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1673
+ showPeak && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1428
1674
  "div",
1429
1675
  {
1430
1676
  "data-testid": `${id}-peak`,
@@ -1445,7 +1691,7 @@ var LevelMeter = ({
1445
1691
  ]
1446
1692
  }
1447
1693
  ),
1448
- !compact && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1694
+ !compact && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1449
1695
  "span",
1450
1696
  {
1451
1697
  style: {
@@ -1458,7 +1704,7 @@ var LevelMeter = ({
1458
1704
  children: active && peakDb > -120 ? `${peakDb.toFixed(0)} dB` : "\u2014"
1459
1705
  }
1460
1706
  ),
1461
- clipped && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1707
+ clipped && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1462
1708
  "span",
1463
1709
  {
1464
1710
  "data-testid": `${id}-clip`,
@@ -1483,7 +1729,7 @@ var LevelMeter = ({
1483
1729
  };
1484
1730
 
1485
1731
  // src/hooks/useTrackLevels.ts
1486
- var import_react5 = require("react");
1732
+ var import_react7 = require("react");
1487
1733
  var meterDiagRLast = /* @__PURE__ */ new Map();
1488
1734
  var POLL_INTERVAL_MS = 33;
1489
1735
  var HIDDEN_RECHECK_MS = 250;
@@ -1494,9 +1740,9 @@ function isHidden() {
1494
1740
  return typeof document !== "undefined" && document.hidden === true;
1495
1741
  }
1496
1742
  function useTrackLevels(host, enabled = true) {
1497
- const mapRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1498
- const listenersRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
1499
- 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);
1500
1746
  if (handleRef.current === null) {
1501
1747
  handleRef.current = {
1502
1748
  getLevel: (trackId) => mapRef.current.get(trackId) ?? null,
@@ -1508,7 +1754,7 @@ function useTrackLevels(host, enabled = true) {
1508
1754
  }
1509
1755
  };
1510
1756
  }
1511
- (0, import_react5.useEffect)(() => {
1757
+ (0, import_react7.useEffect)(() => {
1512
1758
  const notify = () => {
1513
1759
  listenersRef.current.forEach((l) => l());
1514
1760
  };
@@ -1578,8 +1824,8 @@ function sameLevel(a, b) {
1578
1824
  return a.peakDb === b.peakDb && a.clipped === b.clipped;
1579
1825
  }
1580
1826
  function useTrackLevel(handle, trackId) {
1581
- const [level, setLevel] = (0, import_react5.useState)(null);
1582
- (0, import_react5.useEffect)(() => {
1827
+ const [level, setLevel] = (0, import_react7.useState)(null);
1828
+ (0, import_react7.useEffect)(() => {
1583
1829
  if (!handle) {
1584
1830
  setLevel(null);
1585
1831
  return;
@@ -1603,11 +1849,11 @@ function sameMeter(a, b) {
1603
1849
  return a.active === b.active && a.clipped === b.clipped && a.peakDb === b.peakDb && Math.round(a.peakHoldDb * 2) === Math.round(b.peakHoldDb * 2);
1604
1850
  }
1605
1851
  function useTrackMeter(handle, trackId) {
1606
- const [view, setView] = (0, import_react5.useState)(IDLE_METER_VIEW);
1607
- const heldDbRef = (0, import_react5.useRef)(METER_FLOOR_DB);
1608
- const heldAtRef = (0, import_react5.useRef)(0);
1609
- const lastTickRef = (0, import_react5.useRef)(0);
1610
- (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)(() => {
1611
1857
  if (!handle) {
1612
1858
  heldDbRef.current = METER_FLOOR_DB;
1613
1859
  lastTickRef.current = 0;
@@ -1650,8 +1896,8 @@ function useTrackMeter(handle, trackId) {
1650
1896
  return view;
1651
1897
  }
1652
1898
  function useTransportPlaying(host) {
1653
- const [playing, setPlaying] = (0, import_react5.useState)(false);
1654
- (0, import_react5.useEffect)(() => {
1899
+ const [playing, setPlaying] = (0, import_react7.useState)(false);
1900
+ (0, import_react7.useEffect)(() => {
1655
1901
  if (!host) {
1656
1902
  setPlaying(false);
1657
1903
  return;
@@ -1679,7 +1925,7 @@ function useTransportPlaying(host) {
1679
1925
  }
1680
1926
 
1681
1927
  // src/components/TrackMeterStrip.tsx
1682
- var import_jsx_runtime7 = require("react/jsx-runtime");
1928
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1683
1929
  var TrackMeterStrip = ({
1684
1930
  levels,
1685
1931
  trackId,
@@ -1687,12 +1933,12 @@ var TrackMeterStrip = ({
1687
1933
  className
1688
1934
  }) => {
1689
1935
  const meter = useTrackMeter(levels, trackId);
1690
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1936
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1691
1937
  "div",
1692
1938
  {
1693
1939
  "data-testid": "sdk-track-meter",
1694
1940
  className: `w-full px-2 py-1 bg-sas-panel-alt border border-t-0 border-sas-border ${roundBottom ? "rounded-b-sm" : ""} ${className ?? ""}`,
1695
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1941
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1696
1942
  LevelMeter,
1697
1943
  {
1698
1944
  compact: true,
@@ -1708,7 +1954,7 @@ var TrackMeterStrip = ({
1708
1954
  };
1709
1955
 
1710
1956
  // src/components/VolumeSlider.tsx
1711
- var import_react6 = require("react");
1957
+ var import_react8 = require("react");
1712
1958
 
1713
1959
  // src/utils/volume-conversion.ts
1714
1960
  var SLIDER_UNITY = 0.75;
@@ -1730,7 +1976,7 @@ function dbToSlider(db) {
1730
1976
  }
1731
1977
 
1732
1978
  // src/components/VolumeSlider.tsx
1733
- var import_jsx_runtime8 = require("react/jsx-runtime");
1979
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1734
1980
  function formatDb(value) {
1735
1981
  const db = sliderToDb(value);
1736
1982
  if (db <= -60) return "-\u221E dB";
@@ -1738,12 +1984,12 @@ function formatDb(value) {
1738
1984
  return `${sign}${db.toFixed(1)} dB`;
1739
1985
  }
1740
1986
  function useDebouncedCallback(callback, delay) {
1741
- const timeoutRef = (0, import_react6.useRef)(null);
1742
- const callbackRef = (0, import_react6.useRef)(callback);
1743
- (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)(() => {
1744
1990
  callbackRef.current = callback;
1745
1991
  }, [callback]);
1746
- const debouncedCallback = (0, import_react6.useCallback)(
1992
+ const debouncedCallback = (0, import_react8.useCallback)(
1747
1993
  (...args) => {
1748
1994
  if (timeoutRef.current) {
1749
1995
  clearTimeout(timeoutRef.current);
@@ -1754,7 +2000,7 @@ function useDebouncedCallback(callback, delay) {
1754
2000
  },
1755
2001
  [delay]
1756
2002
  );
1757
- (0, import_react6.useEffect)(() => {
2003
+ (0, import_react8.useEffect)(() => {
1758
2004
  return () => {
1759
2005
  if (timeoutRef.current) {
1760
2006
  clearTimeout(timeoutRef.current);
@@ -1769,15 +2015,15 @@ var VolumeSlider = ({
1769
2015
  disabled = false,
1770
2016
  className = ""
1771
2017
  }) => {
1772
- const [localValue, setLocalValue] = (0, import_react6.useState)(value);
1773
- const [isDragging, setIsDragging] = (0, import_react6.useState)(false);
1774
- (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)(() => {
1775
2021
  if (!isDragging) {
1776
2022
  setLocalValue(value);
1777
2023
  }
1778
2024
  }, [value, isDragging]);
1779
2025
  const debouncedOnChange = useDebouncedCallback(onChange, 50);
1780
- const handleChange = (0, import_react6.useCallback)(
2026
+ const handleChange = (0, import_react8.useCallback)(
1781
2027
  (e) => {
1782
2028
  const newValue = parseFloat(e.target.value);
1783
2029
  setLocalValue(newValue);
@@ -1785,19 +2031,19 @@ var VolumeSlider = ({
1785
2031
  },
1786
2032
  [debouncedOnChange]
1787
2033
  );
1788
- const handleMouseDown = (0, import_react6.useCallback)(() => {
2034
+ const handleMouseDown = (0, import_react8.useCallback)(() => {
1789
2035
  setIsDragging(true);
1790
2036
  }, []);
1791
- const handleMouseUp = (0, import_react6.useCallback)(() => {
2037
+ const handleMouseUp = (0, import_react8.useCallback)(() => {
1792
2038
  setIsDragging(false);
1793
2039
  onChange(localValue);
1794
2040
  }, [localValue, onChange]);
1795
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2041
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1796
2042
  "div",
1797
2043
  {
1798
2044
  className: `flex items-center ${className}`,
1799
2045
  title: `Volume: ${formatDb(localValue)}`,
1800
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2046
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1801
2047
  "input",
1802
2048
  {
1803
2049
  type: "range",
@@ -1837,8 +2083,8 @@ var VolumeSlider = ({
1837
2083
  };
1838
2084
 
1839
2085
  // src/components/PanSlider.tsx
1840
- var import_react7 = require("react");
1841
- var import_jsx_runtime9 = require("react/jsx-runtime");
2086
+ var import_react9 = require("react");
2087
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1842
2088
  function toPanDisplay(value) {
1843
2089
  if (Math.abs(value) < 0.02) {
1844
2090
  return "Center";
@@ -1847,12 +2093,12 @@ function toPanDisplay(value) {
1847
2093
  return value < 0 ? `L${percent}` : `R${percent}`;
1848
2094
  }
1849
2095
  function useDebouncedCallback2(callback, delay) {
1850
- const timeoutRef = (0, import_react7.useRef)(null);
1851
- const callbackRef = (0, import_react7.useRef)(callback);
1852
- (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)(() => {
1853
2099
  callbackRef.current = callback;
1854
2100
  }, [callback]);
1855
- const debouncedCallback = (0, import_react7.useCallback)(
2101
+ const debouncedCallback = (0, import_react9.useCallback)(
1856
2102
  (...args) => {
1857
2103
  if (timeoutRef.current) {
1858
2104
  clearTimeout(timeoutRef.current);
@@ -1863,7 +2109,7 @@ function useDebouncedCallback2(callback, delay) {
1863
2109
  },
1864
2110
  [delay]
1865
2111
  );
1866
- (0, import_react7.useEffect)(() => {
2112
+ (0, import_react9.useEffect)(() => {
1867
2113
  return () => {
1868
2114
  if (timeoutRef.current) {
1869
2115
  clearTimeout(timeoutRef.current);
@@ -1878,15 +2124,15 @@ var PanSlider = ({
1878
2124
  disabled = false,
1879
2125
  className = ""
1880
2126
  }) => {
1881
- const [localValue, setLocalValue] = (0, import_react7.useState)(value);
1882
- const [isDragging, setIsDragging] = (0, import_react7.useState)(false);
1883
- (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)(() => {
1884
2130
  if (!isDragging) {
1885
2131
  setLocalValue(value);
1886
2132
  }
1887
2133
  }, [value, isDragging]);
1888
2134
  const debouncedOnChange = useDebouncedCallback2(onChange, 50);
1889
- const handleChange = (0, import_react7.useCallback)(
2135
+ const handleChange = (0, import_react9.useCallback)(
1890
2136
  (e) => {
1891
2137
  const newValue = parseFloat(e.target.value);
1892
2138
  setLocalValue(newValue);
@@ -1894,23 +2140,23 @@ var PanSlider = ({
1894
2140
  },
1895
2141
  [debouncedOnChange]
1896
2142
  );
1897
- const handleMouseDown = (0, import_react7.useCallback)(() => {
2143
+ const handleMouseDown = (0, import_react9.useCallback)(() => {
1898
2144
  setIsDragging(true);
1899
2145
  }, []);
1900
- const handleMouseUp = (0, import_react7.useCallback)(() => {
2146
+ const handleMouseUp = (0, import_react9.useCallback)(() => {
1901
2147
  setIsDragging(false);
1902
2148
  onChange(localValue);
1903
2149
  }, [localValue, onChange]);
1904
- const handleDoubleClick = (0, import_react7.useCallback)(() => {
2150
+ const handleDoubleClick = (0, import_react9.useCallback)(() => {
1905
2151
  setLocalValue(0);
1906
2152
  onChange(0);
1907
2153
  }, [onChange]);
1908
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2154
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1909
2155
  "div",
1910
2156
  {
1911
2157
  className: `flex items-center ${className}`,
1912
2158
  title: `Pan: ${toPanDisplay(localValue)}`,
1913
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2159
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1914
2160
  "input",
1915
2161
  {
1916
2162
  type: "range",
@@ -1951,8 +2197,8 @@ var PanSlider = ({
1951
2197
  };
1952
2198
 
1953
2199
  // src/components/SorceryProgressBar.tsx
1954
- var import_react8 = require("react");
1955
- var import_jsx_runtime10 = require("react/jsx-runtime");
2200
+ var import_react10 = require("react");
2201
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1956
2202
  function calculateTimeBasedTarget(elapsedMs, estimatedDurationMs) {
1957
2203
  const t = elapsedMs / estimatedDurationMs;
1958
2204
  if (t <= 0) return 0;
@@ -1997,20 +2243,20 @@ function SorceryProgressBar({
1997
2243
  onProgressChange,
1998
2244
  estimatedDurationMs
1999
2245
  }) {
2000
- const [progress, setProgress] = (0, import_react8.useState)(initialProgress);
2001
- const timerRef = (0, import_react8.useRef)(null);
2002
- const isLoadingRef = (0, import_react8.useRef)(false);
2003
- const hasStartedRef = (0, import_react8.useRef)(false);
2004
- const startTimeRef = (0, import_react8.useRef)(0);
2005
- const onProgressChangeRef = (0, import_react8.useRef)(onProgressChange);
2006
- 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);
2007
2253
  onProgressChangeRef.current = onProgressChange;
2008
2254
  onCompleteRef.current = onComplete;
2009
- const initialProgressRef = (0, import_react8.useRef)(initialProgress);
2255
+ const initialProgressRef = (0, import_react10.useRef)(initialProgress);
2010
2256
  initialProgressRef.current = initialProgress;
2011
- const estimatedDurationMsRef = (0, import_react8.useRef)(estimatedDurationMs);
2257
+ const estimatedDurationMsRef = (0, import_react10.useRef)(estimatedDurationMs);
2012
2258
  estimatedDurationMsRef.current = estimatedDurationMs;
2013
- (0, import_react8.useEffect)(() => {
2259
+ (0, import_react10.useEffect)(() => {
2014
2260
  const wasLoading = isLoadingRef.current;
2015
2261
  isLoadingRef.current = isLoading;
2016
2262
  if (isLoading && !wasLoading) {
@@ -2072,12 +2318,12 @@ function SorceryProgressBar({
2072
2318
  const displayProgress = Math.floor(progress);
2073
2319
  const isComplete = !isLoading && progress === 100;
2074
2320
  const transitionDuration = progress < 50 ? "300ms" : progress < 80 ? "500ms" : "700ms";
2075
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2321
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2076
2322
  "div",
2077
2323
  {
2078
2324
  className: `relative w-full ${heightClass} bg-sas-panel-alt border border-sas-border rounded-sm overflow-hidden shadow-inner`,
2079
2325
  children: [
2080
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2326
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2081
2327
  "div",
2082
2328
  {
2083
2329
  className: `
@@ -2095,13 +2341,13 @@ function SorceryProgressBar({
2095
2341
  }
2096
2342
  }
2097
2343
  ),
2098
- /* @__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: [
2099
2345
  statusText,
2100
2346
  " ",
2101
2347
  displayProgress,
2102
2348
  "%"
2103
- ] }) : 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 }),
2104
- /* @__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)(
2105
2351
  "div",
2106
2352
  {
2107
2353
  className: "absolute inset-0 pointer-events-none opacity-10",
@@ -2122,7 +2368,7 @@ function SorceryProgressBar({
2122
2368
  }
2123
2369
 
2124
2370
  // src/components/TrackRow.tsx
2125
- var import_jsx_runtime11 = require("react/jsx-runtime");
2371
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2126
2372
  function TrackRow({
2127
2373
  track,
2128
2374
  prompt,
@@ -2151,6 +2397,7 @@ function TrackRow({
2151
2397
  onFxToggle,
2152
2398
  onFxPresetChange,
2153
2399
  onFxDryWetChange,
2400
+ externalFxHost,
2154
2401
  onToggleFxDrawer,
2155
2402
  onProgressChange,
2156
2403
  accentColor = "#A78BFA",
@@ -2181,7 +2428,7 @@ function TrackRow({
2181
2428
  levels
2182
2429
  }) {
2183
2430
  const { muted: isMuted, solo: isSoloed, volume: currentVolume, pan: currentPan } = runtimeState;
2184
- const [confirmDelete, setConfirmDelete] = import_react9.default.useState(false);
2431
+ const [confirmDelete, setConfirmDelete] = import_react11.default.useState(false);
2185
2432
  const needsGeneration = !!(prompt?.trim() && !hasMidi && !isGenerating);
2186
2433
  const hasFxActive = Object.values(fxDetailState).some(
2187
2434
  (d) => d.enabled
@@ -2196,8 +2443,8 @@ function TrackRow({
2196
2443
  };
2197
2444
  const borderColorStyle = needsGeneration ? void 0 : accentColor;
2198
2445
  const borderClass = needsGeneration ? "border-amber-400 animate-pulse" : "border-sas-border";
2199
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-testid": "sdk-track-row-wrapper", className: "w-full", ...drag?.rowProps ?? {}, children: [
2200
- /* @__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)(
2201
2448
  "div",
2202
2449
  {
2203
2450
  "data-testid": "sdk-track-row",
@@ -2207,7 +2454,7 @@ function TrackRow({
2207
2454
  borderLeftWidth: "3px"
2208
2455
  },
2209
2456
  children: [
2210
- drag && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2457
+ drag && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2211
2458
  "div",
2212
2459
  {
2213
2460
  "data-testid": "sdk-drag-handle",
@@ -2215,10 +2462,10 @@ function TrackRow({
2215
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",
2216
2463
  title: "Drag to reorder",
2217
2464
  "aria-label": "Drag to reorder track",
2218
- 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 })
2219
2466
  }
2220
2467
  ),
2221
- 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)(
2222
2469
  SorceryProgressBar,
2223
2470
  {
2224
2471
  isLoading: true,
@@ -2229,14 +2476,14 @@ function TrackRow({
2229
2476
  estimatedDurationMs: estimatedGenerationMs
2230
2477
  }
2231
2478
  ) }),
2232
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2479
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2233
2480
  "div",
2234
2481
  {
2235
2482
  "data-testid": "sdk-track-content",
2236
2483
  className: `flex flex-col flex-1 min-w-0 relative z-10 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
2237
2484
  title: soloedOut ? "Silenced \u2014 another track is soloed" : void 0,
2238
2485
  children: [
2239
- contentSlot ? contentSlot : onPromptChange ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2486
+ contentSlot ? contentSlot : onPromptChange ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2240
2487
  "input",
2241
2488
  {
2242
2489
  type: "text",
@@ -2249,10 +2496,10 @@ function TrackRow({
2249
2496
  className: "sas-input w-full px-2 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed"
2250
2497
  }
2251
2498
  ) : null,
2252
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-2 mt-1", children: [
2253
- 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 }),
2254
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "vol:" }),
2255
- /* @__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)(
2256
2503
  VolumeSlider,
2257
2504
  {
2258
2505
  value: currentVolume,
@@ -2261,8 +2508,8 @@ function TrackRow({
2261
2508
  className: "flex-1 min-w-[40px]"
2262
2509
  }
2263
2510
  ),
2264
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: "pan:" }),
2265
- /* @__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)(
2266
2513
  PanSlider,
2267
2514
  {
2268
2515
  value: currentPan,
@@ -2275,27 +2522,27 @@ function TrackRow({
2275
2522
  ]
2276
2523
  }
2277
2524
  ),
2278
- error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2525
+ error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2279
2526
  "div",
2280
2527
  {
2281
2528
  "data-testid": "sdk-error-indicator",
2282
2529
  className: "flex-shrink-0 relative z-10 self-stretch flex items-center px-1 group cursor-help",
2283
2530
  title: error,
2284
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative", children: [
2285
- /* @__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)(
2286
2533
  import_lucide_react.AlertCircle,
2287
2534
  {
2288
2535
  className: "w-5 h-5 text-red-500 animate-pulse",
2289
2536
  strokeWidth: 2.5
2290
2537
  }
2291
2538
  ),
2292
- /* @__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 })
2293
2540
  ] })
2294
2541
  }
2295
2542
  ),
2296
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-0.5 flex-shrink-0 relative z-30 justify-center", children: [
2297
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-1 items-center", children: [
2298
- 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)(
2299
2546
  "button",
2300
2547
  {
2301
2548
  "data-testid": "sdk-generate-button",
@@ -2306,7 +2553,7 @@ function TrackRow({
2306
2553
  children: "Create"
2307
2554
  }
2308
2555
  ),
2309
- onCopy && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2556
+ onCopy && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2310
2557
  "button",
2311
2558
  {
2312
2559
  "data-testid": "sdk-copy-button",
@@ -2317,7 +2564,7 @@ function TrackRow({
2317
2564
  children: "Copy"
2318
2565
  }
2319
2566
  ),
2320
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2567
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2321
2568
  "button",
2322
2569
  {
2323
2570
  "data-testid": "sdk-mute-button",
@@ -2327,7 +2574,7 @@ function TrackRow({
2327
2574
  children: "M"
2328
2575
  }
2329
2576
  ),
2330
- onDelete && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2577
+ onDelete && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2331
2578
  "button",
2332
2579
  {
2333
2580
  "data-testid": "sdk-delete-button",
@@ -2338,8 +2585,8 @@ function TrackRow({
2338
2585
  }
2339
2586
  )
2340
2587
  ] }),
2341
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-1 items-center", children: [
2342
- 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)(
2343
2590
  "button",
2344
2591
  {
2345
2592
  "data-testid": "sdk-shuffle-button",
@@ -2350,7 +2597,7 @@ function TrackRow({
2350
2597
  children: "Shuffle"
2351
2598
  }
2352
2599
  ),
2353
- onToggleFxDrawer && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2600
+ onToggleFxDrawer && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2354
2601
  "button",
2355
2602
  {
2356
2603
  "data-testid": "sdk-fx-button",
@@ -2361,7 +2608,7 @@ function TrackRow({
2361
2608
  children: "FX"
2362
2609
  }
2363
2610
  ),
2364
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2611
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2365
2612
  "button",
2366
2613
  {
2367
2614
  "data-testid": "sdk-solo-button",
@@ -2372,7 +2619,7 @@ function TrackRow({
2372
2619
  children: "S"
2373
2620
  }
2374
2621
  ),
2375
- onToggleDrawer && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2622
+ onToggleDrawer && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2376
2623
  "button",
2377
2624
  {
2378
2625
  "data-testid": "sdk-plugin-button",
@@ -2380,7 +2627,7 @@ function TrackRow({
2380
2627
  disabled: isGenerating,
2381
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"}`,
2382
2629
  title: `Sound \u2014 presets & history${instrumentMissing ? " (instrument missing)" : ""}`,
2383
- 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 })
2384
2631
  }
2385
2632
  )
2386
2633
  ] })
@@ -2388,13 +2635,13 @@ function TrackRow({
2388
2635
  ]
2389
2636
  }
2390
2637
  ),
2391
- levels && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TrackMeterStrip, { levels, trackId: track.id, roundBottom: !drawerOpen }),
2392
- 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)(
2393
2640
  "div",
2394
2641
  {
2395
2642
  "data-testid": "sdk-track-drawer",
2396
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",
2397
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2644
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2398
2645
  TrackDrawer,
2399
2646
  {
2400
2647
  activeTab: drawerTab,
@@ -2404,6 +2651,7 @@ function TrackRow({
2404
2651
  onFxToggle,
2405
2652
  onFxPresetChange,
2406
2653
  onFxDryWetChange,
2654
+ externalFxHost,
2407
2655
  fxDisabled: isGenerating,
2408
2656
  instruments: availableInstruments,
2409
2657
  currentPluginId: currentInstrumentPluginId ?? null,
@@ -2430,13 +2678,13 @@ function TrackRow({
2430
2678
  )
2431
2679
  }
2432
2680
  ),
2433
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2681
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2434
2682
  ConfirmDialog,
2435
2683
  {
2436
2684
  open: confirmDelete,
2437
2685
  title: "Delete track?",
2438
- message: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2439
- /* @__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" }),
2440
2688
  " will be permanently removed from this scene. This cannot be undone."
2441
2689
  ] }),
2442
2690
  confirmLabel: "Delete",
@@ -2452,13 +2700,13 @@ function TrackRow({
2452
2700
  }
2453
2701
 
2454
2702
  // src/components/CrossfadeTrackRow.tsx
2455
- var import_react10 = __toESM(require("react"));
2456
- var import_jsx_runtime12 = require("react/jsx-runtime");
2703
+ var import_react12 = __toESM(require("react"));
2704
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2457
2705
  function LayerCaption({ tag, layer }) {
2458
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2459
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2460
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2461
- 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: [
2462
2710
  "\xB7 ",
2463
2711
  layer.soundLabel
2464
2712
  ] })
@@ -2477,8 +2725,8 @@ function CrossfadeTrackRow({
2477
2725
  levels,
2478
2726
  accentColor = "#9333EA"
2479
2727
  }) {
2480
- const [confirmDelete, setConfirmDelete] = import_react10.default.useState(false);
2481
- 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)(
2482
2730
  TrackRow,
2483
2731
  {
2484
2732
  track: { id: layer.trackId, name: "", role: layer.role },
@@ -2488,23 +2736,23 @@ function CrossfadeTrackRow({
2488
2736
  drawerTab: "fx",
2489
2737
  levels,
2490
2738
  accentColor,
2491
- contentSlot: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LayerCaption, { tag, layer }),
2739
+ contentSlot: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(LayerCaption, { tag, layer }),
2492
2740
  onMuteToggle,
2493
2741
  onSoloToggle,
2494
2742
  onVolumeChange: (v) => onVolumeChange(slot, v),
2495
2743
  onPanChange: (p) => onPanChange(slot, p)
2496
2744
  }
2497
2745
  );
2498
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2746
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2499
2747
  "div",
2500
2748
  {
2501
2749
  "data-testid": "crossfade-track-row",
2502
2750
  className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
2503
2751
  style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
2504
2752
  children: [
2505
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2506
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] font-bold uppercase tracking-wide", style: { color: accentColor }, children: "\u21C4 Crossfade" }),
2507
- /* @__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)(
2508
2756
  "button",
2509
2757
  {
2510
2758
  "data-testid": "crossfade-delete-button",
@@ -2517,8 +2765,8 @@ function CrossfadeTrackRow({
2517
2765
  )
2518
2766
  ] }),
2519
2767
  renderLayer(origin, "origin", "Origin"),
2520
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "crossfade-slider-row", children: [
2521
- /* @__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)(
2522
2770
  "span",
2523
2771
  {
2524
2772
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
@@ -2526,7 +2774,7 @@ function CrossfadeTrackRow({
2526
2774
  children: origin.sourceName ?? origin.name
2527
2775
  }
2528
2776
  ),
2529
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2777
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2530
2778
  "input",
2531
2779
  {
2532
2780
  type: "range",
@@ -2542,7 +2790,7 @@ function CrossfadeTrackRow({
2542
2790
  "aria-label": "Crossfade position"
2543
2791
  }
2544
2792
  ),
2545
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2793
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2546
2794
  "span",
2547
2795
  {
2548
2796
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
@@ -2552,12 +2800,12 @@ function CrossfadeTrackRow({
2552
2800
  )
2553
2801
  ] }),
2554
2802
  renderLayer(target, "target", "Target"),
2555
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2803
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2556
2804
  ConfirmDialog,
2557
2805
  {
2558
2806
  open: confirmDelete,
2559
2807
  title: "Delete crossfade?",
2560
- 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." }),
2561
2809
  confirmLabel: "Delete",
2562
2810
  onConfirm: () => {
2563
2811
  setConfirmDelete(false);
@@ -2800,22 +3048,22 @@ function defaultFadeGesture(role) {
2800
3048
  }
2801
3049
 
2802
3050
  // src/components/FadeTrackRow.tsx
2803
- var import_react11 = __toESM(require("react"));
2804
- var import_jsx_runtime13 = require("react/jsx-runtime");
3051
+ var import_react13 = __toESM(require("react"));
3052
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2805
3053
  function FadeCaption({
2806
3054
  layer,
2807
3055
  direction,
2808
3056
  gesture
2809
3057
  }) {
2810
3058
  const tag = direction === "in" ? "Fade in" : "Fade out";
2811
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
2812
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
2813
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
2814
- 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: [
2815
3063
  "\xB7 ",
2816
3064
  layer.soundLabel
2817
3065
  ] }),
2818
- /* @__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: [
2819
3067
  "\xB7 ",
2820
3068
  gesture
2821
3069
  ] })
@@ -2836,20 +3084,20 @@ function FadeTrackRow({
2836
3084
  levels,
2837
3085
  accentColor = "#9333EA"
2838
3086
  }) {
2839
- const [confirmDelete, setConfirmDelete] = import_react11.default.useState(false);
3087
+ const [confirmDelete, setConfirmDelete] = import_react13.default.useState(false);
2840
3088
  const leftLabel = direction === "in" ? "(silent)" : layer.sourceName ?? layer.name;
2841
3089
  const rightLabel = direction === "in" ? layer.sourceName ?? layer.name : "(silent)";
2842
3090
  const verb = effect && effect !== "fade" ? effect.charAt(0).toUpperCase() + effect.slice(1) : "Fade";
2843
3091
  const badge = direction === "in" ? `\u2197 ${verb} in` : `\u2198 ${verb} out`;
2844
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3092
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2845
3093
  "div",
2846
3094
  {
2847
3095
  "data-testid": "fade-track-row",
2848
3096
  className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
2849
3097
  style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
2850
3098
  children: [
2851
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
2852
- /* @__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)(
2853
3101
  "span",
2854
3102
  {
2855
3103
  "data-testid": "fade-direction-badge",
@@ -2858,7 +3106,7 @@ function FadeTrackRow({
2858
3106
  children: badge
2859
3107
  }
2860
3108
  ),
2861
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3109
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2862
3110
  "button",
2863
3111
  {
2864
3112
  "data-testid": "fade-delete-button",
@@ -2870,7 +3118,7 @@ function FadeTrackRow({
2870
3118
  }
2871
3119
  )
2872
3120
  ] }),
2873
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3121
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2874
3122
  TrackRow,
2875
3123
  {
2876
3124
  track: { id: layer.trackId, name: "", role: layer.role },
@@ -2880,15 +3128,15 @@ function FadeTrackRow({
2880
3128
  drawerTab: "fx",
2881
3129
  levels,
2882
3130
  accentColor,
2883
- contentSlot: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FadeCaption, { layer, direction, gesture }),
3131
+ contentSlot: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(FadeCaption, { layer, direction, gesture }),
2884
3132
  onMuteToggle,
2885
3133
  onSoloToggle,
2886
3134
  onVolumeChange,
2887
3135
  onPanChange
2888
3136
  }
2889
3137
  ),
2890
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "fade-slider-row", children: [
2891
- /* @__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)(
2892
3140
  "span",
2893
3141
  {
2894
3142
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
@@ -2896,7 +3144,7 @@ function FadeTrackRow({
2896
3144
  children: leftLabel
2897
3145
  }
2898
3146
  ),
2899
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3147
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2900
3148
  "input",
2901
3149
  {
2902
3150
  type: "range",
@@ -2912,7 +3160,7 @@ function FadeTrackRow({
2912
3160
  "aria-label": "Fade position"
2913
3161
  }
2914
3162
  ),
2915
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3163
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2916
3164
  "span",
2917
3165
  {
2918
3166
  className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
@@ -2921,12 +3169,12 @@ function FadeTrackRow({
2921
3169
  }
2922
3170
  )
2923
3171
  ] }),
2924
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3172
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2925
3173
  ConfirmDialog,
2926
3174
  {
2927
3175
  open: confirmDelete,
2928
3176
  title: "Delete fade?",
2929
- 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." }),
2930
3178
  confirmLabel: "Delete",
2931
3179
  onConfirm: () => {
2932
3180
  setConfirmDelete(false);
@@ -2942,8 +3190,8 @@ function FadeTrackRow({
2942
3190
  }
2943
3191
 
2944
3192
  // src/components/FadeModal.tsx
2945
- var import_react12 = require("react");
2946
- var import_jsx_runtime14 = require("react/jsx-runtime");
3193
+ var import_react14 = require("react");
3194
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2947
3195
  function shortId(dbId) {
2948
3196
  return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
2949
3197
  }
@@ -2986,7 +3234,7 @@ function OrphanRow({
2986
3234
  }) {
2987
3235
  const primary = track.prompt?.trim() || track.name;
2988
3236
  const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(" \xB7 ");
2989
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3237
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2990
3238
  "button",
2991
3239
  {
2992
3240
  type: "button",
@@ -2998,8 +3246,8 @@ function OrphanRow({
2998
3246
  disabled,
2999
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"}`,
3000
3248
  children: [
3001
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3002
- 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 })
3003
3251
  ]
3004
3252
  }
3005
3253
  );
@@ -3016,14 +3264,14 @@ function FadeModal({
3016
3264
  onCreate,
3017
3265
  testIdPrefix = "fade-modal"
3018
3266
  }) {
3019
- const [load, setLoad] = (0, import_react12.useState)({ status: "loading" });
3020
- const [selectedDbId, setSelectedDbId] = (0, import_react12.useState)("");
3021
- const [isCreating, setIsCreating] = (0, import_react12.useState)(false);
3022
- const [error, setError] = (0, import_react12.useState)(null);
3023
- const [fromName, setFromName] = (0, import_react12.useState)(null);
3024
- const [toName, setToName] = (0, import_react12.useState)(null);
3025
- const cancelRef = (0, import_react12.useRef)(null);
3026
- 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 () => {
3027
3275
  if (!host.listSceneFamilyTracks) {
3028
3276
  setLoad({ status: "error", message: "This host does not support fades." });
3029
3277
  return;
@@ -3043,7 +3291,7 @@ function FadeModal({
3043
3291
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
3044
3292
  }
3045
3293
  }, [host, fromSceneId, toSceneId]);
3046
- (0, import_react12.useEffect)(() => {
3294
+ (0, import_react14.useEffect)(() => {
3047
3295
  if (open) {
3048
3296
  setError(null);
3049
3297
  setIsCreating(false);
@@ -3051,29 +3299,29 @@ function FadeModal({
3051
3299
  void refresh();
3052
3300
  }
3053
3301
  }, [open, refresh]);
3054
- const excludeSet = (0, import_react12.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3055
- 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)(
3056
3304
  () => load.status === "ready" ? computeOrphans(load.from, load.to, excludeSet) : { fadeOut: [], fadeIn: [] },
3057
3305
  [load, excludeSet]
3058
3306
  );
3059
- const allOrphans = (0, import_react12.useMemo)(
3307
+ const allOrphans = (0, import_react14.useMemo)(
3060
3308
  () => [
3061
3309
  ...fadeOut.map((t) => ({ track: t, direction: "out" })),
3062
3310
  ...fadeIn.map((t) => ({ track: t, direction: "in" }))
3063
3311
  ],
3064
3312
  [fadeOut, fadeIn]
3065
3313
  );
3066
- (0, import_react12.useEffect)(() => {
3314
+ (0, import_react14.useEffect)(() => {
3067
3315
  if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {
3068
3316
  setSelectedDbId(allOrphans[0]?.track.dbId ?? "");
3069
3317
  }
3070
3318
  }, [allOrphans, selectedDbId]);
3071
3319
  const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;
3072
3320
  const canCreate = !isCreating && !!selected;
3073
- const handleClose = (0, import_react12.useCallback)(() => {
3321
+ const handleClose = (0, import_react14.useCallback)(() => {
3074
3322
  if (!isCreating) onClose();
3075
3323
  }, [isCreating, onClose]);
3076
- const handleCreate = (0, import_react12.useCallback)(async () => {
3324
+ const handleCreate = (0, import_react14.useCallback)(async () => {
3077
3325
  if (!selected) return;
3078
3326
  setIsCreating(true);
3079
3327
  setError(null);
@@ -3094,16 +3342,16 @@ function FadeModal({
3094
3342
  if (!open) return null;
3095
3343
  const renderSection = (heading, list, section) => {
3096
3344
  if (list.length === 0) return null;
3097
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "block", children: [
3098
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
3099
- /* @__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)(
3100
3348
  "div",
3101
3349
  {
3102
3350
  role: "radiogroup",
3103
3351
  "aria-label": heading,
3104
3352
  "data-testid": `${testIdPrefix}-${section === "out" ? "fade-out" : "fade-in"}-list`,
3105
3353
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
3106
- children: list.map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3354
+ children: list.map((t) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3107
3355
  OrphanRow,
3108
3356
  {
3109
3357
  track: t,
@@ -3119,32 +3367,32 @@ function FadeModal({
3119
3367
  )
3120
3368
  ] });
3121
3369
  };
3122
- 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)(
3123
3371
  "div",
3124
3372
  {
3125
3373
  className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
3126
3374
  onClick: (e) => e.stopPropagation(),
3127
3375
  "data-testid": `${testIdPrefix}-box`,
3128
3376
  children: [
3129
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
3130
- /* @__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: [
3131
3379
  "Tracks with no counterpart between",
3132
3380
  " ",
3133
- /* @__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" }),
3134
3382
  " and",
3135
3383
  " ",
3136
- /* @__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" }),
3137
3385
  " can gracefully fade out (leaving) or fade in (entering) across this transition."
3138
3386
  ] }),
3139
- load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3140
- load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3141
- 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: [
3142
3390
  renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ""}`, fadeOut, "out"),
3143
3391
  renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ""}`, fadeIn, "in")
3144
3392
  ] })),
3145
- error && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3146
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
3147
- /* @__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)(
3148
3396
  "button",
3149
3397
  {
3150
3398
  ref: cancelRef,
@@ -3155,7 +3403,7 @@ function FadeModal({
3155
3403
  children: "Cancel"
3156
3404
  }
3157
3405
  ),
3158
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3406
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3159
3407
  "button",
3160
3408
  {
3161
3409
  "data-testid": `${testIdPrefix}-confirm`,
@@ -3172,8 +3420,8 @@ function FadeModal({
3172
3420
  }
3173
3421
 
3174
3422
  // src/components/ImportTrackModal.tsx
3175
- var import_react13 = require("react");
3176
- var import_jsx_runtime15 = require("react/jsx-runtime");
3423
+ var import_react15 = require("react");
3424
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3177
3425
  function ImportTrackModal({
3178
3426
  host,
3179
3427
  open,
@@ -3185,10 +3433,10 @@ function ImportTrackModal({
3185
3433
  onPick,
3186
3434
  onPortTrack
3187
3435
  }) {
3188
- const [load, setLoad] = (0, import_react13.useState)({ status: "loading" });
3189
- const [selectedSceneId, setSelectedSceneId] = (0, import_react13.useState)(null);
3190
- const [importingTrackId, setImportingTrackId] = (0, import_react13.useState)(null);
3191
- 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 () => {
3192
3440
  if (!host.listImportableTracks) {
3193
3441
  setLoad({ status: "error", message: "This host does not support importing tracks." });
3194
3442
  return;
@@ -3204,14 +3452,14 @@ function ImportTrackModal({
3204
3452
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load scenes." });
3205
3453
  }
3206
3454
  }, [host, mode, onPortTrack]);
3207
- (0, import_react13.useEffect)(() => {
3455
+ (0, import_react15.useEffect)(() => {
3208
3456
  if (open) {
3209
3457
  setSelectedSceneId(null);
3210
3458
  setImportingTrackId(null);
3211
3459
  void refresh();
3212
3460
  }
3213
3461
  }, [open, refresh]);
3214
- const handleImport = (0, import_react13.useCallback)(
3462
+ const handleImport = (0, import_react15.useCallback)(
3215
3463
  async (track, sourceSceneId, sceneName, isSameScene) => {
3216
3464
  if (isSameScene && onPortTrack) {
3217
3465
  if (!track.importable) return;
@@ -3252,16 +3500,16 @@ function ImportTrackModal({
3252
3500
  if (!open) return null;
3253
3501
  const scenes = load.status === "ready" ? load.scenes : [];
3254
3502
  const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;
3255
- 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)(
3256
3504
  "div",
3257
3505
  {
3258
3506
  className: "w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
3259
3507
  onClick: (e) => e.stopPropagation(),
3260
3508
  "data-testid": `${testIdPrefix}-modal`,
3261
3509
  children: [
3262
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
3263
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", children: [
3264
- 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)(
3265
3513
  "button",
3266
3514
  {
3267
3515
  className: "text-sas-muted hover:text-sas-accent text-xs",
@@ -3270,9 +3518,9 @@ function ImportTrackModal({
3270
3518
  children: "\u2190"
3271
3519
  }
3272
3520
  ),
3273
- /* @__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 })
3274
3522
  ] }),
3275
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3523
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3276
3524
  "button",
3277
3525
  {
3278
3526
  className: "text-sas-muted hover:text-sas-accent text-sm",
@@ -3282,30 +3530,30 @@ function ImportTrackModal({
3282
3530
  }
3283
3531
  )
3284
3532
  ] }),
3285
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "overflow-y-auto p-2 flex-1", children: [
3286
- 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" }),
3287
- 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 }),
3288
- 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." }),
3289
- 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)(
3290
3538
  "button",
3291
3539
  {
3292
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",
3293
3541
  onClick: () => setSelectedSceneId(scene.sceneId),
3294
3542
  "data-testid": `${testIdPrefix}-scene`,
3295
3543
  children: [
3296
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate", children: scene.sceneName }),
3297
- /* @__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: [
3298
3546
  scene.tracks.length,
3299
3547
  " \u2192"
3300
3548
  ] })
3301
3549
  ]
3302
3550
  }
3303
3551
  ) }, scene.sceneId)) }),
3304
- 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) => {
3305
3553
  const busy = importingTrackId === track.trackId;
3306
3554
  const gated = mode === "track" && !track.importable;
3307
3555
  const disabled = gated || busy;
3308
- 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)(
3309
3557
  "button",
3310
3558
  {
3311
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"}`,
@@ -3315,14 +3563,14 @@ function ImportTrackModal({
3315
3563
  "data-testid": `${testIdPrefix}-track`,
3316
3564
  "data-importable": mode === "sound" || track.importable ? "true" : "false",
3317
3565
  children: [
3318
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "truncate", children: [
3566
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "truncate", children: [
3319
3567
  track.name,
3320
- 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: [
3321
3569
  " \xB7 ",
3322
3570
  track.role
3323
3571
  ] }) : null
3324
3572
  ] }),
3325
- 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
3326
3574
  ]
3327
3575
  }
3328
3576
  ) }, track.dbId);
@@ -3334,8 +3582,8 @@ function ImportTrackModal({
3334
3582
  }
3335
3583
 
3336
3584
  // src/components/CrossfadeModal.tsx
3337
- var import_react14 = require("react");
3338
- var import_jsx_runtime16 = require("react/jsx-runtime");
3585
+ var import_react16 = require("react");
3586
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3339
3587
  function shortId2(dbId) {
3340
3588
  return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
3341
3589
  }
@@ -3348,7 +3596,7 @@ function CandidateRow({
3348
3596
  }) {
3349
3597
  const primary = track.prompt?.trim() || track.name;
3350
3598
  const meta = [track.role, shortId2(track.dbId)].filter(Boolean).join(" \xB7 ");
3351
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3599
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3352
3600
  "button",
3353
3601
  {
3354
3602
  type: "button",
@@ -3360,8 +3608,8 @@ function CandidateRow({
3360
3608
  disabled,
3361
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"}`,
3362
3610
  children: [
3363
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
3364
- 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 })
3365
3613
  ]
3366
3614
  }
3367
3615
  );
@@ -3378,15 +3626,15 @@ function CrossfadeModal({
3378
3626
  onCreate,
3379
3627
  testIdPrefix = "crossfade-modal"
3380
3628
  }) {
3381
- const [load, setLoad] = (0, import_react14.useState)({ status: "loading" });
3382
- const [originDbId, setOriginDbId] = (0, import_react14.useState)("");
3383
- const [targetDbId, setTargetDbId] = (0, import_react14.useState)("");
3384
- const [isCreating, setIsCreating] = (0, import_react14.useState)(false);
3385
- const [error, setError] = (0, import_react14.useState)(null);
3386
- const [fromName, setFromName] = (0, import_react14.useState)(null);
3387
- const [toName, setToName] = (0, import_react14.useState)(null);
3388
- const cancelRef = (0, import_react14.useRef)(null);
3389
- 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 () => {
3390
3638
  if (!host.listSceneFamilyTracks) {
3391
3639
  setLoad({ status: "error", message: "This host does not support crossfade tracks." });
3392
3640
  return;
@@ -3406,7 +3654,7 @@ function CrossfadeModal({
3406
3654
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
3407
3655
  }
3408
3656
  }, [host, fromSceneId, toSceneId]);
3409
- (0, import_react14.useEffect)(() => {
3657
+ (0, import_react16.useEffect)(() => {
3410
3658
  if (open) {
3411
3659
  setError(null);
3412
3660
  setIsCreating(false);
@@ -3415,21 +3663,21 @@ function CrossfadeModal({
3415
3663
  void refresh();
3416
3664
  }
3417
3665
  }, [open, refresh]);
3418
- const excludeSet = (0, import_react14.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3419
- const originCandidates = (0, import_react14.useMemo)(
3666
+ const excludeSet = (0, import_react16.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3667
+ const originCandidates = (0, import_react16.useMemo)(
3420
3668
  () => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
3421
3669
  [load, excludeSet]
3422
3670
  );
3423
- const targetCandidates = (0, import_react14.useMemo)(
3671
+ const targetCandidates = (0, import_react16.useMemo)(
3424
3672
  () => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
3425
3673
  [load, excludeSet]
3426
3674
  );
3427
- (0, import_react14.useEffect)(() => {
3675
+ (0, import_react16.useEffect)(() => {
3428
3676
  if (!originCandidates.some((t) => t.dbId === originDbId)) {
3429
3677
  setOriginDbId(originCandidates[0]?.dbId ?? "");
3430
3678
  }
3431
3679
  }, [originCandidates, originDbId]);
3432
- (0, import_react14.useEffect)(() => {
3680
+ (0, import_react16.useEffect)(() => {
3433
3681
  if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
3434
3682
  setTargetDbId(targetCandidates[0]?.dbId ?? "");
3435
3683
  }
@@ -3437,10 +3685,10 @@ function CrossfadeModal({
3437
3685
  const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
3438
3686
  const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
3439
3687
  const canCreate = !isCreating && !!originTrack && !!targetTrack;
3440
- const handleClose = (0, import_react14.useCallback)(() => {
3688
+ const handleClose = (0, import_react16.useCallback)(() => {
3441
3689
  if (!isCreating) onClose();
3442
3690
  }, [isCreating, onClose]);
3443
- const handleCreate = (0, import_react14.useCallback)(async () => {
3691
+ const handleCreate = (0, import_react16.useCallback)(async () => {
3444
3692
  if (!originTrack || !targetTrack) return;
3445
3693
  setIsCreating(true);
3446
3694
  setError(null);
@@ -3458,26 +3706,26 @@ function CrossfadeModal({
3458
3706
  const fromLabel = fromName ?? fromSceneName ?? null;
3459
3707
  const toLabel = toName ?? toSceneName ?? null;
3460
3708
  if (!open) return null;
3461
- 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)(
3462
3710
  "div",
3463
3711
  {
3464
3712
  className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
3465
3713
  onClick: (e) => e.stopPropagation(),
3466
3714
  "data-testid": `${testIdPrefix}-box`,
3467
3715
  children: [
3468
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
3469
- /* @__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: [
3470
3718
  "Bridge a track from",
3471
3719
  " ",
3472
- /* @__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" }),
3473
3721
  " into one from",
3474
3722
  " ",
3475
- /* @__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" }),
3476
3724
  ". Both layers share one generated part; each keeps its own preset."
3477
3725
  ] }),
3478
- load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
3479
- load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
3480
- 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)(
3481
3729
  "div",
3482
3730
  {
3483
3731
  className: "text-xs text-sas-muted py-4 text-center",
@@ -3488,20 +3736,20 @@ function CrossfadeModal({
3488
3736
  ". Add one (or free one from another crossfade) first."
3489
3737
  ]
3490
3738
  }
3491
- ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3492
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "block", children: [
3493
- /* @__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: [
3494
3742
  "Origin ",
3495
3743
  fromLabel ? `(${fromLabel})` : "(top)"
3496
3744
  ] }),
3497
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3745
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3498
3746
  "div",
3499
3747
  {
3500
3748
  role: "radiogroup",
3501
3749
  "aria-label": "Origin track",
3502
3750
  "data-testid": `${testIdPrefix}-origin-list`,
3503
3751
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
3504
- children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3752
+ children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3505
3753
  CandidateRow,
3506
3754
  {
3507
3755
  track: t,
@@ -3515,23 +3763,23 @@ function CrossfadeModal({
3515
3763
  }
3516
3764
  )
3517
3765
  ] }),
3518
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "block", children: [
3519
- /* @__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: [
3520
3768
  "Target ",
3521
3769
  toLabel ? `(${toLabel})` : "(bottom)"
3522
3770
  ] }),
3523
- 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: [
3524
3772
  "No available tracks in ",
3525
3773
  toLabel ?? "the target scene",
3526
3774
  " to crossfade into."
3527
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3775
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3528
3776
  "div",
3529
3777
  {
3530
3778
  role: "radiogroup",
3531
3779
  "aria-label": "Target track",
3532
3780
  "data-testid": `${testIdPrefix}-target-list`,
3533
3781
  className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
3534
- children: targetCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3782
+ children: targetCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3535
3783
  CandidateRow,
3536
3784
  {
3537
3785
  track: t,
@@ -3546,9 +3794,9 @@ function CrossfadeModal({
3546
3794
  )
3547
3795
  ] })
3548
3796
  ] })),
3549
- error && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
3550
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
3551
- /* @__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)(
3552
3800
  "button",
3553
3801
  {
3554
3802
  ref: cancelRef,
@@ -3559,7 +3807,7 @@ function CrossfadeModal({
3559
3807
  children: "Cancel"
3560
3808
  }
3561
3809
  ),
3562
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3810
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3563
3811
  "button",
3564
3812
  {
3565
3813
  "data-testid": `${testIdPrefix}-confirm`,
@@ -3576,10 +3824,10 @@ function CrossfadeModal({
3576
3824
  }
3577
3825
 
3578
3826
  // src/components/TransitionDesigner.tsx
3579
- var import_react16 = require("react");
3827
+ var import_react18 = require("react");
3580
3828
 
3581
3829
  // src/hooks/useTrackReorder.ts
3582
- var import_react15 = require("react");
3830
+ var import_react17 = require("react");
3583
3831
  function moveItem(arr, from, to) {
3584
3832
  const next = arr.slice();
3585
3833
  if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
@@ -3596,12 +3844,12 @@ function useTrackReorder({
3596
3844
  getId,
3597
3845
  onError
3598
3846
  }) {
3599
- const [draggingIndex, setDraggingIndex] = (0, import_react15.useState)(null);
3600
- const [dragOverIndex, setDragOverIndex] = (0, import_react15.useState)(null);
3601
- const fromRef = (0, import_react15.useRef)(null);
3602
- 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);
3603
3851
  itemsRef.current = items;
3604
- const dragPropsFor = (0, import_react15.useCallback)(
3852
+ const dragPropsFor = (0, import_react17.useCallback)(
3605
3853
  (index) => ({
3606
3854
  handleProps: {
3607
3855
  draggable: true,
@@ -3783,7 +4031,7 @@ function dbIdsFromKeys(keys) {
3783
4031
  }
3784
4032
 
3785
4033
  // src/components/TransitionDesigner.tsx
3786
- var import_jsx_runtime17 = require("react/jsx-runtime");
4034
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3787
4035
  var CROSSFADE_ESTIMATE_MS = 15e3;
3788
4036
  var FADE_ESTIMATE_MS = 11e3;
3789
4037
  var CREATE_ALL_CONCURRENCY = 5;
@@ -3807,44 +4055,44 @@ function TransitionDesigner({
3807
4055
  familyLabel,
3808
4056
  testIdPrefix = "transition-designer"
3809
4057
  }) {
3810
- const [load, setLoad] = (0, import_react16.useState)({ status: "loading" });
3811
- const [fromName, setFromName] = (0, import_react16.useState)(null);
3812
- const [toName, setToName] = (0, import_react16.useState)(null);
3813
- const [originSlots, setOriginSlots] = (0, import_react16.useState)([]);
3814
- const [targetSlots, setTargetSlots] = (0, import_react16.useState)([]);
3815
- const [creatingKeys, setCreatingKeys] = (0, import_react16.useState)(() => /* @__PURE__ */ new Set());
3816
- const [rowErrors, setRowErrors] = (0, import_react16.useState)({});
3817
- const [rowEffects, setRowEffects] = (0, import_react16.useState)({});
3818
- 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);
3819
4067
  rowEffectsRef.current = rowEffects;
3820
4068
  const audioEffectsEnabled = !!onCreateAudioTransition;
3821
- const excludeRef = (0, import_react16.useRef)(excludeSourceDbIds);
4069
+ const excludeRef = (0, import_react18.useRef)(excludeSourceDbIds);
3822
4070
  excludeRef.current = excludeSourceDbIds;
3823
- const originSlotsRef = (0, import_react16.useRef)(originSlots);
4071
+ const originSlotsRef = (0, import_react18.useRef)(originSlots);
3824
4072
  originSlotsRef.current = originSlots;
3825
- const targetSlotsRef = (0, import_react16.useRef)(targetSlots);
4073
+ const targetSlotsRef = (0, import_react18.useRef)(targetSlots);
3826
4074
  targetSlotsRef.current = targetSlots;
3827
- const creatingKeysRef = (0, import_react16.useRef)(creatingKeys);
4075
+ const creatingKeysRef = (0, import_react18.useRef)(creatingKeys);
3828
4076
  creatingKeysRef.current = creatingKeys;
3829
- const dragRef = (0, import_react16.useRef)(null);
3830
- const [dragging, setDragging] = (0, import_react16.useState)(null);
3831
- const [dragOver, setDragOver] = (0, import_react16.useState)(null);
3832
- const excludeSet = (0, import_react16.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
3833
- 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)(
3834
4082
  () => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
3835
4083
  [load, excludeSet]
3836
4084
  );
3837
- const targetPool = (0, import_react16.useMemo)(
4085
+ const targetPool = (0, import_react18.useMemo)(
3838
4086
  () => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
3839
4087
  [load, excludeSet]
3840
4088
  );
3841
- const originById = (0, import_react16.useMemo)(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);
3842
- const targetById = (0, import_react16.useMemo)(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);
3843
- 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);
3844
4092
  originByIdRef.current = originById;
3845
- const targetByIdRef = (0, import_react16.useRef)(targetById);
4093
+ const targetByIdRef = (0, import_react18.useRef)(targetById);
3846
4094
  targetByIdRef.current = targetById;
3847
- const refresh = (0, import_react16.useCallback)(async () => {
4095
+ const refresh = (0, import_react18.useCallback)(async () => {
3848
4096
  if (!host.listSceneFamilyTracks) {
3849
4097
  setLoad({ status: "error", message: "This host does not support transition tracks." });
3850
4098
  return;
@@ -3879,10 +4127,10 @@ function TransitionDesigner({
3879
4127
  });
3880
4128
  }
3881
4129
  }, [host, fromSceneId, toSceneId, transitionSceneId]);
3882
- (0, import_react16.useEffect)(() => {
4130
+ (0, import_react18.useEffect)(() => {
3883
4131
  void refresh();
3884
4132
  }, [refresh]);
3885
- (0, import_react16.useEffect)(() => {
4133
+ (0, import_react18.useEffect)(() => {
3886
4134
  if (load.status !== "ready") return;
3887
4135
  const [po, pt] = padPair(
3888
4136
  reconcileSlots(originSlotsRef.current, originPool.map((t) => t.dbId)),
@@ -3891,7 +4139,7 @@ function TransitionDesigner({
3891
4139
  if (!slotsEqual(po, originSlotsRef.current)) setOriginSlots(po);
3892
4140
  if (!slotsEqual(pt, targetSlotsRef.current)) setTargetSlots(pt);
3893
4141
  }, [originPool, targetPool, load.status]);
3894
- const mutate = (0, import_react16.useCallback)(
4142
+ const mutate = (0, import_react18.useCallback)(
3895
4143
  (nextOrigin, nextTarget) => {
3896
4144
  const norm = normalizeSlots(nextOrigin, nextTarget);
3897
4145
  const [po, pt] = padPair(norm.originOrder, norm.targetOrder);
@@ -3904,7 +4152,7 @@ function TransitionDesigner({
3904
4152
  },
3905
4153
  [host, transitionSceneId]
3906
4154
  );
3907
- const setRowEffect = (0, import_react16.useCallback)(
4155
+ const setRowEffect = (0, import_react18.useCallback)(
3908
4156
  (sourceDbId, effect) => {
3909
4157
  setRowEffects((prev) => {
3910
4158
  const next = { ...prev, [sourceDbId]: effect };
@@ -3918,7 +4166,7 @@ function TransitionDesigner({
3918
4166
  },
3919
4167
  [host, transitionSceneId]
3920
4168
  );
3921
- const insertGapAbove = (0, import_react16.useCallback)(
4169
+ const insertGapAbove = (0, import_react18.useCallback)(
3922
4170
  (col, index) => {
3923
4171
  const slots = col === "origin" ? originSlots : targetSlots;
3924
4172
  const next = [...slots.slice(0, index), null, ...slots.slice(index)];
@@ -3927,7 +4175,7 @@ function TransitionDesigner({
3927
4175
  },
3928
4176
  [originSlots, targetSlots, mutate]
3929
4177
  );
3930
- const removeGap = (0, import_react16.useCallback)(
4178
+ const removeGap = (0, import_react18.useCallback)(
3931
4179
  (col, index) => {
3932
4180
  const slots = col === "origin" ? originSlots : targetSlots;
3933
4181
  const next = slots.filter((_, i) => i !== index);
@@ -3936,7 +4184,7 @@ function TransitionDesigner({
3936
4184
  },
3937
4185
  [originSlots, targetSlots, mutate]
3938
4186
  );
3939
- const handleDrop = (0, import_react16.useCallback)(
4187
+ const handleDrop = (0, import_react18.useCallback)(
3940
4188
  (col, to) => {
3941
4189
  const from = dragRef.current;
3942
4190
  dragRef.current = null;
@@ -3948,16 +4196,16 @@ function TransitionDesigner({
3948
4196
  },
3949
4197
  [originSlots, targetSlots, mutate]
3950
4198
  );
3951
- const rows = (0, import_react16.useMemo)(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);
3952
- const creatingDbIds = (0, import_react16.useMemo)(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);
3953
- 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)(
3954
4202
  () => rows.filter((r) => {
3955
4203
  const k = rowKey(r);
3956
4204
  return k !== null && !creatingKeys.has(k);
3957
4205
  }).length,
3958
4206
  [rows, creatingKeys]
3959
4207
  );
3960
- const createRow = (0, import_react16.useCallback)(
4208
+ const createRow = (0, import_react18.useCallback)(
3961
4209
  async (row) => {
3962
4210
  const key = rowKey(row);
3963
4211
  if (!key || !row.type || creatingKeysRef.current.has(key)) return;
@@ -4011,7 +4259,7 @@ function TransitionDesigner({
4011
4259
  },
4012
4260
  [onCreateCrossfade, onCreateFade, onCreateAudioTransition]
4013
4261
  );
4014
- const createAll = (0, import_react16.useCallback)(async () => {
4262
+ const createAll = (0, import_react18.useCallback)(async () => {
4015
4263
  const eligible = buildRowSlots(originSlotsRef.current, targetSlotsRef.current).filter((r) => {
4016
4264
  const k = rowKey(r);
4017
4265
  return k !== null && !creatingKeysRef.current.has(k);
@@ -4079,15 +4327,15 @@ function TransitionDesigner({
4079
4327
  const base = "group relative rounded-sm border px-2 py-1.5 text-left transition-colors select-none";
4080
4328
  const tone = isDragTarget ? "border-sas-accent bg-sas-accent/10" : "border-sas-border bg-sas-panel";
4081
4329
  if (slotId === null) {
4082
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4330
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4083
4331
  "div",
4084
4332
  {
4085
4333
  ...cellDragProps(col, index, false),
4086
4334
  "data-testid": `${testIdPrefix}-${col}-gap-${index}`,
4087
4335
  className: `${base} ${tone} border-dashed flex items-center justify-between ${isDragging ? "opacity-40" : "opacity-70"}`,
4088
4336
  children: [
4089
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "\u2014 gap \u2014" }),
4090
- /* @__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)(
4091
4339
  "button",
4092
4340
  {
4093
4341
  type: "button",
@@ -4104,7 +4352,7 @@ function TransitionDesigner({
4104
4352
  }
4105
4353
  const primary = track ? track.prompt?.trim() || track.name : slotId;
4106
4354
  const meta = track ? [track.role, shortId3(track.dbId)].filter(Boolean).join(" \xB7 ") : "missing";
4107
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4355
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4108
4356
  "div",
4109
4357
  {
4110
4358
  ...cellDragProps(col, index, locked),
@@ -4112,13 +4360,13 @@ function TransitionDesigner({
4112
4360
  "data-value": slotId,
4113
4361
  className: `${base} ${tone} ${isDragging ? "opacity-40" : ""} ${locked ? "opacity-60" : "cursor-grab active:cursor-grabbing"}`,
4114
4362
  title: track ? track.dbId : "Track no longer available",
4115
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-start gap-1", children: [
4116
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-muted/60 text-xs leading-tight pt-0.5", "aria-hidden": true, children: "\u283F" }),
4117
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "min-w-0 flex-1", children: [
4118
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-text truncate", children: primary }),
4119
- 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 })
4120
4368
  ] }),
4121
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4369
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4122
4370
  "button",
4123
4371
  {
4124
4372
  type: "button",
@@ -4134,22 +4382,22 @@ function TransitionDesigner({
4134
4382
  }
4135
4383
  );
4136
4384
  };
4137
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
4138
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
4139
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
4140
- /* @__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 }),
4141
4389
  " \u2192",
4142
4390
  " ",
4143
- /* @__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 }),
4144
4392
  familyLabel ? ` \xB7 ${familyLabel}` : "",
4145
4393
  " \xB7 line up a track on each side to crossfade; leave one blank (or insert a gap) to fade."
4146
4394
  ] }),
4147
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
4148
- 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: [
4149
4397
  creatingKeys.size,
4150
4398
  " creating\u2026"
4151
4399
  ] }),
4152
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4400
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4153
4401
  "button",
4154
4402
  {
4155
4403
  type: "button",
@@ -4166,49 +4414,49 @@ function TransitionDesigner({
4166
4414
  )
4167
4415
  ] })
4168
4416
  ] }),
4169
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "grid grid-cols-[1fr_auto_1fr] gap-2", children: [
4170
- /* @__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: [
4171
4419
  "Origin (",
4172
4420
  fromLabel,
4173
4421
  ")"
4174
4422
  ] }),
4175
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted text-center px-2", children: "Transition" }),
4176
- /* @__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: [
4177
4425
  "Target (",
4178
4426
  toLabel,
4179
4427
  ")"
4180
4428
  ] })
4181
4429
  ] }),
4182
- load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
4183
- 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 }),
4184
- 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: [
4185
4433
  "No tracks to arrange in this panel for either scene. Add tracks to ",
4186
4434
  fromLabel,
4187
4435
  " or ",
4188
4436
  toLabel,
4189
4437
  " ",
4190
4438
  "first (or free one by deleting an existing crossfade/fade)."
4191
- ] }) : /* @__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) => {
4192
4440
  const key = rowKey(row);
4193
4441
  const isCreatingThis = key !== null && creatingKeys.has(key);
4194
4442
  const errMsg = key !== null ? rowErrors[key] : void 0;
4195
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4443
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4196
4444
  "div",
4197
4445
  {
4198
4446
  "data-testid": `${testIdPrefix}-row-${i}`,
4199
4447
  className: "grid grid-cols-[1fr_auto_1fr] gap-2 items-center",
4200
4448
  children: [
4201
4449
  renderCell("origin", i, row.originId),
4202
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
4203
- !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)(
4204
4452
  "span",
4205
4453
  {
4206
4454
  "data-testid": `${testIdPrefix}-type-${i}`,
4207
4455
  className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-accent/50 text-sas-accent",
4208
4456
  children: TYPE_LABEL[row.type]
4209
4457
  }
4210
- ) : audioEffectsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-1", "data-testid": `${testIdPrefix}-type-${i}`, children: [
4211
- /* @__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)(
4212
4460
  "select",
4213
4461
  {
4214
4462
  "data-testid": `${testIdPrefix}-effect-${i}`,
@@ -4218,11 +4466,11 @@ function TransitionDesigner({
4218
4466
  if (id) setRowEffect(id, e.target.value);
4219
4467
  },
4220
4468
  className: "text-[10px] bg-sas-panel border border-sas-border rounded-sm px-1 py-0.5 text-sas-text",
4221
- 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))
4222
4470
  }
4223
4471
  ),
4224
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[9px] text-sas-muted", children: row.type === "fade-out" ? "out" : "in" })
4225
- ] }) : /* @__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)(
4226
4474
  "span",
4227
4475
  {
4228
4476
  "data-testid": `${testIdPrefix}-type-${i}`,
@@ -4230,7 +4478,7 @@ function TransitionDesigner({
4230
4478
  children: TYPE_LABEL[row.type]
4231
4479
  }
4232
4480
  ),
4233
- 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)(
4234
4482
  SorceryProgressBar,
4235
4483
  {
4236
4484
  isLoading: true,
@@ -4238,7 +4486,7 @@ function TransitionDesigner({
4238
4486
  statusText: "CREATING",
4239
4487
  estimatedDurationMs: row.type === "crossfade" ? CROSSFADE_ESTIMATE_MS : FADE_ESTIMATE_MS
4240
4488
  }
4241
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4489
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4242
4490
  "button",
4243
4491
  {
4244
4492
  type: "button",
@@ -4249,7 +4497,7 @@ function TransitionDesigner({
4249
4497
  children: "Create"
4250
4498
  }
4251
4499
  ),
4252
- errMsg && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4500
+ errMsg && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4253
4501
  "span",
4254
4502
  {
4255
4503
  "data-testid": `${testIdPrefix}-row-error-${i}`,
@@ -4268,10 +4516,11 @@ function TransitionDesigner({
4268
4516
  }
4269
4517
 
4270
4518
  // src/components/PanelMasterStrip.tsx
4271
- var import_react17 = require("react");
4272
- var import_jsx_runtime18 = require("react/jsx-runtime");
4519
+ var import_react19 = require("react");
4520
+ var import_jsx_runtime19 = require("react/jsx-runtime");
4273
4521
  function PanelMasterStrip({
4274
4522
  bus,
4523
+ levels = null,
4275
4524
  availableFx = [],
4276
4525
  fxLoading = false,
4277
4526
  soloedOut = false,
@@ -4287,22 +4536,22 @@ function PanelMasterStrip({
4287
4536
  onToggleFxEnabled,
4288
4537
  onShowFxEditor
4289
4538
  }) {
4290
- const [search, setSearch] = (0, import_react17.useState)("");
4291
- const filtered = (0, import_react17.useMemo)(() => {
4539
+ const [search, setSearch] = (0, import_react19.useState)("");
4540
+ const filtered = (0, import_react19.useMemo)(() => {
4292
4541
  const q = search.trim().toLowerCase();
4293
4542
  if (!q) return availableFx;
4294
4543
  return availableFx.filter(
4295
4544
  (fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
4296
4545
  );
4297
4546
  }, [availableFx, search]);
4298
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4547
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4299
4548
  "div",
4300
4549
  {
4301
4550
  "data-testid": "panel-master-strip",
4302
- className: `flex flex-col gap-1 px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt/50 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
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" : ""}`,
4303
4552
  children: [
4304
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", children: [
4305
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4553
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2", children: [
4554
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4306
4555
  "span",
4307
4556
  {
4308
4557
  className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
@@ -4310,15 +4559,38 @@ function PanelMasterStrip({
4310
4559
  children: "BUS"
4311
4560
  }
4312
4561
  ),
4313
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "w-24", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4314
- VolumeSlider,
4315
- {
4316
- value: dbToSlider(bus.volume),
4317
- onChange: (sliderValue) => onVolumeChange(sliderToDb(sliderValue)),
4318
- disabled
4319
- }
4320
- ) }),
4321
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
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)(
4322
4594
  "button",
4323
4595
  {
4324
4596
  "data-testid": "bus-mute-button",
@@ -4329,7 +4601,7 @@ function PanelMasterStrip({
4329
4601
  children: "M"
4330
4602
  }
4331
4603
  ),
4332
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4604
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4333
4605
  "button",
4334
4606
  {
4335
4607
  "data-testid": "bus-solo-button",
@@ -4340,14 +4612,14 @@ function PanelMasterStrip({
4340
4612
  children: "S"
4341
4613
  }
4342
4614
  ),
4343
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
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)(
4344
4616
  "span",
4345
4617
  {
4346
4618
  "data-testid": `bus-fx-chip-${fx.index}`,
4347
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"}`,
4348
4620
  title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
4349
4621
  children: [
4350
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4622
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4351
4623
  "button",
4352
4624
  {
4353
4625
  "data-testid": `bus-fx-toggle-${fx.index}`,
@@ -4358,7 +4630,7 @@ function PanelMasterStrip({
4358
4630
  children: fx.enabled ? "\u25CF" : "\u25CB"
4359
4631
  }
4360
4632
  ),
4361
- onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4633
+ onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4362
4634
  "button",
4363
4635
  {
4364
4636
  "data-testid": `bus-fx-edit-${fx.index}`,
@@ -4368,8 +4640,8 @@ function PanelMasterStrip({
4368
4640
  title: `Open ${fx.name} editor`,
4369
4641
  children: fx.name
4370
4642
  }
4371
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
4372
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4643
+ ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
4644
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4373
4645
  "button",
4374
4646
  {
4375
4647
  "data-testid": `bus-fx-remove-${fx.index}`,
@@ -4384,21 +4656,21 @@ function PanelMasterStrip({
4384
4656
  },
4385
4657
  `${fx.index}:${fx.pluginId}`
4386
4658
  )) }),
4387
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4659
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4388
4660
  "button",
4389
4661
  {
4390
4662
  "data-testid": "bus-fx-add-button",
4391
4663
  onClick: () => onToggleFxPicker(!fxPickerOpen),
4392
4664
  disabled,
4393
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`,
4394
- title: "Add an FX plugin to the panel bus",
4395
- children: "FX +"
4666
+ title: fxPickerOpen ? "Close the FX picker" : "Add an FX plugin to the panel bus",
4667
+ children: fxPickerOpen ? "FX \u25B4" : "FX +"
4396
4668
  }
4397
4669
  )
4398
4670
  ] }),
4399
- fxPickerOpen && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
4400
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", children: [
4401
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
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)(
4402
4674
  "input",
4403
4675
  {
4404
4676
  type: "text",
@@ -4408,19 +4680,19 @@ function PanelMasterStrip({
4408
4680
  className: "sas-input flex-1 px-2 py-1 text-xs"
4409
4681
  }
4410
4682
  ),
4411
- onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4683
+ onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4412
4684
  "button",
4413
4685
  {
4414
4686
  onClick: () => onRefreshFx(),
4415
4687
  disabled: fxLoading,
4416
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",
4417
- title: "Re-scan plugins",
4689
+ title: "Re-scan plugins \u2014 picks up newly installed FX and retries any that failed a previous scan",
4418
4690
  children: fxLoading ? "..." : "Refresh"
4419
4691
  }
4420
4692
  )
4421
4693
  ] }),
4422
- fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
4423
- filtered.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
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)(
4424
4696
  "button",
4425
4697
  {
4426
4698
  "data-testid": `bus-fx-pick-${fx.pluginId}`,
@@ -4428,13 +4700,13 @@ function PanelMasterStrip({
4428
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",
4429
4701
  title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
4430
4702
  children: [
4431
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
4432
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
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() })
4433
4705
  ]
4434
4706
  },
4435
4707
  fx.pluginId
4436
4708
  )),
4437
- filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime18.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" })
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" })
4438
4710
  ] })
4439
4711
  ] })
4440
4712
  ]
@@ -4443,16 +4715,18 @@ function PanelMasterStrip({
4443
4715
  }
4444
4716
 
4445
4717
  // src/hooks/usePanelBus.ts
4446
- var import_react18 = require("react");
4718
+ var import_react20 = require("react");
4719
+ var LEVELS_POLL_MS = 66;
4447
4720
  function usePanelBus(host, activeSceneId) {
4448
4721
  const supported = typeof host.getPanelBusState === "function";
4449
- const [bus, setBus] = (0, import_react18.useState)(null);
4450
- const [availableFx, setAvailableFx] = (0, import_react18.useState)([]);
4451
- const [fxLoading, setFxLoading] = (0, import_react18.useState)(false);
4452
- const [fxPickerOpen, setFxPickerOpen] = (0, import_react18.useState)(false);
4453
- const fxLoadedRef = (0, import_react18.useRef)(false);
4454
- const loadSeqRef = (0, import_react18.useRef)(0);
4455
- const reload = (0, import_react18.useCallback)(async () => {
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 () => {
4456
4730
  if (!supported || !activeSceneId || !host.getPanelBusState) {
4457
4731
  setBus(null);
4458
4732
  return;
@@ -4464,18 +4738,40 @@ function usePanelBus(host, activeSceneId) {
4464
4738
  } catch {
4465
4739
  }
4466
4740
  }, [host, activeSceneId, supported]);
4467
- (0, import_react18.useEffect)(() => {
4741
+ (0, import_react20.useEffect)(() => {
4468
4742
  setBus(null);
4469
4743
  setFxPickerOpen(false);
4470
4744
  void reload();
4471
4745
  }, [reload]);
4472
- const loadFxList = (0, import_react18.useCallback)(
4473
- async (force) => {
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 (opts) => {
4474
4770
  if (!supported || !host.getAvailableFx) return;
4475
- if (fxLoadedRef.current && !force) return;
4771
+ if (fxLoadedRef.current && !opts.force && !opts.rescan) return;
4476
4772
  setFxLoading(true);
4477
4773
  try {
4478
- const list = await host.getAvailableFx();
4774
+ const list = opts.rescan && host.rescanAvailableFx ? await host.rescanAvailableFx() : await host.getAvailableFx();
4479
4775
  setAvailableFx(list);
4480
4776
  fxLoadedRef.current = true;
4481
4777
  } catch {
@@ -4485,14 +4781,14 @@ function usePanelBus(host, activeSceneId) {
4485
4781
  },
4486
4782
  [host, supported]
4487
4783
  );
4488
- const openPicker = (0, import_react18.useCallback)(
4784
+ const openPicker = (0, import_react20.useCallback)(
4489
4785
  (open) => {
4490
4786
  setFxPickerOpen(open);
4491
- if (open) void loadFxList(false);
4787
+ if (open) void loadFxList({});
4492
4788
  },
4493
4789
  [loadFxList]
4494
4790
  );
4495
- const mutate = (0, import_react18.useCallback)(
4791
+ const mutate = (0, import_react20.useCallback)(
4496
4792
  (fn) => {
4497
4793
  if (!fn || !activeSceneId) return;
4498
4794
  void (async () => {
@@ -4508,11 +4804,12 @@ function usePanelBus(host, activeSceneId) {
4508
4804
  return {
4509
4805
  supported,
4510
4806
  bus,
4807
+ levels,
4511
4808
  availableFx,
4512
4809
  fxLoading,
4513
4810
  fxPickerOpen,
4514
4811
  setFxPickerOpen: openPicker,
4515
- refreshFx: () => void loadFxList(true),
4812
+ refreshFx: () => void loadFxList({ rescan: true }),
4516
4813
  reload,
4517
4814
  onVolumeChange: (volumeDb) => mutate(host.setPanelBusVolume && (() => host.setPanelBusVolume(activeSceneId, volumeDb))),
4518
4815
  onMuteToggle: () => mutate(
@@ -4535,8 +4832,8 @@ function usePanelBus(host, activeSceneId) {
4535
4832
  }
4536
4833
 
4537
4834
  // src/components/DownloadPackButton.tsx
4538
- var import_react19 = require("react");
4539
- var import_jsx_runtime19 = require("react/jsx-runtime");
4835
+ var import_react21 = require("react");
4836
+ var import_jsx_runtime20 = require("react/jsx-runtime");
4540
4837
  function formatSize(bytes) {
4541
4838
  if (!bytes || bytes <= 0) return "";
4542
4839
  const gb = bytes / 1024 ** 3;
@@ -4552,10 +4849,10 @@ var DownloadPackButton = ({
4552
4849
  variant = "compact",
4553
4850
  onDownloadComplete
4554
4851
  }) => {
4555
- const [status, setStatus] = (0, import_react19.useState)("idle");
4556
- const [progress, setProgress] = (0, import_react19.useState)(0);
4557
- const [errorMessage, setErrorMessage] = (0, import_react19.useState)(null);
4558
- (0, import_react19.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)(() => {
4559
4856
  const unsub = host.onSamplePackProgress(packId, (p) => {
4560
4857
  setStatus(p.status);
4561
4858
  setProgress(p.progress);
@@ -4570,7 +4867,7 @@ var DownloadPackButton = ({
4570
4867
  });
4571
4868
  return unsub;
4572
4869
  }, [host, packId, onDownloadComplete]);
4573
- const handleClick = (0, import_react19.useCallback)(async () => {
4870
+ const handleClick = (0, import_react21.useCallback)(async () => {
4574
4871
  if (status !== "idle" && status !== "error") return;
4575
4872
  try {
4576
4873
  setStatus("downloading");
@@ -4624,8 +4921,8 @@ var DownloadPackButton = ({
4624
4921
  } else {
4625
4922
  className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
4626
4923
  }
4627
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
4628
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4924
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { children: [
4925
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4629
4926
  "button",
4630
4927
  {
4631
4928
  "data-testid": `download-pack-button-${packId}`,
@@ -4636,12 +4933,12 @@ var DownloadPackButton = ({
4636
4933
  children: buttonLabel
4637
4934
  }
4638
4935
  ),
4639
- variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime19.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 })
4640
4937
  ] });
4641
4938
  };
4642
4939
 
4643
4940
  // src/components/SamplePackCTACard.tsx
4644
- var import_jsx_runtime20 = require("react/jsx-runtime");
4941
+ var import_jsx_runtime21 = require("react/jsx-runtime");
4645
4942
  var SamplePackCTACard = ({
4646
4943
  host,
4647
4944
  pack,
@@ -4649,7 +4946,7 @@ var SamplePackCTACard = ({
4649
4946
  onDownloadComplete
4650
4947
  }) => {
4651
4948
  if (status === "checking") {
4652
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4949
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4653
4950
  "div",
4654
4951
  {
4655
4952
  "data-testid": `sample-pack-cta-checking-${pack.packId}`,
@@ -4660,16 +4957,16 @@ var SamplePackCTACard = ({
4660
4957
  }
4661
4958
  const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
4662
4959
  const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
4663
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
4960
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4664
4961
  "div",
4665
4962
  {
4666
4963
  "data-testid": `sample-pack-cta-${pack.packId}`,
4667
4964
  className: "flex flex-col items-center justify-center py-12 px-6 text-center",
4668
4965
  children: [
4669
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
4670
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
4671
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
4672
- /* @__PURE__ */ (0, import_jsx_runtime20.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)(
4673
4970
  DownloadPackButton,
4674
4971
  {
4675
4972
  host,
@@ -4686,7 +4983,7 @@ var SamplePackCTACard = ({
4686
4983
  };
4687
4984
 
4688
4985
  // src/components/WaveformView.tsx
4689
- var import_react20 = require("react");
4986
+ var import_react22 = require("react");
4690
4987
 
4691
4988
  // src/components/waveform.ts
4692
4989
  function computePeaks(audioBuffer, bins, targetSamples) {
@@ -4749,7 +5046,7 @@ function drawWaveform(canvas, peaks, options = {}) {
4749
5046
  }
4750
5047
 
4751
5048
  // src/components/WaveformView.tsx
4752
- var import_jsx_runtime21 = require("react/jsx-runtime");
5049
+ var import_jsx_runtime22 = require("react/jsx-runtime");
4753
5050
  var WaveformView = ({
4754
5051
  host,
4755
5052
  filePath,
@@ -4758,9 +5055,9 @@ var WaveformView = ({
4758
5055
  fillStyle,
4759
5056
  targetSamples
4760
5057
  }) => {
4761
- const canvasRef = (0, import_react20.useRef)(null);
4762
- const [peaks, setPeaks] = (0, import_react20.useState)(null);
4763
- (0, import_react20.useEffect)(() => {
5058
+ const canvasRef = (0, import_react22.useRef)(null);
5059
+ const [peaks, setPeaks] = (0, import_react22.useState)(null);
5060
+ (0, import_react22.useEffect)(() => {
4764
5061
  let cancelled = false;
4765
5062
  let audioContext = null;
4766
5063
  (async () => {
@@ -4786,7 +5083,7 @@ var WaveformView = ({
4786
5083
  cancelled = true;
4787
5084
  };
4788
5085
  }, [host, filePath, bins, targetSamples]);
4789
- (0, import_react20.useEffect)(() => {
5086
+ (0, import_react22.useEffect)(() => {
4790
5087
  if (!peaks) return;
4791
5088
  const canvas = canvasRef.current;
4792
5089
  if (!canvas) return;
@@ -4797,7 +5094,7 @@ var WaveformView = ({
4797
5094
  observer.observe(canvas);
4798
5095
  return () => observer.disconnect();
4799
5096
  }, [peaks, fillStyle]);
4800
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
5097
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4801
5098
  "canvas",
4802
5099
  {
4803
5100
  ref: canvasRef,
@@ -4808,8 +5105,8 @@ var WaveformView = ({
4808
5105
  };
4809
5106
 
4810
5107
  // src/components/ScrollingWaveform.tsx
4811
- var import_react21 = require("react");
4812
- var import_jsx_runtime22 = require("react/jsx-runtime");
5108
+ var import_react23 = require("react");
5109
+ var import_jsx_runtime23 = require("react/jsx-runtime");
4813
5110
  var ScrollingWaveform = ({
4814
5111
  getPeakDb,
4815
5112
  active,
@@ -4817,11 +5114,11 @@ var ScrollingWaveform = ({
4817
5114
  className,
4818
5115
  fillStyle
4819
5116
  }) => {
4820
- const canvasRef = (0, import_react21.useRef)(null);
4821
- const ringRef = (0, import_react21.useRef)(new Float32Array(columns));
4822
- const writeIdxRef = (0, import_react21.useRef)(0);
4823
- const rafRef = (0, import_react21.useRef)(null);
4824
- (0, import_react21.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)(() => {
4825
5122
  if (ringRef.current.length !== columns) {
4826
5123
  const next = new Float32Array(columns);
4827
5124
  const prev = ringRef.current;
@@ -4833,7 +5130,7 @@ var ScrollingWaveform = ({
4833
5130
  writeIdxRef.current = writeIdxRef.current % columns;
4834
5131
  }
4835
5132
  }, [columns]);
4836
- (0, import_react21.useEffect)(() => {
5133
+ (0, import_react23.useEffect)(() => {
4837
5134
  if (!active) {
4838
5135
  if (rafRef.current !== null) {
4839
5136
  cancelAnimationFrame(rafRef.current);
@@ -4885,7 +5182,7 @@ var ScrollingWaveform = ({
4885
5182
  }
4886
5183
  };
4887
5184
  }, [active, getPeakDb, fillStyle]);
4888
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5185
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4889
5186
  "canvas",
4890
5187
  {
4891
5188
  ref: canvasRef,
@@ -4896,8 +5193,8 @@ var ScrollingWaveform = ({
4896
5193
  };
4897
5194
 
4898
5195
  // src/components/OffsetScrubber.tsx
4899
- var import_react22 = require("react");
4900
- var import_jsx_runtime23 = require("react/jsx-runtime");
5196
+ var import_react24 = require("react");
5197
+ var import_jsx_runtime24 = require("react/jsx-runtime");
4901
5198
  var SLIDER_HEIGHT_PX = 28;
4902
5199
  var TICK_HEIGHT_PX = 14;
4903
5200
  var DOWNBEAT_TICK_HEIGHT_PX = 22;
@@ -4910,40 +5207,40 @@ function OffsetScrubber({
4910
5207
  onChange,
4911
5208
  disabled = false
4912
5209
  }) {
4913
- const trackRef = (0, import_react22.useRef)(null);
4914
- const [draftOffset, setDraftOffset] = (0, import_react22.useState)(offsetSamples);
4915
- const [isDragging, setIsDragging] = (0, import_react22.useState)(false);
4916
- (0, import_react22.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)(() => {
4917
5214
  if (!isDragging) setDraftOffset(offsetSamples);
4918
5215
  }, [offsetSamples, isDragging]);
4919
5216
  const sampleRate = cuePoints?.sample_rate ?? 44100;
4920
5217
  const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
4921
- const beatsForRange = (0, import_react22.useMemo)(() => {
5218
+ const beatsForRange = (0, import_react24.useMemo)(() => {
4922
5219
  return Math.round(60 / projectBpm * sampleRate);
4923
5220
  }, [projectBpm, sampleRate]);
4924
5221
  const rangeSamples = beatsForRange * meter;
4925
- const sampleToFraction = (0, import_react22.useCallback)(
5222
+ const sampleToFraction = (0, import_react24.useCallback)(
4926
5223
  (sample) => {
4927
5224
  const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
4928
5225
  return (clamped + rangeSamples) / (2 * rangeSamples);
4929
5226
  },
4930
5227
  [rangeSamples]
4931
5228
  );
4932
- const fractionToSample = (0, import_react22.useCallback)(
5229
+ const fractionToSample = (0, import_react24.useCallback)(
4933
5230
  (fraction) => {
4934
5231
  const clamped = Math.max(0, Math.min(1, fraction));
4935
5232
  return Math.round(clamped * 2 * rangeSamples - rangeSamples);
4936
5233
  },
4937
5234
  [rangeSamples]
4938
5235
  );
4939
- const snapTargets = (0, import_react22.useMemo)(() => {
5236
+ const snapTargets = (0, import_react24.useMemo)(() => {
4940
5237
  if (!cuePoints || cuePoints.beats.length === 0) return [];
4941
5238
  const downbeat = cuePoints.beats[0];
4942
5239
  const positives = cuePoints.beats.map((b) => b - downbeat);
4943
5240
  const negatives = positives.slice(1).map((p) => -p);
4944
5241
  return [...negatives, ...positives].sort((a, b) => a - b);
4945
5242
  }, [cuePoints]);
4946
- const snapToBeat = (0, import_react22.useCallback)(
5243
+ const snapToBeat = (0, import_react24.useCallback)(
4947
5244
  (sample) => {
4948
5245
  if (snapTargets.length === 0) return sample;
4949
5246
  let best = snapTargets[0];
@@ -4959,7 +5256,7 @@ function OffsetScrubber({
4959
5256
  },
4960
5257
  [snapTargets]
4961
5258
  );
4962
- const handlePointerDown = (0, import_react22.useCallback)(
5259
+ const handlePointerDown = (0, import_react24.useCallback)(
4963
5260
  (e) => {
4964
5261
  if (disabled || !cuePoints) return;
4965
5262
  e.preventDefault();
@@ -4993,7 +5290,7 @@ function OffsetScrubber({
4993
5290
  },
4994
5291
  [disabled, cuePoints, fractionToSample, onChange, snapToBeat]
4995
5292
  );
4996
- const handleResetToZero = (0, import_react22.useCallback)(() => {
5293
+ const handleResetToZero = (0, import_react24.useCallback)(() => {
4997
5294
  if (disabled) return;
4998
5295
  setDraftOffset(0);
4999
5296
  onChange(0);
@@ -5001,7 +5298,7 @@ function OffsetScrubber({
5001
5298
  const thumbFraction = sampleToFraction(draftOffset);
5002
5299
  const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
5003
5300
  const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
5004
- const ticks = (0, import_react22.useMemo)(() => {
5301
+ const ticks = (0, import_react24.useMemo)(() => {
5005
5302
  if (!cuePoints) return [];
5006
5303
  const downbeat = cuePoints.beats[0] ?? 0;
5007
5304
  return cuePoints.beats.map((b, i) => {
@@ -5012,9 +5309,9 @@ function OffsetScrubber({
5012
5309
  });
5013
5310
  }, [cuePoints, sampleToFraction]);
5014
5311
  const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
5015
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
5016
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
5017
- /* @__PURE__ */ (0, import_jsx_runtime23.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)(
5018
5315
  "div",
5019
5316
  {
5020
5317
  ref: trackRef,
@@ -5030,7 +5327,7 @@ function OffsetScrubber({
5030
5327
  "aria-valuenow": draftOffset,
5031
5328
  "aria-disabled": isDisabled,
5032
5329
  children: [
5033
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5330
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5034
5331
  "div",
5035
5332
  {
5036
5333
  "aria-hidden": "true",
@@ -5038,7 +5335,7 @@ function OffsetScrubber({
5038
5335
  style: { left: "50%" }
5039
5336
  }
5040
5337
  ),
5041
- ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5338
+ ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5042
5339
  "div",
5043
5340
  {
5044
5341
  "data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
@@ -5053,7 +5350,7 @@ function OffsetScrubber({
5053
5350
  },
5054
5351
  t.i
5055
5352
  )),
5056
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5353
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5057
5354
  "div",
5058
5355
  {
5059
5356
  "data-testid": "offset-scrubber-thumb",
@@ -5070,7 +5367,7 @@ function OffsetScrubber({
5070
5367
  ]
5071
5368
  }
5072
5369
  ),
5073
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5370
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5074
5371
  "span",
5075
5372
  {
5076
5373
  "data-testid": "offset-scrubber-readout",
@@ -5078,7 +5375,7 @@ function OffsetScrubber({
5078
5375
  children: formatOffset(draftOffset, sampleRate)
5079
5376
  }
5080
5377
  ),
5081
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5378
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5082
5379
  "button",
5083
5380
  {
5084
5381
  type: "button",
@@ -5090,7 +5387,7 @@ function OffsetScrubber({
5090
5387
  children: "\u2316"
5091
5388
  }
5092
5389
  ),
5093
- bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5390
+ bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5094
5391
  "span",
5095
5392
  {
5096
5393
  "data-testid": "offset-bpm-mismatch",
@@ -5162,16 +5459,16 @@ function synthesizeCuePoints({
5162
5459
  }
5163
5460
 
5164
5461
  // src/panel-core/useGeneratorPanelCore.tsx
5165
- var import_react27 = require("react");
5462
+ var import_react29 = require("react");
5166
5463
 
5167
5464
  // src/hooks/useSceneState.ts
5168
- var import_react23 = require("react");
5465
+ var import_react25 = require("react");
5169
5466
  function useSceneState(activeSceneId, initialValue) {
5170
- const [stateMap, setStateMap] = (0, import_react23.useState)(() => /* @__PURE__ */ new Map());
5171
- const activeSceneIdRef = (0, import_react23.useRef)(activeSceneId);
5467
+ const [stateMap, setStateMap] = (0, import_react25.useState)(() => /* @__PURE__ */ new Map());
5468
+ const activeSceneIdRef = (0, import_react25.useRef)(activeSceneId);
5172
5469
  activeSceneIdRef.current = activeSceneId;
5173
5470
  const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
5174
- const setForCurrentScene = (0, import_react23.useCallback)((value) => {
5471
+ const setForCurrentScene = (0, import_react25.useCallback)((value) => {
5175
5472
  const sid = activeSceneIdRef.current;
5176
5473
  if (sid === null) return;
5177
5474
  setStateMap((prev) => {
@@ -5182,7 +5479,7 @@ function useSceneState(activeSceneId, initialValue) {
5182
5479
  return newMap;
5183
5480
  });
5184
5481
  }, [initialValue]);
5185
- const setForScene = (0, import_react23.useCallback)((sceneId, value) => {
5482
+ const setForScene = (0, import_react25.useCallback)((sceneId, value) => {
5186
5483
  setStateMap((prev) => {
5187
5484
  const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
5188
5485
  const next = typeof value === "function" ? value(current) : value;
@@ -5195,10 +5492,10 @@ function useSceneState(activeSceneId, initialValue) {
5195
5492
  }
5196
5493
 
5197
5494
  // src/hooks/useAnySolo.ts
5198
- var import_react24 = require("react");
5495
+ var import_react26 = require("react");
5199
5496
  function useAnySolo(host) {
5200
- const [anySolo, setAnySolo] = (0, import_react24.useState)(false);
5201
- (0, import_react24.useEffect)(() => {
5497
+ const [anySolo, setAnySolo] = (0, import_react26.useState)(false);
5498
+ (0, import_react26.useEffect)(() => {
5202
5499
  let active = true;
5203
5500
  const refresh = () => {
5204
5501
  host.isAnySoloActive().then((v) => {
@@ -5217,7 +5514,7 @@ function useAnySolo(host) {
5217
5514
  }
5218
5515
 
5219
5516
  // src/hooks/useSoundHistory.ts
5220
- var import_react25 = require("react");
5517
+ var import_react27 = require("react");
5221
5518
  var EMPTY = { entries: [], cursor: -1 };
5222
5519
  function sameDescriptor(a, b) {
5223
5520
  if (a === b) return true;
@@ -5229,14 +5526,14 @@ function sameDescriptor(a, b) {
5229
5526
  }
5230
5527
  function useSoundHistory(applySound, opts = {}) {
5231
5528
  const max = Math.max(2, opts.max ?? 24);
5232
- const applyRef = (0, import_react25.useRef)(applySound);
5529
+ const applyRef = (0, import_react27.useRef)(applySound);
5233
5530
  applyRef.current = applySound;
5234
- const onChangeRef = (0, import_react25.useRef)(opts.onChange);
5531
+ const onChangeRef = (0, import_react27.useRef)(opts.onChange);
5235
5532
  onChangeRef.current = opts.onChange;
5236
- const dataRef = (0, import_react25.useRef)({});
5237
- const [, setVersion] = (0, import_react25.useState)(0);
5238
- const bump = (0, import_react25.useCallback)(() => setVersion((v) => v + 1), []);
5239
- const commit = (0, import_react25.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)(
5240
5537
  (trackId, next, notify) => {
5241
5538
  dataRef.current = { ...dataRef.current, [trackId]: next };
5242
5539
  bump();
@@ -5244,7 +5541,7 @@ function useSoundHistory(applySound, opts = {}) {
5244
5541
  },
5245
5542
  [bump]
5246
5543
  );
5247
- const record = (0, import_react25.useCallback)(
5544
+ const record = (0, import_react27.useCallback)(
5248
5545
  (trackId, descriptor, label) => {
5249
5546
  const h = dataRef.current[trackId];
5250
5547
  const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
@@ -5259,7 +5556,7 @@ function useSoundHistory(applySound, opts = {}) {
5259
5556
  },
5260
5557
  [max, commit]
5261
5558
  );
5262
- const restoreTo = (0, import_react25.useCallback)(
5559
+ const restoreTo = (0, import_react27.useCallback)(
5263
5560
  async (trackId, index) => {
5264
5561
  const h = dataRef.current[trackId];
5265
5562
  if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
@@ -5269,7 +5566,7 @@ function useSoundHistory(applySound, opts = {}) {
5269
5566
  },
5270
5567
  [commit]
5271
5568
  );
5272
- const undo = (0, import_react25.useCallback)(
5569
+ const undo = (0, import_react27.useCallback)(
5273
5570
  (trackId) => {
5274
5571
  const h = dataRef.current[trackId];
5275
5572
  if (!h || h.cursor <= 0) return Promise.resolve(false);
@@ -5277,7 +5574,7 @@ function useSoundHistory(applySound, opts = {}) {
5277
5574
  },
5278
5575
  [restoreTo]
5279
5576
  );
5280
- const toggleFavorite = (0, import_react25.useCallback)(
5577
+ const toggleFavorite = (0, import_react27.useCallback)(
5281
5578
  (trackId, index) => {
5282
5579
  const h = dataRef.current[trackId];
5283
5580
  if (!h || index < 0 || index >= h.entries.length) return;
@@ -5286,7 +5583,7 @@ function useSoundHistory(applySound, opts = {}) {
5286
5583
  },
5287
5584
  [commit]
5288
5585
  );
5289
- const restore = (0, import_react25.useCallback)(
5586
+ const restore = (0, import_react27.useCallback)(
5290
5587
  (trackId, state) => {
5291
5588
  const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
5292
5589
  const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
@@ -5295,15 +5592,15 @@ function useSoundHistory(applySound, opts = {}) {
5295
5592
  },
5296
5593
  [commit]
5297
5594
  );
5298
- const list = (0, import_react25.useCallback)(
5595
+ const list = (0, import_react27.useCallback)(
5299
5596
  (trackId) => dataRef.current[trackId] ?? EMPTY,
5300
5597
  []
5301
5598
  );
5302
- const canUndo = (0, import_react25.useCallback)((trackId) => {
5599
+ const canUndo = (0, import_react27.useCallback)((trackId) => {
5303
5600
  const h = dataRef.current[trackId];
5304
5601
  return !!h && h.cursor > 0;
5305
5602
  }, []);
5306
- const clear = (0, import_react25.useCallback)(
5603
+ const clear = (0, import_react27.useCallback)(
5307
5604
  (trackId) => {
5308
5605
  if (dataRef.current[trackId]) {
5309
5606
  const next = { ...dataRef.current };
@@ -5315,11 +5612,11 @@ function useSoundHistory(applySound, opts = {}) {
5315
5612
  },
5316
5613
  [bump]
5317
5614
  );
5318
- const reset = (0, import_react25.useCallback)(() => {
5615
+ const reset = (0, import_react27.useCallback)(() => {
5319
5616
  dataRef.current = {};
5320
5617
  bump();
5321
5618
  }, [bump]);
5322
- return (0, import_react25.useMemo)(
5619
+ return (0, import_react27.useMemo)(
5323
5620
  () => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
5324
5621
  [record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
5325
5622
  );
@@ -5452,7 +5749,7 @@ function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
5452
5749
  }
5453
5750
 
5454
5751
  // src/panel-core/useTransitionOps.ts
5455
- var import_react26 = require("react");
5752
+ var import_react28 = require("react");
5456
5753
  function useTransitionOps({
5457
5754
  host,
5458
5755
  adapter,
@@ -5469,8 +5766,8 @@ function useTransitionOps({
5469
5766
  resolvedFades
5470
5767
  }) {
5471
5768
  const { identity } = adapter;
5472
- const appliedFadeAutomationRef = (0, import_react26.useRef)(/* @__PURE__ */ new Set());
5473
- const applyCrossfadeAutomation = (0, import_react26.useCallback)(
5769
+ const appliedFadeAutomationRef = (0, import_react28.useRef)(/* @__PURE__ */ new Set());
5770
+ const applyCrossfadeAutomation = (0, import_react28.useCallback)(
5474
5771
  async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
5475
5772
  if (host.setTrackVolumeAutomation) {
5476
5773
  const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
@@ -5487,7 +5784,7 @@ function useTransitionOps({
5487
5784
  },
5488
5785
  [host]
5489
5786
  );
5490
- const applyFadeAutomation = (0, import_react26.useCallback)(
5787
+ const applyFadeAutomation = (0, import_react28.useCallback)(
5491
5788
  async (trackId, direction, bars, bpm, sliderPos, gesture) => {
5492
5789
  if (!host.setTrackVolumeAutomation) return;
5493
5790
  const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
@@ -5496,8 +5793,8 @@ function useTransitionOps({
5496
5793
  },
5497
5794
  [host]
5498
5795
  );
5499
- const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react26.useState)(false);
5500
- const handleCreateCrossfade = (0, import_react26.useCallback)(
5796
+ const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react28.useState)(false);
5797
+ const handleCreateCrossfade = (0, import_react28.useCallback)(
5501
5798
  async (origin, target) => {
5502
5799
  const scene = activeSceneId;
5503
5800
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5625,8 +5922,8 @@ function useTransitionOps({
5625
5922
  loadTracks
5626
5923
  ]
5627
5924
  );
5628
- const [isCreatingFade, setIsCreatingFade] = (0, import_react26.useState)(false);
5629
- const handleCreateFade = (0, import_react26.useCallback)(
5925
+ const [isCreatingFade, setIsCreatingFade] = (0, import_react28.useState)(false);
5926
+ const handleCreateFade = (0, import_react28.useCallback)(
5630
5927
  async (selection, direction, gesture) => {
5631
5928
  const scene = activeSceneId;
5632
5929
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5736,7 +6033,7 @@ function useTransitionOps({
5736
6033
  loadTracks
5737
6034
  ]
5738
6035
  );
5739
- const handleCrossfadeMute = (0, import_react26.useCallback)(
6036
+ const handleCrossfadeMute = (0, import_react28.useCallback)(
5740
6037
  (pair) => {
5741
6038
  const newMuted = !pair.origin.runtimeState.muted;
5742
6039
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5751,7 +6048,7 @@ function useTransitionOps({
5751
6048
  },
5752
6049
  [host, setTracks]
5753
6050
  );
5754
- const handleCrossfadeSolo = (0, import_react26.useCallback)(
6051
+ const handleCrossfadeSolo = (0, import_react28.useCallback)(
5755
6052
  (pair) => {
5756
6053
  const newSolo = !pair.origin.runtimeState.solo;
5757
6054
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5766,7 +6063,7 @@ function useTransitionOps({
5766
6063
  },
5767
6064
  [host, setTracks]
5768
6065
  );
5769
- const handleCrossfadeDelete = (0, import_react26.useCallback)(
6066
+ const handleCrossfadeDelete = (0, import_react28.useCallback)(
5770
6067
  async (pair) => {
5771
6068
  try {
5772
6069
  for (const member of [pair.origin, pair.target]) {
@@ -5792,8 +6089,8 @@ function useTransitionOps({
5792
6089
  },
5793
6090
  [host, activeSceneId, setCrossfadePairsMeta, setTracks]
5794
6091
  );
5795
- const crossfadeSliderTimers = (0, import_react26.useRef)({});
5796
- const handleCrossfadeSlider = (0, import_react26.useCallback)(
6092
+ const crossfadeSliderTimers = (0, import_react28.useRef)({});
6093
+ const handleCrossfadeSlider = (0, import_react28.useCallback)(
5797
6094
  (pair, pos) => {
5798
6095
  setCrossfadePairsMeta(
5799
6096
  (prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
@@ -5826,7 +6123,7 @@ function useTransitionOps({
5826
6123
  },
5827
6124
  [host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
5828
6125
  );
5829
- const handleFadeDelete = (0, import_react26.useCallback)(
6126
+ const handleFadeDelete = (0, import_react28.useCallback)(
5830
6127
  async (fade) => {
5831
6128
  try {
5832
6129
  await host.deleteTrack(fade.track.handle.id);
@@ -5846,8 +6143,8 @@ function useTransitionOps({
5846
6143
  },
5847
6144
  [host, activeSceneId, setFadesMeta, setTracks]
5848
6145
  );
5849
- const fadeSliderTimers = (0, import_react26.useRef)({});
5850
- const handleFadeSlider = (0, import_react26.useCallback)(
6146
+ const fadeSliderTimers = (0, import_react28.useRef)({});
6147
+ const handleFadeSlider = (0, import_react28.useCallback)(
5851
6148
  (fade, pos) => {
5852
6149
  setFadesMeta(
5853
6150
  (prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
@@ -5877,8 +6174,8 @@ function useTransitionOps({
5877
6174
  },
5878
6175
  [host, activeSceneId, applyFadeAutomation, setFadesMeta]
5879
6176
  );
5880
- const lastResyncKeyRef = (0, import_react26.useRef)("");
5881
- (0, import_react26.useEffect)(() => {
6177
+ const lastResyncKeyRef = (0, import_react28.useRef)("");
6178
+ (0, import_react28.useEffect)(() => {
5882
6179
  if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
5883
6180
  return;
5884
6181
  }
@@ -5919,7 +6216,7 @@ function useTransitionOps({
5919
6216
  cancelled = true;
5920
6217
  };
5921
6218
  }, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
5922
- (0, import_react26.useEffect)(() => {
6219
+ (0, import_react28.useEffect)(() => {
5923
6220
  if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
5924
6221
  void (async () => {
5925
6222
  const mc = await host.getMusicalContext();
@@ -5953,7 +6250,7 @@ function useTransitionOps({
5953
6250
  }
5954
6251
 
5955
6252
  // src/panel-core/useGeneratorPanelCore.tsx
5956
- var import_jsx_runtime24 = require("react/jsx-runtime");
6253
+ var import_jsx_runtime25 = require("react/jsx-runtime");
5957
6254
  var EMPTY_PLACEHOLDERS = [];
5958
6255
  function useGeneratorPanelCore({
5959
6256
  ui,
@@ -5973,8 +6270,8 @@ function useGeneratorPanelCore({
5973
6270
  } = ui;
5974
6271
  const { identity, features } = adapter;
5975
6272
  const logTag = identity.logTag;
5976
- const adapterRef = (0, import_react27.useRef)(adapter);
5977
- (0, import_react27.useEffect)(() => {
6273
+ const adapterRef = (0, import_react29.useRef)(adapter);
6274
+ (0, import_react29.useEffect)(() => {
5978
6275
  if (adapterRef.current !== adapter) {
5979
6276
  adapterRef.current = adapter;
5980
6277
  console.warn(
@@ -5984,27 +6281,27 @@ function useGeneratorPanelCore({
5984
6281
  }, [adapter, logTag]);
5985
6282
  const supportsMeters = typeof host.getTrackLevels === "function";
5986
6283
  const trackLevels = useTrackLevels(host, isExpanded);
5987
- const [tracks, setTracks] = (0, import_react27.useState)([]);
5988
- const [isLoadingTracks, setIsLoadingTracks] = (0, import_react27.useState)(false);
5989
- const [importOpen, setImportOpen] = (0, import_react27.useState)(false);
5990
- const [soundImportTarget, setSoundImportTarget] = (0, import_react27.useState)(null);
5991
- const [designerView, setDesignerView] = (0, import_react27.useState)(false);
5992
- const [transitionSourceTotal, setTransitionSourceTotal] = (0, import_react27.useState)(0);
5993
- const [crossfadePairsMeta, setCrossfadePairsMeta] = (0, import_react27.useState)([]);
5994
- const [fadesMeta, setFadesMeta] = (0, import_react27.useState)([]);
5995
- const [genericGroupMetas, setGenericGroupMetas] = (0, import_react27.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)({});
5996
6293
  const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
5997
6294
  const [placeholders, , setPlaceholdersForScene] = useSceneState(
5998
6295
  activeSceneId,
5999
6296
  EMPTY_PLACEHOLDERS
6000
6297
  );
6001
- const saveTimeoutRefs = (0, import_react27.useRef)({});
6002
- const editLoadStartedRef = (0, import_react27.useRef)(/* @__PURE__ */ new Set());
6003
- const [availableInstruments, setAvailableInstruments] = (0, import_react27.useState)([]);
6004
- const [instrumentsLoading, setInstrumentsLoading] = (0, import_react27.useState)(false);
6005
- const engineToDbIdRef = (0, import_react27.useRef)(/* @__PURE__ */ new Map());
6006
- const tracksLoadedForSceneRef = (0, import_react27.useRef)(null);
6007
- const persistSoundHistory = (0, import_react27.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)(
6008
6305
  (trackId, state) => {
6009
6306
  if (!activeSceneId) return;
6010
6307
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -6024,7 +6321,7 @@ function useGeneratorPanelCore({
6024
6321
  setItems: setTracks,
6025
6322
  getId: (t) => t.handle.dbId
6026
6323
  });
6027
- const loadTracks = (0, import_react27.useCallback)(
6324
+ const loadTracks = (0, import_react29.useCallback)(
6028
6325
  async (incremental = false) => {
6029
6326
  const sceneAtStart = activeSceneId;
6030
6327
  if (!sceneAtStart) {
@@ -6149,18 +6446,18 @@ function useGeneratorPanelCore({
6149
6446
  },
6150
6447
  [host, activeSceneId, soundHistory, adapter, logTag]
6151
6448
  );
6152
- (0, import_react27.useEffect)(() => {
6449
+ (0, import_react29.useEffect)(() => {
6153
6450
  loadTracks();
6154
6451
  }, [loadTracks]);
6155
- (0, import_react27.useEffect)(() => {
6452
+ (0, import_react29.useEffect)(() => {
6156
6453
  const map = /* @__PURE__ */ new Map();
6157
6454
  for (const t of tracks) {
6158
6455
  map.set(t.handle.id, t.handle.dbId);
6159
6456
  }
6160
6457
  engineToDbIdRef.current = map;
6161
6458
  }, [tracks]);
6162
- const loadedCompletedIdsRef = (0, import_react27.useRef)(/* @__PURE__ */ new Set());
6163
- (0, import_react27.useEffect)(() => {
6459
+ const loadedCompletedIdsRef = (0, import_react29.useRef)(/* @__PURE__ */ new Set());
6460
+ (0, import_react29.useEffect)(() => {
6164
6461
  if (placeholders.length === 0) {
6165
6462
  loadedCompletedIdsRef.current.clear();
6166
6463
  return;
@@ -6179,16 +6476,16 @@ function useGeneratorPanelCore({
6179
6476
  loadTracks(true);
6180
6477
  }
6181
6478
  }, [placeholders, loadTracks, logTag]);
6182
- const adoptAndLoad = (0, import_react27.useCallback)(() => {
6479
+ const adoptAndLoad = (0, import_react29.useCallback)(() => {
6183
6480
  loadTracks(true);
6184
6481
  }, [loadTracks]);
6185
- (0, import_react27.useEffect)(() => {
6482
+ (0, import_react29.useEffect)(() => {
6186
6483
  const unsub = host.onEngineReady(() => {
6187
6484
  adoptAndLoad();
6188
6485
  });
6189
6486
  return unsub;
6190
6487
  }, [host, adoptAndLoad]);
6191
- (0, import_react27.useEffect)(() => {
6488
+ (0, import_react29.useEffect)(() => {
6192
6489
  if (typeof host.onAfterAgentMutation !== "function") return;
6193
6490
  let timer = null;
6194
6491
  const unsub = host.onAfterAgentMutation(() => {
@@ -6203,13 +6500,13 @@ function useGeneratorPanelCore({
6203
6500
  if (timer) clearTimeout(timer);
6204
6501
  };
6205
6502
  }, [host, loadTracks]);
6206
- (0, import_react27.useEffect)(() => {
6503
+ (0, import_react29.useEffect)(() => {
6207
6504
  const unsub = host.onTrackStateChange((trackId, state) => {
6208
6505
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
6209
6506
  });
6210
6507
  return unsub;
6211
6508
  }, [host]);
6212
- (0, import_react27.useEffect)(() => {
6509
+ (0, import_react29.useEffect)(() => {
6213
6510
  if (!features.bulkComposePlaceholders) return;
6214
6511
  console.log(`[${logTag}] Subscribing to composeProgress`);
6215
6512
  const unsub = host.onComposeProgress((event) => {
@@ -6243,7 +6540,7 @@ function useGeneratorPanelCore({
6243
6540
  });
6244
6541
  return unsub;
6245
6542
  }, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
6246
- (0, import_react27.useEffect)(() => {
6543
+ (0, import_react29.useEffect)(() => {
6247
6544
  const refs = saveTimeoutRefs;
6248
6545
  return () => {
6249
6546
  for (const timeout of Object.values(refs.current)) {
@@ -6251,9 +6548,9 @@ function useGeneratorPanelCore({
6251
6548
  }
6252
6549
  };
6253
6550
  }, []);
6254
- const isAddingTrackRef = (0, import_react27.useRef)(false);
6255
- const [isAddingTrack, setIsAddingTrack] = (0, import_react27.useState)(false);
6256
- const handleAddTrack = (0, import_react27.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 () => {
6257
6554
  if (isAddingTrackRef.current) return;
6258
6555
  if (!activeSceneId) {
6259
6556
  host.showToast("warning", "Select SCENE");
@@ -6293,7 +6590,7 @@ function useGeneratorPanelCore({
6293
6590
  setIsAddingTrack(false);
6294
6591
  }
6295
6592
  }, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
6296
- const handlePortTrack = (0, import_react27.useCallback)(
6593
+ const handlePortTrack = (0, import_react29.useCallback)(
6297
6594
  async (sel) => {
6298
6595
  if (!activeSceneId) {
6299
6596
  host.showToast("warning", "Select SCENE");
@@ -6350,7 +6647,7 @@ function useGeneratorPanelCore({
6350
6647
  },
6351
6648
  [host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
6352
6649
  );
6353
- const handleSoundImportPick = (0, import_react27.useCallback)(
6650
+ const handleSoundImportPick = (0, import_react29.useCallback)(
6354
6651
  async (sel) => {
6355
6652
  const target = soundImportTarget;
6356
6653
  if (!target || !host.getTrackSound) {
@@ -6381,8 +6678,8 @@ function useGeneratorPanelCore({
6381
6678
  },
6382
6679
  [soundImportTarget, host, adapter, identity.familyKey, soundHistory]
6383
6680
  );
6384
- const [isExportingMidi, setIsExportingMidi] = (0, import_react27.useState)(false);
6385
- const handleExportMidi = (0, import_react27.useCallback)(async () => {
6681
+ const [isExportingMidi, setIsExportingMidi] = (0, import_react29.useState)(false);
6682
+ const handleExportMidi = (0, import_react29.useCallback)(async () => {
6386
6683
  if (isExportingMidi) return;
6387
6684
  setIsExportingMidi(true);
6388
6685
  try {
@@ -6413,10 +6710,10 @@ function useGeneratorPanelCore({
6413
6710
  const xfFromId = sceneContext?.transitionFromSceneId ?? null;
6414
6711
  const xfToId = sceneContext?.transitionToSceneId ?? null;
6415
6712
  const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
6416
- (0, import_react27.useEffect)(() => {
6713
+ (0, import_react29.useEffect)(() => {
6417
6714
  if (!canCrossfade) setDesignerView(false);
6418
6715
  }, [canCrossfade]);
6419
- (0, import_react27.useEffect)(() => {
6716
+ (0, import_react29.useEffect)(() => {
6420
6717
  if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
6421
6718
  setTransitionSourceTotal(0);
6422
6719
  return;
@@ -6432,12 +6729,12 @@ function useGeneratorPanelCore({
6432
6729
  };
6433
6730
  }, [canCrossfade, xfFromId, xfToId, host]);
6434
6731
  const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
6435
- (0, import_react27.useEffect)(() => {
6732
+ (0, import_react29.useEffect)(() => {
6436
6733
  if (!onHeaderContent) return;
6437
6734
  const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
6438
6735
  onHeaderContent(
6439
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex gap-1 items-center", children: [
6440
- features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime24.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)(
6441
6738
  "button",
6442
6739
  {
6443
6740
  "data-testid": `import-from-scene-${identity.familyKey}-button`,
@@ -6451,7 +6748,7 @@ function useGeneratorPanelCore({
6451
6748
  children: identity.importTrackLabel ?? "Import Track"
6452
6749
  }
6453
6750
  ),
6454
- (!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
6751
+ (!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6455
6752
  "button",
6456
6753
  {
6457
6754
  "data-testid": `add-${identity.familyKey}-track-button`,
@@ -6467,7 +6764,7 @@ function useGeneratorPanelCore({
6467
6764
  children: identity.addTrackLabel ?? "Add Track"
6468
6765
  }
6469
6766
  ),
6470
- canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
6767
+ canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
6471
6768
  "button",
6472
6769
  {
6473
6770
  "data-testid": `${identity.familyKey}-view-toggle`,
@@ -6486,7 +6783,7 @@ function useGeneratorPanelCore({
6486
6783
  title: designerView ? "Back to the track list" : "Open the transition designer",
6487
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",
6488
6785
  children: [
6489
- transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
6786
+ transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6490
6787
  "span",
6491
6788
  {
6492
6789
  className: "absolute inset-y-0 left-0 bg-sas-accent/25",
@@ -6494,7 +6791,7 @@ function useGeneratorPanelCore({
6494
6791
  "aria-hidden": true
6495
6792
  }
6496
6793
  ),
6497
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "relative", children: [
6794
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "relative", children: [
6498
6795
  "\u21C4 ",
6499
6796
  designerView ? "Transition" : "Tracks",
6500
6797
  transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
@@ -6525,7 +6822,7 @@ function useGeneratorPanelCore({
6525
6822
  identity,
6526
6823
  features.importTracks
6527
6824
  ]);
6528
- (0, import_react27.useEffect)(() => {
6825
+ (0, import_react29.useEffect)(() => {
6529
6826
  if (!onLoading) return;
6530
6827
  const anyGenerating = tracks.some((t) => t.isGenerating);
6531
6828
  onLoading(isLoadingTracks || anyGenerating || isBulkActive);
@@ -6533,7 +6830,7 @@ function useGeneratorPanelCore({
6533
6830
  onLoading(false);
6534
6831
  };
6535
6832
  }, [onLoading, isLoadingTracks, tracks, isBulkActive]);
6536
- const handleDeleteTrack = (0, import_react27.useCallback)(
6833
+ const handleDeleteTrack = (0, import_react29.useCallback)(
6537
6834
  async (trackId) => {
6538
6835
  try {
6539
6836
  await host.deleteTrack(trackId);
@@ -6549,7 +6846,7 @@ function useGeneratorPanelCore({
6549
6846
  },
6550
6847
  [host, activeSceneId]
6551
6848
  );
6552
- const handlePromptChange = (0, import_react27.useCallback)(
6849
+ const handlePromptChange = (0, import_react29.useCallback)(
6553
6850
  (trackId, prompt) => {
6554
6851
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
6555
6852
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -6565,7 +6862,7 @@ function useGeneratorPanelCore({
6565
6862
  },
6566
6863
  [host, activeSceneId]
6567
6864
  );
6568
- const resolvedGenericGroups = (0, import_react27.useMemo)(() => {
6865
+ const resolvedGenericGroups = (0, import_react29.useMemo)(() => {
6569
6866
  const out = {};
6570
6867
  for (const ext of adapter.groupExtensions ?? []) {
6571
6868
  out[ext.metaKey] = resolveTrackGroups(
@@ -6579,18 +6876,18 @@ function useGeneratorPanelCore({
6579
6876
  }
6580
6877
  return out;
6581
6878
  }, [adapter, genericGroupMetas, tracks]);
6582
- const genericGroupMemberDbIds = (0, import_react27.useMemo)(() => {
6879
+ const genericGroupMemberDbIds = (0, import_react29.useMemo)(() => {
6583
6880
  const s = /* @__PURE__ */ new Set();
6584
6881
  for (const r of Object.values(resolvedGenericGroups)) {
6585
6882
  for (const dbId of r.memberDbIds) s.add(dbId);
6586
6883
  }
6587
6884
  return s;
6588
6885
  }, [resolvedGenericGroups]);
6589
- const engineToDbId = (0, import_react27.useCallback)(
6886
+ const engineToDbId = (0, import_react29.useCallback)(
6590
6887
  (trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
6591
6888
  []
6592
6889
  );
6593
- const updateTrack = (0, import_react27.useCallback)(
6890
+ const updateTrack = (0, import_react29.useCallback)(
6594
6891
  (trackId, patch) => {
6595
6892
  setTracks(
6596
6893
  (prev) => prev.map(
@@ -6600,18 +6897,18 @@ function useGeneratorPanelCore({
6600
6897
  },
6601
6898
  []
6602
6899
  );
6603
- const markEditLoaded = (0, import_react27.useCallback)((trackId) => {
6900
+ const markEditLoaded = (0, import_react29.useCallback)((trackId) => {
6604
6901
  editLoadStartedRef.current.add(trackId);
6605
6902
  }, []);
6606
- const tracksRef = (0, import_react27.useRef)(tracks);
6607
- (0, import_react27.useEffect)(() => {
6903
+ const tracksRef = (0, import_react29.useRef)(tracks);
6904
+ (0, import_react29.useEffect)(() => {
6608
6905
  tracksRef.current = tracks;
6609
6906
  }, [tracks]);
6610
- const resolvedGenericGroupsRef = (0, import_react27.useRef)(resolvedGenericGroups);
6611
- (0, import_react27.useEffect)(() => {
6907
+ const resolvedGenericGroupsRef = (0, import_react29.useRef)(resolvedGenericGroups);
6908
+ (0, import_react29.useEffect)(() => {
6612
6909
  resolvedGenericGroupsRef.current = resolvedGenericGroups;
6613
6910
  }, [resolvedGenericGroups]);
6614
- const makeServices = (0, import_react27.useCallback)(() => {
6911
+ const makeServices = (0, import_react29.useCallback)(() => {
6615
6912
  return {
6616
6913
  host,
6617
6914
  activeSceneId,
@@ -6630,7 +6927,7 @@ function useGeneratorPanelCore({
6630
6927
  resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
6631
6928
  };
6632
6929
  }, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
6633
- const handleGenerate = (0, import_react27.useCallback)(
6930
+ const handleGenerate = (0, import_react29.useCallback)(
6634
6931
  async (trackId) => {
6635
6932
  const track = tracks.find((t) => t.handle.id === trackId);
6636
6933
  if (!track || !track.prompt.trim()) return;
@@ -6657,7 +6954,7 @@ function useGeneratorPanelCore({
6657
6954
  },
6658
6955
  [host, adapter, tracks, isAuthenticated, makeServices]
6659
6956
  );
6660
- const handleMuteToggle = (0, import_react27.useCallback)(
6957
+ const handleMuteToggle = (0, import_react29.useCallback)(
6661
6958
  (trackId) => {
6662
6959
  const track = tracks.find((t) => t.handle.id === trackId);
6663
6960
  if (!track) return;
@@ -6677,7 +6974,7 @@ function useGeneratorPanelCore({
6677
6974
  },
6678
6975
  [host, tracks]
6679
6976
  );
6680
- const handleSoloToggle = (0, import_react27.useCallback)(
6977
+ const handleSoloToggle = (0, import_react29.useCallback)(
6681
6978
  (trackId) => {
6682
6979
  const track = tracks.find((t) => t.handle.id === trackId);
6683
6980
  if (!track) return;
@@ -6697,7 +6994,7 @@ function useGeneratorPanelCore({
6697
6994
  },
6698
6995
  [host, tracks]
6699
6996
  );
6700
- const handleVolumeChange = (0, import_react27.useCallback)(
6997
+ const handleVolumeChange = (0, import_react29.useCallback)(
6701
6998
  (trackId, volume) => {
6702
6999
  setTracks(
6703
7000
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
@@ -6707,7 +7004,7 @@ function useGeneratorPanelCore({
6707
7004
  },
6708
7005
  [host]
6709
7006
  );
6710
- const handlePanChange = (0, import_react27.useCallback)(
7007
+ const handlePanChange = (0, import_react29.useCallback)(
6711
7008
  (trackId, pan) => {
6712
7009
  setTracks(
6713
7010
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
@@ -6717,7 +7014,7 @@ function useGeneratorPanelCore({
6717
7014
  },
6718
7015
  [host]
6719
7016
  );
6720
- const handleShuffle = (0, import_react27.useCallback)(
7017
+ const handleShuffle = (0, import_react29.useCallback)(
6721
7018
  async (trackId) => {
6722
7019
  const track = tracks.find((t) => t.handle.id === trackId);
6723
7020
  if (!track) return;
@@ -6759,7 +7056,7 @@ function useGeneratorPanelCore({
6759
7056
  },
6760
7057
  [host, adapter, tracks, soundHistory, logTag]
6761
7058
  );
6762
- const handleCopy = (0, import_react27.useCallback)(
7059
+ const handleCopy = (0, import_react29.useCallback)(
6763
7060
  async (trackId) => {
6764
7061
  try {
6765
7062
  const newHandle = await host.duplicateTrack(trackId);
@@ -6772,7 +7069,7 @@ function useGeneratorPanelCore({
6772
7069
  },
6773
7070
  [host, loadTracks]
6774
7071
  );
6775
- const handleFxToggle = (0, import_react27.useCallback)(
7072
+ const handleFxToggle = (0, import_react29.useCallback)(
6776
7073
  (trackId, category, enabled) => {
6777
7074
  setTracks(
6778
7075
  (prev) => prev.map(
@@ -6795,7 +7092,7 @@ function useGeneratorPanelCore({
6795
7092
  },
6796
7093
  [host]
6797
7094
  );
6798
- const handleFxPresetChange = (0, import_react27.useCallback)(
7095
+ const handleFxPresetChange = (0, import_react29.useCallback)(
6799
7096
  (trackId, category, presetIndex) => {
6800
7097
  setTracks(
6801
7098
  (prev) => prev.map(
@@ -6821,7 +7118,7 @@ function useGeneratorPanelCore({
6821
7118
  },
6822
7119
  [host]
6823
7120
  );
6824
- const handleFxDryWetChange = (0, import_react27.useCallback)(
7121
+ const handleFxDryWetChange = (0, import_react29.useCallback)(
6825
7122
  (trackId, category, value) => {
6826
7123
  setTracks(
6827
7124
  (prev) => prev.map(
@@ -6833,7 +7130,7 @@ function useGeneratorPanelCore({
6833
7130
  },
6834
7131
  [host]
6835
7132
  );
6836
- const toggleFxDrawer = (0, import_react27.useCallback)(
7133
+ const toggleFxDrawer = (0, import_react29.useCallback)(
6837
7134
  (trackId) => {
6838
7135
  setTracks(
6839
7136
  (prev) => prev.map((t) => {
@@ -6855,7 +7152,7 @@ function useGeneratorPanelCore({
6855
7152
  },
6856
7153
  [host, tracks]
6857
7154
  );
6858
- const loadEditNotes = (0, import_react27.useCallback)(
7155
+ const loadEditNotes = (0, import_react29.useCallback)(
6859
7156
  async (trackId) => {
6860
7157
  try {
6861
7158
  const mc = await host.getMusicalContext();
@@ -6873,7 +7170,7 @@ function useGeneratorPanelCore({
6873
7170
  },
6874
7171
  [host, logTag]
6875
7172
  );
6876
- const handleNotesChange = (0, import_react27.useCallback)(
7173
+ const handleNotesChange = (0, import_react29.useCallback)(
6877
7174
  (trackId, notes) => {
6878
7175
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
6879
7176
  const key = `edit:${trackId}`;
@@ -6903,7 +7200,7 @@ function useGeneratorPanelCore({
6903
7200
  },
6904
7201
  [host]
6905
7202
  );
6906
- const handleTabChange = (0, import_react27.useCallback)(
7203
+ const handleTabChange = (0, import_react29.useCallback)(
6907
7204
  (trackId, tab) => {
6908
7205
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
6909
7206
  if (tab === "fx") {
@@ -6928,10 +7225,10 @@ function useGeneratorPanelCore({
6928
7225
  },
6929
7226
  [host, availableInstruments.length, instrumentsLoading, loadEditNotes]
6930
7227
  );
6931
- const handleProgressChange = (0, import_react27.useCallback)((trackId, pct) => {
7228
+ const handleProgressChange = (0, import_react29.useCallback)((trackId, pct) => {
6932
7229
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
6933
7230
  }, []);
6934
- const handleToggleDrawer = (0, import_react27.useCallback)((trackId) => {
7231
+ const handleToggleDrawer = (0, import_react29.useCallback)((trackId) => {
6935
7232
  setTracks(
6936
7233
  (prev) => prev.map((t) => {
6937
7234
  if (t.handle.id !== trackId) return t;
@@ -6940,7 +7237,7 @@ function useGeneratorPanelCore({
6940
7237
  })
6941
7238
  );
6942
7239
  }, []);
6943
- const handleInstrumentSelect = (0, import_react27.useCallback)(
7240
+ const handleInstrumentSelect = (0, import_react29.useCallback)(
6944
7241
  async (trackId, pluginId) => {
6945
7242
  const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
6946
7243
  if (isDefaultInstrument) {
@@ -6993,7 +7290,7 @@ function useGeneratorPanelCore({
6993
7290
  },
6994
7291
  [host, identity.defaultInstrumentPluginId, logTag]
6995
7292
  );
6996
- const handleShowEditor = (0, import_react27.useCallback)(
7293
+ const handleShowEditor = (0, import_react29.useCallback)(
6997
7294
  async (trackId) => {
6998
7295
  try {
6999
7296
  await host.showInstrumentEditor(trackId);
@@ -7004,12 +7301,12 @@ function useGeneratorPanelCore({
7004
7301
  },
7005
7302
  [host]
7006
7303
  );
7007
- const handleBackToInstruments = (0, import_react27.useCallback)((trackId) => {
7304
+ const handleBackToInstruments = (0, import_react29.useCallback)((trackId) => {
7008
7305
  setTracks(
7009
7306
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
7010
7307
  );
7011
7308
  }, []);
7012
- const handleRefreshInstruments = (0, import_react27.useCallback)(() => {
7309
+ const handleRefreshInstruments = (0, import_react29.useCallback)(() => {
7013
7310
  setInstrumentsLoading(true);
7014
7311
  host.getAvailableInstruments().then((instruments) => {
7015
7312
  setAvailableInstruments(instruments);
@@ -7018,13 +7315,13 @@ function useGeneratorPanelCore({
7018
7315
  setInstrumentsLoading(false);
7019
7316
  });
7020
7317
  }, [host]);
7021
- const onAuditionNote = (0, import_react27.useCallback)(
7318
+ const onAuditionNote = (0, import_react29.useCallback)(
7022
7319
  (trackId, pitch, velocity, ms) => {
7023
7320
  void host.auditionNote(trackId, pitch, velocity, ms);
7024
7321
  },
7025
7322
  [host]
7026
7323
  );
7027
- const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react27.useMemo)(() => {
7324
+ const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react29.useMemo)(() => {
7028
7325
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
7029
7326
  const pairs = [];
7030
7327
  const members = /* @__PURE__ */ new Set();
@@ -7039,7 +7336,7 @@ function useGeneratorPanelCore({
7039
7336
  }
7040
7337
  return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
7041
7338
  }, [tracks, crossfadePairsMeta]);
7042
- const { resolvedFades, fadeMemberDbIds } = (0, import_react27.useMemo)(() => {
7339
+ const { resolvedFades, fadeMemberDbIds } = (0, import_react29.useMemo)(() => {
7043
7340
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
7044
7341
  const list = [];
7045
7342
  const members = /* @__PURE__ */ new Set();
@@ -7067,7 +7364,7 @@ function useGeneratorPanelCore({
7067
7364
  resolvedCrossfadePairs,
7068
7365
  resolvedFades
7069
7366
  });
7070
- const setGroupMute = (0, import_react27.useCallback)(
7367
+ const setGroupMute = (0, import_react29.useCallback)(
7071
7368
  (trackIds, muted) => {
7072
7369
  for (const id of trackIds) {
7073
7370
  setTracks(
@@ -7079,7 +7376,7 @@ function useGeneratorPanelCore({
7079
7376
  },
7080
7377
  [host]
7081
7378
  );
7082
- const setGroupSolo = (0, import_react27.useCallback)(
7379
+ const setGroupSolo = (0, import_react29.useCallback)(
7083
7380
  (trackIds, solo) => {
7084
7381
  for (const id of trackIds) {
7085
7382
  setTracks(
@@ -7091,7 +7388,7 @@ function useGeneratorPanelCore({
7091
7388
  },
7092
7389
  [host]
7093
7390
  );
7094
- const deleteGroup = (0, import_react27.useCallback)(
7391
+ const deleteGroup = (0, import_react29.useCallback)(
7095
7392
  async (members, cleanupKeySuffixes) => {
7096
7393
  for (const member of members) {
7097
7394
  try {
@@ -7111,7 +7408,7 @@ function useGeneratorPanelCore({
7111
7408
  },
7112
7409
  [host, activeSceneId, loadTracks]
7113
7410
  );
7114
- const handlers = (0, import_react27.useMemo)(
7411
+ const handlers = (0, import_react29.useMemo)(
7115
7412
  () => ({
7116
7413
  promptChange: handlePromptChange,
7117
7414
  generate: (trackId) => {
@@ -7225,8 +7522,8 @@ function useGeneratorPanelCore({
7225
7522
  }
7226
7523
 
7227
7524
  // src/panel-core/GeneratorPanelShell.tsx
7228
- var import_react28 = __toESM(require("react"));
7229
- var import_jsx_runtime25 = require("react/jsx-runtime");
7525
+ var import_react30 = __toESM(require("react"));
7526
+ var import_jsx_runtime26 = require("react/jsx-runtime");
7230
7527
  function GeneratorPanelShell({ core, slots }) {
7231
7528
  const {
7232
7529
  ui,
@@ -7281,7 +7578,7 @@ function GeneratorPanelShell({ core, slots }) {
7281
7578
  const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
7282
7579
  const panelBus = usePanelBus(host, activeSceneId);
7283
7580
  const { identity, features } = adapter;
7284
- const buildRowProps = (0, import_react28.useCallback)(
7581
+ const buildRowProps = (0, import_react30.useCallback)(
7285
7582
  (track, drag) => {
7286
7583
  const id = track.handle.id;
7287
7584
  const pickerProps = features.instrumentPicker ? {
@@ -7333,6 +7630,7 @@ function GeneratorPanelShell({ core, slots }) {
7333
7630
  onVolumeChange: (vol) => handlers.volumeChange(id, vol),
7334
7631
  onPanChange: (pan) => handlers.panChange(id, pan),
7335
7632
  onFxToggle: (cat, enabled) => handleFxToggle(id, cat, enabled),
7633
+ externalFxHost: host,
7336
7634
  onFxPresetChange: (cat, idx) => handleFxPresetChange(id, cat, idx),
7337
7635
  onFxDryWetChange: (cat, val) => handleFxDryWetChange(id, cat, val),
7338
7636
  onToggleFxDrawer: () => handlers.toggleFxDrawer(id),
@@ -7380,12 +7678,12 @@ function GeneratorPanelShell({ core, slots }) {
7380
7678
  ]
7381
7679
  );
7382
7680
  if (!activeSceneId) {
7383
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7681
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7384
7682
  "div",
7385
7683
  {
7386
7684
  "data-testid": `no-scene-placeholder-${identity.familyKey}`,
7387
7685
  className: "flex items-center justify-center py-8",
7388
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7686
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7389
7687
  "button",
7390
7688
  {
7391
7689
  onClick: () => onSelectScene?.(),
@@ -7397,12 +7695,12 @@ function GeneratorPanelShell({ core, slots }) {
7397
7695
  );
7398
7696
  }
7399
7697
  if (!sceneContext?.hasContract) {
7400
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7698
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7401
7699
  "div",
7402
7700
  {
7403
7701
  "data-testid": `no-contract-placeholder-${identity.familyKey}`,
7404
7702
  className: "flex items-center justify-center py-8",
7405
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7703
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7406
7704
  "button",
7407
7705
  {
7408
7706
  onClick: () => onOpenContract?.(),
@@ -7414,7 +7712,7 @@ function GeneratorPanelShell({ core, slots }) {
7414
7712
  );
7415
7713
  }
7416
7714
  if (features.bulkComposePlaceholders && isComposing) {
7417
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime25.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" }) });
7418
7716
  }
7419
7717
  const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
7420
7718
  if (activePlaceholders.length > 0) {
@@ -7425,18 +7723,18 @@ function GeneratorPanelShell({ core, slots }) {
7425
7723
  tracksByDbId.set(t.handle.id, t);
7426
7724
  }
7427
7725
  }
7428
- return /* @__PURE__ */ (0, import_jsx_runtime25.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) => {
7429
7727
  const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
7430
7728
  if (loadedTrack) {
7431
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7729
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7432
7730
  }
7433
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7731
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7434
7732
  "div",
7435
7733
  {
7436
7734
  "data-testid": "bulk-placeholder-track",
7437
7735
  className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
7438
7736
  style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
7439
- children: /* @__PURE__ */ (0, import_jsx_runtime25.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" })
7440
7738
  },
7441
7739
  ph.id
7442
7740
  );
@@ -7448,13 +7746,13 @@ function GeneratorPanelShell({ core, slots }) {
7448
7746
  supportsMeters,
7449
7747
  levels: supportsMeters ? trackLevels : void 0,
7450
7748
  handlers,
7451
- renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime25.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),
7452
7750
  setGroupMute,
7453
7751
  setGroupSolo,
7454
7752
  deleteGroup
7455
7753
  };
7456
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
7457
- features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime25.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)(
7458
7756
  ImportTrackModal,
7459
7757
  {
7460
7758
  host,
@@ -7467,7 +7765,7 @@ function GeneratorPanelShell({ core, slots }) {
7467
7765
  testIdPrefix: `${identity.familyKey}-import`
7468
7766
  }
7469
7767
  ),
7470
- features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7768
+ features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7471
7769
  ImportTrackModal,
7472
7770
  {
7473
7771
  host,
@@ -7482,7 +7780,7 @@ function GeneratorPanelShell({ core, slots }) {
7482
7780
  }
7483
7781
  ),
7484
7782
  slots?.modals,
7485
- canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7783
+ canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7486
7784
  TransitionDesigner,
7487
7785
  {
7488
7786
  host,
@@ -7499,11 +7797,12 @@ function GeneratorPanelShell({ core, slots }) {
7499
7797
  testIdPrefix: `${identity.familyKey}-transition-designer`
7500
7798
  }
7501
7799
  ) }),
7502
- !(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
7503
- panelBus.supported && panelBus.bus && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
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)(
7504
7802
  PanelMasterStrip,
7505
7803
  {
7506
7804
  bus: panelBus.bus,
7805
+ levels: panelBus.levels,
7507
7806
  availableFx: panelBus.availableFx,
7508
7807
  fxLoading: panelBus.fxLoading,
7509
7808
  soloedOut: anySolo && !panelBus.bus.soloed,
@@ -7520,7 +7819,7 @@ function GeneratorPanelShell({ core, slots }) {
7520
7819
  }
7521
7820
  ),
7522
7821
  slots?.beforeRows,
7523
- resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7822
+ resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7524
7823
  CrossfadeTrackRow,
7525
7824
  {
7526
7825
  accentColor: identity.transitionAccentColor,
@@ -7557,7 +7856,7 @@ function GeneratorPanelShell({ core, slots }) {
7557
7856
  },
7558
7857
  pair.groupId
7559
7858
  )),
7560
- resolvedFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7859
+ resolvedFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7561
7860
  FadeTrackRow,
7562
7861
  {
7563
7862
  accentColor: identity.transitionAccentColor,
@@ -7583,20 +7882,20 @@ function GeneratorPanelShell({ core, slots }) {
7583
7882
  fade.dbId
7584
7883
  )),
7585
7884
  (adapter.groupExtensions ?? []).flatMap(
7586
- (ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react28.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}`))
7587
7886
  ),
7588
7887
  tracks.map((track, index) => {
7589
7888
  if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
7590
7889
  return null;
7591
7890
  }
7592
- return /* @__PURE__ */ (0, import_jsx_runtime25.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);
7593
7892
  }),
7594
7893
  slots?.afterRows
7595
7894
  ] })),
7596
7895
  features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
7597
7896
  const hasAnyMidi = tracks.some((t) => t.hasMidi);
7598
7897
  const exportDisabled = isExportingMidi || !hasAnyMidi;
7599
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7898
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7600
7899
  "button",
7601
7900
  {
7602
7901
  "data-testid": "export-midi-tracks-button",
@@ -7664,7 +7963,7 @@ function createSurgeSoundAdapter(host, overrides = {}) {
7664
7963
  }
7665
7964
 
7666
7965
  // src/constants/sdk-version.ts
7667
- var PLUGIN_SDK_VERSION = "2.37.0";
7966
+ var PLUGIN_SDK_VERSION = "2.40.0";
7668
7967
 
7669
7968
  // src/utils/format-concurrent-tracks.ts
7670
7969
  function formatConcurrentTracks(ctx) {
@@ -7852,6 +8151,7 @@ function pickTopKWeighted(scored, options = {}) {
7852
8151
  TEXTURAL_ROLES,
7853
8152
  TRANSITION_DESIGNER_DRAFT_KEY,
7854
8153
  TrackDrawer,
8154
+ TrackExternalFxSection,
7855
8155
  TrackMeterStrip,
7856
8156
  TrackRow,
7857
8157
  TransitionDesigner,
@@ -7908,6 +8208,7 @@ function pickTopKWeighted(scored, options = {}) {
7908
8208
  usePanelBus,
7909
8209
  useSceneState,
7910
8210
  useSoundHistory,
8211
+ useTrackExternalFx,
7911
8212
  useTrackLevel,
7912
8213
  useTrackLevels,
7913
8214
  useTrackMeter,