@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.js CHANGED
@@ -62,6 +62,7 @@ __export(index_exports, {
62
62
  PLUGIN_SDK_VERSION: () => PLUGIN_SDK_VERSION,
63
63
  PX_PER_BEAT: () => PX_PER_BEAT,
64
64
  PanSlider: () => PanSlider,
65
+ PanelMasterStrip: () => PanelMasterStrip,
65
66
  PianoRollEditor: () => PianoRollEditor,
66
67
  PluginError: () => PluginError,
67
68
  RESIZE_HANDLE_PX: () => RESIZE_HANDLE_PX,
@@ -126,6 +127,7 @@ __export(index_exports, {
126
127
  transposeNotes: () => transposeNotes,
127
128
  useAnySolo: () => useAnySolo,
128
129
  useGeneratorPanelCore: () => useGeneratorPanelCore,
130
+ usePanelBus: () => usePanelBus,
129
131
  useSceneState: () => useSceneState,
130
132
  useSoundHistory: () => useSoundHistory,
131
133
  useTrackLevel: () => useTrackLevel,
@@ -4265,9 +4267,276 @@ function TransitionDesigner({
4265
4267
  ] });
4266
4268
  }
4267
4269
 
4268
- // src/components/DownloadPackButton.tsx
4270
+ // src/components/PanelMasterStrip.tsx
4269
4271
  var import_react17 = require("react");
4270
4272
  var import_jsx_runtime18 = require("react/jsx-runtime");
4273
+ function PanelMasterStrip({
4274
+ bus,
4275
+ availableFx = [],
4276
+ fxLoading = false,
4277
+ soloedOut = false,
4278
+ disabled = false,
4279
+ fxPickerOpen,
4280
+ onToggleFxPicker,
4281
+ onRefreshFx,
4282
+ onVolumeChange,
4283
+ onMuteToggle,
4284
+ onSoloToggle,
4285
+ onAddFx,
4286
+ onRemoveFx,
4287
+ onToggleFxEnabled,
4288
+ onShowFxEditor
4289
+ }) {
4290
+ const [search, setSearch] = (0, import_react17.useState)("");
4291
+ const filtered = (0, import_react17.useMemo)(() => {
4292
+ const q = search.trim().toLowerCase();
4293
+ if (!q) return availableFx;
4294
+ return availableFx.filter(
4295
+ (fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
4296
+ );
4297
+ }, [availableFx, search]);
4298
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4299
+ "div",
4300
+ {
4301
+ "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" : ""}`,
4303
+ children: [
4304
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", children: [
4305
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4306
+ "span",
4307
+ {
4308
+ className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
4309
+ title: "Panel mix bus \u2014 volume, mute/solo and FX applied to this panel's summed output",
4310
+ children: "BUS"
4311
+ }
4312
+ ),
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)(
4322
+ "button",
4323
+ {
4324
+ "data-testid": "bus-mute-button",
4325
+ onClick: onMuteToggle,
4326
+ disabled,
4327
+ 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`,
4328
+ title: bus.muted ? "Unmute panel bus" : "Mute panel bus (silences the whole panel)",
4329
+ children: "M"
4330
+ }
4331
+ ),
4332
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4333
+ "button",
4334
+ {
4335
+ "data-testid": "bus-solo-button",
4336
+ onClick: onSoloToggle,
4337
+ disabled,
4338
+ 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`,
4339
+ title: bus.soloed ? "Unsolo panel bus" : "Solo this panel (silences other panels/tracks in scope)",
4340
+ children: "S"
4341
+ }
4342
+ ),
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)(
4344
+ "span",
4345
+ {
4346
+ "data-testid": `bus-fx-chip-${fx.index}`,
4347
+ 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
+ title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
4349
+ children: [
4350
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4351
+ "button",
4352
+ {
4353
+ "data-testid": `bus-fx-toggle-${fx.index}`,
4354
+ onClick: () => onToggleFxEnabled(fx.index, !fx.enabled),
4355
+ disabled,
4356
+ className: "hover:opacity-70 disabled:opacity-50",
4357
+ title: fx.enabled ? `Bypass ${fx.name}` : `Enable ${fx.name}`,
4358
+ children: fx.enabled ? "\u25CF" : "\u25CB"
4359
+ }
4360
+ ),
4361
+ onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4362
+ "button",
4363
+ {
4364
+ "data-testid": `bus-fx-edit-${fx.index}`,
4365
+ onClick: () => onShowFxEditor(fx.index),
4366
+ disabled,
4367
+ className: "max-w-[80px] truncate hover:underline disabled:opacity-50",
4368
+ title: `Open ${fx.name} editor`,
4369
+ children: fx.name
4370
+ }
4371
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
4372
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4373
+ "button",
4374
+ {
4375
+ "data-testid": `bus-fx-remove-${fx.index}`,
4376
+ onClick: () => onRemoveFx(fx.index),
4377
+ disabled,
4378
+ className: "text-sas-muted/60 hover:text-sas-danger disabled:opacity-50",
4379
+ title: `Remove ${fx.name} from the bus`,
4380
+ children: "\u2715"
4381
+ }
4382
+ )
4383
+ ]
4384
+ },
4385
+ `${fx.index}:${fx.pluginId}`
4386
+ )) }),
4387
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4388
+ "button",
4389
+ {
4390
+ "data-testid": "bus-fx-add-button",
4391
+ onClick: () => onToggleFxPicker(!fxPickerOpen),
4392
+ disabled,
4393
+ 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 +"
4396
+ }
4397
+ )
4398
+ ] }),
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)(
4402
+ "input",
4403
+ {
4404
+ type: "text",
4405
+ value: search,
4406
+ onChange: (e) => setSearch(e.target.value),
4407
+ placeholder: "Search FX...",
4408
+ className: "sas-input flex-1 px-2 py-1 text-xs"
4409
+ }
4410
+ ),
4411
+ onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4412
+ "button",
4413
+ {
4414
+ onClick: () => onRefreshFx(),
4415
+ disabled: fxLoading,
4416
+ 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",
4418
+ children: fxLoading ? "..." : "Refresh"
4419
+ }
4420
+ )
4421
+ ] }),
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)(
4424
+ "button",
4425
+ {
4426
+ "data-testid": `bus-fx-pick-${fx.pluginId}`,
4427
+ onClick: () => onAddFx(fx.pluginId),
4428
+ 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
+ title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
4430
+ 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() })
4433
+ ]
4434
+ },
4435
+ fx.pluginId
4436
+ )),
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" })
4438
+ ] })
4439
+ ] })
4440
+ ]
4441
+ }
4442
+ );
4443
+ }
4444
+
4445
+ // src/hooks/usePanelBus.ts
4446
+ var import_react18 = require("react");
4447
+ function usePanelBus(host, activeSceneId) {
4448
+ 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 () => {
4456
+ if (!supported || !activeSceneId || !host.getPanelBusState) {
4457
+ setBus(null);
4458
+ return;
4459
+ }
4460
+ const seq = ++loadSeqRef.current;
4461
+ try {
4462
+ const state = await host.getPanelBusState(activeSceneId);
4463
+ if (loadSeqRef.current === seq) setBus(state);
4464
+ } catch {
4465
+ }
4466
+ }, [host, activeSceneId, supported]);
4467
+ (0, import_react18.useEffect)(() => {
4468
+ setBus(null);
4469
+ setFxPickerOpen(false);
4470
+ void reload();
4471
+ }, [reload]);
4472
+ const loadFxList = (0, import_react18.useCallback)(
4473
+ async (force) => {
4474
+ if (!supported || !host.getAvailableFx) return;
4475
+ if (fxLoadedRef.current && !force) return;
4476
+ setFxLoading(true);
4477
+ try {
4478
+ const list = await host.getAvailableFx();
4479
+ setAvailableFx(list);
4480
+ fxLoadedRef.current = true;
4481
+ } catch {
4482
+ } finally {
4483
+ setFxLoading(false);
4484
+ }
4485
+ },
4486
+ [host, supported]
4487
+ );
4488
+ const openPicker = (0, import_react18.useCallback)(
4489
+ (open) => {
4490
+ setFxPickerOpen(open);
4491
+ if (open) void loadFxList(false);
4492
+ },
4493
+ [loadFxList]
4494
+ );
4495
+ const mutate = (0, import_react18.useCallback)(
4496
+ (fn) => {
4497
+ if (!fn || !activeSceneId) return;
4498
+ void (async () => {
4499
+ try {
4500
+ await fn();
4501
+ } catch {
4502
+ }
4503
+ await reload();
4504
+ })();
4505
+ },
4506
+ [activeSceneId, reload]
4507
+ );
4508
+ return {
4509
+ supported,
4510
+ bus,
4511
+ availableFx,
4512
+ fxLoading,
4513
+ fxPickerOpen,
4514
+ setFxPickerOpen: openPicker,
4515
+ refreshFx: () => void loadFxList(true),
4516
+ reload,
4517
+ onVolumeChange: (volumeDb) => mutate(host.setPanelBusVolume && (() => host.setPanelBusVolume(activeSceneId, volumeDb))),
4518
+ onMuteToggle: () => mutate(
4519
+ host.setPanelBusMute && (() => host.setPanelBusMute(activeSceneId, !(bus?.muted ?? false)))
4520
+ ),
4521
+ onSoloToggle: () => mutate(
4522
+ host.setPanelBusSolo && (() => host.setPanelBusSolo(activeSceneId, !(bus?.soloed ?? false)))
4523
+ ),
4524
+ onAddFx: (pluginId) => mutate(host.loadPanelBusFx && (async () => {
4525
+ await host.loadPanelBusFx(activeSceneId, pluginId);
4526
+ })),
4527
+ onRemoveFx: (fxIndex) => mutate(host.removePanelBusFx && (() => host.removePanelBusFx(activeSceneId, fxIndex))),
4528
+ onToggleFxEnabled: (fxIndex, enabled) => mutate(
4529
+ host.setPanelBusFxEnabled && (() => host.setPanelBusFxEnabled(activeSceneId, fxIndex, enabled))
4530
+ ),
4531
+ onShowFxEditor: (fxIndex) => mutate(
4532
+ host.showPanelBusFxEditor && (() => host.showPanelBusFxEditor(activeSceneId, fxIndex))
4533
+ )
4534
+ };
4535
+ }
4536
+
4537
+ // src/components/DownloadPackButton.tsx
4538
+ var import_react19 = require("react");
4539
+ var import_jsx_runtime19 = require("react/jsx-runtime");
4271
4540
  function formatSize(bytes) {
4272
4541
  if (!bytes || bytes <= 0) return "";
4273
4542
  const gb = bytes / 1024 ** 3;
@@ -4283,10 +4552,10 @@ var DownloadPackButton = ({
4283
4552
  variant = "compact",
4284
4553
  onDownloadComplete
4285
4554
  }) => {
4286
- const [status, setStatus] = (0, import_react17.useState)("idle");
4287
- const [progress, setProgress] = (0, import_react17.useState)(0);
4288
- const [errorMessage, setErrorMessage] = (0, import_react17.useState)(null);
4289
- (0, import_react17.useEffect)(() => {
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)(() => {
4290
4559
  const unsub = host.onSamplePackProgress(packId, (p) => {
4291
4560
  setStatus(p.status);
4292
4561
  setProgress(p.progress);
@@ -4301,7 +4570,7 @@ var DownloadPackButton = ({
4301
4570
  });
4302
4571
  return unsub;
4303
4572
  }, [host, packId, onDownloadComplete]);
4304
- const handleClick = (0, import_react17.useCallback)(async () => {
4573
+ const handleClick = (0, import_react19.useCallback)(async () => {
4305
4574
  if (status !== "idle" && status !== "error") return;
4306
4575
  try {
4307
4576
  setStatus("downloading");
@@ -4355,8 +4624,8 @@ var DownloadPackButton = ({
4355
4624
  } else {
4356
4625
  className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
4357
4626
  }
4358
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4359
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4627
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
4628
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4360
4629
  "button",
4361
4630
  {
4362
4631
  "data-testid": `download-pack-button-${packId}`,
@@ -4367,12 +4636,12 @@ var DownloadPackButton = ({
4367
4636
  children: buttonLabel
4368
4637
  }
4369
4638
  ),
4370
- variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
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 })
4371
4640
  ] });
4372
4641
  };
4373
4642
 
4374
4643
  // src/components/SamplePackCTACard.tsx
4375
- var import_jsx_runtime19 = require("react/jsx-runtime");
4644
+ var import_jsx_runtime20 = require("react/jsx-runtime");
4376
4645
  var SamplePackCTACard = ({
4377
4646
  host,
4378
4647
  pack,
@@ -4380,7 +4649,7 @@ var SamplePackCTACard = ({
4380
4649
  onDownloadComplete
4381
4650
  }) => {
4382
4651
  if (status === "checking") {
4383
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4652
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4384
4653
  "div",
4385
4654
  {
4386
4655
  "data-testid": `sample-pack-cta-checking-${pack.packId}`,
@@ -4391,16 +4660,16 @@ var SamplePackCTACard = ({
4391
4660
  }
4392
4661
  const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
4393
4662
  const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
4394
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4663
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
4395
4664
  "div",
4396
4665
  {
4397
4666
  "data-testid": `sample-pack-cta-${pack.packId}`,
4398
4667
  className: "flex flex-col items-center justify-center py-12 px-6 text-center",
4399
4668
  children: [
4400
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
4401
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
4402
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
4403
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
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)(
4404
4673
  DownloadPackButton,
4405
4674
  {
4406
4675
  host,
@@ -4417,7 +4686,7 @@ var SamplePackCTACard = ({
4417
4686
  };
4418
4687
 
4419
4688
  // src/components/WaveformView.tsx
4420
- var import_react18 = require("react");
4689
+ var import_react20 = require("react");
4421
4690
 
4422
4691
  // src/components/waveform.ts
4423
4692
  function computePeaks(audioBuffer, bins, targetSamples) {
@@ -4480,7 +4749,7 @@ function drawWaveform(canvas, peaks, options = {}) {
4480
4749
  }
4481
4750
 
4482
4751
  // src/components/WaveformView.tsx
4483
- var import_jsx_runtime20 = require("react/jsx-runtime");
4752
+ var import_jsx_runtime21 = require("react/jsx-runtime");
4484
4753
  var WaveformView = ({
4485
4754
  host,
4486
4755
  filePath,
@@ -4489,9 +4758,9 @@ var WaveformView = ({
4489
4758
  fillStyle,
4490
4759
  targetSamples
4491
4760
  }) => {
4492
- const canvasRef = (0, import_react18.useRef)(null);
4493
- const [peaks, setPeaks] = (0, import_react18.useState)(null);
4494
- (0, import_react18.useEffect)(() => {
4761
+ const canvasRef = (0, import_react20.useRef)(null);
4762
+ const [peaks, setPeaks] = (0, import_react20.useState)(null);
4763
+ (0, import_react20.useEffect)(() => {
4495
4764
  let cancelled = false;
4496
4765
  let audioContext = null;
4497
4766
  (async () => {
@@ -4517,7 +4786,7 @@ var WaveformView = ({
4517
4786
  cancelled = true;
4518
4787
  };
4519
4788
  }, [host, filePath, bins, targetSamples]);
4520
- (0, import_react18.useEffect)(() => {
4789
+ (0, import_react20.useEffect)(() => {
4521
4790
  if (!peaks) return;
4522
4791
  const canvas = canvasRef.current;
4523
4792
  if (!canvas) return;
@@ -4528,7 +4797,7 @@ var WaveformView = ({
4528
4797
  observer.observe(canvas);
4529
4798
  return () => observer.disconnect();
4530
4799
  }, [peaks, fillStyle]);
4531
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4800
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4532
4801
  "canvas",
4533
4802
  {
4534
4803
  ref: canvasRef,
@@ -4539,8 +4808,8 @@ var WaveformView = ({
4539
4808
  };
4540
4809
 
4541
4810
  // src/components/ScrollingWaveform.tsx
4542
- var import_react19 = require("react");
4543
- var import_jsx_runtime21 = require("react/jsx-runtime");
4811
+ var import_react21 = require("react");
4812
+ var import_jsx_runtime22 = require("react/jsx-runtime");
4544
4813
  var ScrollingWaveform = ({
4545
4814
  getPeakDb,
4546
4815
  active,
@@ -4548,11 +4817,11 @@ var ScrollingWaveform = ({
4548
4817
  className,
4549
4818
  fillStyle
4550
4819
  }) => {
4551
- const canvasRef = (0, import_react19.useRef)(null);
4552
- const ringRef = (0, import_react19.useRef)(new Float32Array(columns));
4553
- const writeIdxRef = (0, import_react19.useRef)(0);
4554
- const rafRef = (0, import_react19.useRef)(null);
4555
- (0, import_react19.useEffect)(() => {
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)(() => {
4556
4825
  if (ringRef.current.length !== columns) {
4557
4826
  const next = new Float32Array(columns);
4558
4827
  const prev = ringRef.current;
@@ -4564,7 +4833,7 @@ var ScrollingWaveform = ({
4564
4833
  writeIdxRef.current = writeIdxRef.current % columns;
4565
4834
  }
4566
4835
  }, [columns]);
4567
- (0, import_react19.useEffect)(() => {
4836
+ (0, import_react21.useEffect)(() => {
4568
4837
  if (!active) {
4569
4838
  if (rafRef.current !== null) {
4570
4839
  cancelAnimationFrame(rafRef.current);
@@ -4616,7 +4885,7 @@ var ScrollingWaveform = ({
4616
4885
  }
4617
4886
  };
4618
4887
  }, [active, getPeakDb, fillStyle]);
4619
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4888
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4620
4889
  "canvas",
4621
4890
  {
4622
4891
  ref: canvasRef,
@@ -4627,8 +4896,8 @@ var ScrollingWaveform = ({
4627
4896
  };
4628
4897
 
4629
4898
  // src/components/OffsetScrubber.tsx
4630
- var import_react20 = require("react");
4631
- var import_jsx_runtime22 = require("react/jsx-runtime");
4899
+ var import_react22 = require("react");
4900
+ var import_jsx_runtime23 = require("react/jsx-runtime");
4632
4901
  var SLIDER_HEIGHT_PX = 28;
4633
4902
  var TICK_HEIGHT_PX = 14;
4634
4903
  var DOWNBEAT_TICK_HEIGHT_PX = 22;
@@ -4641,40 +4910,40 @@ function OffsetScrubber({
4641
4910
  onChange,
4642
4911
  disabled = false
4643
4912
  }) {
4644
- const trackRef = (0, import_react20.useRef)(null);
4645
- const [draftOffset, setDraftOffset] = (0, import_react20.useState)(offsetSamples);
4646
- const [isDragging, setIsDragging] = (0, import_react20.useState)(false);
4647
- (0, import_react20.useEffect)(() => {
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)(() => {
4648
4917
  if (!isDragging) setDraftOffset(offsetSamples);
4649
4918
  }, [offsetSamples, isDragging]);
4650
4919
  const sampleRate = cuePoints?.sample_rate ?? 44100;
4651
4920
  const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
4652
- const beatsForRange = (0, import_react20.useMemo)(() => {
4921
+ const beatsForRange = (0, import_react22.useMemo)(() => {
4653
4922
  return Math.round(60 / projectBpm * sampleRate);
4654
4923
  }, [projectBpm, sampleRate]);
4655
4924
  const rangeSamples = beatsForRange * meter;
4656
- const sampleToFraction = (0, import_react20.useCallback)(
4925
+ const sampleToFraction = (0, import_react22.useCallback)(
4657
4926
  (sample) => {
4658
4927
  const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
4659
4928
  return (clamped + rangeSamples) / (2 * rangeSamples);
4660
4929
  },
4661
4930
  [rangeSamples]
4662
4931
  );
4663
- const fractionToSample = (0, import_react20.useCallback)(
4932
+ const fractionToSample = (0, import_react22.useCallback)(
4664
4933
  (fraction) => {
4665
4934
  const clamped = Math.max(0, Math.min(1, fraction));
4666
4935
  return Math.round(clamped * 2 * rangeSamples - rangeSamples);
4667
4936
  },
4668
4937
  [rangeSamples]
4669
4938
  );
4670
- const snapTargets = (0, import_react20.useMemo)(() => {
4939
+ const snapTargets = (0, import_react22.useMemo)(() => {
4671
4940
  if (!cuePoints || cuePoints.beats.length === 0) return [];
4672
4941
  const downbeat = cuePoints.beats[0];
4673
4942
  const positives = cuePoints.beats.map((b) => b - downbeat);
4674
4943
  const negatives = positives.slice(1).map((p) => -p);
4675
4944
  return [...negatives, ...positives].sort((a, b) => a - b);
4676
4945
  }, [cuePoints]);
4677
- const snapToBeat = (0, import_react20.useCallback)(
4946
+ const snapToBeat = (0, import_react22.useCallback)(
4678
4947
  (sample) => {
4679
4948
  if (snapTargets.length === 0) return sample;
4680
4949
  let best = snapTargets[0];
@@ -4690,7 +4959,7 @@ function OffsetScrubber({
4690
4959
  },
4691
4960
  [snapTargets]
4692
4961
  );
4693
- const handlePointerDown = (0, import_react20.useCallback)(
4962
+ const handlePointerDown = (0, import_react22.useCallback)(
4694
4963
  (e) => {
4695
4964
  if (disabled || !cuePoints) return;
4696
4965
  e.preventDefault();
@@ -4724,7 +4993,7 @@ function OffsetScrubber({
4724
4993
  },
4725
4994
  [disabled, cuePoints, fractionToSample, onChange, snapToBeat]
4726
4995
  );
4727
- const handleResetToZero = (0, import_react20.useCallback)(() => {
4996
+ const handleResetToZero = (0, import_react22.useCallback)(() => {
4728
4997
  if (disabled) return;
4729
4998
  setDraftOffset(0);
4730
4999
  onChange(0);
@@ -4732,7 +5001,7 @@ function OffsetScrubber({
4732
5001
  const thumbFraction = sampleToFraction(draftOffset);
4733
5002
  const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
4734
5003
  const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
4735
- const ticks = (0, import_react20.useMemo)(() => {
5004
+ const ticks = (0, import_react22.useMemo)(() => {
4736
5005
  if (!cuePoints) return [];
4737
5006
  const downbeat = cuePoints.beats[0] ?? 0;
4738
5007
  return cuePoints.beats.map((b, i) => {
@@ -4743,9 +5012,9 @@ function OffsetScrubber({
4743
5012
  });
4744
5013
  }, [cuePoints, sampleToFraction]);
4745
5014
  const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
4746
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
4747
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
4748
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
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)(
4749
5018
  "div",
4750
5019
  {
4751
5020
  ref: trackRef,
@@ -4761,7 +5030,7 @@ function OffsetScrubber({
4761
5030
  "aria-valuenow": draftOffset,
4762
5031
  "aria-disabled": isDisabled,
4763
5032
  children: [
4764
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5033
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4765
5034
  "div",
4766
5035
  {
4767
5036
  "aria-hidden": "true",
@@ -4769,7 +5038,7 @@ function OffsetScrubber({
4769
5038
  style: { left: "50%" }
4770
5039
  }
4771
5040
  ),
4772
- ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5041
+ ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4773
5042
  "div",
4774
5043
  {
4775
5044
  "data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
@@ -4784,7 +5053,7 @@ function OffsetScrubber({
4784
5053
  },
4785
5054
  t.i
4786
5055
  )),
4787
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5056
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4788
5057
  "div",
4789
5058
  {
4790
5059
  "data-testid": "offset-scrubber-thumb",
@@ -4801,7 +5070,7 @@ function OffsetScrubber({
4801
5070
  ]
4802
5071
  }
4803
5072
  ),
4804
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5073
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4805
5074
  "span",
4806
5075
  {
4807
5076
  "data-testid": "offset-scrubber-readout",
@@ -4809,7 +5078,7 @@ function OffsetScrubber({
4809
5078
  children: formatOffset(draftOffset, sampleRate)
4810
5079
  }
4811
5080
  ),
4812
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5081
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4813
5082
  "button",
4814
5083
  {
4815
5084
  type: "button",
@@ -4821,7 +5090,7 @@ function OffsetScrubber({
4821
5090
  children: "\u2316"
4822
5091
  }
4823
5092
  ),
4824
- bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5093
+ bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4825
5094
  "span",
4826
5095
  {
4827
5096
  "data-testid": "offset-bpm-mismatch",
@@ -4893,16 +5162,16 @@ function synthesizeCuePoints({
4893
5162
  }
4894
5163
 
4895
5164
  // src/panel-core/useGeneratorPanelCore.tsx
4896
- var import_react25 = require("react");
5165
+ var import_react27 = require("react");
4897
5166
 
4898
5167
  // src/hooks/useSceneState.ts
4899
- var import_react21 = require("react");
5168
+ var import_react23 = require("react");
4900
5169
  function useSceneState(activeSceneId, initialValue) {
4901
- const [stateMap, setStateMap] = (0, import_react21.useState)(() => /* @__PURE__ */ new Map());
4902
- const activeSceneIdRef = (0, import_react21.useRef)(activeSceneId);
5170
+ const [stateMap, setStateMap] = (0, import_react23.useState)(() => /* @__PURE__ */ new Map());
5171
+ const activeSceneIdRef = (0, import_react23.useRef)(activeSceneId);
4903
5172
  activeSceneIdRef.current = activeSceneId;
4904
5173
  const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
4905
- const setForCurrentScene = (0, import_react21.useCallback)((value) => {
5174
+ const setForCurrentScene = (0, import_react23.useCallback)((value) => {
4906
5175
  const sid = activeSceneIdRef.current;
4907
5176
  if (sid === null) return;
4908
5177
  setStateMap((prev) => {
@@ -4913,7 +5182,7 @@ function useSceneState(activeSceneId, initialValue) {
4913
5182
  return newMap;
4914
5183
  });
4915
5184
  }, [initialValue]);
4916
- const setForScene = (0, import_react21.useCallback)((sceneId, value) => {
5185
+ const setForScene = (0, import_react23.useCallback)((sceneId, value) => {
4917
5186
  setStateMap((prev) => {
4918
5187
  const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
4919
5188
  const next = typeof value === "function" ? value(current) : value;
@@ -4926,10 +5195,10 @@ function useSceneState(activeSceneId, initialValue) {
4926
5195
  }
4927
5196
 
4928
5197
  // src/hooks/useAnySolo.ts
4929
- var import_react22 = require("react");
5198
+ var import_react24 = require("react");
4930
5199
  function useAnySolo(host) {
4931
- const [anySolo, setAnySolo] = (0, import_react22.useState)(false);
4932
- (0, import_react22.useEffect)(() => {
5200
+ const [anySolo, setAnySolo] = (0, import_react24.useState)(false);
5201
+ (0, import_react24.useEffect)(() => {
4933
5202
  let active = true;
4934
5203
  const refresh = () => {
4935
5204
  host.isAnySoloActive().then((v) => {
@@ -4948,7 +5217,7 @@ function useAnySolo(host) {
4948
5217
  }
4949
5218
 
4950
5219
  // src/hooks/useSoundHistory.ts
4951
- var import_react23 = require("react");
5220
+ var import_react25 = require("react");
4952
5221
  var EMPTY = { entries: [], cursor: -1 };
4953
5222
  function sameDescriptor(a, b) {
4954
5223
  if (a === b) return true;
@@ -4960,14 +5229,14 @@ function sameDescriptor(a, b) {
4960
5229
  }
4961
5230
  function useSoundHistory(applySound, opts = {}) {
4962
5231
  const max = Math.max(2, opts.max ?? 24);
4963
- const applyRef = (0, import_react23.useRef)(applySound);
5232
+ const applyRef = (0, import_react25.useRef)(applySound);
4964
5233
  applyRef.current = applySound;
4965
- const onChangeRef = (0, import_react23.useRef)(opts.onChange);
5234
+ const onChangeRef = (0, import_react25.useRef)(opts.onChange);
4966
5235
  onChangeRef.current = opts.onChange;
4967
- const dataRef = (0, import_react23.useRef)({});
4968
- const [, setVersion] = (0, import_react23.useState)(0);
4969
- const bump = (0, import_react23.useCallback)(() => setVersion((v) => v + 1), []);
4970
- const commit = (0, import_react23.useCallback)(
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)(
4971
5240
  (trackId, next, notify) => {
4972
5241
  dataRef.current = { ...dataRef.current, [trackId]: next };
4973
5242
  bump();
@@ -4975,7 +5244,7 @@ function useSoundHistory(applySound, opts = {}) {
4975
5244
  },
4976
5245
  [bump]
4977
5246
  );
4978
- const record = (0, import_react23.useCallback)(
5247
+ const record = (0, import_react25.useCallback)(
4979
5248
  (trackId, descriptor, label) => {
4980
5249
  const h = dataRef.current[trackId];
4981
5250
  const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
@@ -4990,7 +5259,7 @@ function useSoundHistory(applySound, opts = {}) {
4990
5259
  },
4991
5260
  [max, commit]
4992
5261
  );
4993
- const restoreTo = (0, import_react23.useCallback)(
5262
+ const restoreTo = (0, import_react25.useCallback)(
4994
5263
  async (trackId, index) => {
4995
5264
  const h = dataRef.current[trackId];
4996
5265
  if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
@@ -5000,7 +5269,7 @@ function useSoundHistory(applySound, opts = {}) {
5000
5269
  },
5001
5270
  [commit]
5002
5271
  );
5003
- const undo = (0, import_react23.useCallback)(
5272
+ const undo = (0, import_react25.useCallback)(
5004
5273
  (trackId) => {
5005
5274
  const h = dataRef.current[trackId];
5006
5275
  if (!h || h.cursor <= 0) return Promise.resolve(false);
@@ -5008,7 +5277,7 @@ function useSoundHistory(applySound, opts = {}) {
5008
5277
  },
5009
5278
  [restoreTo]
5010
5279
  );
5011
- const toggleFavorite = (0, import_react23.useCallback)(
5280
+ const toggleFavorite = (0, import_react25.useCallback)(
5012
5281
  (trackId, index) => {
5013
5282
  const h = dataRef.current[trackId];
5014
5283
  if (!h || index < 0 || index >= h.entries.length) return;
@@ -5017,7 +5286,7 @@ function useSoundHistory(applySound, opts = {}) {
5017
5286
  },
5018
5287
  [commit]
5019
5288
  );
5020
- const restore = (0, import_react23.useCallback)(
5289
+ const restore = (0, import_react25.useCallback)(
5021
5290
  (trackId, state) => {
5022
5291
  const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
5023
5292
  const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
@@ -5026,15 +5295,15 @@ function useSoundHistory(applySound, opts = {}) {
5026
5295
  },
5027
5296
  [commit]
5028
5297
  );
5029
- const list = (0, import_react23.useCallback)(
5298
+ const list = (0, import_react25.useCallback)(
5030
5299
  (trackId) => dataRef.current[trackId] ?? EMPTY,
5031
5300
  []
5032
5301
  );
5033
- const canUndo = (0, import_react23.useCallback)((trackId) => {
5302
+ const canUndo = (0, import_react25.useCallback)((trackId) => {
5034
5303
  const h = dataRef.current[trackId];
5035
5304
  return !!h && h.cursor > 0;
5036
5305
  }, []);
5037
- const clear = (0, import_react23.useCallback)(
5306
+ const clear = (0, import_react25.useCallback)(
5038
5307
  (trackId) => {
5039
5308
  if (dataRef.current[trackId]) {
5040
5309
  const next = { ...dataRef.current };
@@ -5046,11 +5315,11 @@ function useSoundHistory(applySound, opts = {}) {
5046
5315
  },
5047
5316
  [bump]
5048
5317
  );
5049
- const reset = (0, import_react23.useCallback)(() => {
5318
+ const reset = (0, import_react25.useCallback)(() => {
5050
5319
  dataRef.current = {};
5051
5320
  bump();
5052
5321
  }, [bump]);
5053
- return (0, import_react23.useMemo)(
5322
+ return (0, import_react25.useMemo)(
5054
5323
  () => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
5055
5324
  [record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
5056
5325
  );
@@ -5183,7 +5452,7 @@ function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
5183
5452
  }
5184
5453
 
5185
5454
  // src/panel-core/useTransitionOps.ts
5186
- var import_react24 = require("react");
5455
+ var import_react26 = require("react");
5187
5456
  function useTransitionOps({
5188
5457
  host,
5189
5458
  adapter,
@@ -5200,8 +5469,8 @@ function useTransitionOps({
5200
5469
  resolvedFades
5201
5470
  }) {
5202
5471
  const { identity } = adapter;
5203
- const appliedFadeAutomationRef = (0, import_react24.useRef)(/* @__PURE__ */ new Set());
5204
- const applyCrossfadeAutomation = (0, import_react24.useCallback)(
5472
+ const appliedFadeAutomationRef = (0, import_react26.useRef)(/* @__PURE__ */ new Set());
5473
+ const applyCrossfadeAutomation = (0, import_react26.useCallback)(
5205
5474
  async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
5206
5475
  if (host.setTrackVolumeAutomation) {
5207
5476
  const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
@@ -5218,7 +5487,7 @@ function useTransitionOps({
5218
5487
  },
5219
5488
  [host]
5220
5489
  );
5221
- const applyFadeAutomation = (0, import_react24.useCallback)(
5490
+ const applyFadeAutomation = (0, import_react26.useCallback)(
5222
5491
  async (trackId, direction, bars, bpm, sliderPos, gesture) => {
5223
5492
  if (!host.setTrackVolumeAutomation) return;
5224
5493
  const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
@@ -5227,8 +5496,8 @@ function useTransitionOps({
5227
5496
  },
5228
5497
  [host]
5229
5498
  );
5230
- const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react24.useState)(false);
5231
- const handleCreateCrossfade = (0, import_react24.useCallback)(
5499
+ const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react26.useState)(false);
5500
+ const handleCreateCrossfade = (0, import_react26.useCallback)(
5232
5501
  async (origin, target) => {
5233
5502
  const scene = activeSceneId;
5234
5503
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5356,8 +5625,8 @@ function useTransitionOps({
5356
5625
  loadTracks
5357
5626
  ]
5358
5627
  );
5359
- const [isCreatingFade, setIsCreatingFade] = (0, import_react24.useState)(false);
5360
- const handleCreateFade = (0, import_react24.useCallback)(
5628
+ const [isCreatingFade, setIsCreatingFade] = (0, import_react26.useState)(false);
5629
+ const handleCreateFade = (0, import_react26.useCallback)(
5361
5630
  async (selection, direction, gesture) => {
5362
5631
  const scene = activeSceneId;
5363
5632
  const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
@@ -5467,7 +5736,7 @@ function useTransitionOps({
5467
5736
  loadTracks
5468
5737
  ]
5469
5738
  );
5470
- const handleCrossfadeMute = (0, import_react24.useCallback)(
5739
+ const handleCrossfadeMute = (0, import_react26.useCallback)(
5471
5740
  (pair) => {
5472
5741
  const newMuted = !pair.origin.runtimeState.muted;
5473
5742
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5482,7 +5751,7 @@ function useTransitionOps({
5482
5751
  },
5483
5752
  [host, setTracks]
5484
5753
  );
5485
- const handleCrossfadeSolo = (0, import_react24.useCallback)(
5754
+ const handleCrossfadeSolo = (0, import_react26.useCallback)(
5486
5755
  (pair) => {
5487
5756
  const newSolo = !pair.origin.runtimeState.solo;
5488
5757
  for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
@@ -5497,7 +5766,7 @@ function useTransitionOps({
5497
5766
  },
5498
5767
  [host, setTracks]
5499
5768
  );
5500
- const handleCrossfadeDelete = (0, import_react24.useCallback)(
5769
+ const handleCrossfadeDelete = (0, import_react26.useCallback)(
5501
5770
  async (pair) => {
5502
5771
  try {
5503
5772
  for (const member of [pair.origin, pair.target]) {
@@ -5523,8 +5792,8 @@ function useTransitionOps({
5523
5792
  },
5524
5793
  [host, activeSceneId, setCrossfadePairsMeta, setTracks]
5525
5794
  );
5526
- const crossfadeSliderTimers = (0, import_react24.useRef)({});
5527
- const handleCrossfadeSlider = (0, import_react24.useCallback)(
5795
+ const crossfadeSliderTimers = (0, import_react26.useRef)({});
5796
+ const handleCrossfadeSlider = (0, import_react26.useCallback)(
5528
5797
  (pair, pos) => {
5529
5798
  setCrossfadePairsMeta(
5530
5799
  (prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
@@ -5557,7 +5826,7 @@ function useTransitionOps({
5557
5826
  },
5558
5827
  [host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
5559
5828
  );
5560
- const handleFadeDelete = (0, import_react24.useCallback)(
5829
+ const handleFadeDelete = (0, import_react26.useCallback)(
5561
5830
  async (fade) => {
5562
5831
  try {
5563
5832
  await host.deleteTrack(fade.track.handle.id);
@@ -5577,8 +5846,8 @@ function useTransitionOps({
5577
5846
  },
5578
5847
  [host, activeSceneId, setFadesMeta, setTracks]
5579
5848
  );
5580
- const fadeSliderTimers = (0, import_react24.useRef)({});
5581
- const handleFadeSlider = (0, import_react24.useCallback)(
5849
+ const fadeSliderTimers = (0, import_react26.useRef)({});
5850
+ const handleFadeSlider = (0, import_react26.useCallback)(
5582
5851
  (fade, pos) => {
5583
5852
  setFadesMeta(
5584
5853
  (prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
@@ -5608,8 +5877,8 @@ function useTransitionOps({
5608
5877
  },
5609
5878
  [host, activeSceneId, applyFadeAutomation, setFadesMeta]
5610
5879
  );
5611
- const lastResyncKeyRef = (0, import_react24.useRef)("");
5612
- (0, import_react24.useEffect)(() => {
5880
+ const lastResyncKeyRef = (0, import_react26.useRef)("");
5881
+ (0, import_react26.useEffect)(() => {
5613
5882
  if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
5614
5883
  return;
5615
5884
  }
@@ -5650,7 +5919,7 @@ function useTransitionOps({
5650
5919
  cancelled = true;
5651
5920
  };
5652
5921
  }, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
5653
- (0, import_react24.useEffect)(() => {
5922
+ (0, import_react26.useEffect)(() => {
5654
5923
  if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
5655
5924
  void (async () => {
5656
5925
  const mc = await host.getMusicalContext();
@@ -5684,7 +5953,7 @@ function useTransitionOps({
5684
5953
  }
5685
5954
 
5686
5955
  // src/panel-core/useGeneratorPanelCore.tsx
5687
- var import_jsx_runtime23 = require("react/jsx-runtime");
5956
+ var import_jsx_runtime24 = require("react/jsx-runtime");
5688
5957
  var EMPTY_PLACEHOLDERS = [];
5689
5958
  function useGeneratorPanelCore({
5690
5959
  ui,
@@ -5704,8 +5973,8 @@ function useGeneratorPanelCore({
5704
5973
  } = ui;
5705
5974
  const { identity, features } = adapter;
5706
5975
  const logTag = identity.logTag;
5707
- const adapterRef = (0, import_react25.useRef)(adapter);
5708
- (0, import_react25.useEffect)(() => {
5976
+ const adapterRef = (0, import_react27.useRef)(adapter);
5977
+ (0, import_react27.useEffect)(() => {
5709
5978
  if (adapterRef.current !== adapter) {
5710
5979
  adapterRef.current = adapter;
5711
5980
  console.warn(
@@ -5715,27 +5984,27 @@ function useGeneratorPanelCore({
5715
5984
  }, [adapter, logTag]);
5716
5985
  const supportsMeters = typeof host.getTrackLevels === "function";
5717
5986
  const trackLevels = useTrackLevels(host, isExpanded);
5718
- const [tracks, setTracks] = (0, import_react25.useState)([]);
5719
- const [isLoadingTracks, setIsLoadingTracks] = (0, import_react25.useState)(false);
5720
- const [importOpen, setImportOpen] = (0, import_react25.useState)(false);
5721
- const [soundImportTarget, setSoundImportTarget] = (0, import_react25.useState)(null);
5722
- const [designerView, setDesignerView] = (0, import_react25.useState)(false);
5723
- const [transitionSourceTotal, setTransitionSourceTotal] = (0, import_react25.useState)(0);
5724
- const [crossfadePairsMeta, setCrossfadePairsMeta] = (0, import_react25.useState)([]);
5725
- const [fadesMeta, setFadesMeta] = (0, import_react25.useState)([]);
5726
- const [genericGroupMetas, setGenericGroupMetas] = (0, import_react25.useState)({});
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)({});
5727
5996
  const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
5728
5997
  const [placeholders, , setPlaceholdersForScene] = useSceneState(
5729
5998
  activeSceneId,
5730
5999
  EMPTY_PLACEHOLDERS
5731
6000
  );
5732
- const saveTimeoutRefs = (0, import_react25.useRef)({});
5733
- const editLoadStartedRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
5734
- const [availableInstruments, setAvailableInstruments] = (0, import_react25.useState)([]);
5735
- const [instrumentsLoading, setInstrumentsLoading] = (0, import_react25.useState)(false);
5736
- const engineToDbIdRef = (0, import_react25.useRef)(/* @__PURE__ */ new Map());
5737
- const tracksLoadedForSceneRef = (0, import_react25.useRef)(null);
5738
- const persistSoundHistory = (0, import_react25.useCallback)(
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)(
5739
6008
  (trackId, state) => {
5740
6009
  if (!activeSceneId) return;
5741
6010
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -5755,7 +6024,7 @@ function useGeneratorPanelCore({
5755
6024
  setItems: setTracks,
5756
6025
  getId: (t) => t.handle.dbId
5757
6026
  });
5758
- const loadTracks = (0, import_react25.useCallback)(
6027
+ const loadTracks = (0, import_react27.useCallback)(
5759
6028
  async (incremental = false) => {
5760
6029
  const sceneAtStart = activeSceneId;
5761
6030
  if (!sceneAtStart) {
@@ -5880,18 +6149,18 @@ function useGeneratorPanelCore({
5880
6149
  },
5881
6150
  [host, activeSceneId, soundHistory, adapter, logTag]
5882
6151
  );
5883
- (0, import_react25.useEffect)(() => {
6152
+ (0, import_react27.useEffect)(() => {
5884
6153
  loadTracks();
5885
6154
  }, [loadTracks]);
5886
- (0, import_react25.useEffect)(() => {
6155
+ (0, import_react27.useEffect)(() => {
5887
6156
  const map = /* @__PURE__ */ new Map();
5888
6157
  for (const t of tracks) {
5889
6158
  map.set(t.handle.id, t.handle.dbId);
5890
6159
  }
5891
6160
  engineToDbIdRef.current = map;
5892
6161
  }, [tracks]);
5893
- const loadedCompletedIdsRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
5894
- (0, import_react25.useEffect)(() => {
6162
+ const loadedCompletedIdsRef = (0, import_react27.useRef)(/* @__PURE__ */ new Set());
6163
+ (0, import_react27.useEffect)(() => {
5895
6164
  if (placeholders.length === 0) {
5896
6165
  loadedCompletedIdsRef.current.clear();
5897
6166
  return;
@@ -5910,16 +6179,16 @@ function useGeneratorPanelCore({
5910
6179
  loadTracks(true);
5911
6180
  }
5912
6181
  }, [placeholders, loadTracks, logTag]);
5913
- const adoptAndLoad = (0, import_react25.useCallback)(() => {
6182
+ const adoptAndLoad = (0, import_react27.useCallback)(() => {
5914
6183
  loadTracks(true);
5915
6184
  }, [loadTracks]);
5916
- (0, import_react25.useEffect)(() => {
6185
+ (0, import_react27.useEffect)(() => {
5917
6186
  const unsub = host.onEngineReady(() => {
5918
6187
  adoptAndLoad();
5919
6188
  });
5920
6189
  return unsub;
5921
6190
  }, [host, adoptAndLoad]);
5922
- (0, import_react25.useEffect)(() => {
6191
+ (0, import_react27.useEffect)(() => {
5923
6192
  if (typeof host.onAfterAgentMutation !== "function") return;
5924
6193
  let timer = null;
5925
6194
  const unsub = host.onAfterAgentMutation(() => {
@@ -5934,13 +6203,13 @@ function useGeneratorPanelCore({
5934
6203
  if (timer) clearTimeout(timer);
5935
6204
  };
5936
6205
  }, [host, loadTracks]);
5937
- (0, import_react25.useEffect)(() => {
6206
+ (0, import_react27.useEffect)(() => {
5938
6207
  const unsub = host.onTrackStateChange((trackId, state) => {
5939
6208
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
5940
6209
  });
5941
6210
  return unsub;
5942
6211
  }, [host]);
5943
- (0, import_react25.useEffect)(() => {
6212
+ (0, import_react27.useEffect)(() => {
5944
6213
  if (!features.bulkComposePlaceholders) return;
5945
6214
  console.log(`[${logTag}] Subscribing to composeProgress`);
5946
6215
  const unsub = host.onComposeProgress((event) => {
@@ -5974,7 +6243,7 @@ function useGeneratorPanelCore({
5974
6243
  });
5975
6244
  return unsub;
5976
6245
  }, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
5977
- (0, import_react25.useEffect)(() => {
6246
+ (0, import_react27.useEffect)(() => {
5978
6247
  const refs = saveTimeoutRefs;
5979
6248
  return () => {
5980
6249
  for (const timeout of Object.values(refs.current)) {
@@ -5982,9 +6251,9 @@ function useGeneratorPanelCore({
5982
6251
  }
5983
6252
  };
5984
6253
  }, []);
5985
- const isAddingTrackRef = (0, import_react25.useRef)(false);
5986
- const [isAddingTrack, setIsAddingTrack] = (0, import_react25.useState)(false);
5987
- const handleAddTrack = (0, import_react25.useCallback)(async () => {
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 () => {
5988
6257
  if (isAddingTrackRef.current) return;
5989
6258
  if (!activeSceneId) {
5990
6259
  host.showToast("warning", "Select SCENE");
@@ -6024,7 +6293,7 @@ function useGeneratorPanelCore({
6024
6293
  setIsAddingTrack(false);
6025
6294
  }
6026
6295
  }, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
6027
- const handlePortTrack = (0, import_react25.useCallback)(
6296
+ const handlePortTrack = (0, import_react27.useCallback)(
6028
6297
  async (sel) => {
6029
6298
  if (!activeSceneId) {
6030
6299
  host.showToast("warning", "Select SCENE");
@@ -6081,7 +6350,7 @@ function useGeneratorPanelCore({
6081
6350
  },
6082
6351
  [host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
6083
6352
  );
6084
- const handleSoundImportPick = (0, import_react25.useCallback)(
6353
+ const handleSoundImportPick = (0, import_react27.useCallback)(
6085
6354
  async (sel) => {
6086
6355
  const target = soundImportTarget;
6087
6356
  if (!target || !host.getTrackSound) {
@@ -6112,8 +6381,8 @@ function useGeneratorPanelCore({
6112
6381
  },
6113
6382
  [soundImportTarget, host, adapter, identity.familyKey, soundHistory]
6114
6383
  );
6115
- const [isExportingMidi, setIsExportingMidi] = (0, import_react25.useState)(false);
6116
- const handleExportMidi = (0, import_react25.useCallback)(async () => {
6384
+ const [isExportingMidi, setIsExportingMidi] = (0, import_react27.useState)(false);
6385
+ const handleExportMidi = (0, import_react27.useCallback)(async () => {
6117
6386
  if (isExportingMidi) return;
6118
6387
  setIsExportingMidi(true);
6119
6388
  try {
@@ -6144,10 +6413,10 @@ function useGeneratorPanelCore({
6144
6413
  const xfFromId = sceneContext?.transitionFromSceneId ?? null;
6145
6414
  const xfToId = sceneContext?.transitionToSceneId ?? null;
6146
6415
  const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
6147
- (0, import_react25.useEffect)(() => {
6416
+ (0, import_react27.useEffect)(() => {
6148
6417
  if (!canCrossfade) setDesignerView(false);
6149
6418
  }, [canCrossfade]);
6150
- (0, import_react25.useEffect)(() => {
6419
+ (0, import_react27.useEffect)(() => {
6151
6420
  if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
6152
6421
  setTransitionSourceTotal(0);
6153
6422
  return;
@@ -6163,12 +6432,12 @@ function useGeneratorPanelCore({
6163
6432
  };
6164
6433
  }, [canCrossfade, xfFromId, xfToId, host]);
6165
6434
  const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
6166
- (0, import_react25.useEffect)(() => {
6435
+ (0, import_react27.useEffect)(() => {
6167
6436
  if (!onHeaderContent) return;
6168
6437
  const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
6169
6438
  onHeaderContent(
6170
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex gap-1 items-center", children: [
6171
- features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
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)(
6172
6441
  "button",
6173
6442
  {
6174
6443
  "data-testid": `import-from-scene-${identity.familyKey}-button`,
@@ -6182,7 +6451,7 @@ function useGeneratorPanelCore({
6182
6451
  children: identity.importTrackLabel ?? "Import Track"
6183
6452
  }
6184
6453
  ),
6185
- (!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6454
+ (!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
6186
6455
  "button",
6187
6456
  {
6188
6457
  "data-testid": `add-${identity.familyKey}-track-button`,
@@ -6198,7 +6467,7 @@ function useGeneratorPanelCore({
6198
6467
  children: identity.addTrackLabel ?? "Add Track"
6199
6468
  }
6200
6469
  ),
6201
- canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
6470
+ canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
6202
6471
  "button",
6203
6472
  {
6204
6473
  "data-testid": `${identity.familyKey}-view-toggle`,
@@ -6217,7 +6486,7 @@ function useGeneratorPanelCore({
6217
6486
  title: designerView ? "Back to the track list" : "Open the transition designer",
6218
6487
  className: "relative overflow-hidden px-2 py-0.5 text-[10px] font-medium rounded-sm border border-sas-accent/40 text-sas-accent transition-colors hover:border-sas-accent disabled:opacity-50",
6219
6488
  children: [
6220
- transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6489
+ transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
6221
6490
  "span",
6222
6491
  {
6223
6492
  className: "absolute inset-y-0 left-0 bg-sas-accent/25",
@@ -6225,7 +6494,7 @@ function useGeneratorPanelCore({
6225
6494
  "aria-hidden": true
6226
6495
  }
6227
6496
  ),
6228
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "relative", children: [
6497
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "relative", children: [
6229
6498
  "\u21C4 ",
6230
6499
  designerView ? "Transition" : "Tracks",
6231
6500
  transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
@@ -6256,7 +6525,7 @@ function useGeneratorPanelCore({
6256
6525
  identity,
6257
6526
  features.importTracks
6258
6527
  ]);
6259
- (0, import_react25.useEffect)(() => {
6528
+ (0, import_react27.useEffect)(() => {
6260
6529
  if (!onLoading) return;
6261
6530
  const anyGenerating = tracks.some((t) => t.isGenerating);
6262
6531
  onLoading(isLoadingTracks || anyGenerating || isBulkActive);
@@ -6264,7 +6533,7 @@ function useGeneratorPanelCore({
6264
6533
  onLoading(false);
6265
6534
  };
6266
6535
  }, [onLoading, isLoadingTracks, tracks, isBulkActive]);
6267
- const handleDeleteTrack = (0, import_react25.useCallback)(
6536
+ const handleDeleteTrack = (0, import_react27.useCallback)(
6268
6537
  async (trackId) => {
6269
6538
  try {
6270
6539
  await host.deleteTrack(trackId);
@@ -6280,7 +6549,7 @@ function useGeneratorPanelCore({
6280
6549
  },
6281
6550
  [host, activeSceneId]
6282
6551
  );
6283
- const handlePromptChange = (0, import_react25.useCallback)(
6552
+ const handlePromptChange = (0, import_react27.useCallback)(
6284
6553
  (trackId, prompt) => {
6285
6554
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
6286
6555
  const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
@@ -6296,7 +6565,7 @@ function useGeneratorPanelCore({
6296
6565
  },
6297
6566
  [host, activeSceneId]
6298
6567
  );
6299
- const resolvedGenericGroups = (0, import_react25.useMemo)(() => {
6568
+ const resolvedGenericGroups = (0, import_react27.useMemo)(() => {
6300
6569
  const out = {};
6301
6570
  for (const ext of adapter.groupExtensions ?? []) {
6302
6571
  out[ext.metaKey] = resolveTrackGroups(
@@ -6310,18 +6579,18 @@ function useGeneratorPanelCore({
6310
6579
  }
6311
6580
  return out;
6312
6581
  }, [adapter, genericGroupMetas, tracks]);
6313
- const genericGroupMemberDbIds = (0, import_react25.useMemo)(() => {
6582
+ const genericGroupMemberDbIds = (0, import_react27.useMemo)(() => {
6314
6583
  const s = /* @__PURE__ */ new Set();
6315
6584
  for (const r of Object.values(resolvedGenericGroups)) {
6316
6585
  for (const dbId of r.memberDbIds) s.add(dbId);
6317
6586
  }
6318
6587
  return s;
6319
6588
  }, [resolvedGenericGroups]);
6320
- const engineToDbId = (0, import_react25.useCallback)(
6589
+ const engineToDbId = (0, import_react27.useCallback)(
6321
6590
  (trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
6322
6591
  []
6323
6592
  );
6324
- const updateTrack = (0, import_react25.useCallback)(
6593
+ const updateTrack = (0, import_react27.useCallback)(
6325
6594
  (trackId, patch) => {
6326
6595
  setTracks(
6327
6596
  (prev) => prev.map(
@@ -6331,18 +6600,18 @@ function useGeneratorPanelCore({
6331
6600
  },
6332
6601
  []
6333
6602
  );
6334
- const markEditLoaded = (0, import_react25.useCallback)((trackId) => {
6603
+ const markEditLoaded = (0, import_react27.useCallback)((trackId) => {
6335
6604
  editLoadStartedRef.current.add(trackId);
6336
6605
  }, []);
6337
- const tracksRef = (0, import_react25.useRef)(tracks);
6338
- (0, import_react25.useEffect)(() => {
6606
+ const tracksRef = (0, import_react27.useRef)(tracks);
6607
+ (0, import_react27.useEffect)(() => {
6339
6608
  tracksRef.current = tracks;
6340
6609
  }, [tracks]);
6341
- const resolvedGenericGroupsRef = (0, import_react25.useRef)(resolvedGenericGroups);
6342
- (0, import_react25.useEffect)(() => {
6610
+ const resolvedGenericGroupsRef = (0, import_react27.useRef)(resolvedGenericGroups);
6611
+ (0, import_react27.useEffect)(() => {
6343
6612
  resolvedGenericGroupsRef.current = resolvedGenericGroups;
6344
6613
  }, [resolvedGenericGroups]);
6345
- const makeServices = (0, import_react25.useCallback)(() => {
6614
+ const makeServices = (0, import_react27.useCallback)(() => {
6346
6615
  return {
6347
6616
  host,
6348
6617
  activeSceneId,
@@ -6361,7 +6630,7 @@ function useGeneratorPanelCore({
6361
6630
  resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
6362
6631
  };
6363
6632
  }, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
6364
- const handleGenerate = (0, import_react25.useCallback)(
6633
+ const handleGenerate = (0, import_react27.useCallback)(
6365
6634
  async (trackId) => {
6366
6635
  const track = tracks.find((t) => t.handle.id === trackId);
6367
6636
  if (!track || !track.prompt.trim()) return;
@@ -6388,7 +6657,7 @@ function useGeneratorPanelCore({
6388
6657
  },
6389
6658
  [host, adapter, tracks, isAuthenticated, makeServices]
6390
6659
  );
6391
- const handleMuteToggle = (0, import_react25.useCallback)(
6660
+ const handleMuteToggle = (0, import_react27.useCallback)(
6392
6661
  (trackId) => {
6393
6662
  const track = tracks.find((t) => t.handle.id === trackId);
6394
6663
  if (!track) return;
@@ -6408,7 +6677,7 @@ function useGeneratorPanelCore({
6408
6677
  },
6409
6678
  [host, tracks]
6410
6679
  );
6411
- const handleSoloToggle = (0, import_react25.useCallback)(
6680
+ const handleSoloToggle = (0, import_react27.useCallback)(
6412
6681
  (trackId) => {
6413
6682
  const track = tracks.find((t) => t.handle.id === trackId);
6414
6683
  if (!track) return;
@@ -6428,7 +6697,7 @@ function useGeneratorPanelCore({
6428
6697
  },
6429
6698
  [host, tracks]
6430
6699
  );
6431
- const handleVolumeChange = (0, import_react25.useCallback)(
6700
+ const handleVolumeChange = (0, import_react27.useCallback)(
6432
6701
  (trackId, volume) => {
6433
6702
  setTracks(
6434
6703
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
@@ -6438,7 +6707,7 @@ function useGeneratorPanelCore({
6438
6707
  },
6439
6708
  [host]
6440
6709
  );
6441
- const handlePanChange = (0, import_react25.useCallback)(
6710
+ const handlePanChange = (0, import_react27.useCallback)(
6442
6711
  (trackId, pan) => {
6443
6712
  setTracks(
6444
6713
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
@@ -6448,7 +6717,7 @@ function useGeneratorPanelCore({
6448
6717
  },
6449
6718
  [host]
6450
6719
  );
6451
- const handleShuffle = (0, import_react25.useCallback)(
6720
+ const handleShuffle = (0, import_react27.useCallback)(
6452
6721
  async (trackId) => {
6453
6722
  const track = tracks.find((t) => t.handle.id === trackId);
6454
6723
  if (!track) return;
@@ -6490,7 +6759,7 @@ function useGeneratorPanelCore({
6490
6759
  },
6491
6760
  [host, adapter, tracks, soundHistory, logTag]
6492
6761
  );
6493
- const handleCopy = (0, import_react25.useCallback)(
6762
+ const handleCopy = (0, import_react27.useCallback)(
6494
6763
  async (trackId) => {
6495
6764
  try {
6496
6765
  const newHandle = await host.duplicateTrack(trackId);
@@ -6503,7 +6772,7 @@ function useGeneratorPanelCore({
6503
6772
  },
6504
6773
  [host, loadTracks]
6505
6774
  );
6506
- const handleFxToggle = (0, import_react25.useCallback)(
6775
+ const handleFxToggle = (0, import_react27.useCallback)(
6507
6776
  (trackId, category, enabled) => {
6508
6777
  setTracks(
6509
6778
  (prev) => prev.map(
@@ -6526,7 +6795,7 @@ function useGeneratorPanelCore({
6526
6795
  },
6527
6796
  [host]
6528
6797
  );
6529
- const handleFxPresetChange = (0, import_react25.useCallback)(
6798
+ const handleFxPresetChange = (0, import_react27.useCallback)(
6530
6799
  (trackId, category, presetIndex) => {
6531
6800
  setTracks(
6532
6801
  (prev) => prev.map(
@@ -6552,7 +6821,7 @@ function useGeneratorPanelCore({
6552
6821
  },
6553
6822
  [host]
6554
6823
  );
6555
- const handleFxDryWetChange = (0, import_react25.useCallback)(
6824
+ const handleFxDryWetChange = (0, import_react27.useCallback)(
6556
6825
  (trackId, category, value) => {
6557
6826
  setTracks(
6558
6827
  (prev) => prev.map(
@@ -6564,7 +6833,7 @@ function useGeneratorPanelCore({
6564
6833
  },
6565
6834
  [host]
6566
6835
  );
6567
- const toggleFxDrawer = (0, import_react25.useCallback)(
6836
+ const toggleFxDrawer = (0, import_react27.useCallback)(
6568
6837
  (trackId) => {
6569
6838
  setTracks(
6570
6839
  (prev) => prev.map((t) => {
@@ -6586,7 +6855,7 @@ function useGeneratorPanelCore({
6586
6855
  },
6587
6856
  [host, tracks]
6588
6857
  );
6589
- const loadEditNotes = (0, import_react25.useCallback)(
6858
+ const loadEditNotes = (0, import_react27.useCallback)(
6590
6859
  async (trackId) => {
6591
6860
  try {
6592
6861
  const mc = await host.getMusicalContext();
@@ -6604,7 +6873,7 @@ function useGeneratorPanelCore({
6604
6873
  },
6605
6874
  [host, logTag]
6606
6875
  );
6607
- const handleNotesChange = (0, import_react25.useCallback)(
6876
+ const handleNotesChange = (0, import_react27.useCallback)(
6608
6877
  (trackId, notes) => {
6609
6878
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
6610
6879
  const key = `edit:${trackId}`;
@@ -6634,7 +6903,7 @@ function useGeneratorPanelCore({
6634
6903
  },
6635
6904
  [host]
6636
6905
  );
6637
- const handleTabChange = (0, import_react25.useCallback)(
6906
+ const handleTabChange = (0, import_react27.useCallback)(
6638
6907
  (trackId, tab) => {
6639
6908
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
6640
6909
  if (tab === "fx") {
@@ -6659,10 +6928,10 @@ function useGeneratorPanelCore({
6659
6928
  },
6660
6929
  [host, availableInstruments.length, instrumentsLoading, loadEditNotes]
6661
6930
  );
6662
- const handleProgressChange = (0, import_react25.useCallback)((trackId, pct) => {
6931
+ const handleProgressChange = (0, import_react27.useCallback)((trackId, pct) => {
6663
6932
  setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
6664
6933
  }, []);
6665
- const handleToggleDrawer = (0, import_react25.useCallback)((trackId) => {
6934
+ const handleToggleDrawer = (0, import_react27.useCallback)((trackId) => {
6666
6935
  setTracks(
6667
6936
  (prev) => prev.map((t) => {
6668
6937
  if (t.handle.id !== trackId) return t;
@@ -6671,7 +6940,7 @@ function useGeneratorPanelCore({
6671
6940
  })
6672
6941
  );
6673
6942
  }, []);
6674
- const handleInstrumentSelect = (0, import_react25.useCallback)(
6943
+ const handleInstrumentSelect = (0, import_react27.useCallback)(
6675
6944
  async (trackId, pluginId) => {
6676
6945
  const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
6677
6946
  if (isDefaultInstrument) {
@@ -6724,7 +6993,7 @@ function useGeneratorPanelCore({
6724
6993
  },
6725
6994
  [host, identity.defaultInstrumentPluginId, logTag]
6726
6995
  );
6727
- const handleShowEditor = (0, import_react25.useCallback)(
6996
+ const handleShowEditor = (0, import_react27.useCallback)(
6728
6997
  async (trackId) => {
6729
6998
  try {
6730
6999
  await host.showInstrumentEditor(trackId);
@@ -6735,12 +7004,12 @@ function useGeneratorPanelCore({
6735
7004
  },
6736
7005
  [host]
6737
7006
  );
6738
- const handleBackToInstruments = (0, import_react25.useCallback)((trackId) => {
7007
+ const handleBackToInstruments = (0, import_react27.useCallback)((trackId) => {
6739
7008
  setTracks(
6740
7009
  (prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
6741
7010
  );
6742
7011
  }, []);
6743
- const handleRefreshInstruments = (0, import_react25.useCallback)(() => {
7012
+ const handleRefreshInstruments = (0, import_react27.useCallback)(() => {
6744
7013
  setInstrumentsLoading(true);
6745
7014
  host.getAvailableInstruments().then((instruments) => {
6746
7015
  setAvailableInstruments(instruments);
@@ -6749,13 +7018,13 @@ function useGeneratorPanelCore({
6749
7018
  setInstrumentsLoading(false);
6750
7019
  });
6751
7020
  }, [host]);
6752
- const onAuditionNote = (0, import_react25.useCallback)(
7021
+ const onAuditionNote = (0, import_react27.useCallback)(
6753
7022
  (trackId, pitch, velocity, ms) => {
6754
7023
  void host.auditionNote(trackId, pitch, velocity, ms);
6755
7024
  },
6756
7025
  [host]
6757
7026
  );
6758
- const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react25.useMemo)(() => {
7027
+ const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react27.useMemo)(() => {
6759
7028
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
6760
7029
  const pairs = [];
6761
7030
  const members = /* @__PURE__ */ new Set();
@@ -6770,7 +7039,7 @@ function useGeneratorPanelCore({
6770
7039
  }
6771
7040
  return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
6772
7041
  }, [tracks, crossfadePairsMeta]);
6773
- const { resolvedFades, fadeMemberDbIds } = (0, import_react25.useMemo)(() => {
7042
+ const { resolvedFades, fadeMemberDbIds } = (0, import_react27.useMemo)(() => {
6774
7043
  const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
6775
7044
  const list = [];
6776
7045
  const members = /* @__PURE__ */ new Set();
@@ -6798,7 +7067,7 @@ function useGeneratorPanelCore({
6798
7067
  resolvedCrossfadePairs,
6799
7068
  resolvedFades
6800
7069
  });
6801
- const setGroupMute = (0, import_react25.useCallback)(
7070
+ const setGroupMute = (0, import_react27.useCallback)(
6802
7071
  (trackIds, muted) => {
6803
7072
  for (const id of trackIds) {
6804
7073
  setTracks(
@@ -6810,7 +7079,7 @@ function useGeneratorPanelCore({
6810
7079
  },
6811
7080
  [host]
6812
7081
  );
6813
- const setGroupSolo = (0, import_react25.useCallback)(
7082
+ const setGroupSolo = (0, import_react27.useCallback)(
6814
7083
  (trackIds, solo) => {
6815
7084
  for (const id of trackIds) {
6816
7085
  setTracks(
@@ -6822,7 +7091,7 @@ function useGeneratorPanelCore({
6822
7091
  },
6823
7092
  [host]
6824
7093
  );
6825
- const deleteGroup = (0, import_react25.useCallback)(
7094
+ const deleteGroup = (0, import_react27.useCallback)(
6826
7095
  async (members, cleanupKeySuffixes) => {
6827
7096
  for (const member of members) {
6828
7097
  try {
@@ -6842,7 +7111,7 @@ function useGeneratorPanelCore({
6842
7111
  },
6843
7112
  [host, activeSceneId, loadTracks]
6844
7113
  );
6845
- const handlers = (0, import_react25.useMemo)(
7114
+ const handlers = (0, import_react27.useMemo)(
6846
7115
  () => ({
6847
7116
  promptChange: handlePromptChange,
6848
7117
  generate: (trackId) => {
@@ -6956,8 +7225,8 @@ function useGeneratorPanelCore({
6956
7225
  }
6957
7226
 
6958
7227
  // src/panel-core/GeneratorPanelShell.tsx
6959
- var import_react26 = __toESM(require("react"));
6960
- var import_jsx_runtime24 = require("react/jsx-runtime");
7228
+ var import_react28 = __toESM(require("react"));
7229
+ var import_jsx_runtime25 = require("react/jsx-runtime");
6961
7230
  function GeneratorPanelShell({ core, slots }) {
6962
7231
  const {
6963
7232
  ui,
@@ -7010,8 +7279,9 @@ function GeneratorPanelShell({ core, slots }) {
7010
7279
  deleteGroup
7011
7280
  } = core;
7012
7281
  const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
7282
+ const panelBus = usePanelBus(host, activeSceneId);
7013
7283
  const { identity, features } = adapter;
7014
- const buildRowProps = (0, import_react26.useCallback)(
7284
+ const buildRowProps = (0, import_react28.useCallback)(
7015
7285
  (track, drag) => {
7016
7286
  const id = track.handle.id;
7017
7287
  const pickerProps = features.instrumentPicker ? {
@@ -7110,12 +7380,12 @@ function GeneratorPanelShell({ core, slots }) {
7110
7380
  ]
7111
7381
  );
7112
7382
  if (!activeSceneId) {
7113
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7383
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7114
7384
  "div",
7115
7385
  {
7116
7386
  "data-testid": `no-scene-placeholder-${identity.familyKey}`,
7117
7387
  className: "flex items-center justify-center py-8",
7118
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7388
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7119
7389
  "button",
7120
7390
  {
7121
7391
  onClick: () => onSelectScene?.(),
@@ -7127,12 +7397,12 @@ function GeneratorPanelShell({ core, slots }) {
7127
7397
  );
7128
7398
  }
7129
7399
  if (!sceneContext?.hasContract) {
7130
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7400
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7131
7401
  "div",
7132
7402
  {
7133
7403
  "data-testid": `no-contract-placeholder-${identity.familyKey}`,
7134
7404
  className: "flex items-center justify-center py-8",
7135
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7405
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7136
7406
  "button",
7137
7407
  {
7138
7408
  onClick: () => onOpenContract?.(),
@@ -7144,7 +7414,7 @@ function GeneratorPanelShell({ core, slots }) {
7144
7414
  );
7145
7415
  }
7146
7416
  if (features.bulkComposePlaceholders && isComposing) {
7147
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
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" }) });
7148
7418
  }
7149
7419
  const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
7150
7420
  if (activePlaceholders.length > 0) {
@@ -7155,18 +7425,18 @@ function GeneratorPanelShell({ core, slots }) {
7155
7425
  tracksByDbId.set(t.handle.id, t);
7156
7426
  }
7157
7427
  }
7158
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
7428
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
7159
7429
  const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
7160
7430
  if (loadedTrack) {
7161
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7431
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
7162
7432
  }
7163
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7433
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7164
7434
  "div",
7165
7435
  {
7166
7436
  "data-testid": "bulk-placeholder-track",
7167
7437
  className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
7168
7438
  style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
7169
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
7439
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
7170
7440
  },
7171
7441
  ph.id
7172
7442
  );
@@ -7178,13 +7448,13 @@ function GeneratorPanelShell({ core, slots }) {
7178
7448
  supportsMeters,
7179
7449
  levels: supportsMeters ? trackLevels : void 0,
7180
7450
  handlers,
7181
- renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
7451
+ renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
7182
7452
  setGroupMute,
7183
7453
  setGroupSolo,
7184
7454
  deleteGroup
7185
7455
  };
7186
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
7187
- features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
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)(
7188
7458
  ImportTrackModal,
7189
7459
  {
7190
7460
  host,
@@ -7197,7 +7467,7 @@ function GeneratorPanelShell({ core, slots }) {
7197
7467
  testIdPrefix: `${identity.familyKey}-import`
7198
7468
  }
7199
7469
  ),
7200
- features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7470
+ features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7201
7471
  ImportTrackModal,
7202
7472
  {
7203
7473
  host,
@@ -7212,7 +7482,7 @@ function GeneratorPanelShell({ core, slots }) {
7212
7482
  }
7213
7483
  ),
7214
7484
  slots?.modals,
7215
- canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7485
+ canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7216
7486
  TransitionDesigner,
7217
7487
  {
7218
7488
  host,
@@ -7229,9 +7499,28 @@ function GeneratorPanelShell({ core, slots }) {
7229
7499
  testIdPrefix: `${identity.familyKey}-transition-designer`
7230
7500
  }
7231
7501
  ) }),
7232
- !(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
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)(
7504
+ PanelMasterStrip,
7505
+ {
7506
+ bus: panelBus.bus,
7507
+ availableFx: panelBus.availableFx,
7508
+ fxLoading: panelBus.fxLoading,
7509
+ soloedOut: anySolo && !panelBus.bus.soloed,
7510
+ fxPickerOpen: panelBus.fxPickerOpen,
7511
+ onToggleFxPicker: panelBus.setFxPickerOpen,
7512
+ onRefreshFx: panelBus.refreshFx,
7513
+ onVolumeChange: panelBus.onVolumeChange,
7514
+ onMuteToggle: panelBus.onMuteToggle,
7515
+ onSoloToggle: panelBus.onSoloToggle,
7516
+ onAddFx: panelBus.onAddFx,
7517
+ onRemoveFx: panelBus.onRemoveFx,
7518
+ onToggleFxEnabled: panelBus.onToggleFxEnabled,
7519
+ onShowFxEditor: panelBus.onShowFxEditor
7520
+ }
7521
+ ),
7233
7522
  slots?.beforeRows,
7234
- resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7523
+ resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7235
7524
  CrossfadeTrackRow,
7236
7525
  {
7237
7526
  accentColor: identity.transitionAccentColor,
@@ -7268,7 +7557,7 @@ function GeneratorPanelShell({ core, slots }) {
7268
7557
  },
7269
7558
  pair.groupId
7270
7559
  )),
7271
- resolvedFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7560
+ resolvedFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7272
7561
  FadeTrackRow,
7273
7562
  {
7274
7563
  accentColor: identity.transitionAccentColor,
@@ -7294,20 +7583,20 @@ function GeneratorPanelShell({ core, slots }) {
7294
7583
  fade.dbId
7295
7584
  )),
7296
7585
  (adapter.groupExtensions ?? []).flatMap(
7297
- (ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react26.default.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
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}`))
7298
7587
  ),
