@signalsandsorcery/plugin-sdk 2.35.1 → 2.35.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4126,9 +4126,276 @@ function TransitionDesigner({
4126
4126
  ] });
4127
4127
  }
4128
4128
 
4129
- // src/components/DownloadPackButton.tsx
4130
- import { useCallback as useCallback9, useEffect as useEffect10, useState as useState12 } from "react";
4129
+ // src/components/PanelMasterStrip.tsx
4130
+ import { useMemo as useMemo6, useState as useState12 } from "react";
4131
4131
  import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
4132
+ function PanelMasterStrip({
4133
+ bus,
4134
+ availableFx = [],
4135
+ fxLoading = false,
4136
+ soloedOut = false,
4137
+ disabled = false,
4138
+ fxPickerOpen,
4139
+ onToggleFxPicker,
4140
+ onRefreshFx,
4141
+ onVolumeChange,
4142
+ onMuteToggle,
4143
+ onSoloToggle,
4144
+ onAddFx,
4145
+ onRemoveFx,
4146
+ onToggleFxEnabled,
4147
+ onShowFxEditor
4148
+ }) {
4149
+ const [search, setSearch] = useState12("");
4150
+ const filtered = useMemo6(() => {
4151
+ const q = search.trim().toLowerCase();
4152
+ if (!q) return availableFx;
4153
+ return availableFx.filter(
4154
+ (fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
4155
+ );
4156
+ }, [availableFx, search]);
4157
+ return /* @__PURE__ */ jsxs14(
4158
+ "div",
4159
+ {
4160
+ "data-testid": "panel-master-strip",
4161
+ className: `flex flex-col gap-1 px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt/50 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
4162
+ children: [
4163
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
4164
+ /* @__PURE__ */ jsx18(
4165
+ "span",
4166
+ {
4167
+ className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
4168
+ title: "Panel mix bus \u2014 volume, mute/solo and FX applied to this panel's summed output",
4169
+ children: "BUS"
4170
+ }
4171
+ ),
4172
+ /* @__PURE__ */ jsx18("div", { className: "w-24", children: /* @__PURE__ */ jsx18(
4173
+ VolumeSlider,
4174
+ {
4175
+ value: dbToSlider(bus.volume),
4176
+ onChange: (sliderValue) => onVolumeChange(sliderToDb(sliderValue)),
4177
+ disabled
4178
+ }
4179
+ ) }),
4180
+ /* @__PURE__ */ jsx18(
4181
+ "button",
4182
+ {
4183
+ "data-testid": "bus-mute-button",
4184
+ onClick: onMuteToggle,
4185
+ disabled,
4186
+ className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${bus.muted ? "bg-red-600 text-white" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"} disabled:opacity-50`,
4187
+ title: bus.muted ? "Unmute panel bus" : "Mute panel bus (silences the whole panel)",
4188
+ children: "M"
4189
+ }
4190
+ ),
4191
+ /* @__PURE__ */ jsx18(
4192
+ "button",
4193
+ {
4194
+ "data-testid": "bus-solo-button",
4195
+ onClick: onSoloToggle,
4196
+ disabled,
4197
+ className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${bus.soloed ? "bg-amber-500 text-black" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"} disabled:opacity-50`,
4198
+ title: bus.soloed ? "Unsolo panel bus" : "Solo this panel (silences other panels/tracks in scope)",
4199
+ children: "S"
4200
+ }
4201
+ ),
4202
+ /* @__PURE__ */ jsx18("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ jsxs14(
4203
+ "span",
4204
+ {
4205
+ "data-testid": `bus-fx-chip-${fx.index}`,
4206
+ className: `flex items-center gap-1 px-1.5 py-0.5 rounded-sm border text-[10px] whitespace-nowrap ${fx.enabled ? "border-sas-accent/60 text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted/50 bg-sas-panel"}`,
4207
+ title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
4208
+ children: [
4209
+ /* @__PURE__ */ jsx18(
4210
+ "button",
4211
+ {
4212
+ "data-testid": `bus-fx-toggle-${fx.index}`,
4213
+ onClick: () => onToggleFxEnabled(fx.index, !fx.enabled),
4214
+ disabled,
4215
+ className: "hover:opacity-70 disabled:opacity-50",
4216
+ title: fx.enabled ? `Bypass ${fx.name}` : `Enable ${fx.name}`,
4217
+ children: fx.enabled ? "\u25CF" : "\u25CB"
4218
+ }
4219
+ ),
4220
+ onShowFxEditor ? /* @__PURE__ */ jsx18(
4221
+ "button",
4222
+ {
4223
+ "data-testid": `bus-fx-edit-${fx.index}`,
4224
+ onClick: () => onShowFxEditor(fx.index),
4225
+ disabled,
4226
+ className: "max-w-[80px] truncate hover:underline disabled:opacity-50",
4227
+ title: `Open ${fx.name} editor`,
4228
+ children: fx.name
4229
+ }
4230
+ ) : /* @__PURE__ */ jsx18("span", { className: "max-w-[80px] truncate", children: fx.name }),
4231
+ /* @__PURE__ */ jsx18(
4232
+ "button",
4233
+ {
4234
+ "data-testid": `bus-fx-remove-${fx.index}`,
4235
+ onClick: () => onRemoveFx(fx.index),
4236
+ disabled,
4237
+ className: "text-sas-muted/60 hover:text-sas-danger disabled:opacity-50",
4238
+ title: `Remove ${fx.name} from the bus`,
4239
+ children: "\u2715"
4240
+ }
4241
+ )
4242
+ ]
4243
+ },
4244
+ `${fx.index}:${fx.pluginId}`
4245
+ )) }),
4246
+ /* @__PURE__ */ jsx18(
4247
+ "button",
4248
+ {
4249
+ "data-testid": "bus-fx-add-button",
4250
+ onClick: () => onToggleFxPicker(!fxPickerOpen),
4251
+ disabled,
4252
+ className: `px-1.5 py-0.5 rounded-sm border text-xs whitespace-nowrap transition-colors ${fxPickerOpen ? "border-sas-accent text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent"} disabled:opacity-50`,
4253
+ title: "Add an FX plugin to the panel bus",
4254
+ children: "FX +"
4255
+ }
4256
+ )
4257
+ ] }),
4258
+ fxPickerOpen && /* @__PURE__ */ jsxs14("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
4259
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
4260
+ /* @__PURE__ */ jsx18(
4261
+ "input",
4262
+ {
4263
+ type: "text",
4264
+ value: search,
4265
+ onChange: (e) => setSearch(e.target.value),
4266
+ placeholder: "Search FX...",
4267
+ className: "sas-input flex-1 px-2 py-1 text-xs"
4268
+ }
4269
+ ),
4270
+ onRefreshFx && /* @__PURE__ */ jsx18(
4271
+ "button",
4272
+ {
4273
+ onClick: () => onRefreshFx(),
4274
+ disabled: fxLoading,
4275
+ 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",
4276
+ title: "Re-scan plugins",
4277
+ children: fxLoading ? "..." : "Refresh"
4278
+ }
4279
+ )
4280
+ ] }),
4281
+ fxLoading && availableFx.length === 0 ? /* @__PURE__ */ jsx18("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ jsxs14("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
4282
+ filtered.map((fx) => /* @__PURE__ */ jsxs14(
4283
+ "button",
4284
+ {
4285
+ "data-testid": `bus-fx-pick-${fx.pluginId}`,
4286
+ onClick: () => onAddFx(fx.pluginId),
4287
+ className: "flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent",
4288
+ title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
4289
+ children: [
4290
+ /* @__PURE__ */ jsx18("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
4291
+ /* @__PURE__ */ jsx18("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
4292
+ ]
4293
+ },
4294
+ fx.pluginId
4295
+ )),
4296
+ filtered.length === 0 && /* @__PURE__ */ jsx18("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
4297
+ ] })
4298
+ ] })
4299
+ ]
4300
+ }
4301
+ );
4302
+ }
4303
+
4304
+ // src/hooks/usePanelBus.ts
4305
+ import { useCallback as useCallback9, useEffect as useEffect10, useRef as useRef11, useState as useState13 } from "react";
4306
+ function usePanelBus(host, activeSceneId) {
4307
+ const supported = typeof host.getPanelBusState === "function";
4308
+ const [bus, setBus] = useState13(null);
4309
+ const [availableFx, setAvailableFx] = useState13([]);
4310
+ const [fxLoading, setFxLoading] = useState13(false);
4311
+ const [fxPickerOpen, setFxPickerOpen] = useState13(false);
4312
+ const fxLoadedRef = useRef11(false);
4313
+ const loadSeqRef = useRef11(0);
4314
+ const reload = useCallback9(async () => {
4315
+ if (!supported || !activeSceneId || !host.getPanelBusState) {
4316
+ setBus(null);
4317
+ return;
4318
+ }
4319
+ const seq = ++loadSeqRef.current;
4320
+ try {
4321
+ const state = await host.getPanelBusState(activeSceneId);
4322
+ if (loadSeqRef.current === seq) setBus(state);
4323
+ } catch {
4324
+ }
4325
+ }, [host, activeSceneId, supported]);
4326
+ useEffect10(() => {
4327
+ setBus(null);
4328
+ setFxPickerOpen(false);
4329
+ void reload();
4330
+ }, [reload]);
4331
+ const loadFxList = useCallback9(
4332
+ async (force) => {
4333
+ if (!supported || !host.getAvailableFx) return;
4334
+ if (fxLoadedRef.current && !force) return;
4335
+ setFxLoading(true);
4336
+ try {
4337
+ const list = await host.getAvailableFx();
4338
+ setAvailableFx(list);
4339
+ fxLoadedRef.current = true;
4340
+ } catch {
4341
+ } finally {
4342
+ setFxLoading(false);
4343
+ }
4344
+ },
4345
+ [host, supported]
4346
+ );
4347
+ const openPicker = useCallback9(
4348
+ (open) => {
4349
+ setFxPickerOpen(open);
4350
+ if (open) void loadFxList(false);
4351
+ },
4352
+ [loadFxList]
4353
+ );
4354
+ const mutate = useCallback9(
4355
+ (fn) => {
4356
+ if (!fn || !activeSceneId) return;
4357
+ void (async () => {
4358
+ try {
4359
+ await fn();
4360
+ } catch {
4361
+ }
4362
+ await reload();
4363
+ })();
4364
+ },
4365
+ [activeSceneId, reload]
4366
+ );
4367
+ return {
4368
+ supported,
4369
+ bus,
4370
+ availableFx,
4371
+ fxLoading,
4372
+ fxPickerOpen,
4373
+ setFxPickerOpen: openPicker,
4374
+ refreshFx: () => void loadFxList(true),
4375
+ reload,
4376
+ onVolumeChange: (volumeDb) => mutate(host.setPanelBusVolume && (() => host.setPanelBusVolume(activeSceneId, volumeDb))),
4377
+ onMuteToggle: () => mutate(
4378
+ host.setPanelBusMute && (() => host.setPanelBusMute(activeSceneId, !(bus?.muted ?? false)))
4379
+ ),
4380
+ onSoloToggle: () => mutate(
4381
+ host.setPanelBusSolo && (() => host.setPanelBusSolo(activeSceneId, !(bus?.soloed ?? false)))
4382
+ ),
4383
+ onAddFx: (pluginId) => mutate(host.loadPanelBusFx && (async () => {
4384
+ await host.loadPanelBusFx(activeSceneId, pluginId);
4385
+ })),
4386
+ onRemoveFx: (fxIndex) => mutate(host.removePanelBusFx && (() => host.removePanelBusFx(activeSceneId, fxIndex))),
4387
+ onToggleFxEnabled: (fxIndex, enabled) => mutate(
4388
+ host.setPanelBusFxEnabled && (() => host.setPanelBusFxEnabled(activeSceneId, fxIndex, enabled))
4389
+ ),
4390
+ onShowFxEditor: (fxIndex) => mutate(
4391
+ host.showPanelBusFxEditor && (() => host.showPanelBusFxEditor(activeSceneId, fxIndex))
4392
+ )
4393
+ };
4394
+ }
4395
+
4396
+ // src/components/DownloadPackButton.tsx
4397
+ import { useCallback as useCallback10, useEffect as useEffect11, useState as useState14 } from "react";
4398
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
4132
4399
  function formatSize(bytes) {
4133
4400
  if (!bytes || bytes <= 0) return "";
4134
4401
  const gb = bytes / 1024 ** 3;
@@ -4144,10 +4411,10 @@ var DownloadPackButton = ({
4144
4411
  variant = "compact",
4145
4412
  onDownloadComplete
4146
4413
  }) => {
4147
- const [status, setStatus] = useState12("idle");
4148
- const [progress, setProgress] = useState12(0);
4149
- const [errorMessage, setErrorMessage] = useState12(null);
4150
- useEffect10(() => {
4414
+ const [status, setStatus] = useState14("idle");
4415
+ const [progress, setProgress] = useState14(0);
4416
+ const [errorMessage, setErrorMessage] = useState14(null);
4417
+ useEffect11(() => {
4151
4418
  const unsub = host.onSamplePackProgress(packId, (p) => {
4152
4419
  setStatus(p.status);
4153
4420
  setProgress(p.progress);
@@ -4162,7 +4429,7 @@ var DownloadPackButton = ({
4162
4429
  });
4163
4430
  return unsub;
4164
4431
  }, [host, packId, onDownloadComplete]);
4165
- const handleClick = useCallback9(async () => {
4432
+ const handleClick = useCallback10(async () => {
4166
4433
  if (status !== "idle" && status !== "error") return;
4167
4434
  try {
4168
4435
  setStatus("downloading");
@@ -4216,8 +4483,8 @@ var DownloadPackButton = ({
4216
4483
  } else {
4217
4484
  className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
4218
4485
  }
4219
- return /* @__PURE__ */ jsxs14("div", { children: [
4220
- /* @__PURE__ */ jsx18(
4486
+ return /* @__PURE__ */ jsxs15("div", { children: [
4487
+ /* @__PURE__ */ jsx19(
4221
4488
  "button",
4222
4489
  {
4223
4490
  "data-testid": `download-pack-button-${packId}`,
@@ -4228,12 +4495,12 @@ var DownloadPackButton = ({
4228
4495
  children: buttonLabel
4229
4496
  }
4230
4497
  ),
4231
- variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ jsx18("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
4498
+ variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ jsx19("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
4232
4499
  ] });
4233
4500
  };
4234
4501
 
4235
4502
  // src/components/SamplePackCTACard.tsx
4236
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
4503
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
4237
4504
  var SamplePackCTACard = ({
4238
4505
  host,
4239
4506
  pack,
@@ -4241,7 +4508,7 @@ var SamplePackCTACard = ({
4241
4508
  onDownloadComplete
4242
4509
  }) => {
4243
4510
  if (status === "checking") {
4244
- return /* @__PURE__ */ jsx19(
4511
+ return /* @__PURE__ */ jsx20(
4245
4512
  "div",
4246
4513
  {
4247
4514
  "data-testid": `sample-pack-cta-checking-${pack.packId}`,
@@ -4252,16 +4519,16 @@ var SamplePackCTACard = ({
4252
4519
  }
4253
4520
  const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
4254
4521
  const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
4255
- return /* @__PURE__ */ jsxs15(
4522
+ return /* @__PURE__ */ jsxs16(
4256
4523
  "div",
4257
4524
  {
4258
4525
  "data-testid": `sample-pack-cta-${pack.packId}`,
4259
4526
  className: "flex flex-col items-center justify-center py-12 px-6 text-center",
4260
4527
  children: [
4261
- /* @__PURE__ */ jsx19("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
4262
- /* @__PURE__ */ jsx19("div", { className: "text-base text-sas-text mb-1", children: headline }),
4263
- /* @__PURE__ */ jsx19("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
4264
- /* @__PURE__ */ jsx19(
4528
+ /* @__PURE__ */ jsx20("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
4529
+ /* @__PURE__ */ jsx20("div", { className: "text-base text-sas-text mb-1", children: headline }),
4530
+ /* @__PURE__ */ jsx20("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
4531
+ /* @__PURE__ */ jsx20(
4265
4532
  DownloadPackButton,
4266
4533
  {
4267
4534
  host,
@@ -4278,7 +4545,7 @@ var SamplePackCTACard = ({
4278
4545
  };
4279
4546
 
4280
4547
  // src/components/WaveformView.tsx
4281
- import { useEffect as useEffect11, useRef as useRef11, useState as useState13 } from "react";
4548
+ import { useEffect as useEffect12, useRef as useRef12, useState as useState15 } from "react";
4282
4549
 
4283
4550
  // src/components/waveform.ts
4284
4551
  function computePeaks(audioBuffer, bins, targetSamples) {
@@ -4341,7 +4608,7 @@ function drawWaveform(canvas, peaks, options = {}) {
4341
4608
  }
4342
4609
 
4343
4610
  // src/components/WaveformView.tsx
4344
- import { jsx as jsx20 } from "react/jsx-runtime";
4611
+ import { jsx as jsx21 } from "react/jsx-runtime";
4345
4612
  var WaveformView = ({
4346
4613
  host,
4347
4614
  filePath,
@@ -4350,9 +4617,9 @@ var WaveformView = ({
4350
4617
  fillStyle,
4351
4618
  targetSamples
4352
4619
  }) => {
4353
- const canvasRef = useRef11(null);
4354
- const [peaks, setPeaks] = useState13(null);
4355
- useEffect11(() => {
4620
+ const canvasRef = useRef12(null);
4621
+ const [peaks, setPeaks] = useState15(null);
4622
+ useEffect12(() => {
4356
4623
  let cancelled = false;
4357
4624
  let audioContext = null;
4358
4625
  (async () => {
@@ -4378,7 +4645,7 @@ var WaveformView = ({
4378
4645
  cancelled = true;
4379
4646
  };
4380
4647
  }, [host, filePath, bins, targetSamples]);
4381
- useEffect11(() => {
4648
+ useEffect12(() => {
4382
4649
  if (!peaks) return;
4383
4650
  const canvas = canvasRef.current;
4384
4651
  if (!canvas) return;
@@ -4389,7 +4656,7 @@ var WaveformView = ({
4389
4656
  observer.observe(canvas);
4390
4657
  return () => observer.disconnect();
4391
4658
  }, [peaks, fillStyle]);
4392
- return /* @__PURE__ */ jsx20(
4659
+ return /* @__PURE__ */ jsx21(
4393
4660
  "canvas",
4394
4661
  {
4395
4662
  ref: canvasRef,
@@ -4400,8 +4667,8 @@ var WaveformView = ({
4400
4667
  };
4401
4668
 
4402
4669
  // src/components/ScrollingWaveform.tsx
4403
- import { useEffect as useEffect12, useRef as useRef12 } from "react";
4404
- import { jsx as jsx21 } from "react/jsx-runtime";
4670
+ import { useEffect as useEffect13, useRef as useRef13 } from "react";
4671
+ import { jsx as jsx22 } from "react/jsx-runtime";
4405
4672
  var ScrollingWaveform = ({
4406
4673
  getPeakDb,
4407
4674
  active,
@@ -4409,11 +4676,11 @@ var ScrollingWaveform = ({
4409
4676
  className,
4410
4677
  fillStyle
4411
4678
  }) => {
4412
- const canvasRef = useRef12(null);
4413
- const ringRef = useRef12(new Float32Array(columns));
4414
- const writeIdxRef = useRef12(0);
4415
- const rafRef = useRef12(null);
4416
- useEffect12(() => {
4679
+ const canvasRef = useRef13(null);
4680
+ const ringRef = useRef13(new Float32Array(columns));
4681
+ const writeIdxRef = useRef13(0);
4682
+ const rafRef = useRef13(null);
4683
+ useEffect13(() => {
4417
4684
  if (ringRef.current.length !== columns) {
4418
4685
  const next = new Float32Array(columns);
4419
4686
  const prev = ringRef.current;
@@ -4425,7 +4692,7 @@ var ScrollingWaveform = ({
4425
4692
  writeIdxRef.current = writeIdxRef.current % columns;
4426
4693
  }
4427
4694
  }, [columns]);
4428
- useEffect12(() => {
4695
+ useEffect13(() => {
4429
4696
  if (!active) {
4430
4697
  if (rafRef.current !== null) {
4431
4698
  cancelAnimationFrame(rafRef.current);
@@ -4477,7 +4744,7 @@ var ScrollingWaveform = ({
4477
4744
  }
4478
4745
  };
4479
4746
  }, [active, getPeakDb, fillStyle]);
4480
- return /* @__PURE__ */ jsx21(
4747
+ return /* @__PURE__ */ jsx22(
4481
4748
  "canvas",
4482
4749
  {
4483
4750
  ref: canvasRef,
@@ -4488,8 +4755,8 @@ var ScrollingWaveform = ({
4488
4755
  };
4489
4756
 
4490
4757
  // src/components/OffsetScrubber.tsx
4491
- import { useCallback as useCallback10, useEffect as useEffect13, useMemo as useMemo6, useRef as useRef13, useState as useState14 } from "react";
4492
- import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
4758
+ import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo7, useRef as useRef14, useState as useState16 } from "react";
4759
+ import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
4493
4760
  var SLIDER_HEIGHT_PX = 28;
4494
4761
  var TICK_HEIGHT_PX = 14;
4495
4762
  var DOWNBEAT_TICK_HEIGHT_PX = 22;
@@ -4502,40 +4769,40 @@ function OffsetScrubber({
4502
4769
  onChange,
4503
4770
  disabled = false
4504
4771
  }) {
4505
- const trackRef = useRef13(null);
4506
- const [draftOffset, setDraftOffset] = useState14(offsetSamples);
4507
- const [isDragging, setIsDragging] = useState14(false);
4508
- useEffect13(() => {
4772
+ const trackRef = useRef14(null);
4773
+ const [draftOffset, setDraftOffset] = useState16(offsetSamples);
4774
+ const [isDragging, setIsDragging] = useState16(false);
4775
+ useEffect14(() => {
4509
4776
  if (!isDragging) setDraftOffset(offsetSamples);
4510
4777
  }, [offsetSamples, isDragging]);
4511
4778
  const sampleRate = cuePoints?.sample_rate ?? 44100;
4512
4779
  const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
4513
- const beatsForRange = useMemo6(() => {
4780
+ const beatsForRange = useMemo7(() => {
4514
4781
  return Math.round(60 / projectBpm * sampleRate);
4515
4782
  }, [projectBpm, sampleRate]);
4516
4783
  const rangeSamples = beatsForRange * meter;
4517
- const sampleToFraction = useCallback10(
4784
+ const sampleToFraction = useCallback11(
4518
4785
  (sample) => {
4519
4786
  const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
4520
4787
  return (clamped + rangeSamples) / (2 * rangeSamples);
4521
4788
  },
4522
4789
  [rangeSamples]
4523
4790
  );
4524
- const fractionToSample = useCallback10(
4791
+ const fractionToSample = useCallback11(
4525
4792
  (fraction) => {
4526
4793
  const clamped = Math.max(0, Math.min(1, fraction));
4527
4794
  return Math.round(clamped * 2 * rangeSamples - rangeSamples);
4528
4795
  },
4529
4796
  [rangeSamples]
4530
4797
  );
4531
- const snapTargets = useMemo6(() => {
4798
+ const snapTargets = useMemo7(() => {
4532
4799
  if (!cuePoints || cuePoints.beats.length === 0) return [];
4533
4800
  const downbeat = cuePoints.beats[0];
4534
4801
  const positives = cuePoints.beats.map((b) => b - downbeat);
4535
4802
  const negatives = positives.slice(1).map((p) => -p);
4536
4803
  return [...negatives, ...positives].sort((a, b) => a - b);
4537
4804
  }, [cuePoints]);
4538
- const snapToBeat = useCallback10(
4805
+ const snapToBeat = useCallback11(
4539
4806
  (sample) => {
4540
4807
  if (snapTargets.length === 0) return sample;
4541
4808
  let best = snapTargets[0];
@@ -4551,7 +4818,7 @@ function OffsetScrubber({
4551
4818
  },
4552
4819
  [snapTargets]
4553
4820
  );
4554
- const handlePointerDown = useCallback10(
4821
+ const handlePointerDown = useCallback11(
4555
4822
  (e) => {
4556
4823
  if (disabled || !cuePoints) return;
4557
4824
  e.preventDefault();
@@ -4585,7 +4852,7 @@ function OffsetScrubber({
4585
4852
  },
4586
4853
  [disabled, cuePoints, fractionToSample, onChange, snapToBeat]
4587
4854
  );
4588
- const handleResetToZero = useCallback10(() => {
4855
+ const handleResetToZero = useCallback11(() => {
4589
4856
  if (disabled) return;
4590
4857
  setDraftOffset(0);
4591
4858
  onChange(0);
@@ -4593,7 +4860,7 @@ function OffsetScrubber({
4593
4860
  const thumbFraction = sampleToFraction(draftOffset);
4594
4861
  const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
4595
4862
  const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
4596
- const ticks = useMemo6(() => {
4863
+ const ticks = useMemo7(() => {
4597
4864
  if (!cuePoints) return [];
4598
4865
  const downbeat = cuePoints.beats[0] ?? 0;
4599
4866
  return cuePoints.beats.map((b, i) => {
@@ -4604,9 +4871,9 @@ function OffsetScrubber({
4604
4871
  });
4605
4872
  }, [cuePoints, sampleToFraction]);
4606
4873
  const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
4607
- return /* @__PURE__ */ jsxs16("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
4608
- /* @__PURE__ */ jsx22("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
4609
- /* @__PURE__ */ jsxs16(
4874
+ return /* @__PURE__ */ jsxs17("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
4875
+ /* @__PURE__ */ jsx23("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
4876
+ /* @__PURE__ */ jsxs17(
4610
4877
  "div",
4611
4878
  {
4612
4879
  ref: trackRef,
@@ -4622,7 +4889,7 @@ function OffsetScrubber({
4622
4889
  "aria-valuenow": draftOffset,
4623
4890
  "aria-disabled": isDisabled,
4624
4891
  children: [
4625
- /* @__PURE__ */ jsx22(
4892
+ /* @__PURE__ */ jsx23(
4626
4893
  "div",
4627
4894
  {
4628
4895
  "aria-hidden": "true",
@@ -4630,7 +4897,7 @@ function OffsetScrubber({
4630
4897
  style: { left: "50%" }
4631
4898
  }
4632
4899
  ),
4633
- ticks.map((t) => /* @__PURE__ */ jsx22(
4900
+ ticks.map((t) => /* @__PURE__ */ jsx23(
4634
4901
  "div",
4635
4902
  {
4636
4903
  "data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
@@ -4645,7 +4912,7 @@ function OffsetScrubber({
4645
4912
  },
4646
4913
  t.i
4647
4914
  )),
4648
- /* @__PURE__ */ jsx22(
4915
+ /* @__PURE__ */ jsx23(
4649
4916
  "div",
4650
4917
  {
4651
4918
  "data-testid": "offset-scrubber-thumb",
@@ -4662,7 +4929,7 @@ function OffsetScrubber({
4662
4929
  ]
4663
4930
  }
4664
4931
  ),
4665
- /* @__PURE__ */ jsx22(
4932
+ /* @__PURE__ */ jsx23(
4666
4933
  "span",
4667
4934
  {
4668
4935
  "data-testid": "offset-scrubber-readout",
@@ -4670,7 +4937,7 @@ function OffsetScrubber({
4670
4937
  children: formatOffset(draftOffset, sampleRate)
4671
4938
  }
4672
4939
  ),
4673
- /* @__PURE__ */ jsx22(
4940
+ /* @__PURE__ */ jsx23(
4674
4941
  "button",
4675
4942
  {
4676
4943
  type: "button",
@@ -4682,7 +4949,7 @@ function OffsetScrubber({
4682
4949
  children: "\u2316"
4683
4950
  }
4684
4951
  ),
4685
- bpmMismatch && /* @__PURE__ */ jsx22(
4952
+ bpmMismatch && /* @__PURE__ */ jsx23(
4686
4953
  "span",
4687
4954
  {
4688
4955
  "data-testid": "offset-bpm-mismatch",
@@ -4754,16 +5021,16 @@ function synthesizeCuePoints({
4754
5021
  }
4755
5022
 
4756
5023
  // src/panel-core/useGeneratorPanelCore.tsx
4757
- import { useState as useState19, useEffect as useEffect16, useCallback as useCallback14, useRef as useRef17, useMemo as useMemo8 } from "react";
5024
+ import { useState as useState21, useEffect as useEffect17, useCallback as useCallback15, useRef as useRef18, useMemo as useMemo9 } from "react";
4758
5025
 
4759
5026
  // src/hooks/useSceneState.ts
4760
- import { useState as useState15, useCallback as useCallback11, useRef as useRef14 } from "react";
5027
+ import { useState as useState17, useCallback as useCallback12, useRef as useRef15 } from "react";
4761
5028
  function useSceneState(activeSceneId, initialValue) {
4762
- const [stateMap, setStateMap] = useState15(() => /* @__PURE__ */ new Map());
4763
- const activeSceneIdRef = useRef14(activeSceneId);
5029
+ const [stateMap, setStateMap] = useState17(() => /* @__PURE__ */ new Map());
5030
+ const activeSceneIdRef = useRef15(activeSceneId);
4764
5031
  activeSceneIdRef.current = activeSceneId;
4765
5032
  const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
4766
- const setForCurrentScene = useCallback11((value) => {
5033
+ const setForCurrentScene = useCallback12((value) => {
4767
5034
  const sid = activeSceneIdRef.current;
4768
5035
  if (sid === null) return;
4769
5036
  setStateMap((prev) => {
@@ -4774,7 +5041,7 @@ function useSceneState(activeSceneId, initialValue) {
4774
5041
  return newMap;
4775
5042
  });
4776
5043
  }, [initialValue]);
4777
- const setForScene = useCallback11((sceneId, value) => {
5044
+ const setForScene = useCallback12((sceneId, value) => {
4778
5045
  setStateMap((prev) => {
4779
5046
  const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
4780
5047
  const next = typeof value === "function" ? value(current) : value;
@@ -4787,10 +5054,10 @@ function useSceneState(activeSceneId, initialValue) {
4787
5054
  }
4788
5055
 
4789
5056
  // src/hooks/useAnySolo.ts
4790
- import { useEffect as useEffect14, useState as useState16 } from "react";
5057
+ import { useEffect as useEffect15, useState as useState18 } from "react";
4791
5058
  function useAnySolo(host) {
4792
- const [anySolo, setAnySolo] = useState16(false);
4793
- useEffect14(() => {
5059
+ const [anySolo, setAnySolo] = useState18(false);
5060
+ useEffect15(() => {
4794
5061
  let active = true;
4795
5062
  const refresh = () => {
4796
5063
  host.isAnySoloActive().then((v) => {
@@ -4809,7 +5076,7 @@ function useAnySolo(host) {
4809
5076
  }
4810
5077
 
4811
5078
  // src/hooks/useSoundHistory.ts
4812
- import { useCallback as useCallback12, useMemo as useMemo7, useRef as useRef15, useState as useState17 } from "react";
5079
+ import { useCallback as useCallback13, useMemo as useMemo8, useRef as useRef16, useState as useState19 } from "react";
4813
5080
  var EMPTY = { entries: [], cursor: -1 };
4814
5081
  function sameDescriptor(a, b) {
4815
5082
  if (a === b) return true;
@@ -4821,14 +5088,14 @@ function sameDescriptor(a, b) {
4821
5088
  }
4822
5089
  function useSoundHistory(applySound, opts = {}) {
4823
5090
  const max = Math.max(2, opts.max ?? 24);
4824
- const applyRef = useRef15(applySound);
5091
+ const applyRef = useRef16(applySound);
4825
5092
  applyRef.current = applySound;
4826
- const onChangeRef = useRef15(opts.onChange);
5093
+ const onChangeRef = useRef16(opts.onChange);
4827
5094
  onChangeRef.current = opts.onChange;
4828
- const dataRef = useRef15({});
4829
- const [, setVersion] = useState17(0);
4830
- const bump = useCallback12(() => setVersion((v) => v + 1), []);
4831
- const commit = useCallback12(
5095
+ const dataRef = useRef16({});
5096
+ const [, setVersion] = useState19(0);
5097
+ const bump = useCallback13(() => setVersion((v) => v + 1), []);
5098
+ const commit = useCallback13(
4832
5099
  (trackId, next, notify) => {
4833
5100
  dataRef.current = { ...dataRef.current, [trackId]: next };
4834
5101
  bump();
@@ -4836,7 +5103,7 @@ function useSoundHistory(applySound, opts = {}) {
4836
5103
  },
4837
5104
  [bump]
4838
5105
  );
4839
- const record = useCallback12(
5106
+ const record = useCallback13(
4840
5107
  (trackId, descriptor, label) => {
4841
5108
  const h = dataRef.current[trackId];
4842
5109
  const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
@@ -4851,7 +5118,7 @@ function useSoundHistory(applySound, opts = {}) {
4851
5118
  },
4852
5119
  [max, commit]
4853
5120
  );
4854
- const restoreTo = useCallback12(
5121
+ const restoreTo = useCallback13(
4855
5122
  async (trackId, index) => {
4856
5123
  const h = dataRef.current[trackId];
4857
5124
  if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
@@ -4861,7 +5128,7 @@ function useSoundHistory(applySound, opts = {}) {
4861
5128
  },
4862
5129
  [commit]
4863
5130
  );
4864
- const undo = useCallback12(
5131
+ const undo = useCallback13(
4865
5132
  (trackId) => {
4866
5133
  const h = dataRef.current[trackId];
4867
5134
  if (!h || h.cursor <= 0) return Promise.resolve(false);
@@ -4869,7 +5136,7 @@ function useSoundHistory(applySound, opts = {}) {
4869
5136
  },
4870
5137
  [restoreTo]
4871
5138
  );
4872
- const toggleFavorite = useCallback12(
5139
+ const toggleFavorite = useCallback13(
4873
5140
  (trackId, index) => {
4874
5141
  const h = dataRef.current[trackId];
4875
5142
  if (!h || index < 0 || index >= h.entries.length) return;
@@ -4878,7 +5145,7 @@ function useSoundHistory(applySound, opts = {}) {
4878
5145
  },
4879
5146
  [commit]
4880
5147
  );
4881
- const restore = useCallback12(
5148
+ const restore = useCallback13(
4882
5149
  (trackId, state) => {
4883
5150
  const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
4884
5151
  const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
@@ -4887,15 +5154,15 @@ function useSoundHistory(applySound, opts = {}) {
4887
5154
  },
4888
5155
  [commit]
4889
5156
  );
4890
- const list = useCallback12(
5157
+ const list = useCallback13(
4891
5158
  (trackId) => dataRef.current[trackId] ?? EMPTY,
4892
5159
  []
4893
5160
  );
4894
- const canUndo = useCallback12((trackId) => {
5161
+ const canUndo = useCallback13((trackId) => {
4895
5162
  const h = dataRef.current[trackId];
4896
5163
  return !!h && h.cursor > 0;
4897
5164
  }, []);
4898
- const clear = useCallback12(
5165
+ const clear = useCallback13(
4899
5166
  (trackId) => {
4900
5167
  if (dataRef.current[trackId]) {
4901
5168
  const next = { ...dataRef.current };
@@ -4907,11 +5174,11 @@ function useSoundHistory(applySound, opts = {}) {
4907
5174
  },
4908
5175
  [bump]
4909
5176
  );
4910
- const reset = useCallback12(() => {
5177
+ const reset = useCallback13(() => {
4911
5178
  dataRef.current = {};
4912
5179
  bump();
4913
5180
  }, [bump]);
4914
- return useMemo7(
5181
+ return useMemo8(
4915
5182
  () => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
4916
5183
  [record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
4917
5184
  );
@@ -5044,7 +5311,7 @@ function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
5044
5311
  }
5045
5312
 
5046
5313
  // src/panel-core/useTransitionOps.ts
5047
- import { useCallback as useCallback13, useEffect as useEffect15, useRef as useRef16, useState as useState18 } from "react";
5314
+ import { useCallback as useCallback14, useEffect as useEffect16, useRef as useRef17, useState as useState20 } from "react";
5048
5315
  function useTransitionOps({
5049
5316
  host,
5050
5317
  adapter,
@@ -5061,8 +5328,8 @@ function useTransitionOps({
5061
5328
  resolvedFades
5062
5329
  }) {
5063
5330
  const { identity } = adapter;
5064
- const appliedFadeAutomationRef = useRef16(/* @__PURE__ */ new Set());
5065
- const applyCrossfadeAutomation = useCallback13(
5331
+ const appliedFadeAutomationRef = useRef17(/* @__PURE__ */ new Set());
5332
+ const applyCrossfadeAutomation = useCallback14(
5066
5333
  async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
5067
5334
  if (host.setTrackVolumeAutomation) {
5068
5335
  const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
@@ -5079,7 +5346,7 @@ function useTransitionOps({
5079
5346
  },
5080
5347
  [host]
5081
5348
  );
5082
- const applyFadeAutomation = useCallback13(
5349
+ const applyFadeAutomation = useCallback14(
5083
5350
  async (trackId, direction, bars, bpm, sliderPos, gesture) => {
5084
5351
  if (!host.setTrackVolumeAutomation) return;
5085
5352
  const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
@@ -5088,8 +5355,8 @@ function useTransitionOps({
5088
5355
  },
5089
5356
  [host]
5090
5357
  );
5091
- const [isCreatingCrossfade, setIsCreatingCrossfade] = useState18(false);
5092
- const handleCreateCrossfade = useCallback13(
5358
+ const [isCreatingCrossfade, setIsCreatingCrossfade] = useState20(false);
5359
+ const handleCreateCrossfade = useCallback14(
5093
5360
  async (origin, target) => {
5094
5361
  const scene = activeSceneId;
5095
5362
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5217,8 +5484,8 @@ function useTransitionOps({
5217
5484
  loadTracks
5218
5485
  ]
5219
5486
  );
5220
- const [isCreatingFade, setIsCreatingFade] = useState18(false);
5221
- const handleCreateFade = useCallback13(
5487
+ const [isCreatingFade, setIsCreatingFade] = useState20(false);
5488
+ const handleCreateFade = useCallback14(
5222
5489
  async (selection, direction, gesture) => {
5223
5490
  const scene = activeSceneId;
5224
5491
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5328,7 +5595,7 @@ function useTransitionOps({
5328
5595
  loadTracks
5329
5596
  ]
5330
5597
  );
5331
- const handleCrossfadeMute = useCallback13(
5598
+ const handleCrossfadeMute = useCallback14(
5332
5599
  (pair) => {
5333
5600
  const newMuted = !pair.origin.runtimeState.muted;
5334
5601
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5343,7 +5610,7 @@ function useTransitionOps({
5343
5610
  },
5344
5611
  [host, setTracks]
5345
5612
  );
5346
- const handleCrossfadeSolo = useCallback13(
5613
+ const handleCrossfadeSolo = useCallback14(
5347
5614
  (pair) => {
5348
5615
  const newSolo = !pair.origin.runtimeState.solo;
5349
5616
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5358,7 +5625,7 @@ function useTransitionOps({
5358
5625
  },
5359
5626
  [host, setTracks]
5360
5627
  );
5361
- const handleCrossfadeDelete = useCallback13(
5628
+ const handleCrossfadeDelete = useCallback14(
5362
5629
  async (pair) => {
5363
5630
  try {
5364
5631
  for (const member of [pair.origin, pair.target]) {
@@ -5384,8 +5651,8 @@ function useTransitionOps({
5384
5651
  },
5385
5652
  [host, activeSceneId, setCrossfadePairsMeta, setTracks]
5386
5653
  );
5387
- const crossfadeSliderTimers = useRef16({});
5388
- const handleCrossfadeSlider = useCallback13(
5654
+ const crossfadeSliderTimers = useRef17({});
5655
+ const handleCrossfadeSlider = useCallback14(
5389
5656
  (pair, pos) => {
5390
5657
  setCrossfadePairsMeta(
5391
5658
  (prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
@@ -5418,7 +5685,7 @@ function useTransitionOps({
5418
5685
  },
5419
5686
  [host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
5420
5687
  );
5421
- const handleFadeDelete = useCallback13(
5688
+ const handleFadeDelete = useCallback14(
5422
5689
  async (fade) => {
5423
5690
  try {
5424
5691
  await host.deleteTrack(fade.track.handle.id);
@@ -5438,8 +5705,8 @@ function useTransitionOps({
5438
5705
  },
5439
5706
  [host, activeSceneId, setFadesMeta, setTracks]
5440
5707
  );
5441
- const fadeSliderTimers = useRef16({});
5442
- const handleFadeSlider = useCallback13(
5708
+ const fadeSliderTimers = useRef17({});
5709
+ const handleFadeSlider = useCallback14(
5443
5710
  (fade, pos) => {
5444
5711
  setFadesMeta(
5445
5712
  (prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
@@ -5469,8 +5736,8 @@ function useTransitionOps({
5469
5736
  },
5470
5737
  [host, activeSceneId, applyFadeAutomation, setFadesMeta]
5471
5738
  );
5472
- const lastResyncKeyRef = useRef16("");
5473
- useEffect15(() => {
5739
+ const lastResyncKeyRef = useRef17("");
5740
+ useEffect16(() => {
5474
5741
  if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
5475
5742
  return;
5476
5743
  }
@@ -5511,7 +5778,7 @@ function useTransitionOps({
5511
5778
  cancelled = true;
5512
5779
  };
5513
5780
  }, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
5514
- useEffect15(() => {
5781
+ useEffect16(() => {
5515
5782
  if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
5516
5783
  void (async () => {
5517
5784
  const mc = await host.getMusicalContext();
@@ -5545,7 +5812,7 @@ function useTransitionOps({
5545
5812
  }
5546
5813
 
5547
5814
  // src/panel-core/useGeneratorPanelCore.tsx
5548
- import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
5815
+ import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
5549
5816
  var EMPTY_PLACEHOLDERS = [];
5550
5817
  function useGeneratorPanelCore({
5551
5818
  ui,
@@ -5565,8 +5832,8 @@ function useGeneratorPanelCore({
5565
5832
  } = ui;
5566
5833
  const { identity, features } = adapter;
5567
5834
  const logTag = identity.logTag;
5568
- const adapterRef = useRef17(adapter);
5569
- useEffect16(() => {
5835
+ const adapterRef = useRef18(adapter);
5836
+ useEffect17(() => {
5570
5837
  if (adapterRef.current !== adapter) {
5571
5838
  adapterRef.current = adapter;
5572
5839
  console.warn(
@@ -5576,27 +5843,27 @@ function useGeneratorPanelCore({
5576
5843
  }, [adapter, logTag]);
5577
5844
  const supportsMeters = typeof host.getTrackLevels === "function";
5578
5845
  const trackLevels = useTrackLevels(host, isExpanded);
5579
- const [tracks, setTracks] = useState19([]);
5580
- const [isLoadingTracks, setIsLoadingTracks] = useState19(false);
5581
- const [importOpen, setImportOpen] = useState19(false);
5582
- const [soundImportTarget, setSoundImportTarget] = useState19(null);
5583
- const [designerView, setDesignerView] = useState19(false);
5584
- const [transitionSourceTotal, setTransitionSourceTotal] = useState19(0);
5585
- const [crossfadePairsMeta, setCrossfadePairsMeta] = useState19([]);
5586
- const [fadesMeta, setFadesMeta] = useState19([]);
5587
- const [genericGroupMetas, setGenericGroupMetas] = useState19({});
5846
+ const [tracks, setTracks] = useState21([]);
5847
+ const [isLoadingTracks, setIsLoadingTracks] = useState21(false);
5848
+ const [importOpen, setImportOpen] = useState21(false);
5849
+ const [soundImportTarget, setSoundImportTarget] = useState21(null);
5850
+ const [designerView, setDesignerView] = useState21(false);
5851
+ const [transitionSourceTotal, setTransitionSourceTotal] = useState21(0);
5852
+ const [crossfadePairsMeta, setCrossfadePairsMeta] = useState21([]);
5853
+ const [fadesMeta, setFadesMeta] = useState21([]);
5854
+ const [genericGroupMetas, setGenericGroupMetas] = useState21({});
5588
5855
  const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
5589
5856
  const [placeholders, , setPlaceholdersForScene] = useSceneState(
5590
5857
  activeSceneId,
5591
5858
  EMPTY_PLACEHOLDERS
5592
5859
  );
5593
- const saveTimeoutRefs = useRef17({});
5594
- const editLoadStartedRef = useRef17(/* @__PURE__ */ new Set());
5595
- const [availableInstruments, setAvailableInstruments] = useState19([]);
5596
- const [instrumentsLoading, setInstrumentsLoading] = useState19(false);
5597
- const engineToDbIdRef = useRef17(/* @__PURE__ */ new Map());
5598
- const tracksLoadedForSceneRef = useRef17(null);
5599
- const persistSoundHistory = useCallback14(
5860
+ const saveTimeoutRefs = useRef18({});
5861
+ const editLoadStartedRef = useRef18(/* @__PURE__ */ new Set());
5862
+ const [availableInstruments, setAvailableInstruments] = useState21([]);
5863
+ const [instrumentsLoading, setInstrumentsLoading] = useState21(false);
5864
+ const engineToDbIdRef = useRef18(/* @__PURE__ */ new Map());
5865
+ const tracksLoadedForSceneRef = useRef18(null);
5866
+ const persistSoundHistory = useCallback15(
5600
5867
  (trackId, state) => {
5601
5868
  if (!activeSceneId) return;
5602
5869
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -5616,7 +5883,7 @@ function useGeneratorPanelCore({
5616
5883
  setItems: setTracks,
5617
5884
  getId: (t) => t.handle.dbId
5618
5885
  });
5619
- const loadTracks = useCallback14(
5886
+ const loadTracks = useCallback15(
5620
5887
  async (incremental = false) => {
5621
5888
  const sceneAtStart = activeSceneId;
5622
5889
  if (!sceneAtStart) {
@@ -5741,18 +6008,18 @@ function useGeneratorPanelCore({
5741
6008
  },
5742
6009
  [host, activeSceneId, soundHistory, adapter, logTag]
5743
6010
  );
5744
- useEffect16(() => {
6011
+ useEffect17(() => {
5745
6012
  loadTracks();
5746
6013
  }, [loadTracks]);
5747
- useEffect16(() => {
6014
+ useEffect17(() => {
5748
6015
  const map = /* @__PURE__ */ new Map();
5749
6016
  for (const t of tracks) {
5750
6017
  map.set(t.handle.id, t.handle.dbId);
5751
6018
  }
5752
6019
  engineToDbIdRef.current = map;
5753
6020
  }, [tracks]);
5754
- const loadedCompletedIdsRef = useRef17(/* @__PURE__ */ new Set());
5755
- useEffect16(() => {
6021
+ const loadedCompletedIdsRef = useRef18(/* @__PURE__ */ new Set());
6022
+ useEffect17(() => {
5756
6023
  if (placeholders.length === 0) {
5757
6024
  loadedCompletedIdsRef.current.clear();
5758
6025
  return;
@@ -5771,16 +6038,16 @@ function useGeneratorPanelCore({
5771
6038
  loadTracks(true);
5772
6039
  }
5773
6040
  }, [placeholders, loadTracks, logTag]);
5774
- const adoptAndLoad = useCallback14(() => {
6041
+ const adoptAndLoad = useCallback15(() => {
5775
6042
  loadTracks(true);
5776
6043
  }, [loadTracks]);
5777
- useEffect16(() => {
6044
+ useEffect17(() => {
5778
6045
  const unsub = host.onEngineReady(() => {
5779
6046
  adoptAndLoad();
5780
6047
  });
5781
6048
  return unsub;
5782
6049
  }, [host, adoptAndLoad]);
5783
- useEffect16(() => {
6050
+ useEffect17(() => {
5784
6051
  if (typeof host.onAfterAgentMutation !== "function") return;
5785
6052
  let timer = null;
5786
6053
  const unsub = host.onAfterAgentMutation(() => {
@@ -5795,13 +6062,13 @@ function useGeneratorPanelCore({
5795
6062
  if (timer) clearTimeout(timer);
5796
6063
  };
5797
6064
  }, [host, loadTracks]);
5798
- useEffect16(() => {
6065
+ useEffect17(() => {
5799
6066
  const unsub = host.onTrackStateChange((trackId, state) => {
5800
6067
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
5801
6068
  });
5802
6069
  return unsub;
5803
6070
  }, [host]);
5804
- useEffect16(() => {
6071
+ useEffect17(() => {
5805
6072
  if (!features.bulkComposePlaceholders) return;
5806
6073
  console.log(`[${logTag}] Subscribing to composeProgress`);
5807
6074
  const unsub = host.onComposeProgress((event) => {
@@ -5835,7 +6102,7 @@ function useGeneratorPanelCore({
5835
6102
  });
5836
6103
  return unsub;
5837
6104
  }, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
5838
- useEffect16(() => {
6105
+ useEffect17(() => {
5839
6106
  const refs = saveTimeoutRefs;
5840
6107
  return () => {
5841
6108
  for (const timeout of Object.values(refs.current)) {
@@ -5843,9 +6110,9 @@ function useGeneratorPanelCore({
5843
6110
  }
5844
6111
  };
5845
6112
  }, []);
5846
- const isAddingTrackRef = useRef17(false);
5847
- const [isAddingTrack, setIsAddingTrack] = useState19(false);
5848
- const handleAddTrack = useCallback14(async () => {
6113
+ const isAddingTrackRef = useRef18(false);
6114
+ const [isAddingTrack, setIsAddingTrack] = useState21(false);
6115
+ const handleAddTrack = useCallback15(async () => {
5849
6116
  if (isAddingTrackRef.current) return;
5850
6117
  if (!activeSceneId) {
5851
6118
  host.showToast("warning", "Select SCENE");
@@ -5885,7 +6152,7 @@ function useGeneratorPanelCore({
5885
6152
  setIsAddingTrack(false);
5886
6153
  }
5887
6154
  }, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
5888
- const handlePortTrack = useCallback14(
6155
+ const handlePortTrack = useCallback15(
5889
6156
  async (sel) => {
5890
6157
  if (!activeSceneId) {
5891
6158
  host.showToast("warning", "Select SCENE");
@@ -5942,7 +6209,7 @@ function useGeneratorPanelCore({
5942
6209
  },
5943
6210
  [host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
5944
6211
  );
5945
- const handleSoundImportPick = useCallback14(
6212
+ const handleSoundImportPick = useCallback15(
5946
6213
  async (sel) => {
5947
6214
  const target = soundImportTarget;
5948
6215
  if (!target || !host.getTrackSound) {
@@ -5973,8 +6240,8 @@ function useGeneratorPanelCore({
5973
6240
  },
5974
6241
  [soundImportTarget, host, adapter, identity.familyKey, soundHistory]
5975
6242
  );
5976
- const [isExportingMidi, setIsExportingMidi] = useState19(false);
5977
- const handleExportMidi = useCallback14(async () => {
6243
+ const [isExportingMidi, setIsExportingMidi] = useState21(false);
6244
+ const handleExportMidi = useCallback15(async () => {
5978
6245
  if (isExportingMidi) return;
5979
6246
  setIsExportingMidi(true);
5980
6247
  try {
@@ -6005,10 +6272,10 @@ function useGeneratorPanelCore({
6005
6272
  const xfFromId = sceneContext?.transitionFromSceneId ?? null;
6006
6273
  const xfToId = sceneContext?.transitionToSceneId ?? null;
6007
6274
  const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
6008
- useEffect16(() => {
6275
+ useEffect17(() => {
6009
6276
  if (!canCrossfade) setDesignerView(false);
6010
6277
  }, [canCrossfade]);
6011
- useEffect16(() => {
6278
+ useEffect17(() => {
6012
6279
  if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
6013
6280
  setTransitionSourceTotal(0);
6014
6281
  return;
@@ -6024,12 +6291,12 @@ function useGeneratorPanelCore({
6024
6291
  };
6025
6292
  }, [canCrossfade, xfFromId, xfToId, host]);
6026
6293
  const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
6027
- useEffect16(() => {
6294
+ useEffect17(() => {
6028
6295
  if (!onHeaderContent) return;
6029
6296
  const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
6030
6297
  onHeaderContent(
6031
- /* @__PURE__ */ jsxs17("div", { className: "flex gap-1 items-center", children: [
6032
- features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ jsx23(
6298
+ /* @__PURE__ */ jsxs18("div", { className: "flex gap-1 items-center", children: [
6299
+ features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ jsx24(
6033
6300
  "button",
6034
6301
  {
6035
6302
  "data-testid": `import-from-scene-${identity.familyKey}-button`,
@@ -6043,7 +6310,7 @@ function useGeneratorPanelCore({
6043
6310
  children: identity.importTrackLabel ?? "Import Track"
6044
6311
  }
6045
6312
  ),
6046
- (!canCrossfade || !designerView) && /* @__PURE__ */ jsx23(
6313
+ (!canCrossfade || !designerView) && /* @__PURE__ */ jsx24(
6047
6314
  "button",
6048
6315
  {
6049
6316
  "data-testid": `add-${identity.familyKey}-track-button`,
@@ -6059,7 +6326,7 @@ function useGeneratorPanelCore({
6059
6326
  children: identity.addTrackLabel ?? "Add Track"
6060
6327
  }
6061
6328
  ),
6062
- canCrossfade && /* @__PURE__ */ jsxs17(
6329
+ canCrossfade && /* @__PURE__ */ jsxs18(
6063
6330
  "button",
6064
6331
  {
6065
6332
  "data-testid": `${identity.familyKey}-view-toggle`,
@@ -6078,7 +6345,7 @@ function useGeneratorPanelCore({
6078
6345
  title: designerView ? "Back to the track list" : "Open the transition designer",
6079
6346
  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",
6080
6347
  children: [
6081
- transitionSourceTotal > 0 && /* @__PURE__ */ jsx23(
6348
+ transitionSourceTotal > 0 && /* @__PURE__ */ jsx24(
6082
6349
  "span",
6083
6350
  {
6084
6351
  className: "absolute inset-y-0 left-0 bg-sas-accent/25",
@@ -6086,7 +6353,7 @@ function useGeneratorPanelCore({
6086
6353
  "aria-hidden": true
6087
6354
  }
6088
6355
  ),
6089
- /* @__PURE__ */ jsxs17("span", { className: "relative", children: [
6356
+ /* @__PURE__ */ jsxs18("span", { className: "relative", children: [
6090
6357
  "\u21C4 ",
6091
6358
  designerView ? "Transition" : "Tracks",
6092
6359
  transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
@@ -6117,7 +6384,7 @@ function useGeneratorPanelCore({
6117
6384
  identity,
6118
6385
  features.importTracks
6119
6386
  ]);
6120
- useEffect16(() => {
6387
+ useEffect17(() => {
6121
6388
  if (!onLoading) return;
6122
6389
  const anyGenerating = tracks.some((t) => t.isGenerating);
6123
6390
  onLoading(isLoadingTracks || anyGenerating || isBulkActive);
@@ -6125,7 +6392,7 @@ function useGeneratorPanelCore({
6125
6392
  onLoading(false);
6126
6393
  };
6127
6394
  }, [onLoading, isLoadingTracks, tracks, isBulkActive]);
6128
- const handleDeleteTrack = useCallback14(
6395
+ const handleDeleteTrack = useCallback15(
6129
6396
  async (trackId) => {
6130
6397
  try {
6131
6398
  await host.deleteTrack(trackId);
@@ -6141,7 +6408,7 @@ function useGeneratorPanelCore({
6141
6408
  },
6142
6409
  [host, activeSceneId]
6143
6410
  );
6144
- const handlePromptChange = useCallback14(
6411
+ const handlePromptChange = useCallback15(
6145
6412
  (trackId, prompt) => {
6146
6413
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
6147
6414
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -6157,7 +6424,7 @@ function useGeneratorPanelCore({
6157
6424
  },
6158
6425
  [host, activeSceneId]
6159
6426
  );
6160
- const resolvedGenericGroups = useMemo8(() => {
6427
+ const resolvedGenericGroups = useMemo9(() => {
6161
6428
  const out = {};
6162
6429
  for (const ext of adapter.groupExtensions ?? []) {
6163
6430
  out[ext.metaKey] = resolveTrackGroups(
@@ -6171,18 +6438,18 @@ function useGeneratorPanelCore({
6171
6438
  }
6172
6439
  return out;
6173
6440
  }, [adapter, genericGroupMetas, tracks]);
6174
- const genericGroupMemberDbIds = useMemo8(() => {
6441
+ const genericGroupMemberDbIds = useMemo9(() => {
6175
6442
  const s = /* @__PURE__ */ new Set();
6176
6443
  for (const r of Object.values(resolvedGenericGroups)) {
6177
6444
  for (const dbId of r.memberDbIds) s.add(dbId);
6178
6445
  }
6179
6446
  return s;
6180
6447
  }, [resolvedGenericGroups]);
6181
- const engineToDbId = useCallback14(
6448
+ const engineToDbId = useCallback15(
6182
6449
  (trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
6183
6450
  []
6184
6451
  );
6185
- const updateTrack = useCallback14(
6452
+ const updateTrack = useCallback15(
6186
6453
  (trackId, patch) => {
6187
6454
  setTracks(
6188
6455
  (prev) => prev.map(
@@ -6192,18 +6459,18 @@ function useGeneratorPanelCore({
6192
6459
  },
6193
6460
  []
6194
6461
  );
6195
- const markEditLoaded = useCallback14((trackId) => {
6462
+ const markEditLoaded = useCallback15((trackId) => {
6196
6463
  editLoadStartedRef.current.add(trackId);
6197
6464
  }, []);
6198
- const tracksRef = useRef17(tracks);
6199
- useEffect16(() => {
6465
+ const tracksRef = useRef18(tracks);
6466
+ useEffect17(() => {
6200
6467
  tracksRef.current = tracks;
6201
6468
  }, [tracks]);
6202
- const resolvedGenericGroupsRef = useRef17(resolvedGenericGroups);
6203
- useEffect16(() => {
6469
+ const resolvedGenericGroupsRef = useRef18(resolvedGenericGroups);
6470
+ useEffect17(() => {
6204
6471
  resolvedGenericGroupsRef.current = resolvedGenericGroups;
6205
6472
  }, [resolvedGenericGroups]);
6206
- const makeServices = useCallback14(() => {
6473
+ const makeServices = useCallback15(() => {
6207
6474
  return {
6208
6475
  host,
6209
6476
  activeSceneId,
@@ -6222,7 +6489,7 @@ function useGeneratorPanelCore({
6222
6489
  resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
6223
6490
  };
6224
6491
  }, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
6225
- const handleGenerate = useCallback14(
6492
+ const handleGenerate = useCallback15(
6226
6493
  async (trackId) => {
6227
6494
  const track = tracks.find((t) => t.handle.id === trackId);
6228
6495
  if (!track || !track.prompt.trim()) return;
@@ -6249,7 +6516,7 @@ function useGeneratorPanelCore({
6249
6516
  },
6250
6517
  [host, adapter, tracks, isAuthenticated, makeServices]
6251
6518
  );
6252
- const handleMuteToggle = useCallback14(
6519
+ const handleMuteToggle = useCallback15(
6253
6520
  (trackId) => {
6254
6521
  const track = tracks.find((t) => t.handle.id === trackId);
6255
6522
  if (!track) return;
@@ -6269,7 +6536,7 @@ function useGeneratorPanelCore({
6269
6536
  },
6270
6537
  [host, tracks]
6271
6538
  );
6272
- const handleSoloToggle = useCallback14(
6539
+ const handleSoloToggle = useCallback15(
6273
6540
  (trackId) => {
6274
6541
  const track = tracks.find((t) => t.handle.id === trackId);
6275
6542
  if (!track) return;
@@ -6289,7 +6556,7 @@ function useGeneratorPanelCore({
6289
6556
  },
6290
6557
  [host, tracks]
6291
6558
  );
6292
- const handleVolumeChange = useCallback14(
6559
+ const handleVolumeChange = useCallback15(
6293
6560
  (trackId, volume) => {
6294
6561
  setTracks(
6295
6562
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
@@ -6299,7 +6566,7 @@ function useGeneratorPanelCore({
6299
6566
  },
6300
6567
  [host]
6301
6568
  );
6302
- const handlePanChange = useCallback14(
6569
+ const handlePanChange = useCallback15(
6303
6570
  (trackId, pan) => {
6304
6571
  setTracks(
6305
6572
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
@@ -6309,7 +6576,7 @@ function useGeneratorPanelCore({
6309
6576
  },
6310
6577
  [host]
6311
6578
  );
6312
- const handleShuffle = useCallback14(
6579
+ const handleShuffle = useCallback15(
6313
6580
  async (trackId) => {
6314
6581
  const track = tracks.find((t) => t.handle.id === trackId);
6315
6582
  if (!track) return;
@@ -6351,7 +6618,7 @@ function useGeneratorPanelCore({
6351
6618
  },
6352
6619
  [host, adapter, tracks, soundHistory, logTag]
6353
6620
  );
6354
- const handleCopy = useCallback14(
6621
+ const handleCopy = useCallback15(
6355
6622
  async (trackId) => {
6356
6623
  try {
6357
6624
  const newHandle = await host.duplicateTrack(trackId);
@@ -6364,7 +6631,7 @@ function useGeneratorPanelCore({
6364
6631
  },
6365
6632
  [host, loadTracks]
6366
6633
  );
6367
- const handleFxToggle = useCallback14(
6634
+ const handleFxToggle = useCallback15(
6368
6635
  (trackId, category, enabled) => {
6369
6636
  setTracks(
6370
6637
  (prev) => prev.map(
@@ -6387,7 +6654,7 @@ function useGeneratorPanelCore({
6387
6654
  },
6388
6655
  [host]
6389
6656
  );
6390
- const handleFxPresetChange = useCallback14(
6657
+ const handleFxPresetChange = useCallback15(
6391
6658
  (trackId, category, presetIndex) => {
6392
6659
  setTracks(
6393
6660
  (prev) => prev.map(
@@ -6413,7 +6680,7 @@ function useGeneratorPanelCore({
6413
6680
  },
6414
6681
  [host]
6415
6682
  );
6416
- const handleFxDryWetChange = useCallback14(
6683
+ const handleFxDryWetChange = useCallback15(
6417
6684
  (trackId, category, value) => {
6418
6685
  setTracks(
6419
6686
  (prev) => prev.map(
@@ -6425,7 +6692,7 @@ function useGeneratorPanelCore({
6425
6692
  },
6426
6693
  [host]
6427
6694
  );
6428
- const toggleFxDrawer = useCallback14(
6695
+ const toggleFxDrawer = useCallback15(
6429
6696
  (trackId) => {
6430
6697
  setTracks(
6431
6698
  (prev) => prev.map((t) => {
@@ -6447,7 +6714,7 @@ function useGeneratorPanelCore({
6447
6714
  },
6448
6715
  [host, tracks]
6449
6716
  );
6450
- const loadEditNotes = useCallback14(
6717
+ const loadEditNotes = useCallback15(
6451
6718
  async (trackId) => {
6452
6719
  try {
6453
6720
  const mc = await host.getMusicalContext();
@@ -6465,7 +6732,7 @@ function useGeneratorPanelCore({
6465
6732
  },
6466
6733
  [host, logTag]
6467
6734
  );
6468
- const handleNotesChange = useCallback14(
6735
+ const handleNotesChange = useCallback15(
6469
6736
  (trackId, notes) => {
6470
6737
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
6471
6738
  const key = `edit:${trackId}`;
@@ -6495,7 +6762,7 @@ function useGeneratorPanelCore({
6495
6762
  },
6496
6763
  [host]
6497
6764
  );
6498
- const handleTabChange = useCallback14(
6765
+ const handleTabChange = useCallback15(
6499
6766
  (trackId, tab) => {
6500
6767
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
6501
6768
  if (tab === "fx") {
@@ -6520,10 +6787,10 @@ function useGeneratorPanelCore({
6520
6787
  },
6521
6788
  [host, availableInstruments.length, instrumentsLoading, loadEditNotes]
6522
6789
  );
6523
- const handleProgressChange = useCallback14((trackId, pct) => {
6790
+ const handleProgressChange = useCallback15((trackId, pct) => {
6524
6791
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
6525
6792
  }, []);
6526
- const handleToggleDrawer = useCallback14((trackId) => {
6793
+ const handleToggleDrawer = useCallback15((trackId) => {
6527
6794
  setTracks(
6528
6795
  (prev) => prev.map((t) => {
6529
6796
  if (t.handle.id !== trackId) return t;
@@ -6532,7 +6799,7 @@ function useGeneratorPanelCore({
6532
6799
  })
6533
6800
  );
6534
6801
  }, []);
6535
- const handleInstrumentSelect = useCallback14(
6802
+ const handleInstrumentSelect = useCallback15(
6536
6803
  async (trackId, pluginId) => {
6537
6804
  const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
6538
6805
  if (isDefaultInstrument) {
@@ -6585,7 +6852,7 @@ function useGeneratorPanelCore({
6585
6852
  },
6586
6853
  [host, identity.defaultInstrumentPluginId, logTag]
6587
6854
  );
6588
- const handleShowEditor = useCallback14(
6855
+ const handleShowEditor = useCallback15(
6589
6856
  async (trackId) => {
6590
6857
  try {
6591
6858
  await host.showInstrumentEditor(trackId);
@@ -6596,12 +6863,12 @@ function useGeneratorPanelCore({
6596
6863
  },
6597
6864
  [host]
6598
6865
  );
6599
- const handleBackToInstruments = useCallback14((trackId) => {
6866
+ const handleBackToInstruments = useCallback15((trackId) => {
6600
6867
  setTracks(
6601
6868
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
6602
6869
  );
6603
6870
  }, []);
6604
- const handleRefreshInstruments = useCallback14(() => {
6871
+ const handleRefreshInstruments = useCallback15(() => {
6605
6872
  setInstrumentsLoading(true);
6606
6873
  host.getAvailableInstruments().then((instruments) => {
6607
6874
  setAvailableInstruments(instruments);
@@ -6610,13 +6877,13 @@ function useGeneratorPanelCore({
6610
6877
  setInstrumentsLoading(false);
6611
6878
  });
6612
6879
  }, [host]);
6613
- const onAuditionNote = useCallback14(
6880
+ const onAuditionNote = useCallback15(
6614
6881
  (trackId, pitch, velocity, ms) => {
6615
6882
  void host.auditionNote(trackId, pitch, velocity, ms);
6616
6883
  },
6617
6884
  [host]
6618
6885
  );
6619
- const { resolvedCrossfadePairs, crossfadeMemberDbIds } = useMemo8(() => {
6886
+ const { resolvedCrossfadePairs, crossfadeMemberDbIds } = useMemo9(() => {
6620
6887
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
6621
6888
  const pairs = [];
6622
6889
  const members = /* @__PURE__ */ new Set();
@@ -6631,7 +6898,7 @@ function useGeneratorPanelCore({
6631
6898
  }
6632
6899
  return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
6633
6900
  }, [tracks, crossfadePairsMeta]);
6634
- const { resolvedFades, fadeMemberDbIds } = useMemo8(() => {
6901
+ const { resolvedFades, fadeMemberDbIds } = useMemo9(() => {
6635
6902
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
6636
6903
  const list = [];
6637
6904
  const members = /* @__PURE__ */ new Set();
@@ -6659,7 +6926,7 @@ function useGeneratorPanelCore({
6659
6926
  resolvedCrossfadePairs,
6660
6927
  resolvedFades
6661
6928
  });
6662
- const setGroupMute = useCallback14(
6929
+ const setGroupMute = useCallback15(
6663
6930
  (trackIds, muted) => {
6664
6931
  for (const id of trackIds) {
6665
6932
  setTracks(
@@ -6671,7 +6938,7 @@ function useGeneratorPanelCore({
6671
6938
  },
6672
6939
  [host]
6673
6940
  );
6674
- const setGroupSolo = useCallback14(
6941
+ const setGroupSolo = useCallback15(
6675
6942
  (trackIds, solo) => {
6676
6943
  for (const id of trackIds) {
6677
6944
  setTracks(
@@ -6683,7 +6950,7 @@ function useGeneratorPanelCore({
6683
6950
  },
6684
6951
  [host]
6685
6952
  );
6686
- const deleteGroup = useCallback14(
6953
+ const deleteGroup = useCallback15(
6687
6954
  async (members, cleanupKeySuffixes) => {
6688
6955
  for (const member of members) {
6689
6956
  try {
@@ -6703,7 +6970,7 @@ function useGeneratorPanelCore({
6703
6970
  },
6704
6971
  [host, activeSceneId, loadTracks]
6705
6972
  );
6706
- const handlers = useMemo8(
6973
+ const handlers = useMemo9(
6707
6974
  () => ({
6708
6975
  promptChange: handlePromptChange,
6709
6976
  generate: (trackId) => {
@@ -6817,8 +7084,8 @@ function useGeneratorPanelCore({
6817
7084
  }
6818
7085
 
6819
7086
  // src/panel-core/GeneratorPanelShell.tsx
6820
- import React20, { useCallback as useCallback15 } from "react";
6821
- import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
7087
+ import React21, { useCallback as useCallback16 } from "react";
7088
+ import { Fragment as Fragment6, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
6822
7089
  function GeneratorPanelShell({ core, slots }) {
6823
7090
  const {
6824
7091
  ui,
@@ -6871,8 +7138,9 @@ function GeneratorPanelShell({ core, slots }) {
6871
7138
  deleteGroup
6872
7139
  } = core;
6873
7140
  const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
7141
+ const panelBus = usePanelBus(host, activeSceneId);
6874
7142
  const { identity, features } = adapter;
6875
- const buildRowProps = useCallback15(
7143
+ const buildRowProps = useCallback16(
6876
7144
  (track, drag) => {
6877
7145
  const id = track.handle.id;
6878
7146
  const pickerProps = features.instrumentPicker ? {
@@ -6971,12 +7239,12 @@ function GeneratorPanelShell({ core, slots }) {
6971
7239
  ]
6972
7240
  );
6973
7241
  if (!activeSceneId) {
6974
- return /* @__PURE__ */ jsx24(
7242
+ return /* @__PURE__ */ jsx25(
6975
7243
  "div",
6976
7244
  {
6977
7245
  "data-testid": `no-scene-placeholder-${identity.familyKey}`,
6978
7246
  className: "flex items-center justify-center py-8",
6979
- children: /* @__PURE__ */ jsx24(
7247
+ children: /* @__PURE__ */ jsx25(
6980
7248
  "button",
6981
7249
  {
6982
7250
  onClick: () => onSelectScene?.(),
@@ -6988,12 +7256,12 @@ function GeneratorPanelShell({ core, slots }) {
6988
7256
  );
6989
7257
  }
6990
7258
  if (!sceneContext?.hasContract) {
6991
- return /* @__PURE__ */ jsx24(
7259
+ return /* @__PURE__ */ jsx25(
6992
7260
  "div",
6993
7261
  {
6994
7262
  "data-testid": `no-contract-placeholder-${identity.familyKey}`,
6995
7263
  className: "flex items-center justify-center py-8",
6996
- children: /* @__PURE__ */ jsx24(
7264
+ children: /* @__PURE__ */ jsx25(
6997
7265
  "button",
6998
7266
  {
6999
7267
  onClick: () => onOpenContract?.(),
@@ -7005,7 +7273,7 @@ function GeneratorPanelShell({ core, slots }) {
7005
7273
  );
7006
7274
  }
7007
7275
  if (features.bulkComposePlaceholders && isComposing) {
7008
- return /* @__PURE__ */ jsx24("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ jsx24(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
7276
+ return /* @__PURE__ */ jsx25("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ jsx25(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
7009
7277
  }
7010
7278
  const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
7011
7279
  if (activePlaceholders.length > 0) {
@@ -7016,18 +7284,18 @@ function GeneratorPanelShell({ core, slots }) {
7016
7284
  tracksByDbId.set(t.handle.id, t);
7017
7285
  }
7018
7286
  }
7019
- return /* @__PURE__ */ jsx24("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
7287
+ return /* @__PURE__ */ jsx25("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
7020
7288
  const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
7021
7289
  if (loadedTrack) {
7022
- return /* @__PURE__ */ jsx24(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7290
+ return /* @__PURE__ */ jsx25(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7023
7291
  }
7024
- return /* @__PURE__ */ jsx24(
7292
+ return /* @__PURE__ */ jsx25(
7025
7293
  "div",
7026
7294
  {
7027
7295
  "data-testid": "bulk-placeholder-track",
7028
7296
  className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
7029
7297
  style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
7030
- children: /* @__PURE__ */ jsx24(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
7298
+ children: /* @__PURE__ */ jsx25(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
7031
7299
  },
7032
7300
  ph.id
7033
7301
  );
@@ -7039,13 +7307,13 @@ function GeneratorPanelShell({ core, slots }) {
7039
7307
  supportsMeters,
7040
7308
  levels: supportsMeters ? trackLevels : void 0,
7041
7309
  handlers,
7042
- renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ jsx24(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
7310
+ renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ jsx25(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
7043
7311
  setGroupMute,
7044
7312
  setGroupSolo,
7045
7313
  deleteGroup
7046
7314
  };
7047
- return /* @__PURE__ */ jsxs18("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
7048
- features.importTracks && host.listImportableTracks && /* @__PURE__ */ jsx24(
7315
+ return /* @__PURE__ */ jsxs19("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
7316
+ features.importTracks && host.listImportableTracks && /* @__PURE__ */ jsx25(
7049
7317
  ImportTrackModal,
7050
7318
  {
7051
7319
  host,
@@ -7058,7 +7326,7 @@ function GeneratorPanelShell({ core, slots }) {
7058
7326
  testIdPrefix: `${identity.familyKey}-import`
7059
7327
  }
7060
7328
  ),
7061
- features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ jsx24(
7329
+ features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ jsx25(
7062
7330
  ImportTrackModal,
7063
7331
  {
7064
7332
  host,
@@ -7073,7 +7341,7 @@ function GeneratorPanelShell({ core, slots }) {
7073
7341
  }
7074
7342
  ),
7075
7343
  slots?.modals,
7076
- canCrossfade && xfFromId && xfToId && /* @__PURE__ */ jsx24("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ jsx24(
7344
+ canCrossfade && xfFromId && xfToId && /* @__PURE__ */ jsx25("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ jsx25(
7077
7345
  TransitionDesigner,
7078
7346
  {
7079
7347
  host,
@@ -7090,9 +7358,28 @@ function GeneratorPanelShell({ core, slots }) {
7090
7358
  testIdPrefix: `${identity.familyKey}-transition-designer`
7091
7359
  }
7092
7360
  ) }),
7093
- !(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ jsx24("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
7361
+ !(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ jsx25("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ jsxs19(Fragment6, { children: [
7362
+ panelBus.supported && panelBus.bus && /* @__PURE__ */ jsx25(
7363
+ PanelMasterStrip,
7364
+ {
7365
+ bus: panelBus.bus,
7366
+ availableFx: panelBus.availableFx,
7367
+ fxLoading: panelBus.fxLoading,
7368
+ soloedOut: anySolo && !panelBus.bus.soloed,
7369
+ fxPickerOpen: panelBus.fxPickerOpen,
7370
+ onToggleFxPicker: panelBus.setFxPickerOpen,
7371
+ onRefreshFx: panelBus.refreshFx,
7372
+ onVolumeChange: panelBus.onVolumeChange,
7373
+ onMuteToggle: panelBus.onMuteToggle,
7374
+ onSoloToggle: panelBus.onSoloToggle,
7375
+ onAddFx: panelBus.onAddFx,
7376
+ onRemoveFx: panelBus.onRemoveFx,
7377
+ onToggleFxEnabled: panelBus.onToggleFxEnabled,
7378
+ onShowFxEditor: panelBus.onShowFxEditor
7379
+ }
7380
+ ),
7094
7381
  slots?.beforeRows,
7095
- resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ jsx24(
7382
+ resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ jsx25(
7096
7383
  CrossfadeTrackRow,
7097
7384
  {
7098
7385
  accentColor: identity.transitionAccentColor,
@@ -7129,7 +7416,7 @@ function GeneratorPanelShell({ core, slots }) {
7129
7416
  },
7130
7417
  pair.groupId
7131
7418
  )),
7132
- resolvedFades.map((fade) => /* @__PURE__ */ jsx24(
7419
+ resolvedFades.map((fade) => /* @__PURE__ */ jsx25(
7133
7420
  FadeTrackRow,
7134
7421
  {
7135
7422
  accentColor: identity.transitionAccentColor,
@@ -7155,20 +7442,20 @@ function GeneratorPanelShell({ core, slots }) {
7155
7442
  fade.dbId
7156
7443
  )),
7157
7444
  (adapter.groupExtensions ?? []).flatMap(
7158
- (ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ jsx24(React20.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
7445
+ (ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ jsx25(React21.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
7159
7446
  ),
7160
7447
  tracks.map((track, index) => {
7161
7448
  if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
7162
7449
  return null;
7163
7450
  }
7164
- return /* @__PURE__ */ jsx24(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
7451
+ return /* @__PURE__ */ jsx25(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
7165
7452
  }),
7166
7453
  slots?.afterRows
7167
7454
  ] })),
7168
7455
  features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
7169
7456
  const hasAnyMidi = tracks.some((t) => t.hasMidi);
7170
7457
  const exportDisabled = isExportingMidi || !hasAnyMidi;
7171
- return /* @__PURE__ */ jsx24("div", { className: "pt-2", children: /* @__PURE__ */ jsx24(
7458
+ return /* @__PURE__ */ jsx25("div", { className: "pt-2", children: /* @__PURE__ */ jsx25(
7172
7459
  "button",
7173
7460
  {
7174
7461
  "data-testid": "export-midi-tracks-button",
@@ -7236,7 +7523,7 @@ function createSurgeSoundAdapter(host, overrides = {}) {
7236
7523
  }
7237
7524
 
7238
7525
  // src/constants/sdk-version.ts
7239
- var PLUGIN_SDK_VERSION = "2.35.0";
7526
+ var PLUGIN_SDK_VERSION = "2.37.0";
7240
7527
 
7241
7528
  // src/utils/format-concurrent-tracks.ts
7242
7529
  function formatConcurrentTracks(ctx) {
@@ -7411,6 +7698,7 @@ export {
7411
7698
  PLUGIN_SDK_VERSION,
7412
7699
  PX_PER_BEAT,
7413
7700
  PanSlider,
7701
+ PanelMasterStrip,
7414
7702
  PianoRollEditor,
7415
7703
  PluginError,
7416
7704
  RESIZE_HANDLE_PX,
@@ -7475,6 +7763,7 @@ export {
7475
7763
  transposeNotes,
7476
7764
  useAnySolo,
7477
7765
  useGeneratorPanelCore,
7766
+ usePanelBus,
7478
7767
  useSceneState,
7479
7768
  useSoundHistory,
7480
7769
  useTrackLevel,