7299
7588
  tracks.map((track, index) => {
7300
7589
  if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
7301
7590
  return null;
7302
7591
  }
7303
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
7592
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
7304
7593
  }),
7305
7594
  slots?.afterRows
7306
7595
  ] })),
7307
7596
  features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
7308
7597
  const hasAnyMidi = tracks.some((t) => t.hasMidi);
7309
7598
  const exportDisabled = isExportingMidi || !hasAnyMidi;
7310
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7599
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7311
7600
  "button",
7312
7601
  {
7313
7602
  "data-testid": "export-midi-tracks-button",
@@ -7375,7 +7664,7 @@ function createSurgeSoundAdapter(host, overrides = {}) {
7375
7664
  }
7376
7665
 
7377
7666
  // src/constants/sdk-version.ts
7378
- var PLUGIN_SDK_VERSION = "2.35.0";
7667
+ var PLUGIN_SDK_VERSION = "2.37.0";
7379
7668
 
7380
7669
  // src/utils/format-concurrent-tracks.ts
7381
7670
  function formatConcurrentTracks(ctx) {
@@ -7551,6 +7840,7 @@ function pickTopKWeighted(scored, options = {}) {
7551
7840
  PLUGIN_SDK_VERSION,
7552
7841
  PX_PER_BEAT,
7553
7842
  PanSlider,
7843
+ PanelMasterStrip,
7554
7844
  PianoRollEditor,
7555
7845
  PluginError,
7556
7846
  RESIZE_HANDLE_PX,
@@ -7615,6 +7905,7 @@ function pickTopKWeighted(scored, options = {}) {
7615
7905
  transposeNotes,
7616
7906
  useAnySolo,
7617
7907
  useGeneratorPanelCore,
7908
+ usePanelBus,
7618
7909
  useSceneState,
7619
7910
  useSoundHistory,
7620
7911
  useTrackLevel,