@signalsandsorcery/plugin-sdk 2.34.1 → 2.35.1

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.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types/plugin-sdk.types.ts","../src/types/fx-toggle.types.ts","../src/components/TrackRow.tsx","../src/components/TrackDrawer.tsx","../src/constants/fx-presets.ts","../src/components/FxToggleBar.tsx","../src/components/PianoRollEditor.tsx","../src/components/ConfirmDialog.tsx","../src/components/Modal.tsx","../src/components/LevelMeter.tsx","../src/hooks/useTrackLevels.ts","../src/components/TrackMeterStrip.tsx","../src/components/VolumeSlider.tsx","../src/utils/volume-conversion.ts","../src/components/PanSlider.tsx","../src/components/SorceryProgressBar.tsx","../src/components/CrossfadeTrackRow.tsx","../src/crossfade-meta.ts","../src/crossfade-inpaint.ts","../src/fade-meta.ts","../src/components/FadeTrackRow.tsx","../src/components/FadeModal.tsx","../src/components/ImportTrackModal.tsx","../src/components/CrossfadeModal.tsx","../src/components/TransitionDesigner.tsx","../src/hooks/useTrackReorder.ts","../src/transition-designer-meta.ts","../src/components/DownloadPackButton.tsx","../src/components/SamplePackCTACard.tsx","../src/components/WaveformView.tsx","../src/components/waveform.ts","../src/components/ScrollingWaveform.tsx","../src/components/OffsetScrubber.tsx","../src/components/wavPeakAnalyzer.ts","../src/components/synthesizeCuePoints.ts","../src/hooks/useSceneState.ts","../src/hooks/useAnySolo.ts","../src/hooks/useSoundHistory.ts","../src/constants/sdk-version.ts","../src/utils/format-concurrent-tracks.ts","../src/utils/semantic-match.ts"],"sourcesContent":["/**\n * @sas/plugin-sdk — Public API\n *\n * Everything an external plugin author needs to build a generator plugin\n * for Signals & Sorcery.\n */\n\n// ============================================================================\n// Types — Core plugin contract\n// ============================================================================\n\nexport type {\n GeneratorType,\n InstrumentDescriptor,\n GeneratorPlugin,\n PluginUIProps,\n PluginHost,\n ExportedPluginData,\n CreateTrackOptions,\n PluginTrackHandle,\n PluginTrackInfo,\n ImportCandidateTrack,\n ImportCandidateScene,\n SceneFamilyTrack,\n TrackSoundSnapshot,\n ListImportableTracksOptions,\n PluginSynthInfo,\n PluginTrackRuntimeState,\n TrackStateChangeListener,\n PluginFxCategoryDetailState,\n PluginTrackFxDetailState,\n MidiClipData,\n PluginMidiNote,\n MidiWriteResult,\n ReadMidiClip,\n ReadMidiResult,\n ExportMidiBundleOptions,\n ExportMidiBundleResult,\n PostProcessOptions,\n MusicalContext,\n PluginChordTiming,\n PluginGenerationContext,\n PluginConcurrentTrackInfo,\n PluginChordSegment,\n TransportEvent,\n DeckBoundaryEvent,\n PluginTransportState,\n PluginTrackLevel,\n PluginSceneInfo,\n PluginSceneContext,\n BulkAddPlaceholderTrack,\n TransportEventListener,\n DeckBoundaryListener,\n SceneChangeListener,\n UnsubscribeFn,\n LLMGenerationRequest,\n LLMGenerationResult,\n // Tool-use LLM types — agentic plugins (chat panel, etc.) use these via\n // `host.generateWithLLMTools` to drive a Claude-Code-style loop. SDK 2.4.0+.\n LLMPart,\n LLMContent,\n LLMFunctionDeclaration,\n LLMTool,\n LLMGenerationConfig,\n LLMSystemInstruction,\n LLMToolUseRequest,\n LLMUsageMetadata,\n LLMCandidate,\n LLMToolUseResponse,\n PluginPresetData,\n ShufflePresetResult,\n SoundHistoryEntry,\n PluginSettingsSchema,\n SettingDefinition,\n PluginSettingsStore,\n // AI skill surface — lets plugins declare LLM-callable actions\n // registered as namespaced tools (plugin:<id>:<skill>). Required for\n // plugins that expose a `chat` or similar agent-delegation skill.\n PluginSkill,\n PluginSkillInputSchema,\n PluginErrorCode,\n PluginManifest,\n PluginCapabilities,\n PluginFileDialogOptions,\n PluginDownloadOptions,\n PluginHttpRequestOptions,\n PluginHttpResponse,\n PluginSampleFilter,\n PluginSampleInfo,\n PluginSampleImportResult,\n PluginSampleTrackInfo,\n PluginAudioTextureRequest,\n PluginAudioTextureResult,\n PluginCuePoints,\n PluginTrimWindow,\n ComposeSceneOptions,\n ComposeSceneResult,\n ComposeProgressListener,\n ComposeProgressEvent,\n PluginPresetInfo,\n SavePluginPresetOptions,\n PluginAppTool,\n PluginAppToolInputSchema,\n PluginAppToolResult,\n PluginStatus,\n PluginRegistration,\n StemType,\n PluginStemSplitResult,\n PluginStemTrackInfo,\n // Audio recording (since SDK 2.1.0)\n AudioInputDevice,\n RecordingTargetInfo,\n RecordingChunkFinalizedEvent,\n // Drum sampler (since SDK 1.2.0)\n DrumKit,\n // Pitched instrument sampler (since SDK 1.3.0)\n InstrumentZone,\n InstrumentSampler,\n ListAudioFilesOptions,\n} from './types/plugin-sdk.types';\n\nexport { PluginError } from './types/plugin-sdk.types';\n\n// ============================================================================\n// Types — FX toggle system\n// ============================================================================\n\nexport type {\n FxCategory,\n FxPreset,\n MixInterpolation,\n FxPresetConfig,\n FxCategoryDetailState,\n TrackFxDetailState,\n TrackFxState,\n FxPresetDataEntry,\n FxPresetData,\n} from './types/fx-toggle.types';\n\nexport {\n FX_CATEGORIES,\n FX_CHAIN_ORDER,\n FX_ENGINE_PLUGIN_NAMES,\n FX_DISPLAY_LABELS,\n EMPTY_FX_STATE,\n DEFAULT_FX_DRY_WET,\n DEFAULT_FX_CATEGORY_DETAIL,\n EMPTY_FX_DETAIL_STATE,\n} from './types/fx-toggle.types';\n\n// ============================================================================\n// Components\n// ============================================================================\n\nexport { TrackRow, type SDKTrackRowProps } from './components/TrackRow';\nexport {\n CrossfadeTrackRow,\n type CrossfadeTrackRowProps,\n type CrossfadeLayer,\n} from './components/CrossfadeTrackRow';\nexport {\n EQUAL_POWER_GAIN,\n parseCrossfadePairs,\n asCrossfadeMeta,\n soundIdentity,\n hashString,\n buildCrossfadeVolumeCurves,\n type CrossfadeSlot,\n type CrossfadeMeta,\n type CrossfadePairMeta,\n type VolumeAutomationPoint,\n type CrossfadeVolumeCurves,\n} from './crossfade-meta';\nexport { buildCrossfadeInpaintPrompt, type CrossfadeInpaintInput } from './crossfade-inpaint';\nexport {\n parseFades,\n asFadeMeta,\n buildFadeVolumeCurve,\n defaultFadeGesture,\n TEXTURAL_ROLES,\n type FadeDirection,\n type FadeGesture,\n type FadeMeta,\n type FadeEntry,\n} from './fade-meta';\nexport { FadeTrackRow, type FadeTrackRowProps, type FadeLayer } from './components/FadeTrackRow';\nexport { FadeModal, type FadeModalProps, type FadeSelection } from './components/FadeModal';\nexport { ImportTrackModal, type ImportTrackModalProps } from './components/ImportTrackModal';\nexport {\n CrossfadeModal,\n type CrossfadeModalProps,\n type CrossfadeSelection,\n} from './components/CrossfadeModal';\n// Transition Designer — the multi-row, persistent successor to CrossfadeModal +\n// FadeModal: a per-panel staging board that lays out every A→B pairing at once.\n// Reuses the panel's existing create/delete orchestration. Since 2.29.0.\nexport {\n TransitionDesigner,\n type TransitionDesignerProps,\n} from './components/TransitionDesigner';\nexport {\n TRANSITION_DESIGNER_DRAFT_KEY,\n rowType,\n asTransitionDesignerDraft,\n reconcileSlots,\n buildRowSlots,\n normalizeSlots,\n padSlots,\n padPair,\n slotsEqual,\n rowKey,\n dbIdsFromKeys,\n AUDIO_EFFECTS,\n AUDIO_EFFECT_LABEL,\n asAudioEffect,\n type TransitionDesignerDraft,\n type TransitionRowType,\n type DesignerRowSlots,\n type AudioEffect,\n} from './transition-designer-meta';\nexport { ConfirmDialog, type ConfirmDialogProps } from './components/ConfirmDialog';\nexport { Modal, type ModalProps } from './components/Modal';\nexport {\n TrackDrawer,\n type TrackDrawerProps,\n type DrawerTab,\n // Backwards-compatible aliases — the drawer was `InstrumentDrawer` before it\n // grew an FX tab + Import tab and became the unified per-track drawer.\n InstrumentDrawer,\n type TrackDrawerProps as InstrumentDrawerProps,\n} from './components/TrackDrawer';\nexport {\n PianoRollEditor,\n type PianoRollEditorProps,\n PX_PER_BEAT,\n ROW_HEIGHT,\n GUTTER_W,\n DRAG_DEAD_ZONE,\n RESIZE_HANDLE_PX,\n pxToCell,\n cellToPx,\n resizeNoteDuration,\n centerScrollTop,\n transposeNotes,\n pitchToName,\n} from './components/PianoRollEditor';\nexport { VolumeSlider } from './components/VolumeSlider';\nexport { PanSlider } from './components/PanSlider';\nexport { FxToggleBar, type FxToggleBarProps } from './components/FxToggleBar';\nexport { SorceryProgressBar, calculateTimeBasedTarget } from './components/SorceryProgressBar';\nexport { DownloadPackButton, type DownloadPackButtonProps, type DownloadPackButtonVariant } from './components/DownloadPackButton';\nexport {\n SamplePackCTACard,\n type SamplePackCTACardProps,\n type SamplePackCTACardStatus,\n type SamplePackCardInfo,\n} from './components/SamplePackCTACard';\n\n// Waveform / audio-clip UI toolkit — shared by audio-oriented plugins (stems,\n// recorder). Promoted from the app's src/plugins/shared (W9 — so extracted\n// plugins reach it through the SDK, not a relative app path). Since 2.10.0.\nexport { WaveformView, type WaveformViewProps } from './components/WaveformView';\nexport { LevelMeter, type LevelMeterProps } from './components/LevelMeter';\nexport { TrackMeterStrip, type TrackMeterStripProps } from './components/TrackMeterStrip';\nexport { ScrollingWaveform, type ScrollingWaveformProps } from './components/ScrollingWaveform';\nexport { OffsetScrubber, type OffsetScrubberProps } from './components/OffsetScrubber';\nexport { computePeaks, drawWaveform, type WaveformPeaks } from './components/waveform';\nexport { analyzeWavPeak, type PeakAnalysis } from './components/wavPeakAnalyzer';\nexport { synthesizeCuePoints, type SynthesizeCuePointsOptions } from './components/synthesizeCuePoints';\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\nexport { useSceneState } from './hooks/useSceneState';\nexport { useAnySolo } from './hooks/useAnySolo';\nexport {\n useSoundHistory,\n type UseSoundHistoryResult,\n type UseSoundHistoryOptions,\n type TrackSoundHistory,\n} from './hooks/useSoundHistory';\nexport {\n useTrackReorder,\n moveItem,\n type UseTrackReorderOptions,\n type UseTrackReorderResult,\n type TrackRowDragProps,\n} from './hooks/useTrackReorder';\nexport {\n useTrackLevels,\n useTrackLevel,\n useTrackMeter,\n useTransportPlaying,\n type TrackLevelsHandle,\n type TrackMeterView,\n} from './hooks/useTrackLevels';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n// VALID_INSTRUMENT_ROLES (SDK 1.x) removed in 2.0.0 — external plugins now\n// call `host.getValidRoles()` on PluginHost at runtime. The canonical list\n// lives in the assistant (src/music-engine/constants/instrument-classification.ts)\n// and is exposed via that accessor.\nexport { PLUGIN_SDK_VERSION } from './constants/sdk-version';\nexport { FX_PRESET_CONFIGS } from './constants/fx-presets';\n\n// ============================================================================\n// Utils\n// ============================================================================\n\nexport { sliderToDb, dbToSlider, SLIDER_UNITY, DB_MAX, DB_MIN } from './utils/volume-conversion';\nexport { formatConcurrentTracks } from './utils/format-concurrent-tracks';\n\n// Semantic sample matching — pick the closest sample to a text intent by\n// scoring against each sample's StableAudio prompt, with variety-preserving\n// top-k weighted selection. Shared by the drum + instrument resolvers. Since 2.11.0.\nexport {\n tokenizePrompt,\n scorePromptMatch,\n pickTopKWeighted,\n type ScoredCandidate,\n type PickTopKOptions,\n} from './utils/semantic-match';\n","/**\n * Plugin SDK Type Definitions\n *\n * Complete type system for the generator plugin architecture.\n * Plugins implement GeneratorPlugin and interact with the host via PluginHost.\n * All plugin output flows through TracktionEngine (MIDI or audio clips).\n */\n\nimport type { ComponentType, ReactNode } from 'react';\n\n// ============================================================================\n// Core Plugin Interface\n// ============================================================================\n\n/** What kind of Tracktion content this plugin creates */\nexport type GeneratorType = 'midi' | 'audio' | 'sample' | 'hybrid';\n\n/**\n * Drum-kit configuration for `host.setTrackDrumKit`. Prototype shape carries\n * a single sample; future multi-slot kits will extend this with a `notes`\n * map (`Record<midiNote, samplePath>`) for GM-style drum maps.\n */\nexport interface DrumKit {\n /** Absolute path to the sample (WAV, AIFF, FLAC). Triggered on every note-on. */\n samplePath: string;\n}\n\n/**\n * One key-mapped sample zone in a pitched, polyphonic instrument.\n * Used by `host.setTrackInstrumentSampler`.\n *\n * Zones in an InstrumentSampler MUST be disjoint and ordered low to\n * high by rootKey — the engine rejects overlap because Tracktion would\n * otherwise double-trigger every matching sound on each note-on.\n */\nexport interface InstrumentZone {\n /** Absolute path to the zone's sample (WAV, FLAC, AIFF). */\n samplePath: string;\n /** MIDI note this sample sounds at unshifted (0-127). */\n rootKey: number;\n /** Inclusive low end of the key range that triggers this zone (0-127). */\n minKey: number;\n /** Inclusive high end of the key range that triggers this zone (0-127). */\n maxKey: number;\n /**\n * If true, the sampler plays the sample for the duration the note is\n * held and stops on note-off (good for sustaining pads, organs, etc.,\n * whose source has been pre-trimmed to a steady-state region).\n * If false, the sampler plays the sample through to its end ignoring\n * note-off (good for plucks, mallets, percussion).\n */\n openEnded: boolean;\n}\n\n/**\n * Pitched instrument configuration for `host.setTrackInstrumentSampler`.\n * Parallel to `DrumKit` but multi-zone and pitch-aware. A manifest\n * authored by the pitched-sample pipeline reduces to one of these.\n *\n * NOTE: This is distinct from `host.setTrackInstrument(trackId, pluginId)`\n * which loads a VST3/AU synth plugin. `setTrackInstrumentSampler` loads\n * the built-in Tracktion sampler with N pre-rendered zones.\n */\nexport interface InstrumentSampler {\n /** Display name (e.g. \"Bright Warm Pluck\"). Used for diagnostics. */\n name: string;\n /** Disjoint zones, ordered low->high by rootKey. At least one required. */\n zones: ReadonlyArray<InstrumentZone>;\n}\n\n/** Options for `host.listAudioFiles`. */\nexport interface ListAudioFilesOptions {\n /**\n * File extensions to include (dot-prefixed, lowercase). Defaults to\n * `['.wav']`. Other audio formats (`.aif`, `.flac`, `.mp3`) are passed\n * through verbatim; the host does not transcode.\n */\n extensions?: string[];\n /** Walk subdirectories. Defaults to `false`. */\n recursive?: boolean;\n}\n\n/** Describes an available instrument plugin (VST3/AU synth) on the system. */\nexport interface InstrumentDescriptor {\n /** Stable plugin identifier for loading (VST3 TUID or AU component ID) */\n pluginId: string;\n /** Display name */\n name: string;\n /** Plugin manufacturer */\n manufacturer: string;\n /** Plugin format */\n type: 'vst3' | 'au' | 'vst' | 'internal';\n /** Plugin category (from scan) */\n category: string;\n /** Whether this plugin is currently installed/available */\n missing?: boolean;\n}\n\n/** Every generator plugin must implement this interface. */\nexport interface GeneratorPlugin {\n /** Unique ID, npm-style scope: '@sas/synth-generator', '@user/my-plugin' */\n readonly id: string;\n /** Human-readable name shown in accordion header */\n readonly displayName: string;\n /** Semver version string */\n readonly version: string;\n /** Short description for settings/marketplace */\n readonly description: string;\n /** 24x24 icon — data URL, relative path from plugin dir, or undefined */\n readonly icon?: string;\n /** What kind of Tracktion content this plugin creates */\n readonly generatorType: GeneratorType;\n /** Minimum host SDK version this plugin requires */\n readonly minHostVersion?: string;\n\n /**\n * Called once when plugin is loaded. Receives the PluginHost API.\n * If this throws, plugin is marked as failed and not rendered.\n */\n activate(host: PluginHost): Promise<void>;\n\n /**\n * Called when plugin is being unloaded (disable, uninstall, app quit).\n * Must complete within 5 seconds or host force-kills.\n */\n deactivate(): Promise<void>;\n\n /**\n * Return the React component rendered inside the accordion section.\n * Component receives PluginUIProps from the host.\n */\n getUIComponent(): ComponentType<PluginUIProps>;\n\n /**\n * Return JSON Schema for plugin-specific settings.\n * Host auto-renders a settings form. Return null if no settings.\n */\n getSettingsSchema(): PluginSettingsSchema | null;\n\n /**\n * Optional: Called when the active scene changes.\n */\n onSceneChanged?(sceneId: string | null): Promise<void>;\n\n /**\n * Optional: Called when the generation context changes\n * (chords updated, tracks added/removed, BPM changed).\n */\n onContextChanged?(context: MusicalContext): void;\n\n /**\n * Optional: Declare LLM-callable skills this plugin provides.\n * Skills are registered as namespaced tools (plugin:<pluginId>:<skillId>)\n * and become available to AI agents for orchestration.\n *\n * Example: the chat-panel plugin declares a `chat` skill so external\n * agents (Claude Code, OpenClaw) can delegate scene-scoped natural\n * language work to the in-app agent via a single call.\n */\n getSkills?(): PluginSkill[];\n}\n\n// ============================================================================\n// Plugin Skills (AI Harness)\n// ============================================================================\n\n/** An LLM-callable action declared by a plugin. */\nexport interface PluginSkill {\n /** Unique skill id within this plugin (e.g., 'chat', 'generate_bassline') */\n id: string;\n /** Human-readable description — drives LLM tool selection */\n description: string;\n /** JSON Schema for the skill's input parameters */\n inputSchema: PluginSkillInputSchema;\n /** Whether this skill only reads state (no mutations). Default: false */\n isReadOnly?: boolean;\n}\n\n/** JSON Schema shape for skill input parameters. */\nexport interface PluginSkillInputSchema {\n type: 'object';\n properties?: Record<string, unknown>;\n required?: string[];\n}\n\n// ============================================================================\n// Plugin UI Props\n// ============================================================================\n\n/** Props passed to every plugin's React component by the host */\nexport interface PluginUIProps {\n /** The scoped PluginHost API instance for this plugin */\n host: PluginHost;\n /** Currently active scene ID (null if none selected) */\n activeSceneId: string | null;\n /** Whether the user is authenticated (for LLM access) */\n isAuthenticated: boolean;\n /** Whether all systems are connected (engine, gateway) */\n isConnected: boolean;\n /** Which workstation deck this is rendered in */\n deckId?: 'left' | 'right';\n /** Plugin calls this to set/clear header buttons. Pass null to clear. */\n onHeaderContent?: (content: ReactNode | null) => void;\n /** Plugin calls this to show/hide the loading spinner in the header. */\n onLoading?: (loading: boolean) => void;\n /** Scene-level context: contract state, chords, BPM, etc. Null if no scene. */\n sceneContext?: PluginSceneContext | null;\n /** Callback to open the scene selector (Scenes accordion section). */\n onSelectScene?: (() => void) | null;\n /** Callback to open the contract/chords section (for \"Generate a Contract\" CTA). */\n onOpenContract?: (() => void) | null;\n /** Callback to expand this plugin's own accordion section. */\n onExpandSelf?: (() => void) | null;\n /**\n * Whether the host's accordion section for this plugin is currently expanded.\n * Plugin UIs can watch transitions to take focus, refresh data, etc. The host\n * keeps the plugin mounted across collapse/expand to preserve state, so this\n * prop (not mount/unmount) is the signal that the user is actively viewing.\n */\n isExpanded?: boolean;\n}\n\n// ============================================================================\n// PluginHost API\n// ============================================================================\n\n/**\n * Canonical display metadata for a distributable sample pack, sourced from the\n * HOST's pack registry (the same source it uses to download + version-check the\n * bundle). Returned by `host.getSamplePackInfo` so a plugin's download CTA can\n * show the live name / description / size instead of a hardcoded copy that\n * drifts when a new pack version ships. Structurally compatible with\n * `SamplePackCardInfo` (the CTA card prop).\n *\n * @since SDK 2.12.0\n */\nexport interface SamplePackPublicInfo {\n /** Stable pack identifier, e.g. `'sas-instrument-pack'`. */\n packId: string;\n /** Human-readable pack name for the CTA headline. */\n displayName: string;\n /** One-line description of the pack's contents. */\n description: string;\n /** Size in bytes of the default download variant. */\n sizeBytes: number;\n}\n\n/** Scoped API surface that plugins interact with. Plugins NEVER get direct TracktionEngine access. */\nexport interface PluginHost {\n // --- Track Management (ownership-scoped) ---\n\n /** Create a new track in the active scene. Host enforces ownership and scene routing. */\n createTrack(options: CreateTrackOptions): Promise<PluginTrackHandle>;\n\n /** Delete a track previously created by THIS plugin. */\n deleteTrack(trackId: string): Promise<void>;\n\n /** Get all tracks this plugin owns in the active scene. */\n getPluginTracks(): Promise<PluginTrackHandle[]>;\n\n /** Adopt unowned tracks in the active scene matching this plugin's generator type. */\n adoptSceneTracks(): Promise<PluginTrackHandle[]>;\n\n /** Get info about a specific owned track. */\n getTrackInfo(trackId: string): Promise<PluginTrackInfo>;\n\n /** Set track mute state. Only works on owned tracks. */\n setTrackMute(trackId: string, muted: boolean): Promise<void>;\n\n /** Set track volume (linear 0.0 - 1.0). Only works on owned tracks. */\n setTrackVolume(trackId: string, volume: number): Promise<void>;\n /**\n * Set/replace a time-based volume automation curve (a fade envelope) on a track,\n * or clear it with an empty array. Points are {time: seconds, db}; linear between\n * points. Used by crossfade tracks to fade origin↔target across the looped\n * transition. Optional — callers MUST null-check. @since SDK 2.25.0\n */\n setTrackVolumeAutomation?(trackId: string, points: Array<{ time: number; db: number }>): Promise<void>;\n\n /** Set track pan (-1.0 left to 1.0 right). Only works on owned tracks. */\n setTrackPan(trackId: string, pan: number): Promise<void>;\n\n /** Set track solo state. Only works on owned tracks. */\n setTrackSolo(trackId: string, solo: boolean): Promise<void>;\n\n /** Whether ANY track in the project is currently soloed (across all panels).\n * Lets a panel dim its non-soloed rows (the engine silences them via the\n * effective-mute model). Read-only; not ownership-scoped. */\n isAnySoloActive(): Promise<boolean>;\n\n /** Rename a track. Only works on owned tracks. */\n setTrackName(trackId: string, name: string): Promise<void>;\n\n /**\n * Persist a track's musical role to the `tracks.role` column. Call this\n * after an LLM generation classifies the track (e.g. `'bass'`, `'lead'`,\n * `'pad'`, `'fx'`, `'kicks'`) so downstream features — especially the v1\n * transition generator's layer classifier — can see the role.\n *\n * Canonical values understood by the transition classifier include\n * `bass`, `drums`, `lead`, `chords`, `pad`, `arp`, `fx`, `kicks`,\n * `snares`, `hats`, `clap`, `perc`, `riser`, `impact`. Anything else is\n * stored verbatim but won't match the neutral-role set.\n *\n * Only works on owned tracks.\n */\n setTrackRole(trackId: string, role: string): Promise<void>;\n\n /** Shuffle preset: keep MIDI, apply a random preset from the same category. Only works on owned tracks. */\n /**\n * Shuffle preset: keep MIDI, apply a random preset from the same category.\n * `excludeNames` (since SDK 1.5.0) filters preset names out of the random\n * pool; the current preset is always implicitly excluded. Use this to\n * implement a \"no-repeat until full cycle\" shuffle: the panel accumulates\n * the history and resets when shufflePreset throws \"no presets available\".\n */\n shufflePreset(trackId: string, excludeNames?: readonly string[]): Promise<ShufflePresetResult>;\n\n /** Duplicate track: copy MIDI + role to a new track with a different preset. Only works on owned tracks. */\n duplicateTrack(trackId: string): Promise<PluginTrackHandle>;\n\n /**\n * Persist this plugin's track row order for the active scene. Pass the stable\n * track dbIds ({@link PluginTrackHandle.dbId}) in the desired top-to-bottom\n * order. Reload-safe — {@link getPluginTracks} returns tracks in this order\n * across scene switches and project reopen.\n *\n * Per-panel and decoupled from the engine-synced global track order, so\n * reordering one panel never disturbs other plugins' tracks. Tracks omitted\n * from the list (e.g. newly added or duplicated) keep their natural order at\n * the end. Pairs with the {@link useTrackReorder} hook, which drives the\n * drag-and-drop UI and calls this on drop.\n *\n * @since SDK 2.16.0\n */\n reorderTracks(orderedTrackIds: readonly string[]): Promise<void>;\n\n /**\n * Return the canonical list of valid role tokens that the host's\n * classifier and UI understand. Plugins should use this list when\n * building LLM prompts or validating role values before calling\n * {@link setTrackRole}.\n *\n * The assistant owns the canonical taxonomy — plugins MUST NOT ship\n * their own hardcoded list, which would drift from the host. Pair with\n * {@link setTrackRole} to persist a classified role.\n *\n * @since SDK 2.0.0\n */\n getValidRoles(): readonly string[];\n\n // --- FX Operations (ownership-scoped) ---\n\n /** Get detailed FX state for a track (enabled, preset, dry/wet per category). */\n getTrackFxState(trackId: string): Promise<PluginTrackFxDetailState>;\n\n /** Toggle an FX category on/off for a track. */\n toggleTrackFx(trackId: string, category: string, enabled: boolean): Promise<void>;\n\n /** Set FX preset for a track. Returns the new dry/wet value if applicable. */\n setTrackFxPreset(trackId: string, category: string, presetIndex: number): Promise<{ dryWet?: number }>;\n\n /** Set FX dry/wet level for a track. */\n setTrackFxDryWet(trackId: string, category: string, value: number): Promise<void>;\n\n // --- Real-time Track State ---\n\n /** Subscribe to real-time track state changes (mute, solo, volume, pan). Returns unsubscribe fn. */\n onTrackStateChange(listener: TrackStateChangeListener): UnsubscribeFn;\n\n // --- MIDI Operations ---\n\n /** Write MIDI notes to a track this plugin owns. Replaces existing MIDI. */\n writeMidiClip(trackId: string, clip: MidiClipData): Promise<MidiWriteResult>;\n\n /** Clear all MIDI from a track this plugin owns. */\n clearMidi(trackId: string): Promise<void>;\n\n /**\n * Export all tracks owned by this plugin in the active scene as a ZIP bundle\n * of Standard MIDI Files (one .mid per track, named after each track with\n * collision-avoidance suffixes). Prompts the user for a save location.\n *\n * Tracks with no MIDI data are skipped. Returns the path written, or\n * `{ canceled: true }` if the user dismissed the save dialog.\n *\n * @since SDK 1.1.0\n */\n exportTracksAsMidiBundle(\n options?: ExportMidiBundleOptions\n ): Promise<ExportMidiBundleResult>;\n\n /**\n * Run the host's MIDI post-processing pipeline on raw notes.\n * Wraps MidiProcessor: quantize -> swing -> scale -> register -> overlaps -> humanize.\n */\n postProcessMidi(notes: PluginMidiNote[], options: PostProcessOptions): Promise<PluginMidiNote[]>;\n\n /**\n * Read a track's current MIDI notes for in-place editing (e.g. a piano\n * roll). Returns the track's clips with beat-based notes; an empty `clips`\n * array means the track has no MIDI. Reads LIVE engine state (NOT the DB),\n * so it reflects unsaved generator output too and needs no project_id\n * scoping — do not \"fix\" this into a DB query.\n *\n * Ownership-gated like {@link writeMidiClip}. Optional so a plugin built\n * against this SDK still loads on an older host — callers MUST null-check.\n * @since SDK 2.15.0\n */\n readMidiNotes?(trackId: string): Promise<ReadMidiResult>;\n\n // --- Audio Operations ---\n\n /** Place an audio file on a track this plugin owns. */\n writeAudioClip(trackId: string, filePath: string, position?: number): Promise<void>;\n\n /**\n * Render a single track to a temporary WAV file and return its path.\n * Only works on owned tracks. For MIDI/synth tracks the host mutes siblings\n * and renders the scene. For single-clip audio tracks the host MAY take a\n * copy-source fast path.\n * @since SDK 1.2.0\n */\n exportTrackAudio?(trackId: string): Promise<ExportTrackAudioResult>;\n\n /**\n * Run a chain of audio operations on an input WAV via the bundled\n * sas-audio-processor binary. Unsupported ops throw NOT_IMPLEMENTED.\n * @since SDK 1.2.0\n */\n processAudio?(\n inputPath: string,\n operations: AudioProcessingOp[]\n ): Promise<ProcessAudioResult>;\n\n /**\n * Replace a track's audio content. For audio tracks, clears clips and\n * adds the new audio. For MIDI/synth tracks, the original row is stashed\n * in plugin_data and a new audio_tracks row is inserted (MIDI is lost).\n * @since SDK 1.2.0\n */\n replaceTrackAudio?(trackId: string, audioPath: string): Promise<void>;\n\n // --- Plugin/Synth Operations ---\n\n /** Load a VST3/AU plugin onto a track this plugin owns. */\n loadSynthPlugin(trackId: string, pluginName: string): Promise<number>;\n\n /** Set plugin state (base64-encoded preset data). */\n setPluginState(trackId: string, pluginIndex: number, stateBase64: string): Promise<void>;\n\n /** Get current plugin state (base64-encoded). */\n getPluginState(trackId: string, pluginIndex: number): Promise<string>;\n\n /**\n * Set a plugin's RAW VST3/AU state — the plugin's own getStateInformation\n * format, bypassing Tracktion's ValueTree wrapper. Use for third-party\n * instruments (u-he Diva, Serum, …) whose patches the ValueTree round-trip\n * does not faithfully preserve. Default Surge XT presets use setPluginState.\n * @since SDK 2.15.0\n */\n setRawPluginState(trackId: string, pluginIndex: number, stateBase64: string): Promise<void>;\n\n /** Get a plugin's RAW VST3/AU state (see setRawPluginState). @since SDK 2.15.0 */\n getRawPluginState(trackId: string, pluginIndex: number): Promise<string>;\n\n /**\n * Persist a preset as the track's durable sound identity (DB `preset_state`,\n * the shape getTrackSound reads back). setPluginState/setRawPluginState are\n * engine-only — a copied sound that is never persisted has no identity, so\n * drift checks (e.g. the transition-designer preset re-sync) can never\n * observe convergence. Call after applying a copied state to a layer track.\n * @since SDK 2.34.0\n */\n persistTrackPresetState?(\n trackId: string,\n preset: { state: string; stateType: 'raw' | 'valuetree'; name?: string }\n ): Promise<void>;\n\n /** List plugins currently loaded on a track. */\n getTrackPlugins(trackId: string): Promise<PluginSynthInfo[]>;\n\n /** Remove a plugin from a track. */\n removePlugin(trackId: string, pluginIndex: number): Promise<void>;\n\n /** Check if a specific VST/AU plugin is available on the system. */\n isPluginAvailable(pluginName: string): Promise<boolean>;\n\n // --- Instrument Plugin Selection ---\n\n /** Get available instrument plugins (VST3/AU synths) scanned by the engine. */\n getAvailableInstruments(): Promise<InstrumentDescriptor[]>;\n\n /** Get the instrument plugin currently loaded on a track. Null = default (Surge XT). */\n getTrackInstrument(trackId: string): Promise<InstrumentDescriptor | null>;\n\n /** Change the instrument plugin on a track. Preserves MIDI data. */\n setTrackInstrument(trackId: string, pluginId: string): Promise<void>;\n\n /** Open the instrument plugin's native editor GUI as a floating window. */\n showInstrumentEditor(trackId: string): Promise<void>;\n\n /** Close the instrument plugin's editor window. */\n hideInstrumentEditor(trackId: string): Promise<void>;\n\n // --- Drum Sampler ---\n\n /**\n * Load the engine's built-in sampler on the track (if not already\n * present) and configure it with a single one-shot sound. Every MIDI\n * note triggers the loaded sample regardless of pitch — used by the\n * drum-generator plugin where the LLM's emitted pitch is advisory.\n *\n * Idempotent: calling repeatedly on the same track swaps the loaded\n * sample without stacking more sampler instances. The sampler counts\n * as the track's instrument; mixing it with `setTrackInstrument` on\n * the same track is undefined behaviour for now.\n *\n * @since SDK 1.2.0\n */\n setTrackDrumKit(trackId: string, kit: DrumKit): Promise<void>;\n\n /**\n * Load the engine's built-in sampler on the track (if not already\n * present) and configure it with a pitched, polyphonic, multi-zone\n * instrument. Each MIDI note triggers the zone whose [minKey,maxKey]\n * range contains it; the zone is played back pitch-shifted relative\n * to its rootKey. Polyphony is handled by the Tracktion sampler's\n * voice allocator.\n *\n * Used by the instrument-generator plugin to load a pre-rendered\n * pitched-sample manifest. Mutually exclusive with `setTrackDrumKit`\n * on the same track (both occupy the sampler slot) and with\n * `setTrackInstrument(pluginId)` (which loads a VST synth instead).\n *\n * Idempotent: calling repeatedly on the same track swaps the loaded\n * zones without stacking sampler instances.\n *\n * @since SDK 1.3.0\n */\n setTrackInstrumentSampler(trackId: string, instrument: InstrumentSampler): Promise<void>;\n\n // --- Filesystem (sample library scanning) ---\n\n /**\n * List audio files (by default `.wav`) under `rootPath`. Returns\n * absolute file paths. `recursive` defaults to false; pass `true` to\n * walk subdirectories. The drum-generator plugin uses this to\n * lazily discover available samples without round-tripping each\n * folder through `getSamples`.\n *\n * Plugins MUST NOT use this to read paths outside their declared\n * sample roots — the host may add path validation in a later release.\n *\n * @since SDK 1.2.0\n */\n listAudioFiles(rootPath: string, options?: ListAudioFilesOptions): Promise<string[]>;\n\n /**\n * Read a text file's contents from the host filesystem (UTF-8). Returns\n * `null` on any read error (missing file, permission, etc.) — the\n * caller does not need to wrap the call in try/catch.\n *\n * Intended for plugin sample-library metadata: instrument manifest\n * JSON (`<instrument-id>/manifest.json`) and prompt-sibling text\n * (`<id>.txt`). Plugins parse the returned string themselves so the\n * host stays content-agnostic.\n *\n * Plugins MUST NOT use this to read paths outside their declared\n * sample roots — the host may add path validation in a later release.\n *\n * @since SDK 1.4.0\n */\n readTextFile(absolutePath: string): Promise<string | null>;\n\n // --- Scene Context (read-only) ---\n\n /** Get the FULL generation context for the active scene. */\n getGenerationContext(excludeTrackId?: string): Promise<PluginGenerationContext>;\n\n /** Get lightweight musical context (no concurrent track MIDI data). */\n getMusicalContext(): Promise<MusicalContext>;\n\n /** Get the active scene ID. Null if no scene is active. */\n getActiveSceneId(): string | null;\n\n /**\n * Get the bound project's DB id. Null when no project is bound.\n * Optional — older hosts and the renderer-side host proxy may omit it;\n * callers MUST feature-check. Used e.g. to detect project switches for\n * per-project conversation persistence.\n * @since SDK 2.18.0\n */\n getProjectId?(): string | null;\n\n /** Get list of all scenes in the project. */\n getSceneList(): Promise<PluginSceneInfo[]>;\n\n /**\n * Enumerate importable track candidates from OTHER scenes, scoped to this\n * plugin's track type (derived from the plugin id). Each candidate is\n * annotated with `importable` + `disabledReason` — the host computes the\n * harmonic/length/tempo gate so the UI only renders it. By default the active\n * scene is excluded; pass `includeSameScene` to also surface the active\n * scene's MIDI tracks owned by OTHER panels (the cross-panel re-sound source).\n * Scenes with no candidate of this type are omitted.\n *\n * Optional so a plugin built against this SDK still loads on an older host —\n * callers MUST null-check and hide the affordance when absent.\n * @since SDK 2.13.0\n */\n listImportableTracks?(opts?: ListImportableTracksOptions): Promise<ImportCandidateScene[]>;\n\n /**\n * Import a source track (from another scene) into the active scene as a\n * faithful, independent copy, delegating to the `import_track_from_scene`\n * tool. Returns the new track's handle so the panel can append a row.\n * Throws on a gate violation — call only for candidates with `importable`.\n * Optional — callers MUST null-check (see `listImportableTracks`).\n * @since SDK 2.13.0\n */\n importTrack?(opts: { sourceSceneId: string; sourceTrackId: string }): Promise<PluginTrackHandle>;\n\n /**\n * Read a source track's CURRENT sound — sample path (drums), sampler zones\n * (instruments), or Surge preset state (synths) — so a panel can copy just\n * the sound onto another track, IGNORING the contract gate that `importTrack`\n * enforces (\"different contract, same preset\"). Read-only: applies nothing.\n * The selector is the source track's DB row id (`ImportCandidateTrack.dbId`).\n * Returns null when the track has no stored sound. Optional — callers MUST\n * null-check (see `listImportableTracks`).\n * @since SDK 2.14.0\n */\n getTrackSound?(sourceTrackDbId: string): Promise<TrackSoundSnapshot | null>;\n\n /**\n * Read a source track's persisted MIDI by its DB row id — the cross-panel\n * READ half of \"re-sound a part on a different instrument\". Unlike\n * `readMidiNotes` (engine-read, ownership-gated), this reads the DB and is\n * NOT ownership-gated, so a panel can pull a part out of a track owned by a\n * DIFFERENT panel in the same scene (the selector is\n * `ImportCandidateTrack.dbId`, e.g. a `sameScene` candidate). Notes are\n * beat-based, identical shape to `readMidiNotes`; the loop span comes from the\n * source scene. Returns `{ clips: [] }` when the track has no MIDI. Optional —\n * callers MUST null-check (see `listImportableTracks`).\n * @since SDK 2.20.0\n */\n readImportableTrackMidi?(sourceTrackDbId: string): Promise<ReadMidiResult>;\n\n /**\n * List THIS panel's family tracks in a specific scene (by DB id), WITHOUT the\n * import key/length/tempo gate that `listImportableTracks` applies. Powers the\n * crossfade picker: the origin (from) and target (to) scenes of a transition\n * deliberately differ in key, so gating would wrongly hide valid candidates.\n * Project-scoped, read-only. Returns [] for an unknown/empty scene. Optional —\n * callers MUST null-check (see `listImportableTracks`).\n * @since SDK 2.22.0\n */\n listSceneFamilyTracks?(sceneDbId: string): Promise<SceneFamilyTrack[]>;\n\n /**\n * Read a specific scene's musical key (tonic + mode) by db id. Labels the\n * SOURCE keys of a crossfade's origin/target patterns — the active-scene\n * musical context only carries the transition scene's key. Optional — callers\n * MUST null-check. @since SDK 2.24.0\n */\n getSceneKey?(sceneDbId: string): Promise<{ key: string; mode: string } | null>;\n /**\n * Read a scene's human display name by db id (for labelling a crossfade's\n * origin/target scenes). Optional — callers MUST null-check. @since SDK 2.26.0\n */\n getSceneName?(sceneDbId: string): Promise<string | null>;\n\n // --- Transport & Playback Events ---\n\n /** Subscribe to transport state changes. Returns unsubscribe function. */\n onTransportEvent(listener: TransportEventListener): UnsubscribeFn;\n\n /** Subscribe to deck boundary events. Returns unsubscribe function. */\n onDeckBoundary(listener: DeckBoundaryListener): UnsubscribeFn;\n\n /** Subscribe to scene change events. Returns unsubscribe function. */\n onSceneChange(listener: SceneChangeListener): UnsubscribeFn;\n\n /** Get current transport state (one-shot). */\n getTransportState(): Promise<PluginTransportState>;\n\n /**\n * One-shot mono peak level for every track this plugin owns. Drives the\n * cosmetic per-track strip meters; poll at ~30Hz while the transport is\n * playing. The host scopes the result to this plugin's tracks and coalesces\n * the underlying engine read, so a busy engine yields a STALE meter rather\n * than a backlog (playback always wins over the GUI). Optional: guard with\n * `typeof host.getTrackLevels === 'function'` for older hosts.\n * @since SDK 2.21.0\n */\n getTrackLevels?(): Promise<PluginTrackLevel[]>;\n\n // --- LLM Access (metered, authenticated) ---\n\n /** Generate text/JSON via the host's authenticated LLM service. */\n generateWithLLM(request: LLMGenerationRequest): Promise<LLMGenerationResult>;\n\n /**\n * Generate with native tool-use (function calling). Used by agentic plugins\n * (chat panel, etc.) to drive an iterative loop where the model calls tools,\n * observes results, and decides next steps — same loop class as Claude Code\n * or VS Code agent mode.\n *\n * Shape mirrors Gemini's `generateContent` REST surface; the host forwards\n * verbatim to the gateway's Gemini-native passthrough endpoint, which adds\n * the central Google API key. Plugins never see provider credentials.\n *\n * Available since SDK 2.4.0.\n */\n generateWithLLMTools(request: LLMToolUseRequest): Promise<LLMToolUseResponse>;\n\n /**\n * Resolve absolute paths for spawning the bundled `sas` CLI as a subprocess.\n * Used by agentic plugins that drive the CLI as their tool surface (chat\n * panel, etc.). Returns `null` when called from a renderer-side host or\n * when the CLI isn't accessible.\n *\n * Available since SDK 2.4.0.\n */\n getCliPaths(): { appExe: string; cliEntry: string } | null;\n\n /**\n * Resolve the absolute path to a bundled resource directory shipped with\n * the app via `extraResources` (e.g. `'drum-samples'`,\n * `'tracktion-presets'`). In dev, resolves to\n * `<projectRoot>/resources/<name>`. In packaged builds, resolves to\n * `<process.resourcesPath>/<name>`.\n *\n * Returns `null` if the host cannot resolve paths in this context\n * (e.g. Electron mocked out in unit tests). Plugins MUST null-check and\n * either degrade gracefully or fall back to a known dev path.\n *\n * Async by design: the renderer-side host proxy round-trips through IPC.\n *\n * @since SDK 2.7.0\n */\n getBundledResourcePath(name: string): Promise<string | null>;\n\n /** Check if LLM access is available (user authenticated + gateway reachable). */\n isLLMAvailable(): Promise<boolean>;\n\n // --- App Tool Bridge ---\n\n /**\n * List the host's registered app tools. Used by plugins (e.g. the chat\n * panel) that want to expose the same surface external AI agents have.\n *\n * `opts.scope` filters by scope tag — scene-scoped consumers pass\n * `'scene'` to hide project-level tools they shouldn't call. When omitted,\n * every tool regardless of scope is returned.\n *\n * `opts.includeDeferred` (since SDK 2.18.0) opts in to tools flagged with\n * `deferLoading` (progressive disclosure). Default `false` mirrors\n * `/api/v1/actions` — the curated core surface. Used by curation layers\n * that promote specific deferred/project tools onto an agent's default\n * declaration set.\n *\n * @since SDK 1.2.0\n */\n listAppTools(opts?: {\n scope?: 'scene' | 'project';\n includeDeferred?: boolean;\n }): Promise<PluginAppTool[]>;\n\n /**\n * Execute a host app tool by name. Delegates to the in-process\n * ToolRegistry — every call (including this one) broadcasts to the\n * UI's `mutations:tool-executed` channel so renderer state stays\n * fresh whether the call mutates or is read-only. Read-only callers\n * pay zero extra cost since the renderer debounces and skips\n * redundant reloads.\n *\n * For scene-scoped tools tagged with `autoBindSceneId`, the host\n * overrides the caller's `sceneId` param with the currently-active\n * scene. That keeps a scene-bound caller from accidentally targeting\n * another scene.\n *\n * `opts.provenance` (since SDK 2.18.0) stamps the originating actor onto\n * every domain event this call emits — pass `'agent'` from autonomous\n * agent loops so the UI orchestrator can gate auto-navigation, `'user'`\n * when proxying a direct user gesture. Omitted = `'system'`.\n *\n * @since SDK 1.2.0\n */\n executeAppTool(\n name: string,\n params: Record<string, unknown>,\n opts?: { provenance?: 'agent' | 'user' }\n ): Promise<PluginAppToolResult>;\n\n /**\n * Monotonic counter that increments on every state mutation\n * (`broadcastMutation('tool-executed', ...)`). Use as a cache key for\n * derived state that depends on the project: when the counter changes,\n * something mutated; when it doesn't, your cache is still valid.\n *\n * Mostly aimed at performance-sensitive callers like ambient-context\n * builders that want to skip re-querying state when nothing has\n * changed. The counter is process-local — it resets on app restart\n * and is not durable across sessions.\n *\n * Implementation detail: the counter is bumped by `mutation-broadcaster`\n * before the broadcaster fires, so a synchronous `getMutationSeq()`\n * call from inside a mutation listener will see the post-bump value.\n *\n * @since SDK 2.6.0\n */\n getMutationSeq(): number;\n\n // --- Preset System ---\n\n /** Get available preset categories for a synth plugin. */\n getPresetCategories(pluginName: string): Promise<string[]>;\n\n /** Get a random preset from a category. */\n getRandomPreset(category: string): Promise<PluginPresetData | null>;\n\n /** Get a specific preset by name from a category. */\n getPresetByName(category: string, name: string): Promise<PluginPresetData | null>;\n\n /** Use LLM to classify a text description into a preset category. */\n classifyPresetCategory(description: string): Promise<string>;\n\n // --- Storage & Settings ---\n\n /** Get absolute path to this plugin's isolated data directory. */\n getDataDirectory(): string;\n\n /** Persisted key-value settings store. */\n settings: PluginSettingsStore;\n\n // --- Sample Pack Distribution ---\n\n /**\n * Return the absolute path to an installed sample pack's root directory,\n * or `null` if the pack is missing OR its installed version doesn't match\n * what the current app build expects.\n *\n * Plugins should treat `null` as \"show the download CTA\"; do NOT fall back\n * to a hardcoded path. The host owns where samples live (currently\n * `<userData>/samples/<installSubdir>/`).\n *\n * Stable packIds: `'sas-drum-pack'`, `'sas-instrument-pack'`. Both packs\n * are downloaded on demand via the host's pack-download flow; see\n * `host.isSamplePackCurrent` and the renderer-side `DownloadPackButton`.\n *\n * @since SDK 2.7.0\n */\n getSamplePackRoot(packId: string): Promise<string | null>;\n\n /**\n * True if the installed version of `packId` matches the version this app\n * build expects. False if the pack is missing OR the installed version\n * differs (older or newer).\n *\n * Plugins call this on activate to decide between rendering their normal\n * UI vs the \"Sample library not installed / Update available\" CTA.\n *\n * @since SDK 2.7.0\n */\n isSamplePackCurrent(packId: string): Promise<boolean>;\n\n /**\n * Return the currently-installed version string for `packId` (e.g. `'1'`,\n * `'2'`), or `null` if the pack is not installed at all. Reads the\n * `_pack-version.json` marker inside the pack's install dir.\n *\n * Useful for distinguishing the \"missing\" CTA from the \"stale, update\n * available\" CTA — plugins can call this when `isSamplePackCurrent`\n * returns false to pick the right empty-state message.\n *\n * @since SDK 2.7.0\n */\n getSamplePackInstalledVersion(packId: string): Promise<string | null>;\n\n /**\n * Trigger a download + install of `packId` via the host's pack system (the\n * same flow `getSamplePackRoot` / `isSamplePackCurrent` report on). Resolves\n * when the install completes or fails. Plugins call this from a \"download\n * library\" CTA instead of reaching into the app's IPC (`window.electronAPI`)\n * directly.\n *\n * @since SDK 2.8.0\n */\n startSamplePackDownload(\n packId: string\n ): Promise<{ success: boolean; error?: string }>;\n\n /**\n * Subscribe to download/install progress for `packId`. Returns an unsubscribe\n * fn. `status` mirrors the host's pack-download states (e.g. `'downloading' |\n * 'extracting' | 'installing' | 'complete' | 'error'`); `progress` is 0-100.\n *\n * @since SDK 2.8.0\n */\n onSamplePackProgress(\n packId: string,\n listener: (progress: {\n packId?: string;\n status: string;\n progress: number;\n message?: string;\n }) => void\n ): UnsubscribeFn;\n\n /**\n * Return the canonical display metadata (`displayName`, `description`,\n * `sizeBytes`) for `packId` from the host's pack registry — the SAME source\n * the host uses to download + version-check the pack. A plugin's download CTA\n * should prefer this over a hardcoded copy so the size/description stay in\n * sync with whatever bundle the host actually ships (no per-version drift).\n * Resolves `null` for an unknown packId.\n *\n * Optional so a plugin built against this SDK still runs on an older host:\n * callers should fall back to their own static copy when it is absent or\n * returns `null`.\n *\n * @since SDK 2.12.0\n */\n getSamplePackInfo?(packId: string): Promise<SamplePackPublicInfo | null>;\n\n /**\n * Per-pack roots of the USER's imported sample packs for `kind`. Each root\n * is laid out exactly like the corresponding stock pack (drums:\n * `<root>/<role>/<file>.wav` + `.txt` sidecars; instruments:\n * `<root>/<category>/<id>/manifest.json`), so resolvers scan them as\n * additional roots alongside `getSamplePackRoot`. `[]` when nothing is\n * imported. User content lives under `<userData>/user-samples/` — strictly\n * separate on disk; stock pack installs never touch it.\n *\n * Optional for older-host compat: feature-check\n * (`host.getUserSampleRoots?.(...)`) and treat absence as `[]`.\n *\n * @since SDK 2.20.0\n */\n getUserSampleRoots?(kind: 'drums' | 'instruments'): Promise<string[]>;\n\n /**\n * Ask the host app to open its sample-import wizard targeting `kind`.\n * Fire-and-forget; renderer-hosted plugins only (the wizard is an app-level\n * modal — the main-process host no-ops). Library changes land as\n * `onSamplePackProgress` events with packId `user:<kind>` and\n * `status: 'complete'`, so subscribe to that to refresh.\n *\n * @since SDK 2.20.0\n */\n openSampleImportWizard?(kind: 'drums' | 'instruments'): void;\n\n // --- Deck playback ---\n //\n // The two playback decks: `'loop-a'` (composition / cue, headphones) and\n // `'loop-b'` (performance / main). These route through the SAME host path\n // the workstation UI uses, so the deck mutual-exclusivity rules\n // (PlaybackRuleEngine) are enforced identically — a plugin cannot bypass\n // them. Used by playback-driven plugins (e.g. the recorder, which starts\n // loop-a so a take has a backing loop). Available on renderer-hosted plugins.\n\n /**\n * Start a deck playing the given scene/transition. Mirrors the workstation's\n * transport play. `contentType` defaults to `'scene'`.\n *\n * @since SDK 2.9.0\n */\n deckPlay(\n deckId: string,\n contentId?: string,\n contentType?: 'scene' | 'transition'\n ): Promise<{ success: boolean; error?: string; code?: string }>;\n\n /**\n * Stop a deck.\n *\n * @since SDK 2.9.0\n */\n deckStop(deckId: string): Promise<{ success: boolean; error?: string }>;\n\n /**\n * Subscribe to per-deck state changes. Each event carries the `deckId`, the\n * `property` that changed (e.g. `'playing'`), and its new `value`. Returns an\n * unsubscribe fn.\n *\n * @since SDK 2.9.0\n */\n onDeckStateChanged(\n listener: (event: { deckId: string; property: string; value: unknown }) => void\n ): UnsubscribeFn;\n\n /**\n * Subscribe to the \"all decks stopped\" engine event (e.g. global transport\n * stop). Returns an unsubscribe fn.\n *\n * @since SDK 2.9.0\n */\n onAllDecksStopped(listener: () => void): UnsubscribeFn;\n\n // --- Scoped Data API ---\n\n /** Get a value from scene-scoped plugin data. */\n getSceneData<T = unknown>(sceneId: string, key: string): Promise<T | null>;\n\n /** Set a value in scene-scoped plugin data. */\n setSceneData(sceneId: string, key: string, value: unknown): Promise<void>;\n\n /** Get all key-value pairs for a scene. */\n getAllSceneData(sceneId: string): Promise<Record<string, unknown>>;\n\n /** Delete a key from scene-scoped plugin data. */\n deleteSceneData(sceneId: string, key: string): Promise<void>;\n\n /** Get the full project-scoped state object. */\n getProjectData<T = unknown>(key: string): Promise<T | null>;\n\n /** Set a project-scoped data value. */\n setProjectData(key: string, value: unknown): Promise<void>;\n\n // --- Notifications & Progress ---\n\n /** Show a toast notification to the user. */\n showToast(type: 'info' | 'success' | 'warning' | 'error', title: string, message?: string): void;\n\n /** Set progress indicator on a specific track. -1 to hide. */\n setProgress(trackId: string, progress: number): void;\n\n /** Set a global status message in the plugin's accordion header. */\n setStatusMessage(message: string | null): void;\n\n /** Request user confirmation via a modal dialog. */\n confirmAction(title: string, message: string): Promise<boolean>;\n\n // --- File System (Phase 2) ---\n\n /** Show a native file open dialog. Requires 'fileDialog' capability. */\n showOpenDialog(options: PluginFileDialogOptions): Promise<string[] | null>;\n\n /** Show a native file save dialog. Requires 'fileDialog' capability. */\n showSaveDialog(options: PluginFileDialogOptions): Promise<string | null>;\n\n /** Download a file to the plugin's data directory. */\n downloadFile(url: string, filename: string, options?: PluginDownloadOptions): Promise<string>;\n\n /** Copy a file into the plugin's data directory. */\n importFile(sourcePath: string, destFilename: string): Promise<string>;\n\n // --- Network (Phase 2, capability-gated) ---\n\n /** Make an HTTP request. Requires 'network' capability with allowedHosts. */\n httpRequest(options: PluginHttpRequestOptions): Promise<PluginHttpResponse>;\n\n // --- Secure Storage (Phase 2) ---\n\n /** Store a secret in the OS keychain (plugin-scoped). */\n storeSecret(key: string, value: string): Promise<void>;\n\n /** Retrieve a secret from the OS keychain (plugin-scoped). */\n getSecret(key: string): Promise<string | null>;\n\n /** Delete a secret from the OS keychain (plugin-scoped). */\n deleteSecret(key: string): Promise<void>;\n\n // --- Sample Library (Phase 2) ---\n\n /** Query the sample library with optional filters. */\n getSamples(filter?: PluginSampleFilter): Promise<PluginSampleInfo[]>;\n\n /** Get a single sample by ID. */\n getSampleById(id: string): Promise<PluginSampleInfo | null>;\n\n /** Import audio files into the sample library. */\n importSamples(filePaths: string[]): Promise<PluginSampleImportResult>;\n\n /** Create a sample track in the active scene. */\n createSampleTrack(sampleId: string, options?: { name?: string }): Promise<PluginTrackHandle>;\n\n /** Delete a sample track. */\n deleteSampleTrack(trackId: string): Promise<void>;\n\n /** Get all sample tracks in the active scene. Re-establishes ownership. */\n getPluginSampleTracks(): Promise<PluginSampleTrackInfo[]>;\n\n /**\n * Resolve a sample-track row id (as returned by `listSceneFamilyTracks`) back\n * to its library `sampleId` + metadata, so a loop can be re-created in another\n * scene (transition crossfade/fade). Returns null if not a sample track.\n * @since SDK 2.31.0\n */\n getSampleTrackInfo?(dbId: string): Promise<{ sampleId: string; fileName?: string; bpm?: number; key?: string } | null>;\n\n /**\n * Render an audio transition effect onto a sample (offline DSP via the audio\n * tool), returning a NEW library sample to place. Used for stutter / chopped\n * loop transitions. @since SDK 2.32.0\n */\n renderSampleEffect?(\n sampleId: string,\n spec: { effect: 'stutter' | 'chopped'; bars: number; bpm: number; repeats?: number; slices?: number },\n ): Promise<PluginSampleInfo>;\n\n /** Time-stretch a sample to a target BPM. Returns the new sample info. */\n timeStretchSample(sampleId: string, targetBpm: number): Promise<PluginSampleInfo>;\n\n /**\n * Fit a sample to the active scene's `(bpm, length_bars)`. Composes:\n * 1. Time-stretch to scene BPM (no-op if already matching).\n * 2. Chop / loop-stitch / passthrough so the resulting clip's duration\n * equals exactly `length_bars × 4 × (60 / bpm)` seconds.\n *\n * Required because the deck loops the clip at the scene's bar boundary —\n * a 4-bar sample dropped into a 2-bar scene used to over-run; a 4-bar\n * sample dropped into an 8-bar scene used to leave 4 bars of silence.\n *\n * The fitted sample is cached in the library by content hash, so\n * subsequent calls for the same `(sample, bpm, bars)` return instantly.\n */\n fitSampleToScene(sampleId: string): Promise<PluginSampleInfo>;\n\n /**\n * Lightweight one-shot sample audition through the cue (headphone) output.\n *\n * Plays the file via a dedicated SimpleLoopPlayer instance in the audio\n * engine — no Tracktion track or clip is created, no BPM matching, no\n * sync. Calling previewSample again with a different file replaces the\n * current preview cleanly. Independent of loop-b: starting/stopping a\n * preview never affects the performance deck and vice versa.\n */\n previewSample(filePath: string): Promise<void>;\n\n /**\n * Stop any in-flight sample preview started by previewSample(). Safe to\n * call when no preview is active — never throws.\n */\n stopPreview(): Promise<void>;\n\n // --- Audio Generation (Phase 2) ---\n\n /** Invoke the host's audio texture generation pipeline. */\n generateAudioTexture(request: PluginAudioTextureRequest): Promise<PluginAudioTextureResult>;\n\n // --- Audio Cue Points + Offset (Migration 060) ---\n\n /**\n * Persist cue points (detected beat positions) for an audio track.\n * Called once after `writeAudioClip` to remember the trim metadata so the\n * UI can later draw beat ticks and snap-to-beat the manual offset.\n *\n * Pass `null` to clear cue points. Throws OWNERSHIP_VIOLATION if the\n * track wasn't created by this plugin.\n */\n setCuePoints(trackId: string, cues: PluginCuePoints | null): Promise<void>;\n\n /** Read cue points previously written by `setCuePoints`. Returns null when none stored. */\n getCuePoints(trackId: string): Promise<PluginCuePoints | null>;\n\n /**\n * Set the manual sample-offset applied to the track's audio clip during\n * playback. Positive shifts later, negative shifts earlier with head\n * silence. Throws OWNERSHIP_VIOLATION if not owned by this plugin.\n */\n setAudioOffsetSamples(trackId: string, offsetSamples: number): Promise<void>;\n\n /** Read the current manual offset (0 if never set). */\n getAudioOffsetSamples(trackId: string): Promise<number>;\n\n // --- Raw / pre-trim audio metadata (stems trim editor) ---\n\n /**\n * Read raw bytes of an audio file written by the host. The path may be\n * `~app/`-relative or project-relative — the host resolves it using the\n * same logic as `writeAudioClip`. Throws FILE_NOT_FOUND if the path\n * can't be resolved or doesn't exist on disk.\n */\n getAudioFileBytes(filePath: string): Promise<ArrayBuffer>;\n\n /** Persist the original (raw, un-trimmed) audio file path for a track. */\n setRawAudioFilePath(trackId: string, filePath: string | null): Promise<void>;\n\n /** Read the raw audio file path persisted via `setRawAudioFilePath`. */\n getRawAudioFilePath(trackId: string): Promise<string | null>;\n\n /**\n * Persist the cue-points detected in the raw (un-trimmed) audio file.\n * Sample positions are in input-file coordinates.\n */\n setRawCuePoints(trackId: string, cues: PluginCuePoints | null): Promise<void>;\n\n /** Read raw-domain cue points persisted via `setRawCuePoints`. */\n getRawCuePoints(trackId: string): Promise<PluginCuePoints | null>;\n\n /** Persist the current trim window inside the raw audio file. */\n setTrimWindow(trackId: string, window: PluginTrimWindow | null): Promise<void>;\n\n /** Read the current trim window persisted via `setTrimWindow`. */\n getTrimWindow(trackId: string): Promise<PluginTrimWindow | null>;\n\n /**\n * Re-trim the raw audio file at the given sample offset and replace the\n * track's audio clip with the new slice. Persists updated trimmed-domain\n * cue points and the new trim window.\n */\n commitTrimWindow(\n trackId: string,\n startSample: number,\n durationSamples: number,\n ): Promise<{ filePath: string; cuePoints: PluginCuePoints | null }>;\n\n // --- Scene Composition ---\n\n /** Trigger bulk composition for the active scene (LLM plans arrangement, creates tracks, generates MIDI). */\n composeScene(options: ComposeSceneOptions): Promise<ComposeSceneResult>;\n\n /** Subscribe to composition progress events (planning, generating, complete, error). */\n onComposeProgress(listener: ComposeProgressListener): UnsubscribeFn;\n\n /** Subscribe to engine ready events (fires when the engine finishes loading tracks after a scene change). */\n onEngineReady(listener: () => void): UnsubscribeFn;\n\n /**\n * Subscribe to external state mutations (CLI, MCP, or HTTP-API tool calls\n * that bypass plugin-host methods). Fires after such a tool finishes,\n * signalling that scene/track DB state may have changed underneath the\n * plugin's local cache. Use it to refresh state that the plugin doesn't\n * own — e.g. re-running adoptSceneTracks() so AI-created tracks become\n * visible without requiring the user to switch scenes.\n *\n * Optional: only the renderer-side host implements this. Main-side\n * plugins should subscribe to the typed domain-event bus instead.\n */\n onAfterAgentMutation?(listener: () => void): UnsubscribeFn;\n\n // --- MIDI Extensions (Phase 2) ---\n\n /** Audition a single note on a track (fire-and-forget preview). */\n auditionNote(trackId: string, pitch: number, velocity: number, durationMs: number): Promise<void>;\n\n // --- Plugin Presets (Phase 2) ---\n\n /** Get presets for this plugin, optionally filtered by category. */\n getPluginPresets(category?: string): Promise<PluginPresetInfo[]>;\n\n /** Save a new preset for this plugin. */\n savePluginPreset(options: SavePluginPresetOptions): Promise<PluginPresetInfo>;\n\n /** Delete a plugin preset by ID. */\n deletePluginPreset(id: string): Promise<void>;\n\n // --- Performance / Logging (Phase 2) ---\n\n /** Log a performance metric. */\n logMetric(name: string, durationMs: number, metadata?: Record<string, unknown>): void;\n\n /** Start a timer. Returns a stop function that logs the duration. */\n startTimer(name: string): () => void;\n\n // --- Stem Splitting ---\n\n /** Split an audio track into stems (vocals, drums, bass, other). Creates new muted tracks. */\n splitStems(trackId: string): Promise<PluginStemSplitResult>;\n\n /** Check if the stem splitter binary is available. */\n isStemSplitterAvailable(): Promise<boolean>;\n\n // --- Audio Recording (capability-gated, since SDK 2.1.0) ---\n\n /**\n * Enumerate audio input devices visible to the engine. Empty list means\n * no input device is available (or the OS denied permission). Requires\n * `audioCapture` capability.\n * @since SDK 2.1.0\n */\n getAudioInputDevices(): Promise<AudioInputDevice[]>;\n\n /**\n * Snapshot of engine state needed to start a recording session. Reads\n * the engine sample rate, the active scene id, the transition-render\n * lock state, and current BPM/bars. Requires `audioCapture`.\n * @since SDK 2.1.0\n */\n getRecordingTargetInfo(): Promise<RecordingTargetInfo>;\n\n /**\n * Begin a recording session. Engine writes integer-PCM WAV chunks to\n * disk; one chunk per call to `markRecordingChunkBoundary`. Each\n * finalized chunk fires a `RecordingChunkFinalizedEvent` to\n * subscribers of `onRecordingChunkFinalized`. Throws\n * AUDIO_CAPTURE_DENIED on permission failure or if no device is\n * available.\n *\n * Pass `deviceId` to override the platform-configured input (rare —\n * only useful for tests or workflows that need a specific device).\n * Omit it to use the platform's selected input from\n * `AudioRoutingConfig.inputDeviceId` — this is the recommended path\n * for plugins post-SDK-2.2.0.\n *\n * @since SDK 2.1.0 (deviceId required) — 2.2.0 made it optional.\n */\n startTrackRecording(deviceId?: string): Promise<void>;\n\n /**\n * Mark the boundary between two recording chunks. The engine closes the\n * currently-open WAV writer and opens a new one; the closed file fires\n * a `RecordingChunkFinalizedEvent` once flush completes. No-op if no\n * recording session is active.\n *\n * Pass `boundaryHostTimeNs` from `DeckBoundaryEvent.boundaryHostTimeNs`\n * for sample-perfect take alignment (Path 2). The engine then splits\n * the chunk at the EXACT recorder-sample that corresponds to that\n * host-time, eliminating the ~5–50 ms of jitter introduced by the\n * legacy \"split wherever the writer is\" path. Required for any\n * workflow that overlays multiple takes (vocalist comping, layered\n * dubs); optional for single-take captures.\n *\n * @since SDK 2.1.0 — 2.4.0 added optional boundaryHostTimeNs.\n */\n markRecordingChunkBoundary(boundaryHostTimeNs?: number): Promise<void>;\n\n /**\n * Stop the active recording session. The final chunk is closed and\n * finalized; its `RecordingChunkFinalizedEvent` fires before this\n * promise resolves. Returns the path of the final chunk (also delivered\n * via the event for symmetry).\n * @since SDK 2.1.0\n */\n stopTrackRecording(): Promise<{ finalChunkPath: string; durationMs: number }>;\n\n /**\n * Subscribe to chunk-finalized events for this plugin's active recording\n * session. Auto-unsubscribed on `deactivate`. Returns unsubscribe fn.\n * @since SDK 2.1.0\n */\n onRecordingChunkFinalized(\n listener: (event: RecordingChunkFinalizedEvent) => void\n ): UnsubscribeFn;\n\n /**\n * Get the platform-configured audio input device, or null when no\n * device is set. Read-only; configured via the assistant's\n * AudioRoutingPanel. Plugins use this to display the current input\n * to the user without exposing their own picker.\n *\n * @since SDK 2.2.0\n */\n getCurrentInputDevice(): Promise<AudioInputDevice | null>;\n\n /**\n * Subscribe to input-device changes (user picks a new mic in the\n * Audio Routing panel). Listeners should refetch via\n * `getCurrentInputDevice()`. Returns an unsubscribe fn.\n *\n * @since SDK 2.4.0\n */\n onInputDeviceChange(listener: () => void): UnsubscribeFn;\n\n /**\n * Get the platform's mic-to-output round-trip latency offset in\n * samples. 0 = uncalibrated. Plugins recording audio apply this via\n * `setAudioOffsetSamples` so takes line up with the source loop.\n *\n * @since SDK 2.2.0\n */\n getRecordingLatencyOffsetSamples(): Promise<number>;\n\n /**\n * Snapshot of the input level for the most recent audio block.\n * Renderer polls at ~30Hz to drive a level meter / scrolling\n * waveform without an event-channel subscription.\n *\n * @since SDK 2.3.0\n */\n getRecordingInputLevel(): Promise<{\n peakDb: number;\n peakLinear: number;\n clipped: boolean;\n active: boolean;\n }>;\n\n /**\n * Reset the latched clip indicator. Safe regardless of whether\n * monitoring or recording is active.\n *\n * @since SDK 2.3.0\n */\n clearRecordingInputClipIndicator(): Promise<void>;\n}\n\n// ============================================================================\n// Stem Splitting Types\n// ============================================================================\n\n/** Stem type identifiers */\nexport type StemType = 'vocals' | 'drums' | 'bass' | 'other';\n\n/** Result of splitting an audio track into stems */\nexport interface PluginStemSplitResult {\n /** Created stem tracks with audio loaded (all auto-muted) */\n stems: PluginStemTrackInfo[];\n}\n\n/** Information about a single stem track created by stem splitting */\nexport interface PluginStemTrackInfo {\n /** The stem type (vocals, drums, bass, other) */\n stemType: StemType;\n /** Track handle for the new stem track */\n track: PluginTrackHandle;\n}\n\n// ============================================================================\n// Exported Plugin Data Types (for .sasproj portability)\n// ============================================================================\n\nexport interface ExportedPluginData {\n pluginId: string;\n scope: 'project' | 'scene' | 'global';\n scopeId: string | null;\n key: string;\n value: string; // JSON-serialized\n}\n\n// ============================================================================\n// Track Types\n// ============================================================================\n\nexport interface CreateTrackOptions {\n /** Display name for the track. Auto-generated if omitted. */\n name?: string;\n /** Musical role hint: 'bass', 'drums', 'lead', 'chords', 'pad', 'arp', 'fx' */\n role?: string;\n /** Load a synth plugin immediately (default: false) */\n loadSynth?: boolean;\n /** Which synth to load (default: 'Surge XT'). Ignored if loadSynth=false. */\n synthName?: string;\n /**\n * Stable plugin identifier for a custom instrument (VST3 TUID or AU component ID).\n * If provided with loadSynth=true, loads this plugin instead of synthName.\n * Null/undefined = use default (Surge XT).\n */\n instrumentPluginId?: string | null;\n /** Metadata stored in DB. Plugins can use this for plugin-specific data. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface PluginTrackHandle {\n /** Tracktion engine track ID (stable, GUID-based) */\n id: string;\n /** Display name */\n name: string;\n /** Database row ID */\n dbId: string;\n /** Musical role (if set) */\n role?: string;\n /** Prompt from tracks table (fallback when plugin_data not yet populated) */\n prompt?: string;\n /** Custom instrument plugin ID (null = default Surge XT) */\n instrumentPluginId?: string | null;\n /** Custom instrument display name (null = Surge XT) */\n instrumentName?: string | null;\n}\n\n/**\n * One source track offered by `listImportableTracks`, already filtered to the\n * calling panel's type. The host computes the gate; the UI only renders it.\n * @since SDK 2.13.0\n */\nexport interface ImportCandidateTrack {\n /** Source track's engine track id (the selector passed back to importTrack). */\n trackId: string;\n /** Source track's DB row id (globally unique; good React key). */\n dbId: string;\n /** Display name shown in the modal row. */\n name: string;\n /** Musical role if set (drives the row icon). */\n role?: string;\n /** True when this track can be copied into the active scene as-is. */\n importable: boolean;\n /** Why the track is disabled (shown as a tooltip). Present iff `!importable`. */\n disabledReason?: string;\n}\n\n/**\n * One track in a specific scene, returned by `host.listSceneFamilyTracks`,\n * already narrowed to the calling panel's family. Unlike `ImportCandidateTrack`\n * it carries NO import gate — the crossfade picker lists every same-family track\n * in the origin/target scene regardless of key/length. @since SDK 2.22.0\n */\nexport interface SceneFamilyTrack {\n /** Track's DB row id — the selector for getTrackSound + crossfade metadata. */\n dbId: string;\n /** Display name shown in the picker. */\n name: string;\n /** Musical role if set — used to enforce same-role crossfade pairing. */\n role?: string;\n /** Generation prompt, when the track was AI-generated. The crossfade picker\n * shows this as the primary label (users recognise tracks by prompt, not id).\n * @since SDK 2.27.0 */\n prompt?: string;\n}\n\n/**\n * One OTHER scene and its candidate tracks (already type-filtered). Scenes with\n * zero candidates of the panel's type are omitted by the host.\n * @since SDK 2.13.0\n */\nexport interface ImportCandidateScene {\n /** Source scene's engine scene id. */\n sceneId: string;\n /** Source scene's display name. */\n sceneName: string;\n /** Candidate tracks of this panel's type (may include disabled ones). */\n tracks: ImportCandidateTrack[];\n /**\n * True for the synthetic \"this scene — other panels\" entry: the ACTIVE\n * scene's MIDI tracks owned by OTHER panels. Importing one re-sounds the part\n * on the calling panel's instrument (via `readImportableTrackMidi` +\n * `writeMidiClip`) rather than faithfully copying it. Absent/false for\n * ordinary cross-scene entries. @since SDK 2.20.0\n */\n sameScene?: boolean;\n}\n\n/**\n * A source track's current sound, as returned by `host.getTrackSound`. The\n * discriminant matches the panel that reads it: drums → 'sample', instruments →\n * 'instrument', synths → 'preset'. `label` is the human name for the History row.\n * @since SDK 2.14.0\n */\n/**\n * How a synth `state` blob is serialized. `valuetree` is Tracktion's wrapped\n * format (default Surge XT presets); `raw` is the plugin's own\n * getStateInformation format (third-party instruments). Absent ⇒ `valuetree`,\n * for backward compatibility with history recorded before SDK 2.15.0.\n * @since SDK 2.15.0\n */\nexport type SynthStateType = 'raw' | 'valuetree';\n\nexport type TrackSoundSnapshot =\n | { kind: 'sample'; samplePath: string; label: string }\n | { kind: 'instrument'; displayName: string; instrumentId: string | null; zones: InstrumentZone[]; label: string }\n | { kind: 'preset'; state: string; label: string; stateType?: SynthStateType };\n\n/** Options for `PluginHost.listImportableTracks`. @since SDK 2.13.0 */\nexport interface ListImportableTracksOptions {\n /**\n * Coarse content family. 'midi' = synth/drum/instrument, 'audio' = stems,\n * 'sample' = loops. Defaults are derived from the calling plugin id, so\n * panels normally pass nothing.\n */\n family?: 'midi' | 'audio' | 'sample';\n /**\n * When true, prepend the active scene's MIDI tracks owned by OTHER panels as a\n * `sameScene` entry (the cross-panel re-sound source). Off by default so the\n * plain cross-scene import is unchanged. MIDI panels only. @since SDK 2.20.0\n */\n includeSameScene?: boolean;\n}\n\nexport interface PluginTrackInfo extends PluginTrackHandle {\n /** Is track muted? */\n muted: boolean;\n /** Is track soloed? */\n soloed: boolean;\n /** Volume (linear 0-1) */\n volume: number;\n /** Pan (-1 to 1) */\n pan: number;\n /** Loaded plugins on this track */\n plugins: PluginSynthInfo[];\n /** Has MIDI clips? */\n hasMidi: boolean;\n /** Has audio clips? */\n hasAudio: boolean;\n}\n\nexport interface PluginSynthInfo {\n index: number;\n name: string;\n type: string; // 'VST3' | 'AudioUnit' | 'Internal'\n enabled: boolean;\n}\n\n// ============================================================================\n// Real-time Track State Types\n// ============================================================================\n\n/** Real-time runtime state of a track (pushed from engine) */\nexport interface PluginTrackRuntimeState {\n id: string;\n muted: boolean;\n solo: boolean;\n volume: number;\n pan: number;\n}\n\n/** Listener for real-time track state changes */\nexport type TrackStateChangeListener = (trackId: string, state: PluginTrackRuntimeState) => void;\n\n// ============================================================================\n// FX Detail Types (SDK-friendly re-export)\n// ============================================================================\n\n/** Per-category FX detail state */\nexport interface PluginFxCategoryDetailState {\n enabled: boolean;\n presetIndex: number; // 0-4\n dryWet: number; // 0.0-1.0\n}\n\n/** Full FX detail state for a track — one entry per FX category */\nexport type PluginTrackFxDetailState = Record<string, PluginFxCategoryDetailState>;\n\n// ============================================================================\n// MIDI Types\n// ============================================================================\n\nexport interface MidiClipData {\n /** Start time in seconds */\n startTime: number;\n /** End time in seconds */\n endTime: number;\n /** BPM for beat<->time conversion */\n tempo: number;\n /** MIDI notes */\n notes: PluginMidiNote[];\n}\n\nexport interface PluginMidiNote {\n /** MIDI pitch 0-127 */\n pitch: number;\n /** Start position in quarter-note beats (0 = beginning of clip) */\n startBeat: number;\n /** Duration in quarter-note beats */\n durationBeats: number;\n /** Velocity 1-127 */\n velocity: number;\n /** MIDI channel 0-15 (default: 0) */\n channel?: number;\n}\n\nexport interface MidiWriteResult {\n /** Number of notes written */\n notesInserted: number;\n /** Actual bars covered */\n bars: number;\n}\n\n/**\n * One clip returned by {@link PluginHost.readMidiNotes}. `endTime - startTime`\n * (seconds) is the clip's loop span; round-trip it back into\n * {@link MidiClipData} on save so an edit never changes the clip length.\n * @since SDK 2.15.0\n */\nexport interface ReadMidiClip {\n /** Clip start in seconds (engine timeline). */\n startTime: number;\n /** Clip end in seconds. Loop span = endTime - startTime. */\n endTime: number;\n /** Beat-based notes, identical shape to {@link MidiClipData.notes}. */\n notes: PluginMidiNote[];\n}\n\n/**\n * Result of {@link PluginHost.readMidiNotes}: every clip on the track. Drum /\n * instrument / synth tracks are single-clip, so callers normally use\n * `clips[0]`; the array form mirrors the engine and is future-proof.\n * @since SDK 2.15.0\n */\nexport interface ReadMidiResult {\n clips: ReadMidiClip[];\n}\n\n/**\n * Options for {@link PluginHost.exportTracksAsMidiBundle}.\n * @since SDK 1.1.0\n */\nexport interface ExportMidiBundleOptions {\n /** Default ZIP filename suggested in the save dialog (without extension). */\n defaultName?: string;\n}\n\n/**\n * Result of {@link PluginHost.exportTracksAsMidiBundle}.\n * @since SDK 1.1.0\n */\nexport type ExportMidiBundleResult =\n | { success: true; filePath: string; trackCount: number; skippedCount: number }\n | { success: false; canceled: true }\n | { success: false; canceled?: false; error: string };\n\n// ============================================================================\n// Audio Processing Bridge (SDK 1.2.0 — see ai-orchestration-design.md §16)\n// ============================================================================\n\n/** @since SDK 1.2.0 */\nexport interface ExportTrackAudioResult {\n path: string;\n bpm: number;\n durationMs: number;\n fromCopyFastPath?: boolean;\n}\n\n/** @since SDK 1.2.0 */\nexport interface ProcessAudioResult {\n outputPath: string;\n}\n\n/** @since SDK 1.2.0 */\nexport type AudioProcessingOp =\n | { tool: 'normalize' }\n | { tool: 'compress'; params?: { threshold?: number; ratio?: number } }\n | { tool: 'eq'; params?: { low_gain?: number; mid_gain?: number; high_gain?: number } }\n | { tool: 'reverb'; params?: { room_size?: number; dry_wet?: number } }\n | { tool: 'pitch-shift'; params: { semitones: number } }\n | { tool: 'time-stretch'; params: { target_bpm: number } }\n | { tool: 'filter'; params: { type: 'lowpass' | 'highpass'; cutoff: number } }\n | { tool: 'gain'; params: { db: number } }\n | { tool: 'limit' }\n | { tool: 'trim'; params?: { start?: number; end?: number } };\n\nexport interface PostProcessOptions {\n /** Snap notes to grid (default: true) */\n quantize?: boolean;\n /** Grid size: '1/4', '1/8', '1/16', '1/32', '1/8T', '1/16T' (default: '1/16') */\n quantizeGrid?: string;\n /** Quantize strength 0-100 (default: 75) */\n quantizeStrength?: number;\n /** Swing amount 0-100 (default: 0) */\n swing?: number;\n /** Humanize timing/velocity variation 0-100 (default: 0) */\n humanize?: number;\n /** Enforce diatonic scale (default: false). Uses scene key/mode. */\n enforceScale?: boolean;\n /** Clamp notes to pitch range [low, high] */\n clampRegister?: [number, number];\n /** Remove overlapping notes on same pitch/channel (default: true) */\n removeOverlaps?: boolean;\n}\n\n// ============================================================================\n// Context Types\n// ============================================================================\n\nexport interface MusicalContext {\n key: string; // 'C', 'D', 'Eb', 'F#', etc.\n mode: string; // 'major', 'minor', 'dorian', 'mixolydian', etc.\n bpm: number; // 20-960\n bars: number; // Scene length in bars\n genre: string | null; // 'Drum & Bass', 'Lo-fi Hip Hop', etc.\n timeSignature: string; // '4/4', '3/4', '6/8'\n chordProgression: PluginChordTiming[];\n /**\n * The scene's natural-language contract prompt (e.g. \"dark psytrance,\n * driving 130 BPM, claustrophobic\"). Null when the scene has no\n * contract set yet. Auto-prefixed to the LLM by `host.generateWithLLM`\n * so every per-track generation sees the scene-level intent without\n * each plugin having to plumb it through manually.\n * @since SDK 1.2.0\n */\n contractPrompt: string | null;\n}\n\nexport interface PluginChordTiming {\n /** Chord symbol: 'Cm7', 'G', 'Fmaj7', etc. */\n symbol: string;\n /** Start position in quarter notes */\n startQn: number;\n /** End position in quarter notes */\n endQn: number;\n}\n\n/** Full generation context — includes concurrent track MIDI data */\nexport interface PluginGenerationContext {\n chordProgression: {\n key: { tonic: string; mode: string };\n chordsWithTiming: PluginChordTiming[];\n genre: string | null;\n };\n concurrentTracks: PluginConcurrentTrackInfo[];\n /**\n * Count of tracks the host had to drop entirely from `concurrentTracks`\n * because their notes pushed the running total past the cross-track\n * budget. Panels should disclose this to the LLM (e.g. \"… N additional\n * tracks omitted to fit token budget\") so the model knows it is\n * working with partial context.\n * @since SDK 1.2.0\n */\n truncatedTrackCount?: number;\n}\n\nexport interface PluginConcurrentTrackInfo {\n trackId: string;\n role: string | undefined;\n presetCategory: string | null;\n /** Notes organized by which chord they fall under */\n notesByChord: PluginChordSegment[];\n /**\n * The user-typed prompt that produced this track's MIDI (from\n * `tracks.prompt`). Lets the LLM see *intent* alongside the notes —\n * \"punchy 909 kick\" carries more meaning than the kick MIDI alone.\n * @since SDK 1.2.0\n */\n prompt?: string;\n /**\n * True when the host capped this track's notes (per-track budget).\n * The `notesByChord` payload is a prefix of the real content; the\n * total dropped count is `originalNoteCount - sum(notesByChord.notes.length)`.\n * @since SDK 1.2.0\n */\n truncated?: boolean;\n /** The track's full note count before per-track truncation. */\n originalNoteCount?: number;\n}\n\nexport interface PluginChordSegment {\n chord: string;\n chordRangeQn: [number, number];\n notes: PluginMidiNote[];\n}\n\n// ============================================================================\n// Transport Types\n// ============================================================================\n\nexport interface TransportEvent {\n type: 'play' | 'stop' | 'pause' | 'bpmChange' | 'positionChange';\n bpm?: number;\n position?: number; // in seconds\n isPlaying?: boolean;\n}\n\nexport interface DeckBoundaryEvent {\n deckId: string; // 'loop-a', 'loop-b'\n bar: number; // Current bar number (1-based)\n beat: number; // Current beat within bar (1-based)\n loopCount: number; // How many loops completed\n /**\n * Stream-time sample index at which the loop wrap was detected in the\n * audio thread (engine's AudioBoundaryProbe). Undefined when the\n * audio-thread anchor was unavailable. @since SDK 2.4.0\n */\n boundaryAudioSamplePosition?: number;\n /**\n * Monotonic host-time (nanoseconds) at the audio block in which the\n * loop wrap was detected. Same clock as\n * `juce::AudioIODeviceCallbackContext::hostTimeNs`. Pair with\n * `markRecordingChunkBoundary(boundaryHostTimeNs)` for sample-perfect\n * take alignment. @since SDK 2.4.0\n */\n boundaryHostTimeNs?: number;\n}\n\nexport interface PluginTransportState {\n isPlaying: boolean;\n isPaused: boolean;\n bpm: number;\n position: number; // in seconds\n timeSignature: string;\n}\n\n/**\n * Mono peak level for a single track, as reported by `getTrackLevels()`.\n * Drives the cosmetic per-track strip meters. `peakDb` is the max of the\n * L/R channels, floored at -120 (the \"no signal\" sentinel).\n * @since SDK 2.21.0\n */\nexport interface PluginTrackLevel {\n /** Tracktion engine track id — matches `PluginTrackHandle.id`. */\n trackId: string;\n /** Mono peak in dBFS (max of L/R), floored at -120. */\n peakDb: number;\n /** Latched overload since the last poll. */\n clipped: boolean;\n}\n\nexport interface PluginSceneInfo {\n id: string;\n name: string;\n isMuted: boolean;\n}\n\n/** Scene-level contract/context state passed to plugin UIs as a prop */\nexport interface PluginSceneContext {\n /** Whether a contract has been generated (genre or contractPrompt exists AND chords exist) */\n hasContract: boolean;\n /** Original user prompt text (e.g., \"dark psytrance\"). Null if none. */\n contractPrompt: string | null;\n /** Extracted genre. Null if none. */\n genre: string | null;\n /** Musical key. Null if no chord progression. */\n key: { tonic: string; mode: string } | null;\n /** Chord symbols (e.g., [\"Cm\", \"Fm\", \"G\"]). Empty if no chords. */\n chords: string[];\n /** BPM from project tempo */\n bpm: number;\n /** Scene length in bars */\n bars: number;\n /** Whether any synth tracks exist in this scene */\n hasTracks: boolean;\n /** Whether bulk generation is currently in progress */\n isBulkGenerating: boolean;\n /**\n * Scene kind. A 'transition' scene bridges two other scenes (the\n * transition-as-scene feature) and unlocks the crossfade-track UI in the\n * instrument panels; ordinary scenes are 'scene'. Absent on older hosts.\n * @since SDK 2.22.0\n */\n sceneType?: 'scene' | 'transition';\n /** For a transition scene, the DB id of the scene it bridges FROM (origin). Null otherwise. @since SDK 2.22.0 */\n transitionFromSceneId?: string | null;\n /** For a transition scene, the DB id of the scene it bridges TO (target). Null otherwise. @since SDK 2.22.0 */\n transitionToSceneId?: string | null;\n}\n\n/** Placeholder track state for the progressive bulk-add UX */\nexport interface BulkAddPlaceholderTrack {\n id: string;\n planIndex: number;\n role: string;\n description: string;\n status: 'planned' | 'creating' | 'completed' | 'failed';\n error?: string;\n}\n\nexport type TransportEventListener = (event: TransportEvent) => void;\nexport type DeckBoundaryListener = (event: DeckBoundaryEvent) => void;\nexport type SceneChangeListener = (sceneId: string | null) => void;\nexport type UnsubscribeFn = () => void;\n\n// ============================================================================\n// LLM Types\n// ============================================================================\n\nexport interface LLMGenerationRequest {\n /** System prompt (instructions, role, output format) */\n system: string;\n /** User prompt (the actual request) */\n user: string;\n /** Max tokens for response (host may cap this) */\n maxTokens?: number;\n /** Expected response format hint */\n responseFormat?: 'text' | 'json';\n /**\n * If true, the host will NOT auto-prefix the user prompt with musical\n * context (key, BPM, chords, genre, etc.). Default: false (context IS\n * prefixed automatically).\n */\n skipContextPrefix?: boolean;\n}\n\nexport interface LLMGenerationResult {\n /** Raw response text */\n content: string;\n /** Tokens consumed */\n tokensUsed: number;\n /** Model that generated the response */\n model: string;\n}\n\n// ----------------------------------------------------------------------------\n// Tool-use LLM types (Gemini-native shape, since SDK 2.4.0)\n// ----------------------------------------------------------------------------\n//\n// Plugins that want a Claude-Code / VS-Code-agent-mode loop call\n// `host.generateWithLLMTools(...)` with these shapes. The host forwards to\n// the gateway's Gemini-native passthrough endpoint, where Google's API key\n// is added centrally — plugins never see the raw key. Token usage is\n// tracked by the gateway just like `generateWithLLM`.\n//\n// Shapes mirror Gemini's REST `generateContent` surface deliberately. We do\n// not pull in `@google/genai` as a dependency: with the gateway as a\n// passthrough and the host owning auth, an SDK adds no value over typed\n// JSON, and we keep tighter control of breaking changes.\n\n/** A single part of a Gemini-style content block. */\nexport interface LLMPart {\n /** Plain text. Mutually exclusive with functionCall / functionResponse. */\n text?: string;\n /** A tool/function the model is asking the host to invoke. */\n functionCall?: {\n name: string;\n args: Record<string, unknown>;\n /**\n * Opaque signature returned by Gemini 3+ tool-use models. Must be echoed\n * verbatim when the assistant turn is replayed on a later iteration, or\n * the API rejects the request with a 400 (\"Function call is missing a\n * thought_signature in functionCall parts.\"). Pre-Gemini-3 models leave\n * this undefined; preserving it round-trip is safe across families.\n */\n thoughtSignature?: string;\n };\n /** The result of a tool call, fed back into the loop on the next turn. */\n functionResponse?: {\n name: string;\n response: Record<string, unknown>;\n };\n}\n\nexport interface LLMContent {\n /** 'user' = user/tool-result; 'model' = assistant. */\n role: 'user' | 'model';\n parts: LLMPart[];\n}\n\nexport interface LLMFunctionDeclaration {\n name: string;\n description: string;\n /** JSON Schema. Use `type: 'object'` with `properties` for any tool. */\n parameters: {\n type: 'object';\n properties?: Record<string, unknown>;\n required?: string[];\n };\n}\n\nexport interface LLMTool {\n functionDeclarations: LLMFunctionDeclaration[];\n}\n\nexport interface LLMGenerationConfig {\n temperature?: number;\n topP?: number;\n topK?: number;\n maxOutputTokens?: number;\n}\n\nexport interface LLMSystemInstruction {\n parts: { text: string }[];\n}\n\nexport interface LLMToolUseRequest {\n /** Gemini model id (e.g. 'gemini-2.5-flash'). */\n model: string;\n /** Conversation so far, including any tool-result turns. */\n contents: LLMContent[];\n /** System prompt as Gemini-native systemInstruction. */\n systemInstruction?: LLMSystemInstruction;\n /** Tool declarations the model may call. */\n tools?: LLMTool[];\n /** Optional tool-call mode override. */\n toolConfig?: {\n functionCallingConfig?: {\n mode?: 'AUTO' | 'ANY' | 'NONE';\n allowedFunctionNames?: string[];\n };\n };\n generationConfig?: LLMGenerationConfig;\n}\n\nexport interface LLMUsageMetadata {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n}\n\nexport interface LLMCandidate {\n content: LLMContent;\n finishReason?: string;\n index?: number;\n}\n\nexport interface LLMToolUseResponse {\n candidates: LLMCandidate[];\n usageMetadata?: LLMUsageMetadata;\n}\n\n// ============================================================================\n// Preset Types\n// ============================================================================\n\nexport interface PluginPresetData {\n name: string;\n category: string;\n /** Base64-encoded plugin state — pass to setPluginState() */\n state: string;\n}\n\n/** Result of shufflePreset() — the new preset that was applied */\nexport interface ShufflePresetResult {\n presetName: string;\n presetCategory: string;\n}\n\n/**\n * One entry in a track's in-session \"sound history\" — the data behind the\n * TrackRow ↩ back-arrow and the drawer \"History\" tab (see `useSoundHistory`).\n *\n * `descriptor` is opaque to the SDK: each generator plugin defines its own shape\n * (a drum sample path string, an instrument `{ displayName, zones }`, a synth\n * `{ pluginIndex, stateBase64 }`) and is the value handed back to the plugin's\n * `applySound` callback to re-apply the sound.\n */\nexport interface SoundHistoryEntry {\n /** Human-readable label shown in the History list (filename, preset/instrument name). */\n label: string;\n /** Opaque, plugin-defined value used to re-apply this sound. */\n descriptor: unknown;\n /** User-starred. Favorited entries are never auto-evicted by the history cap. */\n favorite?: boolean;\n}\n\n// ============================================================================\n// Settings Types\n// ============================================================================\n\nexport interface PluginSettingsSchema {\n type: 'object';\n properties: Record<string, SettingDefinition>;\n}\n\nexport interface SettingDefinition {\n type: 'string' | 'number' | 'boolean' | 'select';\n label: string;\n description?: string;\n default?: unknown;\n /** For 'select' type */\n options?: Array<{ label: string; value: string }>;\n /** For 'number' type */\n min?: number;\n max?: number;\n}\n\nexport interface PluginSettingsStore {\n get<T>(key: string, defaultValue: T): T;\n set(key: string, value: unknown): void;\n getAll(): Record<string, unknown>;\n /** Subscribe to settings changes. Returns unsubscribe fn. */\n onChange(listener: (key: string, value: unknown) => void): UnsubscribeFn;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\nexport type PluginErrorCode =\n | 'NOT_OWNED' // Tried to modify a track not owned by this plugin\n | 'TRACK_NOT_FOUND' // Track ID doesn't exist in engine\n | 'TRACK_LIMIT_EXCEEDED' // Plugin has too many tracks\n | 'NO_ACTIVE_SCENE' // No scene selected\n | 'ENGINE_ERROR' // Tracktion engine call failed\n | 'INVALID_MIDI' // Malformed MIDI data\n | 'FILE_NOT_FOUND' // Audio file doesn't exist\n | 'INVALID_FORMAT' // Unsupported audio format\n | 'PLUGIN_NOT_FOUND' // VST/AU plugin not installed\n | 'LLM_BUDGET_EXCEEDED' // Over token limit\n | 'LLM_UNAVAILABLE' // Gateway unreachable\n | 'NOT_AUTHENTICATED' // User not logged in\n | 'TIMEOUT' // Operation timed out\n | 'CANCELLED' // User cancelled the operation\n | 'INCOMPATIBLE' // Plugin requires newer SDK version\n | 'CAPABILITY_DENIED' // Plugin lacks required capability\n | 'SECRET_NOT_FOUND' // Secret key doesn't exist\n | 'VALIDATION_ERROR' // Inputs failed schema/format validation\n | 'AUDIO_CAPTURE_DENIED'; // OS-level mic permission denied or input device unavailable\n\nexport class PluginError extends Error {\n public readonly code: PluginErrorCode;\n public readonly details?: Record<string, unknown>;\n\n constructor(\n code: PluginErrorCode,\n message: string,\n details?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'PluginError';\n this.code = code;\n this.details = details;\n }\n}\n\n// ============================================================================\n// Plugin Manifest (on-disk plugin.json)\n// ============================================================================\n\nexport interface PluginManifest {\n id: string;\n displayName: string;\n version: string;\n description: string;\n generatorType: GeneratorType;\n main: string; // e.g., 'dist/index.js'\n renderer?: string; // e.g., 'dist/ui.bundle.js' (UMD bundle for renderer)\n icon?: string; // e.g., 'assets/icon.svg'\n author?: string;\n license?: string;\n minHostVersion?: string;\n capabilities?: PluginCapabilities;\n settings?: Record<string, SettingDefinition>;\n builtIn?: boolean;\n repository?: string; // e.g., 'https://github.com/user/my-plugin'\n}\n\nexport interface PluginCapabilities {\n requiresLLM?: boolean;\n requiresSurgeXT?: boolean;\n requiresNetwork?: boolean;\n /** Allowed network hosts for httpRequest (e.g., ['api.splice.com']) */\n network?: { allowedHosts?: string[] };\n /** Plugin needs native file dialog access */\n fileDialog?: boolean;\n /**\n * Plugin needs microphone / line-in capture. Gates the recording host\n * methods (getAudioInputDevices, startTrackRecording, etc).\n * @since SDK 2.1.0\n */\n audioCapture?: boolean;\n}\n\n// ============================================================================\n// Audio Recording (since SDK 2.1.0)\n// ============================================================================\n\n/**\n * Audio input device exposed by the audio engine. The `deviceId` is the\n * stable identifier returned by JUCE's AudioDeviceManager and accepted as\n * the device argument to `startTrackRecording`.\n * @since SDK 2.1.0\n */\nexport interface AudioInputDevice {\n /** Stable device identifier — passed back to startTrackRecording. */\n deviceId: string;\n /** Human-readable device name (e.g., \"MacBook Pro Microphone\", \"USB Mic\"). */\n label: string;\n /** True if this is the system default input device. */\n isDefault: boolean;\n /** Number of input channels the device supports (1 = mono, 2 = stereo). */\n channelCount: number;\n}\n\n/**\n * Engine state snapshot that an audio-recording plugin needs before\n * starting a session.\n * @since SDK 2.1.0\n */\nexport interface RecordingTargetInfo {\n /** Engine device sample rate, e.g. 44100 or 48000. */\n engineSampleRate: number;\n /** Active scene id, or null when no scene is selected. */\n sceneId: string | null;\n /** True when a transition render lock is held — recorder must refuse. */\n isRenderLocked: boolean;\n /** Current project BPM. */\n bpm: number;\n /** Active scene length in bars (4/4 assumed), or null when no scene. */\n bars: number | null;\n /**\n * Sample-perfect-recording compatibility (Path 2 gate). When false,\n * the recorder must refuse to start a session and surface\n * `recordingCompatibilityReason` to the user — input + output\n * devices cannot be sample-aligned.\n * @since SDK 2.4.0\n */\n canRecordSamplePerfect?: boolean;\n recordingCompatibilityReason?: string;\n}\n\n/**\n * Event payload fired when the engine finalizes a recording chunk WAV\n * file (either at a boundary mark or at session stop).\n * @since SDK 2.1.0\n */\nexport interface RecordingChunkFinalizedEvent {\n /** Absolute path to the finalized WAV file on disk. */\n filePath: string;\n /** Zero-based chunk index within the active session. */\n chunkIndex: number;\n /** Duration of this chunk in milliseconds. */\n durationMs: number;\n /** WAV sample rate. */\n sampleRate: number;\n /** WAV channel count. */\n channels: number;\n /**\n * Sample-perfect-recording metadata (Path 2). When the chunk was\n * closed via a host-time-anchored `markRecordingChunkBoundary` call,\n * carries recorder-local sample positions plus the host-time at\n * which the boundary fired. Undefined / -1 means the boundary\n * lacked a host-time anchor (legacy or stop-driven finalize).\n * @since SDK 2.4.0\n */\n recorderSampleStart?: number;\n recorderSampleEnd?: number;\n boundaryHostTimeNs?: number;\n}\n\n// ============================================================================\n// Phase 2: File System Types\n// ============================================================================\n\nexport interface PluginFileDialogOptions {\n title?: string;\n defaultPath?: string;\n filters?: Array<{ name: string; extensions: string[] }>;\n /** For open dialog: allow selecting multiple files */\n multiSelections?: boolean;\n /** For open dialog: allow selecting directories */\n directories?: boolean;\n}\n\nexport interface PluginDownloadOptions {\n /** HTTP headers to include */\n headers?: Record<string, string>;\n /** Overwrite if file exists (default: false) */\n overwrite?: boolean;\n}\n\n// ============================================================================\n// Phase 2: Network Types\n// ============================================================================\n\nexport interface PluginHttpRequestOptions {\n url: string;\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n headers?: Record<string, string>;\n body?: string | Record<string, unknown>;\n /** Timeout in milliseconds (default: 30000) */\n timeoutMs?: number;\n}\n\nexport interface PluginHttpResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n}\n\n// ============================================================================\n// Phase 2: Sample Library Types\n// ============================================================================\n\nexport interface PluginSampleFilter {\n bpm?: number;\n key?: { tonic: string; mode?: string };\n category?: string;\n searchQuery?: string;\n}\n\nexport interface PluginSampleInfo {\n id: string;\n filename: string;\n filePath: string;\n category: string | null;\n bpm: number | null;\n keyTonic: string | null;\n keyMode: string | null;\n durationSeconds: number | null;\n fileSizeBytes: number | null;\n tags: string[] | null;\n}\n\nexport interface PluginSampleImportResult {\n imported: number;\n skipped: number;\n errors: string[];\n}\n\n/** Sample track with associated sample metadata (returned by getPluginSampleTracks) */\nexport interface PluginSampleTrackInfo {\n track: PluginTrackHandle;\n sample: PluginSampleInfo;\n volume: number;\n pan: number;\n}\n\n// ============================================================================\n// Phase 2: Audio Generation Types\n// ============================================================================\n\nexport interface PluginAudioTextureRequest {\n /** Text prompt describing the audio texture */\n prompt: string;\n /** Duration in seconds (default: scene length) */\n durationSeconds?: number;\n /** Target BPM (default: project BPM) */\n bpm?: number;\n}\n\nexport interface PluginAudioTextureResult {\n /** Path to the generated audio file */\n filePath: string;\n /** Duration of the generated audio in seconds */\n durationSeconds: number;\n /**\n * Beat positions inside the generated audio file plus the detected BPM.\n * Sample positions are relative to the file at `filePath`. Null when the\n * audio-processor did not surface detection data (older binary, fallback\n * path, or processing failed). Persist via `host.setCuePoints` after the\n * clip is written so the OffsetScrubber UI can read them later.\n */\n cuePoints: PluginCuePoints | null;\n /**\n * Path to the un-trimmed (raw) Lyria output. Used by the stems\n * trim editor to draw the full waveform. Persist via\n * `host.setRawAudioFilePath`. Null when no raw file is available.\n */\n rawFilePath?: string | null;\n /** Same beats as `cuePoints` in raw-file sample coordinates. */\n rawCuePoints?: PluginCuePoints | null;\n /**\n * Auto-detected start of the trim window inside the raw file (sample\n * offset). Null when detection was skipped.\n */\n inputStartSample?: number | null;\n}\n\n/**\n * Cue-points sidecar surfaced by the audio-processor `trim` command —\n * sample positions for each detected beat inside the generated WAV.\n * Mirrors the canonical `CuePoints` shape from the assistant; duplicated\n * here so external plugins don't reach into sas-app internals.\n */\nexport interface PluginCuePoints {\n /** Schema version (currently 1). */\n schema: 1;\n /** Sample rate the beat positions are expressed in. */\n sample_rate: number;\n /** Detected BPM (may differ from project BPM). Null when detection failed. */\n detected_bpm: number | null;\n /** Sample position of bar 1 / beat 1 inside the clip. */\n downbeat_sample: number;\n /** Monotone-increasing array of beat positions in samples. */\n beats: number[];\n /** ISO-8601 timestamp of when detection ran. */\n detected_at: string;\n}\n\n/**\n * A trim window inside a raw (un-trimmed) audio file. `start_sample` is\n * the offset from the start of the raw file; `duration_samples` is the\n * length of the trimmed slice. Both are in raw-file sample coordinates.\n */\nexport interface PluginTrimWindow {\n start_sample: number;\n duration_samples: number;\n}\n\n// ============================================================================\n// Scene Composition Types\n// ============================================================================\n\n/** Options for composing a full scene arrangement via LLM. */\nexport interface ComposeSceneOptions {\n /** The contract prompt / musical direction for the arrangement. */\n contractPrompt: string;\n /** Genre hint (e.g. 'techno', 'jazz'). Optional. */\n genre?: string | null;\n}\n\n/** Result from a scene composition. */\nexport interface ComposeSceneResult {\n /** Whether the composition completed successfully. */\n success: boolean;\n /** Number of tracks created. */\n tracksCreated: number;\n /** Error message if not successful. */\n error?: string;\n}\n\n/** Listener for composition progress events. */\nexport type ComposeProgressListener = (event: ComposeProgressEvent) => void;\n\n/** Progress event emitted during scene composition. */\nexport interface ComposeProgressEvent {\n /** Current phase: 'planning' (LLM deciding tracks), 'generating' (creating MIDI), 'complete', 'error'. */\n phase: 'planning' | 'generating' | 'complete' | 'error';\n /** Per-track placeholder state (available once planning is done). */\n placeholders?: BulkAddPlaceholderTrack[];\n /** Error message when phase is 'error'. */\n error?: string;\n /** Scene ID this compose event belongs to (for scene-keyed UI state). */\n sceneId?: string;\n}\n\n// ============================================================================\n// Phase 2: Plugin Preset Types\n// ============================================================================\n\nexport interface PluginPresetInfo {\n id: string;\n name: string;\n category: string | null;\n isBuiltIn: boolean;\n data: Record<string, unknown>;\n}\n\nexport interface SavePluginPresetOptions {\n name: string;\n category?: string;\n data: Record<string, unknown>;\n}\n\n// ============================================================================\n// App Tool Bridge (since SDK 1.2.0)\n// ============================================================================\n\n/** JSON Schema shape for a tool's input params. */\nexport interface PluginAppToolInputSchema {\n type: 'object';\n properties?: Record<string, unknown>;\n required?: string[];\n}\n\n/** Lightweight descriptor returned by `PluginHost.listAppTools`. */\nexport interface PluginAppTool {\n name: string;\n description: string;\n inputSchema: PluginAppToolInputSchema;\n /** `'scene'` = safe for scene-scoped callers. `'project'` = cross-scene. */\n scope?: 'scene' | 'project';\n /**\n * `true` = the operation cannot be undone via the host's checkpoint/undo\n * system (project delete, disk overwrite, external export, …). The host\n * gates such calls behind a user-approval flow when invoked with agent\n * provenance; agent UIs may also surface the flag (e.g. ⚠ in a tool list).\n * @since SDK 2.18.0\n */\n irreversible?: boolean;\n}\n\n/** Result shape returned by `PluginHost.executeAppTool`. */\nexport interface PluginAppToolResult {\n success: boolean;\n action: string;\n message?: string;\n error?: string;\n /**\n * Tool-specific payload. Concrete shape depends on the tool — callers\n * should treat this as opaque unless they know the tool.\n */\n data?: unknown;\n}\n\n// ============================================================================\n// Plugin Registry Types (used by host internals)\n// ============================================================================\n\nexport type PluginStatus = 'pending' | 'active' | 'failed' | 'disabled' | 'incompatible';\n\nexport interface PluginRegistration {\n /** The loaded plugin instance */\n plugin: GeneratorPlugin;\n /** Current status */\n status: PluginStatus;\n /** Resolved manifest from disk */\n manifest: PluginManifest;\n /** The scoped PluginHost instance for this plugin */\n host: PluginHost | null;\n /** Sort order for accordion display */\n sortOrder: number;\n /** Whether the plugin is enabled */\n enabled: boolean;\n /** Error message if status is 'failed' */\n error?: string;\n}\n","/**\n * FX Toggle Types\n *\n * Types and constants for per-track FX toggle buttons.\n * Each track can enable/disable 6 FX categories independently.\n * The engine is the source of truth — no database persistence needed.\n */\n\n/** Available FX categories in signal chain order */\nexport type FxCategory = 'eq' | 'compressor' | 'chorus' | 'phaser' | 'delay' | 'reverb';\n\n/** All FX categories in signal chain order */\nexport const FX_CATEGORIES: readonly FxCategory[] = [\n 'eq',\n 'compressor',\n 'chorus',\n 'phaser',\n 'delay',\n 'reverb',\n] as const;\n\n/** Position in the signal chain (lower = earlier) */\nexport const FX_CHAIN_ORDER: Record<FxCategory, number> = {\n eq: 0,\n compressor: 1,\n chorus: 2,\n phaser: 3,\n delay: 4,\n reverb: 5,\n};\n\n/** Map from FxCategory to Tracktion Engine built-in plugin xmlTypeName */\nexport const FX_ENGINE_PLUGIN_NAMES: Record<FxCategory, string> = {\n eq: '4bandEq',\n compressor: 'compressor',\n chorus: 'chorus',\n phaser: 'phaser',\n delay: 'delay',\n reverb: 'reverb',\n};\n\n/** Display labels for UI buttons */\nexport const FX_DISPLAY_LABELS: Record<FxCategory, string> = {\n eq: 'EQ',\n compressor: 'Comp',\n chorus: 'Chorus',\n phaser: 'Phaser',\n delay: 'Delay',\n reverb: 'Reverb',\n};\n\n/** Per-track FX state: which categories are active */\nexport interface TrackFxState {\n eq: boolean;\n compressor: boolean;\n chorus: boolean;\n phaser: boolean;\n delay: boolean;\n reverb: boolean;\n}\n\n/** Default state: all FX disabled */\nexport const EMPTY_FX_STATE: TrackFxState = {\n eq: false,\n compressor: false,\n chorus: false,\n phaser: false,\n delay: false,\n reverb: false,\n};\n\n// ============================================================================\n// Preset Types\n// ============================================================================\n\n/** A single FX preset definition */\nexport interface FxPreset {\n /** Display name (e.g. \"Room\", \"Hall\") */\n name: string;\n /** Short label for button (e.g. \"RM\", \"HL\") */\n shortLabel: string;\n /** Map from automatable parameter name -> value (set via setPluginParameter) */\n params: Record<string, number>;\n /** CachedValue params set via XML state (getPluginState/setPluginState) */\n xmlStateParams?: Record<string, number>;\n /** BPM-relative delay time multiplier (1.0 = quarter note). When set, Delay Time is computed at apply time. */\n noteMultiplier?: number;\n /** Fixed delay time in ms (non-BPM-synced). Mutually exclusive with noteMultiplier. */\n fixedLengthMs?: number;\n}\n\n/** How dry/wet is applied to the plugin */\nexport type MixInterpolation = 'direct' | 'gain-scale' | 'ratio-scale';\n\n/** Preset configuration for an FX category */\nexport interface FxPresetConfig {\n /** Exactly 5 presets */\n presets: [FxPreset, FxPreset, FxPreset, FxPreset, FxPreset];\n /** Name of the native mix/wet parameter, or null if no native dry/wet */\n mixParamName: string | null;\n /** XML attribute name for dry/wet control (for plugins with no automatable mix param, e.g. chorus/phaser) */\n mixXmlAttr?: string;\n /** How to apply dry/wet (defaults to 'direct') */\n mixInterpolation: MixInterpolation;\n}\n\n/** Per-category detail state for a single FX on a track */\nexport interface FxCategoryDetailState {\n enabled: boolean;\n presetIndex: number; // 0-4\n dryWet: number; // 0.0-1.0\n}\n\n/** Extended FX state per track with preset and dry/wet info */\nexport type TrackFxDetailState = Record<FxCategory, FxCategoryDetailState>;\n\n/** Default dry/wet mix level (33% — musically useful for most effects) */\nexport const DEFAULT_FX_DRY_WET = 0.33;\n\n/** Default detail state for a single category */\nexport const DEFAULT_FX_CATEGORY_DETAIL: FxCategoryDetailState = {\n enabled: false,\n presetIndex: 0,\n dryWet: DEFAULT_FX_DRY_WET,\n};\n\n/** Default detail state: all FX disabled, preset 0, full wet */\nexport const EMPTY_FX_DETAIL_STATE: TrackFxDetailState = {\n eq: { ...DEFAULT_FX_CATEGORY_DETAIL },\n compressor: { ...DEFAULT_FX_CATEGORY_DETAIL },\n chorus: { ...DEFAULT_FX_CATEGORY_DETAIL },\n phaser: { ...DEFAULT_FX_CATEGORY_DETAIL },\n delay: { ...DEFAULT_FX_CATEGORY_DETAIL },\n reverb: { ...DEFAULT_FX_CATEGORY_DETAIL },\n};\n\n/** Persisted FX data for a single category (stored as JSON in database) */\nexport interface FxPresetDataEntry {\n presetIndex: number;\n dryWet: number;\n enabled: boolean;\n}\n\n/** Persisted FX data format (stored as JSON in database) */\nexport type FxPresetData = Partial<Record<FxCategory, FxPresetDataEntry>>;\n","/**\n * SDK TrackRow — Reusable track row component for generator plugins.\n *\n * Renders a complete track UI with prompt input, generation controls,\n * shuffle/copy, volume/pan, mute/solo, FX drawer, and visual states\n * (amber pulse for \"needs generation\", progress overlay, error indicator).\n *\n * Layout matches TrackInput (main branch) for visual parity.\n *\n * Depends only on PluginHost types + existing shared renderer components.\n */\n\nimport React from 'react';\nimport { AlertCircle, ChevronDown, GripVertical } from 'lucide-react';\nimport { TrackDrawer, type DrawerTab } from './TrackDrawer';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { TrackMeterStrip } from './TrackMeterStrip';\nimport type { TrackLevelsHandle } from '../hooks/useTrackLevels';\nimport type { InstrumentDescriptor, SoundHistoryEntry, PluginMidiNote } from '../types/plugin-sdk.types';\nimport type { TrackRowDragProps } from '../hooks/useTrackReorder';\nimport { VolumeSlider } from './VolumeSlider';\nimport { PanSlider } from './PanSlider';\nimport { SorceryProgressBar } from './SorceryProgressBar';\nimport type { TrackFxDetailState, FxCategory } from '../types/fx-toggle.types';\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface SDKTrackRowProps {\n /** Track identity */\n track: { id: string; name: string; role?: string };\n /** Current prompt text (optional — omit when using contentSlot) */\n prompt?: string;\n /** Playback state */\n runtimeState: { muted: boolean; solo: boolean; volume: number; pan: number };\n /** True when ANOTHER track is soloed, so this (non-soloed) track is currently\n * silenced. Renders the row dimmed while leaving its Mute button UNLIT — the\n * engine's effective-mute model silences it without touching user-mute. Purely\n * visual; does not change mute/solo state. */\n soloedOut?: boolean;\n /** FX category states */\n fxDetailState: TrackFxDetailState;\n /** Whether the unified track drawer is open. */\n drawerOpen: boolean;\n /** Which tab the drawer is showing. */\n drawerTab: DrawerTab;\n /** Switch the active drawer tab (tab-strip clicks). Omit for single-tab panels (e.g. loops = FX only). */\n onTabChange?: (tab: DrawerTab) => void;\n /** Generation in progress */\n isGenerating?: boolean;\n /** Auth state */\n isAuthenticated?: boolean;\n /** Error from last generation */\n error?: string | null;\n /** Enables shuffle/copy buttons */\n hasMidi?: boolean;\n /** Progress % (for persistence across scene switches) */\n generationProgress?: number;\n /** For progress bar pacing */\n estimatedGenerationMs?: number;\n /** Prompt edit (optional — omit to hide prompt input) */\n onPromptChange?: (prompt: string) => void;\n /** \"Create\" button / Enter key (optional — omit to hide Create button) */\n onGenerate?: () => void;\n /** Shuffle preset (optional — omit to hide Shuffle button) */\n onShuffle?: () => void;\n /** Duplicate track (optional — omit to hide Copy button) */\n onCopy?: () => void;\n /** Delete track. Optional — omit to hide the delete button (e.g. a composite\n * like CrossfadeTrackRow owns a single delete for the whole pair). */\n onDelete?: () => void;\n /** Custom content replacing the prompt input (e.g., sample info display) */\n contentSlot?: React.ReactNode;\n /** Toggle mute */\n onMuteToggle: () => void;\n /** Toggle solo */\n onSoloToggle: () => void;\n /** Volume slider */\n onVolumeChange: (vol: number) => void;\n /** Pan slider */\n onPanChange: (pan: number) => void;\n /** FX category toggle (optional — omit to hide FX button) */\n onFxToggle?: (cat: FxCategory, enabled: boolean) => void;\n /** FX preset select */\n onFxPresetChange?: (cat: FxCategory, idx: number) => void;\n /** FX dry/wet */\n onFxDryWetChange?: (cat: FxCategory, val: number) => void;\n /** Open/close FX (optional — omit to hide FX button) */\n onToggleFxDrawer?: () => void;\n /** Progress persistence callback */\n onProgressChange?: (pct: number) => void;\n /** Left border accent color */\n accentColor?: string;\n // --- Instrument Plugin Selection ---\n /** Current instrument display name (null/undefined = Surge XT default) */\n instrumentName?: string | null;\n /** Whether the current instrument plugin is missing from the system */\n instrumentMissing?: boolean;\n /** Open/close the drawer to a non-FX tab (the ▾ button). Omit to hide it. */\n onToggleDrawer?: () => void;\n /** Available instrument plugins for the drawer */\n availableInstruments?: InstrumentDescriptor[];\n /** Currently loaded instrument plugin ID */\n currentInstrumentPluginId?: string | null;\n /** Called when user selects an instrument from the drawer */\n onInstrumentSelect?: (pluginId: string) => void;\n /** Whether instrument scan is loading */\n instrumentsLoading?: boolean;\n /** Re-scan for instruments */\n onRefreshInstruments?: () => void;\n // --- Instrument Editor (Stage 2) ---\n /** Pick-tab sub-view: native plugin editor instead of the instrument grid. */\n editorStage?: boolean;\n /** Called when user clicks \"Open Editor\" */\n onShowEditor?: () => void;\n /** Called when user wants to go back from editor view */\n onBackToInstruments?: () => void;\n // --- Sound History (drawer \"History\" tab) ---\n /** Ordered list of sounds this track has had this session. */\n soundHistory?: readonly SoundHistoryEntry[];\n /** Index into soundHistory of the currently-applied sound. */\n soundHistoryCursor?: number;\n /** Restore a sound from the History tab by index. */\n onRestoreSound?: (index: number) => void;\n /** Toggle the favorite (⭐) flag on a history entry. */\n onToggleFavorite?: (index: number) => void;\n /** Open the drawer's sound-import picker; omit to hide the button. */\n onImportSound?: () => void;\n /** Sound-import button label (\"Import Sample\" / \"Import Preset\"). */\n importSoundLabel?: string;\n // --- Edit tab (piano-roll MIDI editor) ---\n /** Current MIDI notes for the piano-roll editor (the 'edit' tab). */\n editNotes?: readonly PluginMidiNote[];\n /** Persist edited notes; PRESENCE of this callback enables the Edit tab. */\n onNotesChange?: (notes: PluginMidiNote[]) => void;\n /** Scene length in bars (piano-roll grid width). */\n editBars?: number;\n /** Scene BPM (piano-roll audition timing). */\n editBpm?: number;\n /** Snap step in quarter notes for the piano roll (default 0.25). */\n editSnap?: number;\n /** Optional single-note preview when the user adds a note. */\n onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;\n // --- Drag-to-reorder ---\n /** Drag props from {@link useTrackReorder}. When present, renders the grip\n * handle and makes the row a drop target. Omit for non-reorderable lists. */\n drag?: TrackRowDragProps;\n // --- Per-track peak meter (cosmetic) ---\n /** Shared meter handle from `useTrackLevels(host, isPlaying)`. When present,\n * a thin peak meter welds to the bottom of the row. Omit to hide it. */\n levels?: TrackLevelsHandle;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function TrackRow({\n track,\n prompt,\n runtimeState,\n soloedOut = false,\n fxDetailState,\n drawerOpen,\n drawerTab,\n onTabChange,\n isGenerating = false,\n isAuthenticated = false,\n error,\n hasMidi = false,\n generationProgress = 0,\n estimatedGenerationMs = 15000,\n onPromptChange,\n onGenerate,\n onShuffle,\n onCopy,\n onDelete,\n contentSlot,\n onMuteToggle,\n onSoloToggle,\n onVolumeChange,\n onPanChange,\n onFxToggle,\n onFxPresetChange,\n onFxDryWetChange,\n onToggleFxDrawer,\n onProgressChange,\n accentColor = '#A78BFA',\n instrumentName,\n instrumentMissing,\n onToggleDrawer,\n availableInstruments,\n currentInstrumentPluginId,\n onInstrumentSelect,\n instrumentsLoading,\n onRefreshInstruments,\n editorStage,\n onShowEditor,\n onBackToInstruments,\n soundHistory,\n soundHistoryCursor,\n onRestoreSound,\n onToggleFavorite,\n onImportSound,\n importSoundLabel,\n editNotes,\n onNotesChange,\n editBars,\n editBpm,\n editSnap,\n onAuditionNote,\n drag,\n levels,\n}: SDKTrackRowProps): React.ReactElement {\n const { muted: isMuted, solo: isSoloed, volume: currentVolume, pan: currentPan } = runtimeState;\n\n // Guard the (irreversible) delete behind a confirmation modal — the bare \"x\"\n // was one stray click away from losing a track's MIDI + sound.\n const [confirmDelete, setConfirmDelete] = React.useState(false);\n\n // \"Needs generation\" = has prompt, no MIDI yet, not currently generating\n const needsGeneration = !!(prompt?.trim() && !hasMidi && !isGenerating);\n\n const hasFxActive = Object.values(fxDetailState).some(\n (d: { enabled: boolean }) => d.enabled\n );\n\n // The two row buttons open the SAME unified drawer to different tabs:\n // FX → the 'fx' tab; ▾ → a non-FX tab (History/Pick/Import).\n const fxTabOpen = drawerOpen && drawerTab === 'fx';\n const soundTabOpen = drawerOpen && drawerTab !== 'fx';\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {\n if (e.key === 'Enter' && !e.shiftKey && onGenerate) {\n e.preventDefault();\n onGenerate();\n }\n };\n\n // Amber pulse class for \"needs generation\" state\n const borderColorStyle = needsGeneration\n ? undefined // handled by className animation\n : accentColor;\n\n const borderClass = needsGeneration\n ? 'border-amber-400 animate-pulse'\n : 'border-sas-border';\n\n return (\n <div data-testid=\"sdk-track-row-wrapper\" className=\"w-full\" {...(drag?.rowProps ?? {})}>\n <div\n data-testid=\"sdk-track-row\"\n className={`relative flex items-stretch gap-1 p-2 ${levels ? 'rounded-t-sm' : 'rounded-sm'} border w-full overflow-hidden ${borderClass} bg-sas-panel-alt ${drag?.isDragging ? 'opacity-40' : ''} ${drag?.isDragTarget ? 'ring-2 ring-sas-accent ring-inset' : ''}`}\n style={{\n borderLeftColor: needsGeneration ? '#f59e0b' : borderColorStyle,\n borderLeftWidth: '3px',\n }}\n >\n {/* Drag-to-reorder grip — only when reorder is enabled. z-30 keeps it\n above the generating overlay; only the grip is draggable, so the\n row's inputs and sliders stay interactive. */}\n {drag && (\n <div\n data-testid=\"sdk-drag-handle\"\n {...drag.handleProps}\n className=\"flex-shrink-0 self-stretch flex items-center -ml-0.5 pr-0.5 text-sas-muted/40 hover:text-sas-muted cursor-grab active:cursor-grabbing relative z-30\"\n title=\"Drag to reorder\"\n aria-label=\"Drag to reorder track\"\n >\n <GripVertical className=\"w-3.5 h-3.5\" strokeWidth={2} />\n </div>\n )}\n\n {/* Generating progress overlay - stops before buttons (right-44) */}\n {isGenerating && (\n <div className=\"absolute left-0 top-0 bottom-0 right-44 z-20\">\n <SorceryProgressBar\n isLoading={true}\n statusText=\"CONJURING MIDI...\"\n heightClass=\"h-full\"\n initialProgress={generationProgress}\n onProgressChange={onProgressChange}\n estimatedDurationMs={estimatedGenerationMs}\n />\n </div>\n )}\n\n {/* Left: Content area (prompt input or custom content slot) with track name, volume, and pan underneath.\n Dimmed when soloed-out (silenced by another track's solo); the Mute/Solo\n buttons below stay full-opacity and interactive so the user can un-solo. */}\n <div\n data-testid=\"sdk-track-content\"\n className={`flex flex-col flex-1 min-w-0 relative z-10 transition-opacity ${soloedOut ? 'opacity-40' : ''}`}\n title={soloedOut ? 'Silenced — another track is soloed' : undefined}\n >\n {contentSlot ? contentSlot : onPromptChange ? (\n <input\n type=\"text\"\n data-testid=\"sdk-prompt-input\"\n value={prompt ?? ''}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => onPromptChange(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Describe your part...\"\n disabled={isGenerating}\n className=\"sas-input w-full px-2 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n ) : null}\n {/* Track name, volume slider, and pan slider in horizontal row */}\n <div className=\"flex items-center gap-2 mt-1\">\n {track.name && (\n <span className=\"text-[10px] text-sas-muted/60 truncate pl-2 flex-shrink-0 max-w-[80px]\" title={track.name}>\n {track.name}\n </span>\n )}\n <span className=\"text-[9px] text-sas-muted/50 flex-shrink-0\">vol:</span>\n <VolumeSlider\n value={currentVolume}\n onChange={onVolumeChange}\n disabled={isGenerating}\n className=\"flex-1 min-w-[40px]\"\n />\n <span className=\"text-[9px] text-sas-muted/50 flex-shrink-0\">pan:</span>\n <PanSlider\n value={currentPan}\n onChange={onPanChange}\n disabled={isGenerating}\n className=\"w-10 flex-shrink-0\"\n />\n </div>\n </div>\n\n {/* Error indicator - shows when generation failed */}\n {error && (\n <div\n data-testid=\"sdk-error-indicator\"\n className=\"flex-shrink-0 relative z-10 self-stretch flex items-center px-1 group cursor-help\"\n title={error}\n >\n <div className=\"relative\">\n <AlertCircle\n className=\"w-5 h-5 text-red-500 animate-pulse\"\n strokeWidth={2.5}\n />\n {/* Tooltip - appears on hover */}\n <div className=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-red-900/95 text-red-100 text-xs rounded shadow-lg whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 max-w-[200px] truncate\">\n {error}\n </div>\n </div>\n </div>\n )}\n\n {/* Right: Button grid (2 rows) - z-30 to stay above generating overlay */}\n <div className=\"flex flex-col gap-0.5 flex-shrink-0 relative z-30 justify-center\">\n {/* Top row: [Create] [Copy] M x — Create/Copy only shown when handlers provided */}\n <div className=\"flex gap-1 items-center\">\n {onGenerate && (\n <button\n data-testid=\"sdk-generate-button\"\n onClick={onGenerate}\n disabled={!isAuthenticated || isGenerating || !prompt?.trim()}\n className={`w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${\n !isAuthenticated || isGenerating\n ? 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n : needsGeneration\n ? 'bg-amber-500/30 border-amber-500 text-amber-400 hover:bg-amber-500 hover:text-sas-bg animate-pulse'\n : prompt?.trim()\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n title={!isAuthenticated ? 'Please log in' : isGenerating ? 'Generating...' : 'Generate MIDI'}\n >\n Create\n </button>\n )}\n {onCopy && (\n <button\n data-testid=\"sdk-copy-button\"\n onClick={onCopy}\n disabled={!hasMidi || isGenerating}\n className={`w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${\n !hasMidi || isGenerating\n ? 'bg-sas-panel border-sas-border text-sas-muted/30 cursor-not-allowed'\n : 'bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={hasMidi ? 'Duplicate track with different preset' : 'Generate MIDI first'}\n >\n Copy\n </button>\n )}\n {/* Mute stays interactive during generation: users often regenerate\n because they dislike the current sound and want to silence the\n track while the new MIDI conjures. Solo + the rest stay disabled. */}\n <button\n data-testid=\"sdk-mute-button\"\n onClick={onMuteToggle}\n className={`px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${\n isMuted\n ? 'bg-red-600 text-white'\n : 'bg-sas-panel-alt text-sas-muted hover:bg-sas-border'\n }`}\n title={isMuted ? 'Unmute track' : 'Mute track'}\n >\n M\n </button>\n {onDelete && (\n <button\n data-testid=\"sdk-delete-button\"\n onClick={() => setConfirmDelete(true)}\n className=\"text-sas-danger/70 hover:text-sas-danger px-1 py-0.5 transition-colors text-sm\"\n title=\"Delete track\"\n >\n x\n </button>\n )}\n </div>\n {/* Bottom row: [Shuffle] [FX] Solo [▾] */}\n <div className=\"flex gap-1 items-center\">\n {onShuffle && (\n <button\n data-testid=\"sdk-shuffle-button\"\n onClick={onShuffle}\n disabled={!hasMidi || isGenerating || !!currentInstrumentPluginId}\n className={`w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${\n !hasMidi || isGenerating || !!currentInstrumentPluginId\n ? 'bg-sas-panel border-sas-border text-sas-muted/30 cursor-not-allowed'\n : 'bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={\n currentInstrumentPluginId\n ? 'Shuffle only works with default Surge XT'\n : hasMidi\n ? 'Re-roll sound (keep MIDI)'\n : 'Generate MIDI first'\n }\n >\n Shuffle\n </button>\n )}\n {onToggleFxDrawer && (\n <button\n data-testid=\"sdk-fx-button\"\n onClick={onToggleFxDrawer}\n disabled={isGenerating}\n className={`w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${\n isGenerating\n ? 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n : fxTabOpen\n ? 'bg-sas-accent border-sas-accent text-sas-bg'\n : hasFxActive\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={fxTabOpen ? 'Hide FX controls' : 'Show FX controls'}\n >\n FX\n </button>\n )}\n <button\n data-testid=\"sdk-solo-button\"\n onClick={onSoloToggle}\n disabled={isGenerating}\n className={`px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${\n isGenerating\n ? 'bg-sas-panel text-sas-muted/50 cursor-not-allowed'\n : isSoloed\n ? 'bg-yellow-500 text-black'\n : 'bg-sas-panel-alt text-sas-muted hover:bg-sas-border'\n }`}\n title={isSoloed ? 'Unsolo track' : 'Solo track'}\n >\n S\n </button>\n {onToggleDrawer && (\n <button\n data-testid=\"sdk-plugin-button\"\n onClick={onToggleDrawer}\n disabled={isGenerating}\n className={`px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${\n isGenerating\n ? 'bg-sas-panel text-sas-muted/50 cursor-not-allowed'\n : soundTabOpen\n ? 'bg-sas-accent border-sas-accent text-sas-bg'\n : instrumentMissing\n ? 'bg-amber-500/20 text-amber-400 hover:bg-amber-500/40'\n : 'bg-sas-panel-alt text-sas-muted hover:bg-sas-border'\n }`}\n title={`Sound — presets & history${instrumentMissing ? ' (instrument missing)' : ''}`}\n >\n <ChevronDown className=\"w-3 h-3\" strokeWidth={2.5} />\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* Thin per-track peak meter, welded to the bottom of the row (cosmetic).\n Isolated in TrackMeterStrip so its ~30Hz updates re-render only the\n strip, never this whole row. Squared bottom when a drawer welds below. */}\n {levels && (\n <TrackMeterStrip levels={levels} trackId={track.id} roundBottom={!drawerOpen} />\n )}\n\n {/* Unified track drawer — one drawer, contextual tabs (FX / Pick / History / Import).\n The FX button opens it to 'fx'; the ▾ button to a non-FX tab. Which tabs\n appear is computed inside TrackDrawer from the callbacks provided. */}\n {drawerOpen && (\n <div\n data-testid=\"sdk-track-drawer\"\n className=\"border border-t-0 border-sas-border bg-sas-bg rounded-b-sm px-3 py-2 max-h-[260px] overflow-y-auto\"\n >\n <TrackDrawer\n activeTab={drawerTab}\n onTabChange={onTabChange}\n trackId={track.id}\n fxState={fxDetailState}\n onFxToggle={onFxToggle}\n onFxPresetChange={onFxPresetChange}\n onFxDryWetChange={onFxDryWetChange}\n fxDisabled={isGenerating}\n instruments={availableInstruments}\n currentPluginId={currentInstrumentPluginId ?? null}\n isLoading={instrumentsLoading ?? false}\n onSelect={onInstrumentSelect}\n onRefresh={onRefreshInstruments}\n editorStage={editorStage}\n onShowEditor={onShowEditor}\n onBackToInstruments={onBackToInstruments}\n selectedInstrumentName={instrumentName}\n soundHistory={soundHistory}\n soundHistoryCursor={soundHistoryCursor}\n onRestoreSound={onRestoreSound}\n onToggleFavorite={onToggleFavorite}\n onImportSound={onImportSound}\n importSoundLabel={importSoundLabel}\n editNotes={editNotes}\n onNotesChange={onNotesChange}\n editBars={editBars}\n editBpm={editBpm}\n editSnap={editSnap}\n onAuditionNote={onAuditionNote}\n />\n </div>\n )}\n\n <ConfirmDialog\n open={confirmDelete}\n title=\"Delete track?\"\n message={\n <>\n <span className=\"text-sas-text\">{track.name?.trim() || 'This track'}</span> will be\n permanently removed from this scene. This cannot be undone.\n </>\n }\n confirmLabel=\"Delete\"\n onConfirm={() => {\n setConfirmDelete(false);\n onDelete?.();\n }}\n onCancel={() => setConfirmDelete(false)}\n testIdPrefix=\"track-delete-confirm\"\n />\n </div>\n );\n}\n\nexport default TrackRow;\n","/**\n * TrackDrawer — the unified per-track drawer body.\n *\n * ONE drawer with a flat contextual tab strip. Which tabs appear is computed\n * from which callbacks the host panel provides:\n * - FX (onFxToggle) — the 6-category FX toggle bar\n * - Pick (onSelect) — instrument-plugin picker (+ native editor stage)\n * - History (onRestoreSound) — sounds this track has had (restore / favorite)\n * - Import (onImportSound) — copy a sound from a matching track in another scene\n *\n * The active tab is CONTROLLED by the host (activeTab / onTabChange) so the\n * track row's FX button and ▾ button can open the SAME drawer to a chosen tab.\n * When only one tab is enabled (e.g. loops = FX only) the strip is hidden and\n * that single view renders directly.\n *\n * (Was `InstrumentDrawer` — renamed once it grew an FX tab + Import tab. A\n * `TrackDrawer as InstrumentDrawer` alias is exported from the barrel for\n * backwards compatibility.)\n */\n\nimport React, { useState, useMemo } from 'react';\nimport type { InstrumentDescriptor, SoundHistoryEntry, PluginMidiNote } from '../types/plugin-sdk.types';\nimport type { FxCategory, TrackFxDetailState } from '../types/fx-toggle.types';\nimport { FxToggleBar } from './FxToggleBar';\nimport { PianoRollEditor } from './PianoRollEditor';\n\n// ============================================================================\n// Tabs\n// ============================================================================\n\n/** The contextual tabs a track drawer can show, in display order. */\nexport type DrawerTab = 'fx' | 'pick' | 'history' | 'import' | 'edit';\n\nconst TAB_LABELS: Record<DrawerTab, string> = {\n fx: 'FX',\n pick: 'Pick',\n history: 'History',\n import: 'Import',\n edit: 'Edit',\n};\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface TrackDrawerProps {\n /** Which tab is active (controlled by the host TrackRow). */\n activeTab: DrawerTab;\n /** Switch tabs (strip clicks). */\n onTabChange?: (tab: DrawerTab) => void;\n\n // --- FX tab (enabled when onFxToggle is provided) ---\n trackId: string;\n fxState: TrackFxDetailState;\n onFxToggle?: (category: FxCategory, enabled: boolean) => void;\n onFxPresetChange?: (category: FxCategory, presetIndex: number) => void;\n onFxDryWetChange?: (category: FxCategory, value: number) => void;\n /** Disable FX controls (e.g. while the track is generating). */\n fxDisabled?: boolean;\n\n // --- Pick tab (enabled when onSelect is provided) ---\n /** Available instrument plugins from engine scan. */\n instruments?: InstrumentDescriptor[];\n /** Currently loaded instrument plugin ID (null = default Surge XT). */\n currentPluginId?: string | null;\n /** Whether the instrument scan is still in progress. */\n isLoading?: boolean;\n /** Called when user selects an instrument (presence enables the Pick tab). */\n onSelect?: (pluginId: string) => void;\n /** Re-scan plugins. */\n onRefresh?: () => void;\n /** Pick-tab sub-view: show the native plugin editor instead of the grid. */\n editorStage?: boolean;\n /** Called when user clicks \"Open Plugin Editor\". */\n onShowEditor?: () => void;\n /** Called when user goes back from the editor to the instrument grid. */\n onBackToInstruments?: () => void;\n /** Name of the selected instrument (shown in the editor header). */\n selectedInstrumentName?: string | null;\n\n // --- History tab (enabled when onRestoreSound is provided) ---\n soundHistory?: readonly SoundHistoryEntry[];\n soundHistoryCursor?: number;\n /** Restore a sound by index; presence enables the History tab. */\n onRestoreSound?: (index: number) => void;\n /** Toggle the favorite (⭐) flag on a history entry; omit to hide the star. */\n onToggleFavorite?: (index: number) => void;\n\n // --- Import tab (enabled when onImportSound is provided) ---\n /** Open the sound-import picker; presence enables the Import tab. */\n onImportSound?: () => void;\n /** Button label, e.g. \"Import Sample\" (drums/instruments) or \"Import Preset\" (synths). */\n importSoundLabel?: string;\n\n // --- Edit tab (enabled when onNotesChange is provided) ---\n /** Current MIDI notes for the piano-roll editor. */\n editNotes?: readonly PluginMidiNote[];\n /** Persist edited notes; PRESENCE of this callback enables the Edit tab. */\n onNotesChange?: (notes: PluginMidiNote[]) => void;\n /** Scene length in bars (piano-roll grid width). Default 4. */\n editBars?: number;\n /** Scene BPM (piano-roll audition timing). Default 120. */\n editBpm?: number;\n /** Snap step in quarter notes for the piano roll (default 0.25). */\n editSnap?: number;\n /** Optional single-note preview when the user adds a note. */\n onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function TrackDrawer({\n activeTab,\n onTabChange,\n trackId,\n fxState,\n onFxToggle,\n onFxPresetChange,\n onFxDryWetChange,\n fxDisabled = false,\n instruments = [],\n currentPluginId = null,\n isLoading = false,\n onSelect,\n onRefresh,\n editorStage = false,\n onShowEditor,\n onBackToInstruments,\n selectedInstrumentName,\n soundHistory,\n soundHistoryCursor = -1,\n onRestoreSound,\n onToggleFavorite,\n onImportSound,\n importSoundLabel,\n editNotes,\n onNotesChange,\n editBars,\n editBpm,\n editSnap,\n onAuditionNote,\n}: TrackDrawerProps): React.ReactElement {\n // --- Hooks (MUST stay above every early return) ---\n const [search, setSearch] = useState('');\n\n const fxEnabled = !!onFxToggle;\n const pickEnabled = !!onSelect;\n const historyEnabled = !!onRestoreSound;\n const importEnabled = !!onImportSound;\n const editEnabled = !!onNotesChange;\n\n const enabledTabs = useMemo((): DrawerTab[] => {\n const tabs: DrawerTab[] = [];\n if (fxEnabled) tabs.push('fx');\n if (pickEnabled) tabs.push('pick');\n if (historyEnabled) tabs.push('history');\n if (importEnabled) tabs.push('import');\n if (editEnabled) tabs.push('edit');\n return tabs;\n }, [fxEnabled, pickEnabled, historyEnabled, importEnabled, editEnabled]);\n\n /** Sentinel pluginId for the default Surge XT entry */\n const SURGE_XT_DEFAULT_ID = 'Surge XT';\n\n // Filter instruments by search query, with selected instrument always first.\n // Computed unconditionally so the hook order is stable across tab switches.\n const filtered = useMemo((): InstrumentDescriptor[] => {\n let all = instruments.filter((i: InstrumentDescriptor) => i.name !== 'Surge XT');\n if (search.trim()) {\n const q = search.toLowerCase();\n all = all.filter(\n (i: InstrumentDescriptor) =>\n i.name.toLowerCase().includes(q) || i.manufacturer.toLowerCase().includes(q),\n );\n }\n if (currentPluginId) {\n const selectedIdx = all.findIndex((i: InstrumentDescriptor) => i.pluginId === currentPluginId);\n if (selectedIdx > 0) {\n const [selected] = all.splice(selectedIdx, 1);\n all.unshift(selected);\n }\n }\n return all;\n }, [instruments, search, currentPluginId]);\n\n // --- Derived (non-hook) values ---\n const history = soundHistory ?? [];\n const effectiveTab: DrawerTab = enabledTabs.includes(activeTab)\n ? activeTab\n : enabledTabs[0] ?? 'fx';\n\n const tabClass = (active: boolean): string =>\n `px-2 py-0.5 text-xs rounded-sm transition-colors ${\n active ? 'bg-sas-accent/20 text-sas-accent font-medium' : 'text-sas-muted hover:text-sas-accent'\n }`;\n\n // The tab strip replaces the old \"Sound\" title. Hidden when only one tab is\n // enabled (e.g. loops = FX only) — that single view renders directly.\n const strip =\n enabledTabs.length > 1 ? (\n <div\n className=\"flex items-center gap-1 border-b border-sas-border pb-1\"\n data-testid=\"sdk-drawer-tabs\"\n >\n {enabledTabs.map((tab: DrawerTab) => (\n <button\n key={tab}\n type=\"button\"\n data-testid={`sdk-drawer-tab-${tab}`}\n onClick={() => onTabChange?.(tab)}\n className={tabClass(effectiveTab === tab)}\n >\n {tab === 'history' && history.length > 0\n ? `History (${history.length})`\n : TAB_LABELS[tab]}\n </button>\n ))}\n </div>\n ) : null;\n\n // Subtle current-sound hint (the \"Sound\" title was removed in favour of tabs).\n const currentSound =\n soundHistoryCursor >= 0 && soundHistoryCursor < history.length\n ? history[soundHistoryCursor].label\n : null;\n\n const header =\n strip || currentSound ? (\n <div className=\"flex flex-col gap-1\" data-testid=\"sdk-drawer-header\">\n {strip}\n {currentSound && (\n <span\n className=\"text-[10px] text-sas-muted/60 truncate px-0.5\"\n title={currentSound}\n >\n {currentSound}\n </span>\n )}\n </div>\n ) : null;\n\n // ---- Edit tab (piano-roll MIDI editor) ----\n if (effectiveTab === 'edit') {\n return (\n <div className=\"flex flex-col gap-2\" data-testid=\"sdk-drawer-edit\">\n {header}\n <PianoRollEditor\n notes={editNotes ?? []}\n onChange={onNotesChange ?? ((): void => {})}\n bars={editBars ?? 4}\n bpm={editBpm ?? 120}\n snap={editSnap}\n onAuditionNote={onAuditionNote}\n />\n </div>\n );\n }\n\n // ---- FX tab ----\n if (effectiveTab === 'fx') {\n return (\n <div className=\"flex flex-col gap-2\" data-testid=\"sdk-drawer-fx\">\n {header}\n <FxToggleBar\n trackId={trackId}\n fxState={fxState}\n onToggle={(_t: string, category: FxCategory, enabled: boolean) =>\n onFxToggle?.(category, enabled)\n }\n onPresetChange={(_t: string, category: FxCategory, presetIndex: number) =>\n onFxPresetChange?.(category, presetIndex)\n }\n onDryWetChange={(_t: string, category: FxCategory, value: number) =>\n onFxDryWetChange?.(category, value)\n }\n disabled={fxDisabled}\n />\n </div>\n );\n }\n\n // ---- Import tab ----\n if (effectiveTab === 'import') {\n const soundNoun = /preset/i.test(importSoundLabel ?? '')\n ? 'preset'\n : /sample/i.test(importSoundLabel ?? '')\n ? 'sample'\n : 'sound';\n return (\n <div className=\"flex flex-col gap-2\" data-testid=\"sdk-drawer-import\">\n {header}\n <p className=\"text-[11px] text-sas-muted/70 leading-snug\">\n Copy the sound from a matching track in another scene — your MIDI stays, only the{' '}\n {soundNoun} changes.\n </p>\n <button\n type=\"button\"\n data-testid=\"sdk-drawer-import-sound\"\n onClick={onImportSound}\n className=\"w-full px-2 py-1.5 text-[11px] rounded-sm border border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent transition-colors\"\n title=\"Copy a sound from a track in another scene (ignores contract)\"\n >\n ⇪ {importSoundLabel ?? 'Import Sound'}\n </button>\n </div>\n );\n }\n\n // ---- History tab ----\n if (effectiveTab === 'history') {\n const order = history.map((_, i) => i).reverse(); // newest first\n return (\n <div className=\"flex flex-col gap-2\">\n {header}\n {history.length === 0 ? (\n <div\n className=\"text-xs text-sas-muted/60 text-center py-3\"\n data-testid=\"sdk-history-empty\"\n >\n No sounds yet — shuffle to build history.\n </div>\n ) : (\n <ul\n className=\"flex flex-col gap-1 max-h-[160px] overflow-y-auto\"\n data-testid=\"sdk-history-list\"\n >\n {order.map((i) => {\n const entry = history[i];\n const isCurrent = i === soundHistoryCursor;\n return (\n <li key={i} className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n data-testid=\"sdk-history-entry\"\n disabled={isCurrent}\n onClick={() => onRestoreSound?.(i)}\n className={`flex-1 min-w-0 flex items-center justify-between px-2 py-1.5 rounded-sm border text-left text-xs transition-colors ${\n isCurrent\n ? 'border-sas-accent bg-sas-accent/20 text-sas-accent cursor-default'\n : 'border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={isCurrent ? 'Current sound' : `Restore: ${entry.label}`}\n >\n <span className=\"truncate\">{entry.label}</span>\n <span className=\"text-[10px] text-sas-muted/60 flex-shrink-0 ml-2\">\n {isCurrent ? '● current' : 'restore'}\n </span>\n </button>\n {onToggleFavorite && (\n <button\n type=\"button\"\n data-testid=\"sdk-history-favorite\"\n onClick={() => onToggleFavorite(i)}\n className={`flex-shrink-0 px-1 py-0.5 text-sm leading-none transition-colors ${\n entry.favorite\n ? 'text-yellow-400'\n : 'text-sas-muted/40 hover:text-yellow-400'\n }`}\n title={entry.favorite ? 'Unfavorite' : 'Favorite (keeps it from being evicted)'}\n >\n {entry.favorite ? '★' : '☆'}\n </button>\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n );\n }\n\n // ---- Pick tab: native editor stage ----\n if (effectiveTab === 'pick' && editorStage) {\n return (\n <div className=\"flex flex-col gap-2\">\n {header}\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => onBackToInstruments?.()}\n 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\"\n >\n &larr; Back\n </button>\n <span className=\"text-xs text-sas-muted font-medium truncate flex-1\">\n {selectedInstrumentName ?? 'Plugin'}\n </span>\n </div>\n <button\n onClick={() => onShowEditor?.()}\n className=\"w-full py-2 text-xs font-medium rounded-sm border border-sas-accent bg-sas-accent/20 text-sas-accent hover:bg-sas-accent/40 transition-colors\"\n >\n Open Plugin Editor\n </button>\n </div>\n );\n }\n\n // ---- Pick tab: instrument grid (default) ----\n const isDefaultSelected = currentPluginId === null;\n const isSelected = (pluginId: string): boolean => pluginId === currentPluginId;\n\n return (\n <div className=\"flex flex-col gap-2\">\n {header}\n {/* Search + Refresh row */}\n <div className=\"flex items-center gap-2\">\n <input\n type=\"text\"\n value={search}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.target.value)}\n placeholder=\"Search instruments...\"\n className=\"sas-input flex-1 px-2 py-1 text-xs\"\n />\n <button\n onClick={() => onRefresh?.()}\n disabled={isLoading}\n 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\"\n title=\"Re-scan plugins\"\n >\n {isLoading ? '...' : 'Refresh'}\n </button>\n </div>\n\n {/* Instrument grid */}\n {isLoading && instruments.length === 0 ? (\n <div className=\"text-xs text-sas-muted/60 text-center py-3\">Scanning plugins...</div>\n ) : (\n <div className=\"grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto\">\n {/* Permanent \"Surge XT (Default)\" entry — always available */}\n <button\n key=\"__surge-xt-default__\"\n onClick={() => onSelect?.(SURGE_XT_DEFAULT_ID)}\n className={`flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors ${\n isDefaultSelected\n ? 'border-sas-accent bg-sas-accent/20 text-sas-accent'\n : 'border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title=\"Surge XT — Default instrument\"\n >\n <span className=\"text-xs font-medium truncate w-full\">\n {isDefaultSelected && '✓ '}Surge XT\n </span>\n <span className=\"text-[9px] text-sas-muted/50 truncate w-full\">Default</span>\n </button>\n {/* Scanned instruments */}\n {filtered.map((inst: InstrumentDescriptor) => {\n const selected = isSelected(inst.pluginId);\n return (\n <button\n key={inst.pluginId}\n onClick={() => onSelect?.(inst.pluginId)}\n className={`flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors ${\n selected\n ? 'border-sas-accent bg-sas-accent/20 text-sas-accent'\n : inst.missing\n ? 'border-amber-500/50 bg-amber-500/10 text-amber-400 hover:border-amber-500'\n : 'border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={`${inst.name} by ${inst.manufacturer} (${inst.type.toUpperCase()})${inst.missing ? ' — MISSING' : ''}`}\n >\n <span className=\"text-xs font-medium truncate w-full\">\n {selected && '✓ '}\n {inst.name}\n </span>\n <span className=\"text-[9px] text-sas-muted/50 truncate w-full\">\n {inst.manufacturer || inst.type.toUpperCase()}\n </span>\n </button>\n );\n })}\n {filtered.length === 0 && (\n <div className=\"col-span-2 text-xs text-sas-muted/60 text-center py-2\">\n {search.trim() ? 'No matches' : 'No other plugins found'}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** Backwards-compatible alias — the drawer was named `InstrumentDrawer` before it grew FX/Import tabs. */\nexport { TrackDrawer as InstrumentDrawer };\n\nexport default TrackDrawer;\n","/**\n * FX Preset Definitions\n *\n * 5 presets per FX category (30 total).\n *\n * Parameter names must match the Tracktion Engine's AutomatableParameter names\n * for each built-in plugin exactly (case-sensitive).\n *\n * Chorus & Phaser have ZERO automatable parameters — all values are set via\n * XML state (CachedValues on the plugin ValueTree).\n *\n * Lives in shared/ so both main process (services) and renderer (UI) can import.\n */\n\nimport type { FxCategory, FxPresetConfig } from '../types/fx-toggle.types';\n\n// ============================================================================\n// EQ (4-Band Equaliser)\n// ============================================================================\n\nconst EQ_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'The Smiley',\n shortLabel: 'SM',\n params: {\n 'Low-shelf freq': 80, 'Low-shelf gain': 4, 'Low-shelf Q': 0.5,\n 'Mid freq 1': 500, 'Mid gain 1': -3, 'Mid Q 1': 0.7,\n 'Mid freq 2': 2000, 'Mid gain 2': -2, 'Mid Q 2': 0.7,\n 'High-shelf freq': 12000, 'High-shelf gain': 4, 'High-shelf Q': 0.5,\n },\n },\n {\n name: 'Telephone',\n shortLabel: 'TP',\n params: {\n 'Low-shelf freq': 400, 'Low-shelf gain': -20, 'Low-shelf Q': 1.0,\n 'Mid freq 1': 1000, 'Mid gain 1': 5, 'Mid Q 1': 2.0,\n 'Mid freq 2': 3000, 'Mid gain 2': -5, 'Mid Q 2': 1.0,\n 'High-shelf freq': 5000, 'High-shelf gain': -20, 'High-shelf Q': 1.0,\n },\n },\n {\n name: 'Warmth',\n shortLabel: 'WM',\n params: {\n 'Low-shelf freq': 120, 'Low-shelf gain': 3, 'Low-shelf Q': 0.7,\n 'Mid freq 1': 400, 'Mid gain 1': 2, 'Mid Q 1': 1.0,\n 'Mid freq 2': 4000, 'Mid gain 2': 0, 'Mid Q 2': 0.5,\n 'High-shelf freq': 10000, 'High-shelf gain': -4, 'High-shelf Q': 0.5,\n },\n },\n {\n name: 'Vocal Air',\n shortLabel: 'VA',\n params: {\n 'Low-shelf freq': 100, 'Low-shelf gain': -6, 'Low-shelf Q': 0.7,\n 'Mid freq 1': 300, 'Mid gain 1': -2, 'Mid Q 1': 1.0,\n 'Mid freq 2': 1500, 'Mid gain 2': 0, 'Mid Q 2': 0.5,\n 'High-shelf freq': 14000, 'High-shelf gain': 6, 'High-shelf Q': 0.4,\n },\n },\n {\n name: 'De-Box',\n shortLabel: 'DB',\n params: {\n 'Low-shelf freq': 60, 'Low-shelf gain': 0, 'Low-shelf Q': 0.5,\n 'Mid freq 1': 350, 'Mid gain 1': -5, 'Mid Q 1': 2.0,\n 'Mid freq 2': 800, 'Mid gain 2': -3, 'Mid Q 2': 2.0,\n 'High-shelf freq': 10000, 'High-shelf gain': 0, 'High-shelf Q': 0.5,\n },\n },\n ],\n mixParamName: null,\n mixInterpolation: 'gain-scale',\n};\n\n// ============================================================================\n// Compressor\n// ============================================================================\n\nconst COMPRESSOR_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Vocal Leveler',\n shortLabel: 'VL',\n params: { 'Threshold': 0.251, 'Ratio': 0.5, 'Attack': 20.0, 'Release': 200.0, 'Output': 2.0 },\n },\n {\n name: 'Drum Smash',\n shortLabel: 'DS',\n params: { 'Threshold': 0.100, 'Ratio': 0.1, 'Attack': 0.5, 'Release': 100.0, 'Output': 8.0 },\n },\n {\n name: 'Bus Glue',\n shortLabel: 'BG',\n params: { 'Threshold': 0.316, 'Ratio': 0.666, 'Attack': 80.0, 'Release': 150.0, 'Output': 1.0 },\n },\n {\n name: 'Bass Anchor',\n shortLabel: 'BA',\n params: { 'Threshold': 0.177, 'Ratio': 0.25, 'Attack': 10.0, 'Release': 250.0, 'Output': 4.0 },\n },\n {\n name: 'Safety Net',\n shortLabel: 'SN',\n params: { 'Threshold': 0.891, 'Ratio': 0.0, 'Attack': 0.3, 'Release': 50.0, 'Output': 0.0 },\n },\n ],\n mixParamName: null,\n mixInterpolation: 'ratio-scale',\n};\n\n// ============================================================================\n// Chorus\n// ============================================================================\n\nconst CHORUS_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Dimension',\n shortLabel: 'DM',\n params: {},\n xmlStateParams: { depthMs: 1.5, speedHz: 0.5, width: 1.0, mixProportion: 0.5 },\n },\n {\n name: '80s Crystal',\n shortLabel: '80',\n params: {},\n xmlStateParams: { depthMs: 4.0, speedHz: 2.5, width: 0.8, mixProportion: 0.4 },\n },\n {\n name: 'Sea Sick',\n shortLabel: 'SS',\n params: {},\n xmlStateParams: { depthMs: 7.0, speedHz: 0.8, width: 0.3, mixProportion: 1.0 },\n },\n {\n name: 'Pseudo-Leslie',\n shortLabel: 'PL',\n params: {},\n xmlStateParams: { depthMs: 2.0, speedHz: 6.0, width: 0.9, mixProportion: 0.7 },\n },\n {\n name: 'Thickener',\n shortLabel: 'TK',\n params: {},\n xmlStateParams: { depthMs: 1.0, speedHz: 0.2, width: 1.0, mixProportion: 0.3 },\n },\n ],\n mixParamName: null,\n mixXmlAttr: 'mixProportion',\n mixInterpolation: 'direct',\n};\n\n// ============================================================================\n// Phaser\n// ============================================================================\n\nconst PHASER_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Slow Burn',\n shortLabel: 'SB',\n params: {},\n xmlStateParams: { depth: 6.0, rate: 0.1, feedback: 0.3 },\n },\n {\n name: 'Funky Quack',\n shortLabel: 'FQ',\n params: {},\n xmlStateParams: { depth: 3.0, rate: 2.0, feedback: 0.8 },\n },\n {\n name: 'Jet Plane',\n shortLabel: 'JP',\n params: {},\n xmlStateParams: { depth: 8.0, rate: 0.2, feedback: 0.9 },\n },\n {\n name: 'Underwater',\n shortLabel: 'UW',\n params: {},\n xmlStateParams: { depth: 1.5, rate: 4.0, feedback: 0.1 },\n },\n {\n name: 'Static Notch',\n shortLabel: 'ST',\n params: {},\n xmlStateParams: { depth: 2.0, rate: 0.05, feedback: 0.6 },\n },\n ],\n mixParamName: null,\n mixXmlAttr: 'depth',\n mixInterpolation: 'direct',\n};\n\n// ============================================================================\n// Delay\n// ============================================================================\n\nconst DELAY_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Vocal Slap',\n shortLabel: 'VS',\n fixedLengthMs: 110,\n params: { 'Feedback': -20.0, 'Mix proportion': 0.25 },\n },\n {\n name: 'Grand Canyon',\n shortLabel: 'GC',\n noteMultiplier: 1.0,\n params: { 'Feedback': -4.0, 'Mix proportion': 0.45 },\n },\n {\n name: 'Wide Doubler',\n shortLabel: 'WD',\n fixedLengthMs: 25,\n params: { 'Feedback': -30.0, 'Mix proportion': 0.5 },\n },\n {\n name: 'Dub Echo',\n shortLabel: 'DE',\n noteMultiplier: 0.6,\n params: { 'Feedback': -1.5, 'Mix proportion': 0.4 },\n },\n {\n name: 'Rhythmic Wash',\n shortLabel: 'RW',\n noteMultiplier: 0.75,\n params: { 'Feedback': -8.0, 'Mix proportion': 0.2 },\n },\n ],\n mixParamName: 'Mix proportion',\n mixInterpolation: 'direct',\n};\n\n// ============================================================================\n// Reverb\n// ============================================================================\n\nconst REVERB_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Drum Room',\n shortLabel: 'DR',\n params: { 'Room Size': 0.2, 'Damping': 0.2, 'Wet Level': 0.15, 'Dry Level': 0.5, 'Width': 0.8 },\n },\n {\n name: 'Vocal Hall',\n shortLabel: 'VH',\n params: { 'Room Size': 0.8, 'Damping': 0.6, 'Wet Level': 0.25, 'Dry Level': 0.5, 'Width': 1.0 },\n },\n {\n name: 'Cathedral',\n shortLabel: 'CT',\n params: { 'Room Size': 1.0, 'Damping': 0.1, 'Wet Level': 0.333, 'Dry Level': 0.2, 'Width': 1.0 },\n },\n {\n name: 'Tile Bathroom',\n shortLabel: 'TB',\n params: { 'Room Size': 0.15, 'Damping': 0.0, 'Wet Level': 0.2, 'Dry Level': 0.5, 'Width': 0.5 },\n },\n {\n name: 'Vintage Plate',\n shortLabel: 'VP',\n params: { 'Room Size': 0.4, 'Damping': 1.0, 'Wet Level': 0.2, 'Dry Level': 0.5, 'Width': 1.0 },\n },\n ],\n mixParamName: 'Wet Level',\n mixInterpolation: 'direct',\n};\n\n// ============================================================================\n// Export\n// ============================================================================\n\n/** All preset configs keyed by FX category */\nexport const FX_PRESET_CONFIGS: Record<FxCategory, FxPresetConfig> = {\n eq: EQ_PRESETS,\n compressor: COMPRESSOR_PRESETS,\n chorus: CHORUS_PRESETS,\n phaser: PHASER_PRESETS,\n delay: DELAY_PRESETS,\n reverb: REVERB_PRESETS,\n};\n","/**\n * FxToggleBar Component\n *\n * Per-track FX control panel with 6 rows (one per FX category).\n * Each row: [Category toggle] [Preset 1-5 buttons] [Dry/Wet slider]\n *\n * Signal chain order: EQ -> Compressor -> Chorus -> Phaser -> Delay -> Reverb\n */\n\nimport React from 'react';\nimport type { FxCategory, TrackFxDetailState, FxCategoryDetailState } from '../types/fx-toggle.types';\nimport { FX_CATEGORIES, FX_DISPLAY_LABELS } from '../types/fx-toggle.types';\nimport { FX_PRESET_CONFIGS } from '../constants/fx-presets';\n\n/** Per-category active colors */\nconst FX_COLORS: Record<FxCategory, string> = {\n eq: 'bg-blue-500',\n compressor: 'bg-orange-500',\n chorus: 'bg-teal-500',\n phaser: 'bg-purple-500',\n delay: 'bg-green-500',\n reverb: 'bg-cyan-500',\n};\n\nexport interface FxToggleBarProps {\n trackId: string;\n fxState: TrackFxDetailState;\n onToggle: (trackId: string, category: FxCategory, enabled: boolean) => void;\n onPresetChange: (trackId: string, category: FxCategory, presetIndex: number) => void;\n onDryWetChange: (trackId: string, category: FxCategory, value: number) => void;\n disabled?: boolean;\n}\n\nexport const FxToggleBar: React.FC<FxToggleBarProps> = ({\n trackId,\n fxState,\n onToggle,\n onPresetChange,\n onDryWetChange,\n disabled = false,\n}) => {\n return (\n <div className=\"flex flex-col gap-1\" data-testid=\"fx-toggle-bar\">\n {FX_CATEGORIES.map((category: FxCategory) => {\n const detail: FxCategoryDetailState = fxState[category];\n const isActive = detail.enabled;\n const label = FX_DISPLAY_LABELS[category];\n const activeColor = FX_COLORS[category];\n const config = FX_PRESET_CONFIGS[category];\n\n return (\n <div key={category} className=\"flex items-center gap-0.5\">\n {/* Category toggle button */}\n <button\n data-testid={`fx-toggle-${category}`}\n disabled={disabled}\n onClick={() => onToggle(trackId, category, !isActive)}\n className={`w-14 py-0.5 text-[10px] font-semibold rounded-sm transition-colors leading-none flex-shrink-0 text-center ${\n disabled\n ? 'bg-sas-panel text-sas-muted/30 cursor-not-allowed'\n : isActive\n ? `${activeColor} text-white`\n : 'bg-sas-panel-alt text-sas-muted/60 hover:bg-sas-border hover:text-sas-muted'\n }`}\n title={`${isActive ? 'Disable' : 'Enable'} ${category.toUpperCase()}`}\n >\n {label}\n </button>\n\n {/* Preset buttons 1-5 */}\n {config.presets.map((preset, idx: number) => (\n <button\n key={idx}\n data-testid={`fx-preset-${category}-${idx}`}\n disabled={disabled || !isActive}\n onClick={() => onPresetChange(trackId, category, idx)}\n className={`w-5 h-5 text-[9px] font-medium rounded-sm transition-colors leading-none flex-shrink-0 ${\n disabled || !isActive\n ? 'bg-sas-panel text-sas-muted/20 cursor-not-allowed'\n : detail.presetIndex === idx\n ? `${activeColor} text-white`\n : 'bg-sas-panel-alt text-sas-muted/50 hover:bg-sas-border hover:text-sas-muted'\n }`}\n title={preset.name}\n >\n {idx + 1}\n </button>\n ))}\n\n {/* Dry/Wet slider */}\n <input\n type=\"range\"\n data-testid={`fx-drywet-${category}`}\n min=\"0\"\n max=\"100\"\n value={Math.round(detail.dryWet * 100)}\n disabled={disabled || !isActive}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n onDryWetChange(trackId, category, Number(e.target.value) / 100)\n }\n className=\"flex-1 min-w-[30px] h-3 accent-sas-accent disabled:opacity-30 cursor-pointer disabled:cursor-not-allowed\"\n title={`Dry/Wet: ${Math.round(detail.dryWet * 100)}%`}\n />\n <span className=\"text-[8px] text-sas-muted/50 w-6 text-right flex-shrink-0\">\n {Math.round(detail.dryWet * 100)}%\n </span>\n </div>\n );\n })}\n </div>\n );\n};\n","/**\n * PianoRollEditor — a compact, DOM-based MIDI note editor for the track drawer.\n *\n * Controlled: `notes` in, `onChange(next)` out. Notes render as absolutely-\n * positioned divs over a beat/pitch grid (DOM, not canvas — so it themes with\n * sas-* tokens and is fully driveable by React Testing Library). Supports:\n * - add : click an empty grid cell\n * - delete : click an existing note (no drag)\n * - move : drag a note's body (snap-quantised)\n * - resize : drag a note's right-edge handle (snap-quantised, ≥ one step)\n * - octave : shift the whole clip ±12 (toolbar) — no velocity lane\n * / marquee yet.\n * On load the viewport auto-scrolls to vertically center the note cluster, so a\n * low melody isn't stranded off-screen at the bottom of the pitch range.\n *\n * Coordinate spaces:\n * pitch (0-127) ── row = hi - pitch ── top px = row * ROW_HEIGHT\n * beat (¼ notes) ─────────────────────── left px = beat * PX_PER_BEAT\n *\n * The pure helpers (`cellToPx` / `pxToCell` / `transposeNotes`) and layout\n * constants are exported so coordinate math can be unit-tested without a DOM.\n */\nimport React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { PluginMidiNote } from '../types/plugin-sdk.types';\n\n// ============================================================================\n// Layout constants (exported for tests)\n// ============================================================================\n\n/** Horizontal pixels per quarter-note beat. */\nexport const PX_PER_BEAT = 24;\n/** Vertical pixels per semitone row. */\nexport const ROW_HEIGHT = 12;\n/** Left keyboard-gutter width (px). */\nexport const GUTTER_W = 28;\n/** Pointer travel (px) before a press on a note becomes a drag instead of a click. */\nexport const DRAG_DEAD_ZONE = 4;\n/** Width (px) of the right-edge grab handle that resizes a note's length. */\nexport const RESIZE_HANDLE_PX = 6;\n/** Max height (px) of the vertical scroll viewport — drives load-time centering. */\nexport const SCROLL_MAX_H = 150;\n\nconst NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as const;\nconst BLACK_KEYS = new Set([1, 3, 6, 8, 10]);\n\nconst SNAP_LABELS: Record<string, string> = {\n '2': '1/2',\n '1': '1/4',\n '0.5': '1/8',\n '0.25': '1/16',\n '0.125': '1/32',\n};\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v));\n}\n\nfunction snapLabel(s: number): string {\n return SNAP_LABELS[String(s)] ?? `${s}`;\n}\n\n// ============================================================================\n// Pure helpers (DOM-free, exported for unit tests)\n// ============================================================================\n\n/** MIDI pitch → scientific note name (60 = C4). */\nexport function pitchToName(pitch: number): string {\n const name = NOTE_NAMES[((pitch % 12) + 12) % 12];\n const octave = Math.floor(pitch / 12) - 1;\n return `${name}${octave}`;\n}\n\n/**\n * Cell (pitch, startBeat) → top-left pixel offset within the grid.\n * `hi` is the highest (top) visible pitch.\n */\nexport function cellToPx(\n pitch: number,\n startBeat: number,\n hi: number,\n): { left: number; top: number } {\n return { left: startBeat * PX_PER_BEAT, top: (hi - pitch) * ROW_HEIGHT };\n}\n\n/**\n * Grid-local pixel → snapped cell. `hi` is the highest visible pitch; the beat\n * snaps to the nearest `snap` step and clamps to `[0, totalBeats - snap]`;\n * pitch clamps to `[0, 127]`.\n */\nexport function pxToCell(\n localX: number,\n localY: number,\n hi: number,\n snap: number,\n bars: number,\n beatsPerBar: number,\n): { pitch: number; startBeat: number } {\n const totalBeats = bars * beatsPerBar;\n const pitch = clamp(hi - Math.floor(localY / ROW_HEIGHT), 0, 127);\n const rawBeat = localX / PX_PER_BEAT;\n const snapped = Math.round(rawBeat / snap) * snap;\n const startBeat = clamp(snapped, 0, Math.max(0, totalBeats - snap));\n return { pitch, startBeat };\n}\n\n/**\n * New `durationBeats` for a note whose right edge is dragged to grid-local pixel\n * `localX`. The end snaps to the nearest `snap` step, is clamped to at least one\n * step past `startBeat`, and never extends beyond the grid's right edge\n * (`bars * beatsPerBar`). `startBeat` and `pitch` are untouched.\n */\nexport function resizeNoteDuration(\n startBeat: number,\n localX: number,\n snap: number,\n bars: number,\n beatsPerBar: number,\n): number {\n const totalBeats = bars * beatsPerBar;\n const snappedEnd = Math.round(localX / PX_PER_BEAT / snap) * snap;\n const end = clamp(snappedEnd, startBeat + snap, totalBeats);\n return end - startBeat;\n}\n\n/**\n * `scrollTop` that vertically centers the bulk of the notes in a `viewportH`-px\n * window. Targets the MEDIAN pitch (robust to a stray high/low outlier — keeps\n * \"where the majority of notes are\" framed) and clamps to the valid scroll\n * range. `hi` is the top visible pitch; `rowCount` the total rows in the grid.\n * Returns 0 when there are no notes.\n */\nexport function centerScrollTop(\n pitches: readonly number[],\n hi: number,\n rowCount: number,\n viewportH: number,\n): number {\n if (pitches.length === 0) return 0;\n const sorted = [...pitches].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n const median = sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;\n // Pixel center of the median row, then offset so it lands mid-viewport.\n const medianRowCenterPx = (hi - median) * ROW_HEIGHT + ROW_HEIGHT / 2;\n const maxScroll = Math.max(0, rowCount * ROW_HEIGHT - viewportH);\n return clamp(medianRowCenterPx - viewportH / 2, 0, maxScroll);\n}\n\n/** Transpose every note by `semitones`, clamping pitch to [0,127] (never drops a note). */\nexport function transposeNotes(\n notes: readonly PluginMidiNote[],\n semitones: number,\n): PluginMidiNote[] {\n return notes.map((n) => ({ ...n, pitch: clamp(n.pitch + semitones, 0, 127) }));\n}\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface PianoRollEditorProps {\n /** Controlled note list (quarter-note beats). The editor never mutates this. */\n notes: readonly PluginMidiNote[];\n /** Emitted on every edit (add / delete / move / transpose) with the full next array. */\n onChange: (next: PluginMidiNote[]) => void;\n /** Scene length in bars → grid width = bars * beatsPerBar * PX_PER_BEAT. */\n bars: number;\n /** BPM — used only for audition timing in v1. */\n bpm: number;\n /** Beats per bar (time-signature numerator). Default 4. */\n beatsPerBar?: number;\n /** Snap step in quarter notes (1 = ¼ note, 0.25 = 1/16). Default 0.25. */\n snap?: number;\n /** Snap steps the toolbar selector offers. Default [1, 0.5, 0.25]. */\n snapOptions?: number[];\n /** Notified when the user changes snap (the editor still tracks it internally). */\n onSnapChange?: (snap: number) => void;\n /** Lowest pitch always visible. Default C2 (36). */\n minPitch?: number;\n /** Highest pitch always visible. Default C6 (84). */\n maxPitch?: number;\n /** Expand the visible window to include notes outside [minPitch,maxPitch]. Default true. */\n autoFit?: boolean;\n /** Optional single-note preview, fired when a note is added. */\n onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;\n /** Velocity for newly-added notes. Default 100. */\n defaultVelocity?: number;\n /** Disable all interaction (e.g. while the track is generating). Default false. */\n disabled?: boolean;\n /** Extra className for the outer container. */\n className?: string;\n /** Test id for the outer container. Default \"sdk-piano-roll\". */\n testId?: string;\n}\n\ninterface DragState {\n /**\n * `pending-*` is an undecided press (becomes the matching committed mode once\n * the pointer travels past {@link DRAG_DEAD_ZONE}, else resolves on pointer-up):\n * pending-note → drag (move) | no travel → delete\n * pending-resize → resize | no travel → delete\n * pending-add → (on up) add a note\n */\n mode: 'pending-note' | 'pending-resize' | 'pending-add' | 'drag' | 'resize';\n /** Index into `notes` for a note press; -1 for an empty-grid press. */\n index: number;\n startX: number;\n startY: number;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function PianoRollEditor({\n notes,\n onChange,\n bars,\n bpm,\n beatsPerBar = 4,\n snap = 0.25,\n snapOptions = [1, 0.5, 0.25],\n onSnapChange,\n minPitch = 36,\n maxPitch = 84,\n autoFit = true,\n onAuditionNote,\n defaultVelocity = 100,\n disabled = false,\n className,\n testId = 'sdk-piano-roll',\n}: PianoRollEditorProps): React.ReactElement {\n const [snapState, setSnapState] = useState(snap);\n const gridRef = useRef<HTMLDivElement | null>(null);\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const dragRef = useRef<DragState | null>(null);\n // True once we've auto-centered the current note set; re-armed when the notes\n // clear or the user octave-shifts, so the view re-frames only on a fresh load.\n const didCenterRef = useRef(false);\n\n // Visible pitch window: the default [minPitch, maxPitch], expanded to include\n // any notes that fall outside (± 2 semitones of headroom). Stable + testable.\n const { lo, hi } = useMemo((): { lo: number; hi: number } => {\n if (autoFit && notes.length > 0) {\n const ps = notes.map((n) => n.pitch);\n return {\n lo: Math.max(0, Math.min(minPitch, Math.min(...ps) - 2)),\n hi: Math.min(127, Math.max(maxPitch, Math.max(...ps) + 2)),\n };\n }\n return { lo: minPitch, hi: maxPitch };\n }, [autoFit, notes, minPitch, maxPitch]);\n\n const rowCount = hi - lo + 1;\n const totalBeats = bars * beatsPerBar;\n const gridWidth = totalBeats * PX_PER_BEAT;\n const gridHeight = rowCount * ROW_HEIGHT;\n\n // Latest values for the stable pointer handlers — avoids stale closures and\n // handler re-binding (the documented render-loop hazard). Assigned during\n // render so the handlers always read current props/state.\n const stateRef = useRef({\n notes, onChange, snapState, hi, bars, beatsPerBar, defaultVelocity, bpm, onAuditionNote, disabled,\n });\n stateRef.current = {\n notes, onChange, snapState, hi, bars, beatsPerBar, defaultVelocity, bpm, onAuditionNote, disabled,\n };\n\n const localCoords = useCallback((clientX: number, clientY: number): { x: number; y: number } => {\n const rect = gridRef.current?.getBoundingClientRect();\n return { x: clientX - (rect?.left ?? 0), y: clientY - (rect?.top ?? 0) };\n }, []);\n\n const handlePointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>): void => {\n if (stateRef.current.disabled) return;\n const target = e.target as HTMLElement;\n const noteEl = target.closest('[data-testid=\"sdk-pr-note\"]') as HTMLElement | null;\n const idxAttr = noteEl?.getAttribute('data-index');\n // A press that lands on the note's right-edge handle resizes; anywhere else\n // on the note moves/deletes; empty grid adds.\n const onResizeHandle = idxAttr != null && target.closest('[data-resize-handle]') != null;\n dragRef.current = {\n mode: idxAttr == null ? 'pending-add' : onResizeHandle ? 'pending-resize' : 'pending-note',\n index: idxAttr != null ? Number(idxAttr) : -1,\n startX: e.clientX,\n startY: e.clientY,\n };\n try {\n (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);\n } catch {\n /* jsdom / unsupported — drag still works via grid-level handlers */\n }\n }, []);\n\n const handlePointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>): void => {\n const drag = dragRef.current;\n if (!drag) return;\n const dist = Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY);\n if (dist > DRAG_DEAD_ZONE) {\n if (drag.mode === 'pending-note') drag.mode = 'drag';\n else if (drag.mode === 'pending-resize') drag.mode = 'resize';\n }\n const s = stateRef.current;\n const { x, y } = localCoords(e.clientX, e.clientY);\n\n if (drag.mode === 'resize') {\n const note = s.notes[drag.index];\n if (!note) return;\n const durationBeats = resizeNoteDuration(note.startBeat, x, s.snapState, s.bars, s.beatsPerBar);\n if (durationBeats === note.durationBeats) return;\n const next = s.notes.map((n, i) => (i === drag.index ? { ...n, durationBeats } : n));\n s.onChange(next);\n return;\n }\n\n if (drag.mode !== 'drag') return;\n const { pitch, startBeat } = pxToCell(x, y, s.hi, s.snapState, s.bars, s.beatsPerBar);\n const next = s.notes.map((n, i) => (i === drag.index ? { ...n, pitch, startBeat } : n));\n s.onChange(next);\n }, [localCoords]);\n\n const handlePointerUp = useCallback((e: React.PointerEvent<HTMLDivElement>): void => {\n const drag = dragRef.current;\n dragRef.current = null;\n if (!drag) return;\n const s = stateRef.current;\n if (s.disabled) return;\n\n if (drag.mode === 'pending-note' || drag.mode === 'pending-resize') {\n // Pressed a note (body or resize handle) without dragging past the dead\n // zone → treat as a plain click → delete it.\n s.onChange(s.notes.filter((_, i) => i !== drag.index));\n return;\n }\n if (drag.mode === 'pending-add') {\n const { x, y } = localCoords(e.clientX, e.clientY);\n const { pitch, startBeat } = pxToCell(x, y, s.hi, s.snapState, s.bars, s.beatsPerBar);\n const note: PluginMidiNote = {\n pitch,\n startBeat,\n durationBeats: s.snapState,\n velocity: s.defaultVelocity,\n channel: 0,\n };\n s.onChange([...s.notes, note]);\n s.onAuditionNote?.(pitch, s.defaultVelocity, Math.max(1, s.snapState * (60 / s.bpm) * 1000));\n }\n // mode 'drag' / 'resize' already emitted their final state during pointermove.\n }, [localCoords]);\n\n const handlePointerCancel = useCallback((): void => {\n dragRef.current = null;\n }, []);\n\n const handleOctave = useCallback((delta: number): void => {\n const s = stateRef.current;\n if (s.disabled) return;\n // The whole clip jumps an octave — re-frame the view onto its new position.\n didCenterRef.current = false;\n s.onChange(transposeNotes(s.notes, delta));\n }, []);\n\n const handleSnapChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>): void => {\n const v = Number(e.target.value);\n setSnapState(v);\n onSnapChange?.(v);\n }, [onSnapChange]);\n\n // Auto-frame the notes on load: the autoFit window already contains every note\n // vertically, but the scroll viewport starts pinned to the top — so a melody\n // sitting low needs a manual scroll to find. Center the note cluster once per\n // load (re-armed on clear / octave-shift), never mid-edit or mid-drag.\n useLayoutEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n if (notes.length === 0) {\n didCenterRef.current = false;\n return;\n }\n if (didCenterRef.current || dragRef.current) return;\n didCenterRef.current = true;\n const viewportH = el.clientHeight || SCROLL_MAX_H;\n el.scrollTop = centerScrollTop(\n notes.map((n) => n.pitch),\n hi,\n rowCount,\n viewportH,\n );\n }, [notes, hi, rowCount]);\n\n // Pitch rows for the keyboard gutter, top (hi) first.\n const rows = useMemo((): number[] => {\n const out: number[] = [];\n for (let p = hi; p >= lo; p--) out.push(p);\n return out;\n }, [hi, lo]);\n\n // Beat columns + bar columns + row lines, drawn purely in CSS so the only\n // hit-testable DOM in the grid is the notes themselves.\n const gridBg = useMemo((): string => {\n const beatPx = PX_PER_BEAT;\n const barPx = PX_PER_BEAT * beatsPerBar;\n return [\n `repeating-linear-gradient(to right, transparent 0 ${beatPx - 1}px, rgba(255,255,255,0.06) ${beatPx - 1}px ${beatPx}px)`,\n `repeating-linear-gradient(to right, transparent 0 ${barPx - 1}px, rgba(255,255,255,0.16) ${barPx - 1}px ${barPx}px)`,\n `repeating-linear-gradient(to bottom, transparent 0 ${ROW_HEIGHT - 1}px, rgba(255,255,255,0.04) ${ROW_HEIGHT - 1}px ${ROW_HEIGHT}px)`,\n ].join(', ');\n }, [beatsPerBar]);\n\n const octaveDisabled = disabled || notes.length === 0;\n\n return (\n <div className={`flex flex-col gap-1 ${className ?? ''}`} data-testid={testId}>\n {/* Toolbar */}\n <div className=\"flex items-center gap-1\" data-testid=\"sdk-pr-toolbar\">\n <button\n type=\"button\"\n data-testid=\"sdk-pr-octave-down\"\n disabled={octaveDisabled}\n onClick={() => handleOctave(-12)}\n className=\"px-1.5 py-0.5 text-[10px] rounded-sm border border-sas-border text-sas-muted hover:text-sas-accent hover:border-sas-accent transition-colors disabled:opacity-40\"\n title=\"Octave down (−12 semitones)\"\n >\n Oct −\n </button>\n <button\n type=\"button\"\n data-testid=\"sdk-pr-octave-up\"\n disabled={octaveDisabled}\n onClick={() => handleOctave(12)}\n className=\"px-1.5 py-0.5 text-[10px] rounded-sm border border-sas-border text-sas-muted hover:text-sas-accent hover:border-sas-accent transition-colors disabled:opacity-40\"\n title=\"Octave up (+12 semitones)\"\n >\n Oct +\n </button>\n <label className=\"flex items-center gap-1 text-[10px] text-sas-muted/70 ml-1\">\n Snap\n <select\n data-testid=\"sdk-pr-snap\"\n value={snapState}\n disabled={disabled}\n onChange={handleSnapChange}\n className=\"sas-input px-1 py-0.5 text-[10px]\"\n >\n {snapOptions.map((s) => (\n <option key={s} value={s}>\n {snapLabel(s)}\n </option>\n ))}\n </select>\n </label>\n <span className=\"text-[10px] text-sas-muted/60 ml-auto\" data-testid=\"sdk-pr-note-count\">\n {notes.length} {notes.length === 1 ? 'note' : 'notes'}\n </span>\n </div>\n\n {/* Scroll region: keyboard gutter + note grid */}\n <div\n ref={scrollRef}\n className=\"overflow-auto border border-sas-border rounded-sm bg-sas-bg\"\n style={{ maxHeight: SCROLL_MAX_H }}\n data-testid=\"sdk-pr-scroll\"\n >\n <div className=\"flex\" style={{ width: GUTTER_W + gridWidth }}>\n {/* Keyboard gutter — pinned left during horizontal scroll */}\n <div\n data-testid=\"sdk-pr-gutter\"\n className=\"sticky left-0 z-10 flex-shrink-0 bg-sas-panel-alt\"\n style={{ width: GUTTER_W }}\n >\n {rows.map((p) => (\n <div\n key={p}\n data-testid=\"sdk-pr-key\"\n data-pitch={p}\n className={`flex items-center justify-end pr-1 text-[8px] leading-none border-b border-sas-border/30 ${\n BLACK_KEYS.has(((p % 12) + 12) % 12)\n ? 'bg-sas-bg text-sas-muted/40'\n : 'text-sas-muted/70'\n }`}\n style={{ height: ROW_HEIGHT }}\n >\n {p % 12 === 0 ? pitchToName(p) : ''}\n </div>\n ))}\n </div>\n\n {/* Note grid */}\n <div\n ref={gridRef}\n data-testid=\"sdk-pr-grid\"\n className=\"relative flex-shrink-0\"\n style={{\n width: gridWidth,\n height: gridHeight,\n backgroundImage: gridBg,\n cursor: disabled ? 'not-allowed' : 'crosshair',\n touchAction: 'none',\n }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerCancel}\n >\n {notes.map((n, i) => {\n const { left, top } = cellToPx(n.pitch, n.startBeat, hi);\n const width = Math.max(3, n.durationBeats * PX_PER_BEAT);\n // Handle never exceeds half the note, so even a 1-step note keeps a\n // left \"body\" zone for moving.\n const handleW = Math.min(RESIZE_HANDLE_PX, width / 2);\n return (\n <div\n key={i}\n data-testid=\"sdk-pr-note\"\n data-index={i}\n data-pitch={n.pitch}\n data-start-beat={n.startBeat}\n data-duration-beats={n.durationBeats}\n className=\"absolute rounded-[2px] bg-sas-accent/80 border border-sas-accent hover:bg-sas-accent\"\n style={{ left, top, width, height: ROW_HEIGHT }}\n title={`${pitchToName(n.pitch)} · beat ${n.startBeat} · ${n.durationBeats}♪ · vel ${n.velocity}`}\n >\n {!disabled && (\n <div\n data-resize-handle=\"\"\n data-testid=\"sdk-pr-note-resize\"\n className=\"absolute top-0 right-0 h-full rounded-r-[2px] hover:bg-sas-bg/40\"\n style={{ width: handleW, cursor: 'ew-resize' }}\n />\n )}\n </div>\n );\n })}\n {notes.length === 0 && (\n <div\n data-testid=\"sdk-pr-empty\"\n className=\"absolute inset-0 flex items-center justify-center text-[10px] text-sas-muted/50 pointer-events-none\"\n >\n No notes — click to add\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n );\n}\n\nexport default PianoRollEditor;\n","/**\n * ConfirmDialog — styled in-app confirmation modal (SDK component).\n *\n * A small, reusable \"are you sure?\" dialog matching the app's dark theme\n * (mirrors ImportTrackModal chrome: sas-panel / sas-border / shadow-xl). It\n * guards destructive actions; the first consumer is track deletion, which was\n * one stray click away from losing a track's MIDI + sound.\n *\n * Controlled component — the caller owns `open` and the confirm/cancel\n * handlers. Escape and a backdrop click both cancel, and the Cancel button is\n * auto-focused on open so a reflexive Enter dismisses rather than deletes.\n *\n * @since SDK 2.17.0\n */\n\nimport React, { useRef } from 'react';\nimport { Modal } from './Modal';\n\nexport interface ConfirmDialogProps {\n /** Controls visibility (the caller owns open/closed). */\n open: boolean;\n /** Bold heading line. */\n title: string;\n /** Body copy — a string or richer node. */\n message: React.ReactNode;\n /** Confirm button label (default \"Delete\"). */\n confirmLabel?: string;\n /** Cancel button label (default \"Cancel\"). */\n cancelLabel?: string;\n /** When true (default), the confirm button reads as a destructive (red) action. */\n destructive?: boolean;\n /** Fired when the user confirms. */\n onConfirm: () => void;\n /** Fired on Cancel, Escape, or backdrop click. */\n onCancel: () => void;\n /** data-testid prefix so each dialog is addressable in tests. */\n testIdPrefix?: string;\n}\n\nexport function ConfirmDialog({\n open,\n title,\n message,\n confirmLabel = 'Delete',\n cancelLabel = 'Cancel',\n destructive = true,\n onConfirm,\n onCancel,\n testIdPrefix = 'confirm-dialog',\n}: ConfirmDialogProps): React.ReactElement | null {\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n // Escape, backdrop click, and focus-on-open are owned by the shared <Modal>.\n return (\n <Modal open={open} onClose={onCancel} testIdPrefix={testIdPrefix} initialFocusRef={cancelRef}>\n <div\n className=\"w-[360px] max-w-[90vw] flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl\"\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n data-testid={`${testIdPrefix}-modal`}\n >\n {/* Header */}\n <div className=\"px-4 py-3 border-b border-sas-border\">\n <span className=\"text-sm font-medium text-sas-text\" data-testid={`${testIdPrefix}-title`}>\n {title}\n </span>\n </div>\n\n {/* Body */}\n <div\n className=\"px-4 py-3 text-xs text-sas-muted leading-relaxed break-words\"\n data-testid={`${testIdPrefix}-message`}\n >\n {message}\n </div>\n\n {/* Footer */}\n <div className=\"flex justify-end gap-2 px-4 py-3 border-t border-sas-border\">\n <button\n ref={cancelRef}\n type=\"button\"\n className=\"px-3 py-1 rounded-sm text-xs font-medium border border-sas-border bg-sas-panel-alt text-sas-text hover:border-sas-accent hover:text-sas-accent transition-colors\"\n onClick={onCancel}\n data-testid={`${testIdPrefix}-cancel`}\n >\n {cancelLabel}\n </button>\n <button\n type=\"button\"\n className={`px-3 py-1 rounded-sm text-xs font-medium border transition-colors ${\n destructive\n ? 'border-sas-danger bg-sas-danger/20 text-sas-danger hover:bg-sas-danger hover:text-sas-bg'\n : 'border-sas-accent bg-sas-accent/20 text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n }`}\n onClick={onConfirm}\n data-testid={`${testIdPrefix}-confirm`}\n >\n {confirmLabel}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport default ConfirmDialog;\n","/**\n * Modal — the SDK's one modal-stacking primitive (portal + z-tier + backdrop).\n *\n * Every SDK modal renders INSIDE a plugin's accordion section, whose animated\n * `overflow-hidden` + `transition-all` wrapper establishes a stacking context.\n * An inline `position: fixed` overlay is therefore scoped to that section and\n * can be painted UNDER a neighbouring panel (the \"import modal invisible on a\n * later open\" bug). This component solves that once: it portals the overlay to\n * <body> — out of every panel's stacking context — at a z-tier above all the\n * app's `z-50` dropdowns/banners but below the toast tier (`z-[9999]`), so\n * toasts still float over modals.\n *\n * Controlled: the caller owns `open` and `onClose`. The caller renders its own\n * dialog box as `children` (keep the box's `onClick={e => e.stopPropagation()}`\n * so inside-clicks don't dismiss). Escape and a backdrop click both close.\n *\n * @since SDK 2.21.0\n */\n\nimport React, { useEffect } from 'react';\nimport { createPortal } from 'react-dom';\n\nexport interface ModalProps {\n /** Controls visibility (the caller owns open/closed). */\n open: boolean;\n /** Close handler — fired on Escape and backdrop click. */\n onClose: () => void;\n /** The dialog box. Give it `onClick={e => e.stopPropagation()}`. */\n children: React.ReactNode;\n /** data-testid prefix; the backdrop is `${testIdPrefix}-overlay`. */\n testIdPrefix?: string;\n /** Close when the backdrop is clicked (default true). */\n closeOnBackdrop?: boolean;\n /** Close on Escape (default true). */\n closeOnEscape?: boolean;\n /** Focused when the modal opens (e.g. a Cancel button) so a reflexive Enter is safe. */\n initialFocusRef?: React.RefObject<HTMLElement>;\n}\n\nexport function Modal({\n open,\n onClose,\n children,\n testIdPrefix = 'modal',\n closeOnBackdrop = true,\n closeOnEscape = true,\n initialFocusRef,\n}: ModalProps): React.ReactElement | null {\n // Escape closes; focus the requested element on open.\n useEffect(() => {\n if (!open) return undefined;\n const onKey = (e: KeyboardEvent): void => {\n if (closeOnEscape && e.key === 'Escape') {\n e.preventDefault();\n onClose();\n }\n };\n window.addEventListener('keydown', onKey);\n initialFocusRef?.current?.focus();\n return () => window.removeEventListener('keydown', onKey);\n }, [open, onClose, closeOnEscape, initialFocusRef]);\n\n if (!open) return null;\n\n return createPortal(\n <div\n className=\"fixed inset-0 z-[1000] flex items-center justify-center bg-black/60\"\n data-testid={`${testIdPrefix}-overlay`}\n onClick={closeOnBackdrop ? onClose : undefined}\n >\n {children}\n </div>,\n document.body,\n );\n}\n\nexport default Modal;\n","/**\n * Shared level-meter component.\n *\n * Renders a horizontal LED-style bar over -60dBFS → 0dBFS:\n * - A fixed left-to-right gradient (green → orange → red), so the color is\n * tied to POSITION: a quiet signal lights only the green left, a hot signal\n * reaches the red right. An \"unlit\" mask hides the gradient beyond the\n * current level.\n * - A deterministic segment grid (the \"LED monitor\" look) drawn as a pure-CSS\n * repeating overlay — constant DOM, no per-frame cost.\n * - An optional peak-hold marker (`peakHoldDb`) — a bright line at the recent\n * maximum that the caller holds/decays (see `useTrackMeter`).\n * - An optional CLIP badge the caller wires up.\n *\n * Pure presentational: takes the current dB + `active` flag (+ optional held\n * peak) and draws. The only production consumer is the per-track strip\n * (`TrackMeterStrip`, via `compact`). `compact` shrinks the bar and drops the\n * numeric dB readout.\n */\n\nimport React from 'react';\n\n// Traffic-light gradient (introduced for the LED meter; the Magic Terminal\n// palette has no green/orange/red tokens). Tweakable.\nconst COLOR_GREEN = '#2BD576';\nconst COLOR_ORANGE = '#F5A623';\nconst COLOR_RED = '#FF4D5E';\nconst COLOR_TRACK_BG = '#121822'; // panel-alt — the unlit bar / mask\nconst COLOR_TRACK_BORDER = '#1F2A3A'; // border\nconst COLOR_SEGMENT_GAP = '#0A0E14'; // dark gutter between LED cells\nconst COLOR_PEAK = '#F7FFFB'; // held-peak marker (bright)\n\n// The positional gradient. Mostly green, orange in the upper-mid, red near the\n// top — the classic meter feel, while still visibly tri-color across the bar.\nconst METER_GRADIENT = `linear-gradient(90deg, ${COLOR_GREEN} 0%, ${COLOR_GREEN} 45%, ${COLOR_ORANGE} 72%, ${COLOR_RED} 90%, ${COLOR_RED} 100%)`;\n\n// Deterministic LED sections + the gutter width between them.\nconst SEGMENTS = 22;\nconst SEGMENT_GAP_PX = 2;\n\n/** dBFS → bar % : -60dB → 0%, 0dB → 100%, clamped. */\nfunction dbToPct(db: number): number {\n return Math.max(0, Math.min(100, ((db + 60) / 60) * 100));\n}\n\nexport interface LevelMeterProps {\n /** Current peak level in dBFS. -120 means \"no signal\". */\n peakDb: number;\n /** True when the underlying audio callback is firing. False = floor. */\n active: boolean;\n /**\n * Held peak in dBFS for the peak-hold marker. Omit to draw no marker. The\n * marker is hidden when this is at/below the visible floor (-60).\n */\n peakHoldDb?: number;\n /** Latched clip flag. When true, render the CLIP badge. */\n clipped?: boolean;\n /** User-clickable handler to clear the latched clip indicator. */\n onClearClip?: () => void;\n /**\n * Thin strip mode for per-track meters: hides the numeric dB readout and\n * shrinks the bar. Keeps the (rare) CLIP badge.\n */\n compact?: boolean;\n /** Optional className overlaid on the wrapper for layout tweaks. */\n className?: string;\n /** Inline test id — make multiple instances distinguishable. */\n 'data-testid'?: string;\n}\n\nexport const LevelMeter: React.FC<LevelMeterProps> = ({\n peakDb,\n active,\n peakHoldDb,\n clipped,\n onClearClip,\n compact = false,\n className,\n 'data-testid': testId,\n}) => {\n const id = testId ?? 'sas-level-meter';\n const widthPct = active ? dbToPct(peakDb) : 0;\n const showPeak = peakHoldDb != null && active && peakHoldDb > -60;\n const peakHoldPct = showPeak ? dbToPct(peakHoldDb!) : 0;\n\n return (\n <div\n className={`sas-level-meter ${className ?? ''}`}\n data-testid={id}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: compact ? 0 : 6,\n }}\n >\n <div\n style={{\n position: 'relative',\n flex: 1,\n height: compact ? 5 : 7,\n background: COLOR_TRACK_BG,\n border: `1px solid ${COLOR_TRACK_BORDER}`,\n borderRadius: 2,\n overflow: 'hidden',\n minWidth: compact ? 0 : 60,\n }}\n >\n {/* Positional green→orange→red gradient, full bar width. */}\n <div style={{ position: 'absolute', inset: 0, background: METER_GRADIENT }} />\n\n {/* Unlit mask: hides the gradient from the current level rightward. */}\n <div\n style={{\n position: 'absolute',\n top: 0,\n bottom: 0,\n left: `${widthPct}%`,\n right: 0,\n background: COLOR_TRACK_BG,\n transition: 'left 30ms linear',\n }}\n />\n\n {/* Deterministic LED segment gutters — pure CSS, constant DOM. */}\n <div\n data-testid={`${id}-segments`}\n style={{\n position: 'absolute',\n inset: 0,\n pointerEvents: 'none',\n backgroundImage: `linear-gradient(90deg, transparent 0, transparent calc(100% - ${SEGMENT_GAP_PX}px), ${COLOR_SEGMENT_GAP} calc(100% - ${SEGMENT_GAP_PX}px), ${COLOR_SEGMENT_GAP} 100%)`,\n backgroundSize: `calc(100% / ${SEGMENTS}) 100%`,\n }}\n />\n\n {/* Peak-hold marker: a bright line at the recent maximum. */}\n {showPeak && (\n <div\n data-testid={`${id}-peak`}\n style={{\n position: 'absolute',\n top: -1,\n bottom: -1,\n left: `${peakHoldPct}%`,\n width: 2,\n marginLeft: -1,\n background: COLOR_PEAK,\n boxShadow: '0 0 4px rgba(247, 255, 251, 0.7)',\n transition: 'left 80ms linear',\n }}\n title=\"Peak\"\n />\n )}\n </div>\n\n {!compact && (\n <span\n style={{\n fontSize: 10,\n color: 'var(--sas-muted, #888)',\n fontVariantNumeric: 'tabular-nums',\n minWidth: 48,\n textAlign: 'right',\n }}\n >\n {active && peakDb > -120 ? `${peakDb.toFixed(0)} dB` : '—'}\n </span>\n )}\n {clipped && (\n <span\n data-testid={`${id}-clip`}\n onClick={onClearClip}\n style={{\n padding: '1px 5px',\n fontSize: 9,\n fontWeight: 'bold',\n background: COLOR_RED,\n color: '#0A0E14',\n borderRadius: 2,\n cursor: onClearClip ? 'pointer' : 'default',\n marginLeft: compact ? 3 : 0,\n }}\n title={onClearClip ? 'Clipped — click to clear' : 'Clipped'}\n >\n CLIP\n </span>\n )}\n </div>\n );\n};\n\nexport default LevelMeter;\n","/**\n * useTrackLevels — drives the cosmetic per-track strip meters.\n *\n * The hard constraint for this feature is \"playback ALWAYS wins over the GUI;\n * NO blocking threads.\" This hook is built around that:\n *\n * - It polls `host.getTrackLevels()` at ~30Hz with a recursive setTimeout that\n * only schedules the NEXT tick AFTER the previous await resolves. That is\n * automatic backpressure: a slow/stalled engine simply slows the meter, it\n * can never queue a backlog of requests. (The host + bridge also coalesce,\n * so a busy engine yields a STALE snapshot, never a pile-up.)\n * - It writes into a ref-held Map and notifies row subscribers, so the OWNING\n * panel never re-renders at 30Hz. Each row reads its own value via\n * `useTrackLevel` and re-renders only itself.\n * - It polls while the panel is mounted and the window is visible, and pauses\n * when the window is hidden. It deliberately does NOT gate on transport\n * \"is playing\": this app drives playback through decks / the clip launcher,\n * and the linear-transport play flag does not track that reliably. When\n * audio is stopped the engine simply returns floor levels, so the bars are\n * empty anyway — no need (and no reliable signal) to stop polling.\n *\n * Usage (panel):\n * const levels = useTrackLevels(host);\n * ...<TrackRow levels={levels} ... /> // row calls useTrackLevel(levels, id)\n */\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { PluginHost, PluginTrackLevel } from '../types/plugin-sdk.types';\n\n/** [MeterDiagR] per-trackId throttle for the dead-meter renderer-side diagnostic. */\nconst meterDiagRLast = new Map<string, number>();\n\n/** Polling cadence — matches the recording input meter (~30Hz). */\nconst POLL_INTERVAL_MS = 33;\n/** Slow idle re-check while the window is hidden (polling is paused). */\nconst HIDDEN_RECHECK_MS = 250;\n\n/** dBFS floor / \"no signal\" sentinel (matches PluginTrackLevel). */\nconst METER_FLOOR_DB = -120;\n/** Hold the peak marker this long after a fresh peak before it starts to fall. */\nconst PEAK_HOLD_MS = 1500;\n/** Fall rate once the hold window expires (dB per second). */\nconst PEAK_DECAY_DB_PER_SEC = 24;\n\n/**\n * Stable handle returned by {@link useTrackLevels}. Rows read their own level\n * and subscribe to per-tick notifications through it; its identity is stable\n * across renders so a row's subscription is set up once.\n */\nexport interface TrackLevelsHandle {\n /** Current level for a track, or null when idle/absent (renders an empty bar). */\n getLevel(trackId: string): PluginTrackLevel | null;\n /** Subscribe to per-tick updates. Returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n}\n\nfunction isHidden(): boolean {\n return typeof document !== 'undefined' && document.hidden === true;\n}\n\n/**\n * Poll every owned track's level while mounted + visible. Returns a stable\n * handle; the owning component does NOT re-render per tick. Pass `enabled =\n * false` to turn it off entirely (e.g. a panel that wants no meters). Safe to\n * call even when the host predates `getTrackLevels` (older SDK) — it stays idle.\n */\nexport function useTrackLevels(\n host: PluginHost | null | undefined,\n enabled: boolean = true\n): TrackLevelsHandle {\n const mapRef = useRef<Map<string, PluginTrackLevel>>(new Map());\n const listenersRef = useRef<Set<() => void>>(new Set());\n\n // Built exactly once so the handle identity is stable across renders.\n const handleRef = useRef<TrackLevelsHandle | null>(null);\n if (handleRef.current === null) {\n handleRef.current = {\n getLevel: (trackId: string) => mapRef.current.get(trackId) ?? null,\n subscribe: (listener: () => void) => {\n listenersRef.current.add(listener);\n return () => {\n listenersRef.current.delete(listener);\n };\n },\n };\n }\n\n useEffect(() => {\n const notify = (): void => {\n listenersRef.current.forEach((l) => l());\n };\n\n const clearToIdle = (): void => {\n if (mapRef.current.size > 0) {\n mapRef.current.clear();\n notify();\n }\n };\n\n const canPoll =\n enabled && !!host && typeof host.getTrackLevels === 'function';\n\n if (!canPoll) {\n clearToIdle();\n return;\n }\n\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const schedule = (delay: number): void => {\n if (stopped) return;\n timer = setTimeout(tick, delay);\n };\n\n const tick = async (): Promise<void> => {\n if (stopped) return;\n\n // Paused while the window is hidden: do no engine work, just idle-poll\n // until it comes back. (visibilitychange below resumes immediately.)\n if (isHidden()) {\n schedule(HIDDEN_RECHECK_MS);\n return;\n }\n\n try {\n const levels = await host!.getTrackLevels!();\n if (stopped) return;\n\n // Rebuild the map: upsert present tracks, drop ones that vanished.\n const seen = new Set<string>();\n for (const lvl of levels) {\n mapRef.current.set(lvl.trackId, lvl);\n seen.add(lvl.trackId);\n }\n for (const key of Array.from(mapRef.current.keys())) {\n if (!seen.has(key)) mapRef.current.delete(key);\n }\n notify();\n } catch {\n // Cosmetic meter: swallow transient read failures and keep polling.\n }\n\n // Schedule the NEXT tick only now — backpressure: never overlap reads.\n schedule(POLL_INTERVAL_MS);\n };\n\n const onVisibility = (): void => {\n if (stopped) return;\n if (!isHidden()) {\n // Becoming visible: cancel the slow idle-poll and resume immediately.\n if (timer) clearTimeout(timer);\n void tick();\n }\n };\n\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', onVisibility);\n }\n\n void tick();\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', onVisibility);\n }\n // Leave the map intact on teardown; the next active effect rebuilds it.\n };\n }, [host, enabled]);\n\n return handleRef.current;\n}\n\n/** Cheap equality so unchanged rows skip re-rendering between ticks. */\nfunction sameLevel(\n a: PluginTrackLevel | null,\n b: PluginTrackLevel | null\n): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n return a.peakDb === b.peakDb && a.clipped === b.clipped;\n}\n\n/**\n * Per-row selector. Subscribes to the shared scheduler and re-renders ONLY the\n * calling component when this track's level changes. Returns null when idle\n * (transport stopped, window hidden, or the track has no meter yet).\n */\nexport function useTrackLevel(\n handle: TrackLevelsHandle | null | undefined,\n trackId: string\n): PluginTrackLevel | null {\n const [level, setLevel] = useState<PluginTrackLevel | null>(null);\n\n useEffect(() => {\n if (!handle) {\n setLevel(null);\n return;\n }\n const update = (): void => {\n const next = handle.getLevel(trackId);\n setLevel((prev) => (sameLevel(prev, next) ? prev : next));\n };\n update(); // seed immediately\n return handle.subscribe(update);\n }, [handle, trackId]);\n\n return level;\n}\n\n/**\n * Per-row meter view-model: the current level plus a held peak for the meter UI.\n */\nexport interface TrackMeterView {\n /** Current mono peak in dBFS (floored at -120). */\n peakDb: number;\n /** Held peak in dBFS — stays at the recent maximum for ~PEAK_HOLD_MS, then falls. */\n peakHoldDb: number;\n /** Latched clip flag for the last poll window. */\n clipped: boolean;\n /** True when the track currently has a live meter row. */\n active: boolean;\n}\n\nconst IDLE_METER_VIEW: TrackMeterView = {\n peakDb: METER_FLOOR_DB,\n peakHoldDb: METER_FLOOR_DB,\n clipped: false,\n active: false,\n};\n\n/** Equality gate for the meter view. Quantizes the held peak to ½ dB so a\n * steady hold and sub-pixel decay don't thrash renders, while a real change\n * (level jitter, decay step, clip, active) still re-renders the strip. */\nfunction sameMeter(a: TrackMeterView, b: TrackMeterView): boolean {\n return (\n a.active === b.active &&\n a.clipped === b.clipped &&\n a.peakDb === b.peakDb &&\n Math.round(a.peakHoldDb * 2) === Math.round(b.peakHoldDb * 2)\n );\n}\n\n/**\n * Per-row meter selector WITH PEAK-HOLD. Like {@link useTrackLevel} it subscribes\n * to the shared ~30Hz scheduler and re-renders only the calling component, but it\n * also tracks a held peak that stays at the recent maximum for ~PEAK_HOLD_MS then\n * decays — so the eye can register where the signal peaked while the bar itself\n * moves fast. No extra timers or rAF: the held value is recomputed on each\n * scheduler notify, using performance.now() for hold/decay timing.\n */\nexport function useTrackMeter(\n handle: TrackLevelsHandle | null | undefined,\n trackId: string\n): TrackMeterView {\n const [view, setView] = useState<TrackMeterView>(IDLE_METER_VIEW);\n\n // Peak-hold state lives in refs so it survives between notifies without\n // forcing a render; only the derived `view` is state.\n const heldDbRef = useRef(METER_FLOOR_DB);\n const heldAtRef = useRef(0);\n const lastTickRef = useRef(0);\n\n useEffect(() => {\n if (!handle) {\n heldDbRef.current = METER_FLOOR_DB;\n lastTickRef.current = 0;\n setView(IDLE_METER_VIEW);\n return;\n }\n\n const update = (): void => {\n const level = handle.getLevel(trackId);\n // [MeterDiagR] throttled per-trackId: does THIS row's lookup HIT or MISS?\n // Pairs with the host [MeterDiag] to prove whether the id the renderer looks\n // up matches the id the levels are keyed by.\n const dNow = Date.now();\n if ((meterDiagRLast.get(trackId) ?? 0) < dNow - 3000) {\n meterDiagRLast.set(trackId, dNow);\n // eslint-disable-next-line no-console\n console.log(`[MeterDiagR] lookup trackId=${trackId} → ${level === null ? 'MISS (no level for this id)' : 'hit'}`);\n }\n const now = performance.now();\n const dtSec = lastTickRef.current ? Math.max(0, (now - lastTickRef.current) / 1000) : 0;\n lastTickRef.current = now;\n\n if (level === null) {\n // No live row for this track — go idle and reset the hold.\n heldDbRef.current = METER_FLOOR_DB;\n setView((prev) => (sameMeter(prev, IDLE_METER_VIEW) ? prev : IDLE_METER_VIEW));\n return;\n }\n\n const p = level.peakDb;\n if (p >= heldDbRef.current) {\n // Fresh peak: snap the held value up and restart the hold window.\n heldDbRef.current = p;\n heldAtRef.current = now;\n } else if (now - heldAtRef.current > PEAK_HOLD_MS) {\n // Hold expired: fall toward the current level.\n heldDbRef.current = Math.max(p, heldDbRef.current - PEAK_DECAY_DB_PER_SEC * dtSec);\n }\n // else: still within the hold window — keep the held value steady.\n\n const next: TrackMeterView = {\n peakDb: p,\n peakHoldDb: heldDbRef.current,\n clipped: level.clipped,\n active: true,\n };\n setView((prev) => (sameMeter(prev, next) ? prev : next));\n };\n\n update(); // seed immediately\n return handle.subscribe(update);\n }, [handle, trackId]);\n\n return view;\n}\n\n/**\n * Track the transport's play/stop state for a plugin. Seeds from\n * `getTransportState()` and follows `onTransportEvent`. Use its result as the\n * `active` arg to {@link useTrackLevels} so meters animate only during playback.\n */\nexport function useTransportPlaying(host: PluginHost | null | undefined): boolean {\n const [playing, setPlaying] = useState(false);\n\n useEffect(() => {\n if (!host) {\n setPlaying(false);\n return;\n }\n let cancelled = false;\n\n host\n .getTransportState()\n .then((state) => {\n if (!cancelled) setPlaying(!!state.isPlaying);\n })\n .catch(() => {\n /* seed best-effort; events will correct it */\n });\n\n const unsub = host.onTransportEvent?.((evt) => {\n if (typeof evt.isPlaying === 'boolean') {\n setPlaying(evt.isPlaying);\n } else if (evt.type === 'play') {\n setPlaying(true);\n } else if (evt.type === 'stop' || evt.type === 'pause') {\n setPlaying(false);\n }\n });\n\n return () => {\n cancelled = true;\n unsub?.();\n };\n }, [host]);\n\n return playing;\n}\n","/**\n * TrackMeterStrip — the thin per-track peak meter welded to the bottom of a\n * track row. Cosmetic: gives a general sense of each track's level and adds\n * motion during playback.\n *\n * This is deliberately its OWN component so the per-row meter selector\n * (`useTrackMeter`) re-renders ONLY this strip at ~30Hz, never the heavy\n * TrackRow around it. Render it as a full-width sibling directly under a row\n * body; it welds on with a squared top edge (like the track drawer does).\n */\n\nimport React from 'react';\nimport { LevelMeter } from './LevelMeter';\nimport { useTrackMeter, type TrackLevelsHandle } from '../hooks/useTrackLevels';\n\nexport interface TrackMeterStripProps {\n /** Shared meter handle from `useTrackLevels(host, isPlaying)`. */\n levels: TrackLevelsHandle;\n /** Tracktion engine track id (matches `PluginTrackHandle.id`). */\n trackId: string;\n /** Round the bottom corners (false when a drawer welds on below). Default true. */\n roundBottom?: boolean;\n /** Optional className for layout tweaks on the wrapper. */\n className?: string;\n}\n\nexport const TrackMeterStrip: React.FC<TrackMeterStripProps> = ({\n levels,\n trackId,\n roundBottom = true,\n className,\n}) => {\n const meter = useTrackMeter(levels, trackId);\n\n return (\n <div\n data-testid=\"sdk-track-meter\"\n className={`w-full px-2 py-1 bg-sas-panel-alt border border-t-0 border-sas-border ${roundBottom ? 'rounded-b-sm' : ''} ${className ?? ''}`}\n >\n <LevelMeter\n compact\n active={meter.active}\n peakDb={meter.peakDb}\n peakHoldDb={meter.peakHoldDb}\n clipped={meter.clipped}\n data-testid={`sdk-track-meter-bar-${trackId}`}\n />\n </div>\n );\n};\n\nexport default TrackMeterStrip;\n","/**\n * VolumeSlider Component\n *\n * Compact horizontal volume slider for track volume control.\n * Uses native HTML range input with custom styling.\n */\n\nimport React, { useCallback, useState, useRef, useEffect } from 'react';\nimport { sliderToDb } from '../utils/volume-conversion';\n\ninterface VolumeSliderProps {\n /** Volume value from 0 to 1 */\n value: number;\n /** Called when volume changes (debounced) */\n onChange: (value: number) => void;\n /** Disable the slider */\n disabled?: boolean;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Format slider value as dB for tooltip display\n */\nfunction formatDb(value: number): string {\n const db = sliderToDb(value);\n if (db <= -60) return '-∞ dB';\n const sign = db >= 0 ? '+' : '';\n return `${sign}${db.toFixed(1)} dB`;\n}\n\n/**\n * Debounce helper for volume changes\n */\nfunction useDebouncedCallback<T extends (...args: never[]) => void>(\n callback: T,\n delay: number\n): T {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n\n // Update callback ref when callback changes\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n const debouncedCallback = useCallback(\n (...args: Parameters<T>) => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n }, delay);\n },\n [delay]\n ) as T;\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return debouncedCallback;\n}\n\nexport const VolumeSlider: React.FC<VolumeSliderProps> = ({\n value,\n onChange,\n disabled = false,\n className = '',\n}) => {\n // Local state for immediate visual feedback\n const [localValue, setLocalValue] = useState(value);\n const [isDragging, setIsDragging] = useState(false);\n\n // Sync local value with prop when not dragging\n useEffect(() => {\n if (!isDragging) {\n setLocalValue(value);\n }\n }, [value, isDragging]);\n\n // Debounced onChange to prevent IPC spam\n const debouncedOnChange = useDebouncedCallback(onChange, 50);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = parseFloat(e.target.value);\n setLocalValue(newValue);\n debouncedOnChange(newValue);\n },\n [debouncedOnChange]\n );\n\n const handleMouseDown = useCallback(() => {\n setIsDragging(true);\n }, []);\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n // Send final value immediately on release\n onChange(localValue);\n }, [localValue, onChange]);\n\n return (\n <div\n className={`flex items-center ${className}`}\n title={`Volume: ${formatDb(localValue)}`}\n >\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={localValue}\n onChange={handleChange}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onTouchStart={handleMouseDown}\n onTouchEnd={handleMouseUp}\n disabled={disabled}\n className={`\n w-full h-1.5 rounded-full appearance-none cursor-pointer\n bg-gray-700\n disabled:opacity-50 disabled:cursor-not-allowed\n [&::-webkit-slider-thumb]:appearance-none\n [&::-webkit-slider-thumb]:w-3\n [&::-webkit-slider-thumb]:h-3\n [&::-webkit-slider-thumb]:rounded-full\n [&::-webkit-slider-thumb]:bg-sas-accent\n [&::-webkit-slider-thumb]:cursor-pointer\n [&::-webkit-slider-thumb]:transition-transform\n [&::-webkit-slider-thumb]:hover:scale-110\n [&::-moz-range-thumb]:w-3\n [&::-moz-range-thumb]:h-3\n [&::-moz-range-thumb]:rounded-full\n [&::-moz-range-thumb]:bg-sas-accent\n [&::-moz-range-thumb]:border-0\n [&::-moz-range-thumb]:cursor-pointer\n `}\n />\n </div>\n );\n};\n\nexport default VolumeSlider;\n","/**\n * Volume Conversion Utilities\n *\n * Converts between UI slider position (0-1) and engine dB values using a power\n * curve with +6 dB headroom. The curve places unity gain (0 dB) at slider 0.75,\n * giving the top 25% of the slider a meaningful 6 dB boost range instead of the\n * previous perceptual dead zone.\n *\n * Mapping:\n * slider 0.00 → -60 dB (silence)\n * slider 0.75 → 0 dB (unity gain)\n * slider 1.00 → +6 dB (max boost)\n */\n\n/** Slider position that maps to 0 dB (unity gain) */\nexport const SLIDER_UNITY = 0.75;\n\n/** Maximum dB value at slider = 1.0 */\nexport const DB_MAX = 6;\n\n/** Minimum dB value (silence floor) */\nexport const DB_MIN = -60;\n\n/**\n * Exponent derived so that slider=1.0 yields exactly DB_MAX dB.\n *\n * gain_at_1 = (1 / SLIDER_UNITY) ^ EXPONENT = 10^(DB_MAX/20)\n * EXPONENT = log(10^(DB_MAX/20)) / log(1/SLIDER_UNITY)\n */\nconst EXPONENT: number =\n Math.log(Math.pow(10, DB_MAX / 20)) / Math.log(1 / SLIDER_UNITY);\n\n/**\n * Convert a UI slider position (0-1) to engine dB.\n *\n * @param slider - Slider value in [0, 1]\n * @returns dB value in [DB_MIN, DB_MAX]\n */\nexport function sliderToDb(slider: number): number {\n if (slider <= 0) return DB_MIN;\n const gain = Math.pow(slider / SLIDER_UNITY, EXPONENT);\n const db = 20 * Math.log10(gain);\n return Math.max(DB_MIN, Math.min(DB_MAX, db));\n}\n\n/**\n * Convert an engine dB value back to a UI slider position (0-1).\n * Inverse of sliderToDb().\n *\n * @param db - Volume in dB\n * @returns Slider value in [0, 1]\n */\nexport function dbToSlider(db: number): number {\n if (db <= DB_MIN) return 0;\n if (db >= DB_MAX) return 1;\n const gain = Math.pow(10, db / 20);\n const slider = SLIDER_UNITY * Math.pow(gain, 1 / EXPONENT);\n return Math.min(1, Math.max(0, slider));\n}\n","/**\n * PanSlider Component\n *\n * Compact horizontal pan slider for track stereo positioning.\n * Range: -1 (left) to +1 (right), 0 = center.\n * No text label - tooltip only.\n */\n\nimport React, { useCallback, useState, useRef, useEffect } from 'react';\n\ninterface PanSliderProps {\n /** Pan value from -1 (left) to 1 (right), 0 = center */\n value: number;\n /** Called when pan changes (debounced) */\n onChange: (value: number) => void;\n /** Disable the slider */\n disabled?: boolean;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Convert pan value (-1 to 1) to display string\n */\nfunction toPanDisplay(value: number): string {\n if (Math.abs(value) < 0.02) {\n return 'Center';\n }\n const percent = Math.abs(Math.round(value * 100));\n return value < 0 ? `L${percent}` : `R${percent}`;\n}\n\n/**\n * Debounce helper for pan changes\n */\nfunction useDebouncedCallback<T extends (...args: never[]) => void>(\n callback: T,\n delay: number\n): T {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n const debouncedCallback = useCallback(\n (...args: Parameters<T>) => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n }, delay);\n },\n [delay]\n ) as T;\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return debouncedCallback;\n}\n\nexport const PanSlider: React.FC<PanSliderProps> = ({\n value,\n onChange,\n disabled = false,\n className = '',\n}) => {\n // Local state for immediate visual feedback\n const [localValue, setLocalValue] = useState(value);\n const [isDragging, setIsDragging] = useState(false);\n\n // Sync local value with prop when not dragging\n useEffect(() => {\n if (!isDragging) {\n setLocalValue(value);\n }\n }, [value, isDragging]);\n\n // Debounced onChange to prevent IPC spam\n const debouncedOnChange = useDebouncedCallback(onChange, 50);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = parseFloat(e.target.value);\n setLocalValue(newValue);\n debouncedOnChange(newValue);\n },\n [debouncedOnChange]\n );\n\n const handleMouseDown = useCallback(() => {\n setIsDragging(true);\n }, []);\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n // Send final value immediately on release\n onChange(localValue);\n }, [localValue, onChange]);\n\n // Double-click to reset to center\n const handleDoubleClick = useCallback(() => {\n setLocalValue(0);\n onChange(0);\n }, [onChange]);\n\n return (\n <div\n className={`flex items-center ${className}`}\n title={`Pan: ${toPanDisplay(localValue)}`}\n >\n <input\n type=\"range\"\n min=\"-1\"\n max=\"1\"\n step=\"0.01\"\n value={localValue}\n onChange={handleChange}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onTouchStart={handleMouseDown}\n onTouchEnd={handleMouseUp}\n onDoubleClick={handleDoubleClick}\n disabled={disabled}\n className={`\n w-full h-1.5 rounded-full appearance-none cursor-pointer\n bg-gray-700\n disabled:opacity-50 disabled:cursor-not-allowed\n [&::-webkit-slider-thumb]:appearance-none\n [&::-webkit-slider-thumb]:w-3\n [&::-webkit-slider-thumb]:h-3\n [&::-webkit-slider-thumb]:rounded-full\n [&::-webkit-slider-thumb]:bg-sas-accent\n [&::-webkit-slider-thumb]:cursor-pointer\n [&::-webkit-slider-thumb]:transition-transform\n [&::-webkit-slider-thumb]:hover:scale-110\n [&::-moz-range-thumb]:w-3\n [&::-moz-range-thumb]:h-3\n [&::-moz-range-thumb]:rounded-full\n [&::-moz-range-thumb]:bg-sas-accent\n [&::-moz-range-thumb]:border-0\n [&::-moz-range-thumb]:cursor-pointer\n `}\n />\n </div>\n );\n};\n\nexport default PanSlider;\n","/**\n * SorceryProgressBar Component\n *\n * A progress bar for long, uncertain wait times (10-30s). Supports two modes:\n *\n * 1. **Time-based mode** (when `estimatedDurationMs` is provided):\n * Uses elapsed time and an ease-out curve to pace progress realistically.\n * Reaches ~90% at the estimated completion time, then asymptotically\n * approaches 95% if the operation runs long.\n *\n * 2. **Phase-based mode** (legacy fallback, no `estimatedDurationMs`):\n * \"Zeno's Paradox\" style - progress moves quickly at first, then\n * asymptotically slows toward 95%.\n *\n * Visual style: Segmented \"retro CLI\" look with glowing teal accent,\n * diagonal stripes, and subtle pulse animation.\n */\n\nimport React, { useState, useEffect, useRef } from 'react';\n\n/**\n * Props for SorceryProgressBar component\n */\ninterface SorceryProgressBarProps {\n /** Whether loading is in progress */\n isLoading: boolean;\n /** Text shown during loading (default: \"CONJURING...\") */\n statusText?: string;\n /** Text shown on completion (default: \"COMPLETE\") */\n completeText?: string;\n /** Callback when loading completes */\n onComplete?: () => void;\n /** Height class override (default: \"h-10\") */\n heightClass?: string;\n /** Initial progress value (0-100) to resume from - persists across scene switches */\n initialProgress?: number;\n /** Callback when progress changes - use to persist progress in parent state */\n onProgressChange?: (progress: number) => void;\n /** Estimated total duration in ms - enables time-aware pacing */\n estimatedDurationMs?: number;\n}\n\n/**\n * Calculates target progress based on elapsed time and estimated duration.\n * Uses an ease-out power curve for natural-feeling progress:\n * - At 10% of estimated time: ~21% (feels responsive early)\n * - At 30% of estimated time: ~53% (good midpoint feel)\n * - At 50% of estimated time: ~74% (past halfway visually)\n * - At 80% of estimated time: ~88% (approaching completion)\n * - At 100% of estimated time: 90% (leaves room for overshoot)\n * - Beyond estimate: asymptotically approaches 95%\n */\nexport function calculateTimeBasedTarget(elapsedMs: number, estimatedDurationMs: number): number {\n const t = elapsedMs / estimatedDurationMs;\n if (t <= 0) return 0;\n\n if (t <= 1.0) {\n // Ease-out power curve reaching 90% at t=1.0\n return 90 * (1 - Math.pow(1 - t, 2.5));\n }\n\n // Beyond estimate: asymptotically approach 95%\n const overshootRatio = (elapsedMs - estimatedDurationMs) / estimatedDurationMs;\n return 90 + 5 * (1 - Math.exp(-overshootRatio * 3));\n}\n\n/**\n * Calculates the next progress value using \"Zeno's Paradox\" algorithm (legacy fallback).\n * - Phase 1 (0-20%): Rapid progress (5-15% per tick)\n * - Phase 2 (20-60%): Steady progress (2-7% per tick)\n * - Phase 3 (60-95%): Asymptotic slowdown\n * - Caps at 95% until actual completion\n */\nfunction calculateNextProgress(currentProgress: number): number {\n if (currentProgress < 20) {\n return currentProgress + Math.random() * 10 + 5;\n }\n if (currentProgress < 60) {\n return currentProgress + Math.random() * 5 + 2;\n }\n if (currentProgress < 95) {\n const remaining = 95 - currentProgress;\n const increment = remaining * (Math.random() * 0.2 + 0.1);\n return currentProgress + Math.max(increment, 0.1);\n }\n return 95;\n}\n\n/**\n * Calculates the next tick interval for phase-based mode (legacy fallback).\n */\nfunction calculateNextTickInterval(progress: number): number {\n if (progress < 30) {\n return Math.random() * 200 + 150; // 150-350ms\n }\n if (progress < 70) {\n return Math.random() * 300 + 200; // 200-500ms\n }\n return Math.random() * 600 + 400; // 400-1000ms\n}\n\n/** Tick interval for time-based mode (ms) */\nconst TIME_BASED_TICK_MIN = 200;\nconst TIME_BASED_TICK_RANGE = 100;\n\n/**\n * SorceryProgressBar - A mystical progress bar for uncertain wait times\n */\nexport function SorceryProgressBar({\n isLoading,\n statusText = 'CONJURING...',\n completeText = 'COMPLETE',\n onComplete,\n heightClass = 'h-10',\n initialProgress = 0,\n onProgressChange,\n estimatedDurationMs,\n}: SorceryProgressBarProps): React.ReactElement | null {\n const [progress, setProgress] = useState<number>(initialProgress);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Initialize to false so first render with isLoading=true triggers animation start\n const isLoadingRef = useRef<boolean>(false);\n const hasStartedRef = useRef<boolean>(false);\n const startTimeRef = useRef<number>(0);\n\n // Store callbacks in refs to avoid dependency issues\n const onProgressChangeRef = useRef(onProgressChange);\n const onCompleteRef = useRef(onComplete);\n onProgressChangeRef.current = onProgressChange;\n onCompleteRef.current = onComplete;\n\n // Store props in refs - only used when loading starts, not as dependencies\n const initialProgressRef = useRef(initialProgress);\n initialProgressRef.current = initialProgress;\n const estimatedDurationMsRef = useRef(estimatedDurationMs);\n estimatedDurationMsRef.current = estimatedDurationMs;\n\n // Effect to handle loading state changes - ONLY depends on isLoading\n useEffect(() => {\n const wasLoading = isLoadingRef.current;\n isLoadingRef.current = isLoading;\n\n if (isLoading && !wasLoading) {\n // Loading just started\n hasStartedRef.current = true;\n startTimeRef.current = Date.now();\n\n // Start fresh or resume from initial progress (read from ref)\n const startProgress = initialProgressRef.current > 0 ? initialProgressRef.current : 0;\n setProgress(startProgress);\n\n const duration = estimatedDurationMsRef.current;\n\n if (duration && duration > 0) {\n // Time-based mode: pace progress using elapsed time\n const tick = (): void => {\n setProgress((prev) => {\n const elapsed = Date.now() - startTimeRef.current;\n const target = calculateTimeBasedTarget(elapsed, duration);\n\n // Add subtle jitter for organic feel (±0.5%)\n const jitter = (Math.random() - 0.5) * 1.0;\n // Move toward target, ensure monotonically increasing, cap at 95%\n const next = Math.min(Math.max(target + jitter, prev + 0.05), 95);\n\n onProgressChangeRef.current?.(next);\n timerRef.current = setTimeout(tick, TIME_BASED_TICK_MIN + Math.random() * TIME_BASED_TICK_RANGE);\n return next;\n });\n };\n\n timerRef.current = setTimeout(tick, TIME_BASED_TICK_MIN);\n } else {\n // Phase-based mode (legacy fallback)\n const tick = (): void => {\n setProgress((prev) => {\n if (prev >= 95) {\n timerRef.current = setTimeout(tick, 1000);\n return 95;\n }\n\n const next = Math.min(calculateNextProgress(prev), 95);\n onProgressChangeRef.current?.(next);\n\n const interval = calculateNextTickInterval(next);\n timerRef.current = setTimeout(tick, interval);\n\n return next;\n });\n };\n\n const firstInterval = calculateNextTickInterval(startProgress);\n timerRef.current = setTimeout(tick, firstInterval);\n }\n } else if (!isLoading && wasLoading && hasStartedRef.current) {\n // Loading just finished - jump to 100%\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n setProgress(100);\n onProgressChangeRef.current?.(100);\n onCompleteRef.current?.();\n hasStartedRef.current = false;\n }\n\n // Cleanup on unmount only\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n };\n // ONLY depend on isLoading - other props are read from refs\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isLoading]);\n\n // Don't render if not loading and progress is 0\n if (!isLoading && progress === 0) {\n return null;\n }\n\n const displayProgress = Math.floor(progress);\n const isComplete = !isLoading && progress === 100;\n\n // Calculate transition duration based on progress phase\n const transitionDuration = progress < 50 ? '300ms' : progress < 80 ? '500ms' : '700ms';\n\n return (\n <div\n className={`relative w-full ${heightClass} bg-sas-panel-alt border border-sas-border rounded-sm overflow-hidden shadow-inner`}\n >\n {/* Progress fill with stripes and glow */}\n <div\n className={`\n h-full\n bg-gradient-to-r from-sas-accent/70 to-sas-accent\n shadow-glow-soft\n sorcery-progress-fill\n animate-progress-stripes\n ${progress > 70 ? 'animate-progress-pulse' : ''}\n transition-all ease-out\n `}\n style={{\n width: `${progress}%`,\n transitionDuration,\n }}\n />\n\n {/* Text overlay */}\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {isLoading && progress < 100 ? (\n <span className=\"font-mono text-xs text-sas-accent font-bold drop-shadow-md tracking-wider\">\n {statusText} {displayProgress}%\n </span>\n ) : isComplete ? (\n <span className=\"font-mono text-xs text-sas-text font-bold drop-shadow-md tracking-wider\">\n {completeText}\n </span>\n ) : null}\n </div>\n\n {/* Scanline overlay for retro CRT effect */}\n <div\n className=\"absolute inset-0 pointer-events-none opacity-10\"\n style={{\n backgroundImage: `repeating-linear-gradient(\n to bottom,\n transparent,\n transparent 2px,\n rgba(0, 0, 0, 0.3) 2px,\n rgba(0, 0, 0, 0.3) 4px\n )`,\n }}\n />\n </div>\n );\n}\n\nexport default SorceryProgressBar;\n","/**\n * CrossfadeTrackRow — a transition \"crossfade track\": two stacked TrackRows\n * (origin on top, target on bottom) joined by a horizontal crossfade slider.\n *\n * Both layers play the SAME generated MIDI; the top wears the ORIGIN scene\n * track's preset and the bottom wears the TARGET scene track's preset. The user\n * cannot regenerate, shuffle, or change the preset/sample on either layer —\n * those controls are simply not wired into the inner TrackRows (the SDK\n * TrackRow is \"controlled by omission\"). What remains: per-layer volume/pan,\n * GROUP mute/solo (both layers toggle together), and a single delete that\n * removes the whole pair.\n *\n * The slider represents WHERE the crossfade happens. In this phase it is\n * centered and non-functional (omit `onSliderChange` → it renders disabled); a\n * later phase wires it to fade origin→target across the bars.\n *\n * @since SDK 2.22.0\n */\nimport React from 'react';\nimport { TrackRow } from './TrackRow';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { EMPTY_FX_DETAIL_STATE } from '../types/fx-toggle.types';\nimport type { TrackLevelsHandle } from '../hooks/useTrackLevels';\nimport type { CrossfadeSlot } from '../crossfade-meta';\n\n/** One layer (engine track) of a crossfade pair. */\nexport interface CrossfadeLayer {\n /** Engine track id of this layer's track (also the meter key). */\n trackId: string;\n /** Display name of this layer's (newly created) track. */\n name: string;\n /** Musical role (same for both layers — crossfades are same-role). */\n role?: string;\n /** Name of the SOURCE track this layer was cloned from (origin/target scene). */\n sourceName?: string;\n /** Human label of the copied preset/sound, shown in the caption. */\n soundLabel?: string;\n /** Playback state for this layer. */\n runtimeState: { muted: boolean; solo: boolean; volume: number; pan: number };\n}\n\nexport interface CrossfadeTrackRowProps {\n /** Top layer — wears the origin (from) scene track's preset. */\n origin: CrossfadeLayer;\n /** Bottom layer — wears the target (to) scene track's preset. */\n target: CrossfadeLayer;\n /** Crossfade position 0..1 (0 = all origin, 1 = all target). Defaults centered. */\n sliderPos?: number;\n /** Toggle mute on BOTH layers together (group mute). */\n onMuteToggle: () => void;\n /** Toggle solo on BOTH layers together (group solo). */\n onSoloToggle: () => void;\n /** Change one layer's volume (per-layer). */\n onVolumeChange: (slot: CrossfadeSlot, volume: number) => void;\n /** Change one layer's pan (per-layer). */\n onPanChange: (slot: CrossfadeSlot, pan: number) => void;\n /** Delete the whole pair. */\n onDelete: () => void;\n /** Move the crossfade point. Omit to render the slider read-only (phase 1). */\n onSliderChange?: (pos: number) => void;\n /** Shared meter handle (welds a peak meter to each layer). */\n levels?: TrackLevelsHandle;\n /** Left-border accent. Defaults to transition purple. */\n accentColor?: string;\n}\n\nfunction LayerCaption({ tag, layer }: { tag: string; layer: CrossfadeLayer }): React.ReactElement {\n return (\n <div className=\"flex items-center gap-1.5 min-w-0 px-2 py-0.5\">\n <span className=\"text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0\">{tag}</span>\n <span className=\"text-[11px] text-sas-text truncate\" title={layer.sourceName ?? layer.name}>\n {layer.sourceName ?? layer.name}\n </span>\n {layer.soundLabel && (\n <span className=\"text-[9px] text-sas-muted/60 truncate flex-shrink-0\" title={layer.soundLabel}>\n · {layer.soundLabel}\n </span>\n )}\n </div>\n );\n}\n\nexport function CrossfadeTrackRow({\n origin,\n target,\n sliderPos = 0.5,\n onMuteToggle,\n onSoloToggle,\n onVolumeChange,\n onPanChange,\n onDelete,\n onSliderChange,\n levels,\n accentColor = '#9333EA',\n}: CrossfadeTrackRowProps): React.ReactElement {\n const [confirmDelete, setConfirmDelete] = React.useState(false);\n\n // A locked crossfade layer. The inner track's `name` is suppressed (the\n // meaningful name lives in the caption); every sound/generation handler is\n // omitted so shuffle / Create / preset-pick / FX / drawer / delete never\n // render. Mute/solo are GROUP-wired (same handler on both layers); volume/pan\n // are per-layer.\n const renderLayer = (layer: CrossfadeLayer, slot: CrossfadeSlot, tag: string): React.ReactElement => (\n <TrackRow\n track={{ id: layer.trackId, name: '', role: layer.role }}\n runtimeState={layer.runtimeState}\n fxDetailState={EMPTY_FX_DETAIL_STATE}\n drawerOpen={false}\n drawerTab=\"fx\"\n levels={levels}\n accentColor={accentColor}\n contentSlot={<LayerCaption tag={tag} layer={layer} />}\n onMuteToggle={onMuteToggle}\n onSoloToggle={onSoloToggle}\n onVolumeChange={(v: number) => onVolumeChange(slot, v)}\n onPanChange={(p: number) => onPanChange(slot, p)}\n />\n );\n\n return (\n <div\n data-testid=\"crossfade-track-row\"\n className=\"w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden\"\n style={{ borderLeftColor: accentColor, borderLeftWidth: '3px' }}\n >\n {/* Header — crossfade label + single delete for the whole pair. */}\n <div className=\"flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60\">\n <span className=\"text-[10px] font-bold uppercase tracking-wide\" style={{ color: accentColor }}>\n ⇄ Crossfade\n </span>\n <button\n data-testid=\"crossfade-delete-button\"\n onClick={() => setConfirmDelete(true)}\n className=\"text-sas-danger/70 hover:text-sas-danger px-1 transition-colors text-sm\"\n title=\"Delete crossfade pair\"\n aria-label=\"Delete crossfade pair\"\n >\n x\n </button>\n </div>\n\n {renderLayer(origin, 'origin', 'Origin')}\n\n {/* Crossfade slider — represents WHERE origin fades into target. Read-only\n (disabled) until the functional fader ships. */}\n <div className=\"flex items-center gap-2 px-3 py-1.5\" data-testid=\"crossfade-slider-row\">\n <span\n className=\"text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0\"\n title={origin.sourceName ?? origin.name}\n >\n {origin.sourceName ?? origin.name}\n </span>\n <input\n type=\"range\"\n data-testid=\"crossfade-slider\"\n min={0}\n max={1}\n step={0.01}\n value={sliderPos}\n disabled={!onSliderChange}\n onChange={\n onSliderChange\n ? (e: React.ChangeEvent<HTMLInputElement>) => onSliderChange(Number(e.target.value))\n : undefined\n }\n style={{ accentColor }}\n className=\"flex-1 disabled:opacity-60 disabled:cursor-not-allowed\"\n aria-label=\"Crossfade position\"\n />\n <span\n className=\"text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0\"\n title={target.sourceName ?? target.name}\n >\n {target.sourceName ?? target.name}\n </span>\n </div>\n\n {renderLayer(target, 'target', 'Target')}\n\n <ConfirmDialog\n open={confirmDelete}\n title=\"Delete crossfade?\"\n message={\n <>\n This crossfade pair (both layers) will be permanently removed from this scene. This cannot\n be undone.\n </>\n }\n confirmLabel=\"Delete\"\n onConfirm={() => {\n setConfirmDelete(false);\n onDelete();\n }}\n onCancel={() => setConfirmDelete(false)}\n testIdPrefix=\"crossfade-delete-confirm\"\n />\n </div>\n );\n}\n\nexport default CrossfadeTrackRow;\n","/**\n * Crossfade-pair metadata — family-agnostic types + parsing shared by every\n * generator panel that supports transition crossfades (synth / drum / instrument).\n *\n * A crossfade pair is two normal tracks linked by a shared `groupId`, persisted\n * in scene plugin_data under `track:<dbId>:crossfade`. Both members play the\n * same MIDI; one wears the origin preset, the other the target preset. The panel\n * owns the family-specific create flow (how a preset/sample is copied) and the\n * render; this module owns only the shape + the scene-data → pairs parse so the\n * logic can't drift across the three panels.\n *\n * @since SDK 2.23.0\n */\n\nimport type { TrackSoundSnapshot } from './types/plugin-sdk.types';\n\n/**\n * Stable, state-aware identity for a track's sound — used to auto-detect when a\n * transition's SOURCE preset/sample has drifted from the copy on its layer. A\n * preset hashes its full STATE blob (so a same-name param tweak still differs); a\n * sample uses its path; an instrument uses its id + zones. Empty string = no sound.\n * @since SDK 2.32.0\n */\nexport function hashString(s: string): string {\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = (((h << 5) + h) ^ s.charCodeAt(i)) | 0;\n return (h >>> 0).toString(36);\n}\nexport function soundIdentity(snap: TrackSoundSnapshot | null | undefined): string {\n if (!snap) return '';\n if (snap.kind === 'preset') return `p:${hashString(snap.state)}`;\n if (snap.kind === 'sample') return `s:${snap.samplePath}`;\n if (snap.kind === 'instrument') return `i:${snap.instrumentId ?? ''}:${hashString(JSON.stringify(snap.zones))}`;\n return '';\n}\n\n/** Which half of the pair a per-layer control / member targets. */\nexport type CrossfadeSlot = 'origin' | 'target';\n\n/**\n * Equal-power center gain (~-3 dB, 1/√2) applied to BOTH crossfade layers so a\n * centered, non-functional slider already sounds like a midpoint blend. The\n * per-layer volume sliders start here; a later phase's fader drives them.\n */\nexport const EQUAL_POWER_GAIN = 0.707;\n\n/**\n * Per-member crossfade metadata (one scene-data value per member track). The two\n * members (origin/target) of a pair share a `groupId`.\n */\nexport interface CrossfadeMeta {\n groupId: string;\n slot: CrossfadeSlot;\n /** DB id of the partner member track. */\n partnerDbId: string;\n /** DB id of the SOURCE track this layer's preset/sample was copied from. */\n sourceTrackDbId: string;\n /** DB id of the scene the source track lives in (the from/to scene). */\n sourceSceneId: string;\n /** Source track display name (shown in the caption). */\n sourceName: string;\n /** Copied preset/sample label (shown in the caption). */\n soundLabel: string;\n /** Crossfade position 0..1 (kept identical on both members). */\n sliderPos: number;\n}\n\n/** A complete crossfade pair (both members present), keyed by groupId. */\nexport interface CrossfadePairMeta {\n groupId: string;\n sliderPos: number;\n originDbId: string;\n targetDbId: string;\n /** DB id of the ORIGIN source track (in the from scene) — drives the \"used once\" exclusion. */\n originSourceDbId: string;\n /** DB id of the TARGET source track (in the to scene). */\n targetSourceDbId: string;\n originSourceName: string;\n originSoundLabel: string;\n targetSourceName: string;\n targetSoundLabel: string;\n}\n\n/** Narrow an unknown scene-data value to CrossfadeMeta (defensive — survives partial blobs). */\nexport function asCrossfadeMeta(val: unknown): CrossfadeMeta | null {\n if (!val || typeof val !== 'object') return null;\n const m = val as Partial<CrossfadeMeta>;\n if (typeof m.groupId !== 'string' || (m.slot !== 'origin' && m.slot !== 'target')) return null;\n if (typeof m.partnerDbId !== 'string') return null;\n return {\n groupId: m.groupId,\n slot: m.slot,\n partnerDbId: m.partnerDbId,\n sourceTrackDbId: typeof m.sourceTrackDbId === 'string' ? m.sourceTrackDbId : '',\n sourceSceneId: typeof m.sourceSceneId === 'string' ? m.sourceSceneId : '',\n sourceName: typeof m.sourceName === 'string' ? m.sourceName : '',\n soundLabel: typeof m.soundLabel === 'string' ? m.soundLabel : '',\n sliderPos: typeof m.sliderPos === 'number' ? m.sliderPos : 0.5,\n };\n}\n\n/**\n * Scan all `track:<dbId>:crossfade` keys in a scene's plugin_data and assemble\n * COMPLETE pairs (both origin + target present). A half-broken group (partner\n * deleted underneath) is omitted, so its surviving member falls back to a normal\n * row instead of vanishing.\n */\nexport function parseCrossfadePairs(sceneData: Record<string, unknown>): CrossfadePairMeta[] {\n const groups = new Map<\n string,\n { origin?: { dbId: string; meta: CrossfadeMeta }; target?: { dbId: string; meta: CrossfadeMeta } }\n >();\n for (const [key, val] of Object.entries(sceneData)) {\n const match = /^track:(.+):crossfade$/.exec(key);\n if (!match) continue;\n const meta = asCrossfadeMeta(val);\n if (!meta) continue;\n const dbId = match[1];\n const g = groups.get(meta.groupId) ?? {};\n if (meta.slot === 'origin') g.origin = { dbId, meta };\n else g.target = { dbId, meta };\n groups.set(meta.groupId, g);\n }\n const pairs: CrossfadePairMeta[] = [];\n for (const [groupId, g] of groups) {\n if (!g.origin || !g.target) continue;\n pairs.push({\n groupId,\n sliderPos: g.origin.meta.sliderPos,\n originDbId: g.origin.dbId,\n targetDbId: g.target.dbId,\n originSourceDbId: g.origin.meta.sourceTrackDbId,\n targetSourceDbId: g.target.meta.sourceTrackDbId,\n originSourceName: g.origin.meta.sourceName,\n originSoundLabel: g.origin.meta.soundLabel,\n targetSourceName: g.target.meta.sourceName,\n targetSoundLabel: g.target.meta.soundLabel,\n });\n }\n return pairs;\n}\n\n// ============================================================================\n// Crossfade volume automation (Phase 3 — the functional fader)\n// ============================================================================\n\n/** One volume-automation point: a dB value at a time offset (seconds from clip start). */\nexport interface VolumeAutomationPoint {\n time: number; // seconds\n db: number; // gain in dB (-80 ≈ silent, 0 = unity)\n}\n\n/** Origin + target volume curves for one crossfade pair. */\nexport interface CrossfadeVolumeCurves {\n origin: VolumeAutomationPoint[];\n target: VolumeAutomationPoint[];\n}\n\n// Exported so fade-meta.ts can reuse the same floor + gain→dB mapping (a fade is\n// a crossfade with one empty endpoint; its volume curve must match exactly).\nexport const FADE_FLOOR_DB = -80;\n\nexport function gainToDb(gain: number): number {\n return gain <= 1e-4 ? FADE_FLOOR_DB : Math.max(FADE_FLOOR_DB, 20 * Math.log10(gain));\n}\n\n/**\n * Equal-power crossfade volume curves over a transition of `bars` at `bpm`.\n * The ORIGIN layer fades OUT and the TARGET fades IN; `sliderPos` (0..1) sets\n * WHERE in time the equal-power (-3 dB) crossover sits — 0 = hand off near the\n * start, 1 = hold the origin until near the end. Points span the clip window\n * [0, durationSeconds] so the engine re-reads them each loop (re-fade per loop).\n * `steps`+1 points with linear interpolation approximate the cos/sin curve.\n *\n * Returns dB point arrays for `host.setTrackVolumeAutomation` — origin on the top\n * layer, target on the bottom. @since SDK 2.25.0\n */\nexport function buildCrossfadeVolumeCurves(\n bars: number,\n bpm: number,\n sliderPos: number,\n steps = 32,\n): CrossfadeVolumeCurves {\n const durationSeconds = (bars * 4 * 60) / Math.max(1, bpm);\n // Keep the crossover off the exact ends so there's always an actual fade.\n const s = Math.min(0.98, Math.max(0.02, sliderPos));\n const round = (n: number): number => Math.round(n * 1000) / 1000;\n const origin: VolumeAutomationPoint[] = [];\n const target: VolumeAutomationPoint[] = [];\n for (let i = 0; i <= steps; i++) {\n const x = i / steps; // normalized time 0..1\n const time = round(x * durationSeconds);\n // Piecewise-linear angle so the equal-power crossover (π/4) lands at x = s.\n const theta = x <= s ? (x / s) * (Math.PI / 4) : Math.PI / 4 + ((x - s) / (1 - s)) * (Math.PI / 4);\n origin.push({ time, db: Math.round(gainToDb(Math.cos(theta)) * 100) / 100 });\n target.push({ time, db: Math.round(gainToDb(Math.sin(theta)) * 100) / 100 });\n }\n return { origin, target };\n}\n","/**\n * Crossfade MIDI inpainting — builds the LLM user-prompt for a bridge that\n * MORPHS the ORIGIN part into the TARGET part.\n *\n * A normal scene generation composes a part standalone from the scene's chords.\n * A crossfade bridge is different: it is INPAINTING between two fixed endpoints.\n * The generated part must begin feeling continuous with the origin pattern and\n * end feeling continuous with the target pattern, transforming between them\n * across the transition's bars.\n *\n * The harmonic frame — Key / mode / BPM / bars / the transition chord\n * progression (with beat timing) / scene contract — is injected AUTOMATICALLY by\n * `host.generateWithLLM` (it prepends the active scene's \"Musical Context\" block\n * unless `skipContextPrefix` is set). So this prompt does NOT restate key/bpm/\n * chords — it adds only the two endpoint patterns + the morph instructions, and\n * references the harmonic frame as \"given above\".\n *\n * REPRESENTATION (researched for Gemini): ABC notation is the LLM-native format\n * for melodic generation, but it's weak for percussion, would need a separate\n * output parser (our output is JSON note-events, already proven with Gemini),\n * and an inpainting task wants input/output FORMAT SYMMETRY. So each endpoint is\n * given as the exact JSON note-events PLUS a pitch-named, bar-structured \"gloss\"\n * — the transferable wins from the research (pitch NAMES over raw MIDI numbers,\n * explicit bar/beat structure) layered on the precise, symmetric JSON. Drums\n * (uniform pitch) get a rhythmic gloss instead of pitch names.\n *\n * This changes only the LLM INPUT framing: the OUTPUT schema is unchanged, so the\n * calling panel keeps its system prompt + parser (and, for drums, its flatten step).\n *\n * @since SDK 2.24.0\n */\nimport type { PluginMidiNote } from './types/plugin-sdk.types';\n\nexport interface CrossfadeInpaintInput {\n /** Musical role of the bridge part (e.g. 'bass'). '' falls back to \"melodic\". */\n role: string;\n /** Transition length in bars (the morph timeline). */\n bars: number;\n /** Display name of the ORIGIN source track (the part the bridge begins from). */\n originName: string;\n /** Display name of the TARGET source track (the part the bridge arrives at). */\n targetName: string;\n /** ORIGIN source scene's key label (e.g. \"G minor\"). Null/omitted = unknown. */\n originKey?: string | null;\n /** TARGET source scene's key label. Null/omitted = unknown. */\n targetKey?: string | null;\n /** ORIGIN pattern notes (beat-based; from the FROM scene). May be empty. */\n originNotes: readonly PluginMidiNote[];\n /** TARGET pattern notes (beat-based; from the TO scene). May be empty. */\n targetNotes: readonly PluginMidiNote[];\n /** Drums: pitch is uniform (flattened), so gloss RHYTHM instead of pitch names. */\n percussive?: boolean;\n}\n\nconst PITCH_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];\n\n/** Round to 3 dp, dropping trailing-zero noise. */\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\n/** MIDI number → scientific pitch name (60 → C4), the app's octave convention. */\nfunction pitchName(p: number): string {\n return `${PITCH_NAMES[((p % 12) + 12) % 12]}${Math.floor(p / 12) - 1}`;\n}\n\n/** Compact a note to the 4 fields the LLM needs (drops channel), beats rounded. */\nfunction compactNote(n: PluginMidiNote): { pitch: number; startBeat: number; durationBeats: number; velocity: number } {\n return { pitch: n.pitch, startBeat: round3(n.startBeat), durationBeats: round3(n.durationBeats), velocity: n.velocity };\n}\n\n/** One-line shape summary so the LLM grasps register/density before the detail. */\nfunction summarize(notes: readonly PluginMidiNote[], percussive: boolean): string {\n if (notes.length === 0) return 'empty (no notes)';\n const span = round3(Math.max(...notes.map((n) => n.startBeat + n.durationBeats)));\n if (percussive) return `${notes.length} hits, spans ~${span} beats`;\n const pitches = notes.map((n) => n.pitch);\n return `${notes.length} notes, ${pitchName(Math.min(...pitches))}–${pitchName(Math.max(...pitches))}, spans ~${span} beats`;\n}\n\n/** Pitch-named (melodic) or rhythmic (drums) gloss, grouped by inferred bar. */\nfunction gloss(notes: readonly PluginMidiNote[], percussive: boolean): string {\n const sorted = [...notes].sort((a, b) => a.startBeat - b.startBeat);\n const maxEnd = Math.max(...sorted.map((n) => n.startBeat + n.durationBeats));\n const bars = Math.max(1, Math.ceil(maxEnd / 4));\n const lines: string[] = [];\n for (let b = 0; b < bars; b++) {\n const inBar = sorted.filter((n) => n.startBeat >= b * 4 && n.startBeat < (b + 1) * 4);\n if (inBar.length === 0) continue;\n const body = percussive\n ? inBar.map((n) => `${round3(n.startBeat)}(v${n.velocity})`).join(' ')\n : inBar.map((n) => `${pitchName(n.pitch)}@${round3(n.startBeat)}`).join(' ');\n lines.push(` Bar ${b + 1}: ${body}`);\n }\n return lines.join('\\n');\n}\n\nfunction patternBlock(\n label: string,\n name: string,\n key: string | null | undefined,\n notes: readonly PluginMidiNote[],\n percussive: boolean,\n): string {\n const keyLabel = key ? ` in ${key}` : '';\n const header = `${label} — \"${name}\"${keyLabel} (${summarize(notes, percussive)}):`;\n if (notes.length === 0) return `${header}\\n (no notes — treat this end as open)`;\n return `${header}\\n${gloss(notes, percussive)}\\n exact JSON: ${JSON.stringify(notes.map(compactNote))}`;\n}\n\n/**\n * Build the inpainting user-prompt. The result is the prompt BODY only — pass it\n * as `request.user` to `host.generateWithLLM` with the panel's normal system\n * prompt and `responseFormat: 'json'`; the harmonic context auto-prefixes.\n */\nexport function buildCrossfadeInpaintPrompt(input: CrossfadeInpaintInput): string {\n const { role, bars, originName, targetName, originKey, targetKey, originNotes, targetNotes } = input;\n const percussive = input.percussive ?? false;\n const part = role || (percussive ? 'drum' : 'melodic');\n const modulation =\n originKey && targetKey\n ? originKey === targetKey\n ? `stays in ${targetKey}`\n : `modulates from ${originKey} toward ${targetKey}`\n : 'resolves toward the destination key';\n\n const lines: string[] = [\n `TASK — TRANSITION BRIDGE (musical inpainting).`,\n `Compose a ${part} part that MORPHS from the ORIGIN pattern into the TARGET pattern across the ${bars} bars`,\n `of this transition. The Key / BPM / chord progression are given above — it ${modulation}; honour that`,\n `frame, don't restate it. Each pattern below is shown as a pitch/rhythm gloss for musicality plus its exact`,\n `JSON; output your bridge in the same JSON note schema (per the system prompt).`,\n ``,\n patternBlock('ORIGIN pattern (where the bridge BEGINS)', originName, originKey, originNotes, percussive),\n ``,\n patternBlock('TARGET pattern (where the bridge must ARRIVE)', targetName, targetKey, targetNotes, percussive),\n ``,\n `Requirements:`,\n `- The FIRST bar feels continuous with the ORIGIN — borrow its register, rhythm, and contour so the seam`,\n ` from the previous scene is seamless.`,\n `- Across the middle bars, gradually transform toward the TARGET (shift register / rhythm / motifs step by step).`,\n `- The LAST bar lands on the TARGET's material and resolves onto the destination chord, so the seam into the`,\n ` next scene is seamless.`,\n `- Stay within the transition chord progression above; favour chord tones at the bar boundaries.`,\n `- This is inpainting between two FIXED endpoints — a listener should not be able to point to where the`,\n ` origin ends or the target begins.`,\n ];\n\n if (originNotes.length === 0 || targetNotes.length === 0) {\n lines.push(\n ``,\n originNotes.length === 0 && targetNotes.length === 0\n ? `(Both endpoints are empty — compose a short ${part} bridge from the chords alone.)`\n : originNotes.length === 0\n ? `(The ORIGIN is empty — begin sparse and grow INTO the TARGET.)`\n : `(The TARGET is empty — begin from the ORIGIN and dissolve toward the destination chord.)`,\n );\n }\n\n return lines.join('\\n');\n}\n","/**\n * Fade metadata — family-agnostic types + parsing for transition orphan fades\n * (synth / drum / instrument panels).\n *\n * A fade is a CROSSFADE WITH ONE EMPTY ENDPOINT: a single generated track that\n * either fades IN (a target-only track entering — `morph(∅ → target)`) or fades\n * OUT (an origin-only track leaving — `morph(origin → ∅)`) across the transition\n * loop. It reuses the same generation pipeline (`buildCrossfadeInpaintPrompt`\n * with one empty endpoint) and the same volume-automation fader as crossfade.\n *\n * Stored in scene plugin_data under `track:<dbId>:fade` (ONE entry per track —\n * unlike crossfade there is no partner / groupId). Kept as a SEPARATE type from\n * crossfade so the load-bearing \"drop a half-broken pair\" guard in\n * `parseCrossfadePairs` stays intact.\n *\n * @since SDK 2.28.0\n */\n\nimport { type VolumeAutomationPoint, FADE_FLOOR_DB, gainToDb } from './crossfade-meta';\n\n/** Which way the lone track fades over the transition. */\nexport type FadeDirection = 'in' | 'out';\n\n/**\n * How the fade is shaped:\n * - `volume` — a one-sided level ramp does the work (DJ-style). Best for\n * textural/sustained material (pads, atmospheres).\n * - `build` — the MIDI carries the fade (the inpaint grows sparse→full on the way\n * in, or dissolves on the way out); the level stays flat. Best for articulated\n * material (lead, bass, drums, winds, vocals).\n */\nexport type FadeGesture = 'volume' | 'build';\n\n/** Per-track fade metadata (one scene-data value per fade track). */\nexport interface FadeMeta {\n direction: FadeDirection;\n gesture: FadeGesture;\n /**\n * Audio transition variant for one-sided LOOP transitions. `'fade'` (default,\n * and the only value MIDI panels write) is a plain level ramp; `stutter` /\n * `chopped` re-render the loop's audio, `delay` adds a delay-throw FX. Shown as\n * a badge on the row. @since SDK 2.32.0\n */\n effect?: 'fade' | 'stutter' | 'chopped' | 'delay';\n /** DB id of the SOURCE track this fade's preset/sample + pattern was seeded from. */\n sourceTrackDbId: string;\n /** DB id of the scene the source track lives in (the from/to scene). */\n sourceSceneId: string;\n /** Source track display name (shown in the caption). */\n sourceName: string;\n /** Copied preset/sample label (shown in the caption). */\n soundLabel: string;\n /** Fade position 0..1 — WHERE in time the fade midpoint sits. */\n sliderPos: number;\n}\n\n/** A fade entry resolved from scene data: the fade track's dbId + its metadata. */\nexport interface FadeEntry {\n dbId: string;\n meta: FadeMeta;\n}\n\n/** Narrow an unknown scene-data value to FadeMeta (defensive — survives partial blobs). */\nexport function asFadeMeta(val: unknown): FadeMeta | null {\n if (!val || typeof val !== 'object') return null;\n const m = val as Partial<FadeMeta>;\n if (m.direction !== 'in' && m.direction !== 'out') return null;\n if (m.gesture !== 'volume' && m.gesture !== 'build') return null;\n const effect =\n m.effect === 'stutter' || m.effect === 'chopped' || m.effect === 'delay' || m.effect === 'fade'\n ? m.effect\n : undefined;\n return {\n direction: m.direction,\n gesture: m.gesture,\n effect,\n sourceTrackDbId: typeof m.sourceTrackDbId === 'string' ? m.sourceTrackDbId : '',\n sourceSceneId: typeof m.sourceSceneId === 'string' ? m.sourceSceneId : '',\n sourceName: typeof m.sourceName === 'string' ? m.sourceName : '',\n soundLabel: typeof m.soundLabel === 'string' ? m.soundLabel : '',\n sliderPos: typeof m.sliderPos === 'number' ? m.sliderPos : 0.5,\n };\n}\n\n/**\n * Scan all `track:<dbId>:fade` keys in a scene's plugin_data and return one entry\n * per valid fade. Unlike crossfade there is no grouping or both-present gate — a\n * fade is intrinsically a single track.\n */\nexport function parseFades(sceneData: Record<string, unknown>): FadeEntry[] {\n const out: FadeEntry[] = [];\n for (const [key, val] of Object.entries(sceneData)) {\n const match = /^track:(.+):fade$/.exec(key);\n if (!match) continue;\n const meta = asFadeMeta(val);\n if (!meta) continue;\n out.push({ dbId: match[1], meta });\n }\n return out;\n}\n\n/**\n * Build a ONE-sided volume-automation curve for a fade over `bars` at `bpm`.\n *\n * - `gesture === 'build'` → flat at unity (0 dB). The compositional build in the\n * MIDI carries the fade; layering a volume ramp on top would double-fade.\n * - `gesture === 'volume'` → an equal-power ramp identical to ONE half of\n * `buildCrossfadeVolumeCurves`: fade-out ≡ its `origin` curve (unity→floor),\n * fade-in ≡ its `target` curve (floor→unity). `sliderPos` sets WHERE the −3 dB\n * midpoint sits in time.\n *\n * Points span [0, durationSeconds] so the engine re-reads them each loop. Returns\n * dB points for `host.setTrackVolumeAutomation`.\n *\n * @since SDK 2.28.0\n */\nexport function buildFadeVolumeCurve(\n bars: number,\n bpm: number,\n direction: FadeDirection,\n sliderPos: number,\n gesture: FadeGesture,\n steps = 32,\n): VolumeAutomationPoint[] {\n const durationSeconds = (bars * 4 * 60) / Math.max(1, bpm);\n\n // build: the notes do the fade — hold the level flat at unity.\n if (gesture === 'build') {\n return [\n { time: 0, db: 0 },\n { time: Math.round(durationSeconds * 1000) / 1000, db: 0 },\n ];\n }\n\n // volume: one half of the equal-power crossfade curve.\n const s = Math.min(0.98, Math.max(0.02, sliderPos));\n const round = (n: number): number => Math.round(n * 1000) / 1000;\n const points: VolumeAutomationPoint[] = [];\n for (let i = 0; i <= steps; i++) {\n const x = i / steps; // normalized time 0..1\n const time = round(x * durationSeconds);\n // Piecewise-linear angle so the equal-power midpoint (π/4) lands at x = s.\n const theta = x <= s ? (x / s) * (Math.PI / 4) : Math.PI / 4 + ((x - s) / (1 - s)) * (Math.PI / 4);\n // fade-out follows cos (unity→floor); fade-in follows sin (floor→unity).\n const gain = direction === 'out' ? Math.cos(theta) : Math.sin(theta);\n points.push({ time, db: Math.round(gainToDb(gain) * 100) / 100 });\n }\n return points;\n}\n\n/**\n * Roles whose fades default to a `volume` (level) ramp — sustained/textural\n * material that enters/leaves by level in real productions. Everything else\n * defaults to `build` (the notes carry the fade).\n *\n * This is a UI default heuristic over role tokens, NOT the canonical role list\n * (that lives in the app's instrument-classification + `host.getValidRoles()`).\n * There is no textural↔articulated axis in the taxonomy, so this small curated\n * subset lives next to its consumer (the fade modal/panels).\n *\n * @since SDK 2.28.0\n */\nexport const TEXTURAL_ROLES: ReadonlySet<string> = new Set<string>([\n 'pads',\n 'pad',\n 'strings',\n 'atmospheres',\n 'atmosphere',\n 'atmos',\n 'drones',\n 'drone',\n 'soundscapes',\n 'soundscape',\n]);\n\n/** Pick the default fade gesture for a track's role (textural → volume, else build). */\nexport function defaultFadeGesture(role: string | null | undefined): FadeGesture {\n if (!role) return 'build';\n const norm = role.toLowerCase().replace(/[\\s_-]+/g, ' ').trim();\n if (TEXTURAL_ROLES.has(norm)) return 'volume';\n for (const token of norm.split(' ')) {\n if (TEXTURAL_ROLES.has(token)) return 'volume';\n }\n return 'build';\n}\n","/**\n * FadeTrackRow — a transition \"fade track\": a single locked TrackRow with a\n * direction badge (Fade in / Fade out) and a one-sided fade slider.\n *\n * A fade is a crossfade with one empty endpoint — a lone generated track that\n * either enters (fade in, for a target-only track) or leaves (fade out, for an\n * origin-only track) across the transition loop. Like a crossfade layer, the\n * sound/generation controls are omitted (the SDK TrackRow is \"controlled by\n * omission\"): no shuffle / Create / preset-pick / FX / drawer / inner-delete.\n * What remains: per-track volume/pan/mute/solo and a single delete.\n *\n * The slider represents WHERE in the loop the fade sits (earlier ↔ later). Omit\n * `onSliderChange` to render it read-only.\n *\n * @since SDK 2.28.0\n */\nimport React from 'react';\nimport { TrackRow } from './TrackRow';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { EMPTY_FX_DETAIL_STATE } from '../types/fx-toggle.types';\nimport type { TrackLevelsHandle } from '../hooks/useTrackLevels';\nimport type { FadeDirection, FadeGesture } from '../fade-meta';\n\n/** The single (engine track) layer of a fade. */\nexport interface FadeLayer {\n /** Engine track id of this fade's track (also the meter key). */\n trackId: string;\n /** Display name of this fade's (newly created) track. */\n name: string;\n /** Musical role (drives the auto gesture). */\n role?: string;\n /** Name of the SOURCE track this fade was seeded from (origin/target scene). */\n sourceName?: string;\n /** Human label of the copied preset/sound, shown in the caption. */\n soundLabel?: string;\n /** Playback state for this track. */\n runtimeState: { muted: boolean; solo: boolean; volume: number; pan: number };\n}\n\nexport interface FadeTrackRowProps {\n /** The lone fade track. */\n layer: FadeLayer;\n /** 'in' (enters across the loop) or 'out' (leaves across the loop). */\n direction: FadeDirection;\n /** How the fade is shaped — shown read-only (volume = level ramp, build = notes). */\n gesture: FadeGesture;\n /** Audio transition variant — relabels the badge (Stutter/Chopped/Delay). @since SDK 2.32.0 */\n effect?: 'fade' | 'stutter' | 'chopped' | 'delay';\n /** Fade position 0..1 — WHERE in time the fade sits. Defaults centered. */\n sliderPos?: number;\n /** Toggle mute. */\n onMuteToggle: () => void;\n /** Toggle solo. */\n onSoloToggle: () => void;\n /** Change the track's volume. */\n onVolumeChange: (volume: number) => void;\n /** Change the track's pan. */\n onPanChange: (pan: number) => void;\n /** Delete the fade. */\n onDelete: () => void;\n /** Move the fade point. Omit to render the slider read-only. */\n onSliderChange?: (pos: number) => void;\n /** Shared meter handle (welds a peak meter to the track). */\n levels?: TrackLevelsHandle;\n /** Left-border accent. Defaults to transition purple. */\n accentColor?: string;\n}\n\nfunction FadeCaption({\n layer,\n direction,\n gesture,\n}: {\n layer: FadeLayer;\n direction: FadeDirection;\n gesture: FadeGesture;\n}): React.ReactElement {\n const tag = direction === 'in' ? 'Fade in' : 'Fade out';\n return (\n <div className=\"flex items-center gap-1.5 min-w-0 px-2 py-0.5\">\n <span className=\"text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0\">{tag}</span>\n <span className=\"text-[11px] text-sas-text truncate\" title={layer.sourceName ?? layer.name}>\n {layer.sourceName ?? layer.name}\n </span>\n {layer.soundLabel && (\n <span className=\"text-[9px] text-sas-muted/60 truncate flex-shrink-0\" title={layer.soundLabel}>\n · {layer.soundLabel}\n </span>\n )}\n <span className=\"text-[9px] text-sas-muted/50 flex-shrink-0\" title={`Fade gesture: ${gesture}`}>\n · {gesture}\n </span>\n </div>\n );\n}\n\nexport function FadeTrackRow({\n layer,\n direction,\n gesture,\n effect,\n sliderPos = 0.5,\n onMuteToggle,\n onSoloToggle,\n onVolumeChange,\n onPanChange,\n onDelete,\n onSliderChange,\n levels,\n accentColor = '#9333EA',\n}: FadeTrackRowProps): React.ReactElement {\n const [confirmDelete, setConfirmDelete] = React.useState(false);\n\n // Slider end labels: a fade-in goes (silent → track), a fade-out goes (track → silent).\n const leftLabel = direction === 'in' ? '(silent)' : (layer.sourceName ?? layer.name);\n const rightLabel = direction === 'in' ? (layer.sourceName ?? layer.name) : '(silent)';\n const verb = effect && effect !== 'fade' ? effect.charAt(0).toUpperCase() + effect.slice(1) : 'Fade';\n const badge = direction === 'in' ? `↗ ${verb} in` : `↘ ${verb} out`;\n\n return (\n <div\n data-testid=\"fade-track-row\"\n className=\"w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden\"\n style={{ borderLeftColor: accentColor, borderLeftWidth: '3px' }}\n >\n {/* Header — direction badge + single delete. */}\n <div className=\"flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60\">\n <span\n data-testid=\"fade-direction-badge\"\n className=\"text-[10px] font-bold uppercase tracking-wide\"\n style={{ color: accentColor }}\n >\n {badge}\n </span>\n <button\n data-testid=\"fade-delete-button\"\n onClick={() => setConfirmDelete(true)}\n className=\"text-sas-danger/70 hover:text-sas-danger px-1 transition-colors text-sm\"\n title=\"Delete fade\"\n aria-label=\"Delete fade\"\n >\n x\n </button>\n </div>\n\n {/* The lone, locked track. Sound/generation controls are omitted; the\n meaningful name + direction live in the caption. */}\n <TrackRow\n track={{ id: layer.trackId, name: '', role: layer.role }}\n runtimeState={layer.runtimeState}\n fxDetailState={EMPTY_FX_DETAIL_STATE}\n drawerOpen={false}\n drawerTab=\"fx\"\n levels={levels}\n accentColor={accentColor}\n contentSlot={<FadeCaption layer={layer} direction={direction} gesture={gesture} />}\n onMuteToggle={onMuteToggle}\n onSoloToggle={onSoloToggle}\n onVolumeChange={onVolumeChange}\n onPanChange={onPanChange}\n />\n\n {/* Fade slider — WHERE in the loop the fade sits. Read-only until wired. */}\n <div className=\"flex items-center gap-2 px-3 py-1.5\" data-testid=\"fade-slider-row\">\n <span\n className=\"text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0\"\n title={leftLabel}\n >\n {leftLabel}\n </span>\n <input\n type=\"range\"\n data-testid=\"fade-slider\"\n min={0}\n max={1}\n step={0.01}\n value={sliderPos}\n disabled={!onSliderChange}\n onChange={\n onSliderChange\n ? (e: React.ChangeEvent<HTMLInputElement>) => onSliderChange(Number(e.target.value))\n : undefined\n }\n style={{ accentColor }}\n className=\"flex-1 disabled:opacity-60 disabled:cursor-not-allowed\"\n aria-label=\"Fade position\"\n />\n <span\n className=\"text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0\"\n title={rightLabel}\n >\n {rightLabel}\n </span>\n </div>\n\n <ConfirmDialog\n open={confirmDelete}\n title=\"Delete fade?\"\n message={<>This fade track will be permanently removed from this scene. This cannot be undone.</>}\n confirmLabel=\"Delete\"\n onConfirm={() => {\n setConfirmDelete(false);\n onDelete();\n }}\n onCancel={() => setConfirmDelete(false)}\n testIdPrefix=\"fade-delete-confirm\"\n />\n </div>\n );\n}\n\nexport default FadeTrackRow;\n","/**\n * FadeModal — \"add a fade\" picker for a transition scene.\n *\n * Shown only inside a `scene_type='transition'` scene. It self-fetches the FROM\n * (origin) and TO (target) scenes' family tracks and diffs them by role to find\n * ORPHANS — tracks with no counterpart on the other side:\n * - origin-only surplus → \"Fade out\" candidates (the track leaves)\n * - target-only surplus → \"Fade in\" candidates (the track enters)\n * Tracks whose role is matched on both sides are crossfade territory and are NOT\n * shown here. A source already used in a crossfade or a fade is hidden (via\n * excludeSourceDbIds).\n *\n * The fade GESTURE (volume vs build) is auto-selected from the track's role and\n * shown read-only — the user does not choose it. On confirm the modal hands the\n * selection + direction + gesture to `onCreate`, which the panel implements\n * (create one track, generate a chord-conforming part, copy the sound, apply a\n * one-sided volume curve). `onCreate` should reject on failure so the modal can\n * show it and stay open.\n *\n * @since SDK 2.28.0\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Modal } from './Modal';\nimport type { PluginHost, SceneFamilyTrack } from '../types/plugin-sdk.types';\nimport { type FadeDirection, type FadeGesture, defaultFadeGesture } from '../fade-meta';\n\n/** A picked orphan track handed to `onCreate`. */\nexport interface FadeSelection {\n /** Source track DB id (selector for getTrackSound + seeding the part). */\n dbId: string;\n /** Display name (for the row caption). */\n name: string;\n /** Musical role of the source track (drives the auto gesture). */\n role?: string;\n}\n\nexport interface FadeModalProps {\n /** Scoped host — the modal calls listSceneFamilyTracks itself. */\n host: PluginHost;\n /** Controls visibility (the panel owns open/closed from its header button). */\n open: boolean;\n /** DB id of the transition's FROM (origin) scene. */\n fromSceneId: string;\n /** DB id of the transition's TO (target) scene. */\n toSceneId: string;\n /** Display name for the origin scene heading (optional). */\n fromSceneName?: string;\n /** Display name for the target scene heading (optional). */\n toSceneName?: string;\n /** Source-track DB ids already used in a crossfade OR a fade — hidden here. */\n excludeSourceDbIds?: readonly string[];\n /** Close handler (Escape, backdrop, Cancel, or after a successful create). */\n onClose: () => void;\n /** Build the fade. Should reject on failure so the modal shows it. */\n onCreate: (selection: FadeSelection, direction: FadeDirection, gesture: FadeGesture) => Promise<void>;\n /** data-testid prefix. */\n testIdPrefix?: string;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; from: SceneFamilyTrack[]; to: SceneFamilyTrack[] };\n\n/** Short, recognisable id prefix — the full id lives in the row's title. */\nfunction shortId(dbId: string): string {\n return dbId.length > 8 ? dbId.slice(0, 8) : dbId;\n}\n\nconst normRole = (r: string | undefined): string => (r ?? '').toLowerCase().trim();\n\n/**\n * Multiset role-diff: per role token, pair min(from,to) as SHARED (crossfade\n * territory — hidden), and return the surplus on each side as orphans.\n */\nfunction computeOrphans(\n from: SceneFamilyTrack[],\n to: SceneFamilyTrack[],\n excludeSet: ReadonlySet<string>,\n): { fadeOut: SceneFamilyTrack[]; fadeIn: SceneFamilyTrack[] } {\n const bucket = (list: SceneFamilyTrack[]): Map<string, SceneFamilyTrack[]> => {\n const m = new Map<string, SceneFamilyTrack[]>();\n for (const t of list) {\n const k = normRole(t.role);\n const arr = m.get(k);\n if (arr) arr.push(t);\n else m.set(k, [t]);\n }\n return m;\n };\n const fromByRole = bucket(from);\n const toByRole = bucket(to);\n const roles = new Set<string>([...fromByRole.keys(), ...toByRole.keys()]);\n const fadeOut: SceneFamilyTrack[] = [];\n const fadeIn: SceneFamilyTrack[] = [];\n for (const role of roles) {\n const f = fromByRole.get(role) ?? [];\n const t = toByRole.get(role) ?? [];\n const shared = Math.min(f.length, t.length);\n fadeOut.push(...f.slice(shared));\n fadeIn.push(...t.slice(shared));\n }\n return {\n fadeOut: fadeOut.filter((x) => !excludeSet.has(x.dbId)),\n fadeIn: fadeIn.filter((x) => !excludeSet.has(x.dbId)),\n };\n}\n\n/**\n * One selectable orphan row. Prompt-first (users recognise tracks by prompt);\n * role + id + the auto gesture sit underneath in a smaller, muted font.\n */\nfunction OrphanRow({\n track,\n gesture,\n selected,\n disabled,\n onSelect,\n testId,\n}: {\n track: SceneFamilyTrack;\n gesture: FadeGesture;\n selected: boolean;\n disabled: boolean;\n onSelect: () => void;\n testId: string;\n}): React.ReactElement {\n const primary = track.prompt?.trim() || track.name;\n const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(' · ');\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n data-testid={testId}\n data-value={track.dbId}\n onClick={onSelect}\n disabled={disabled}\n className={`w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${\n selected\n ? 'bg-sas-accent/15 border-sas-accent'\n : 'bg-sas-panel border-sas-border hover:border-sas-accent/50'\n }`}\n >\n <div className=\"text-xs text-sas-text truncate\" title={primary}>\n {primary}\n </div>\n {meta && (\n <div className=\"text-[10px] text-sas-muted truncate mt-0.5\" title={track.dbId}>\n {meta}\n </div>\n )}\n </button>\n );\n}\n\nexport function FadeModal({\n host,\n open,\n fromSceneId,\n toSceneId,\n fromSceneName,\n toSceneName,\n excludeSourceDbIds,\n onClose,\n onCreate,\n testIdPrefix = 'fade-modal',\n}: FadeModalProps): React.ReactElement | null {\n const [load, setLoad] = useState<LoadState>({ status: 'loading' });\n const [selectedDbId, setSelectedDbId] = useState<string>('');\n const [isCreating, setIsCreating] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [fromName, setFromName] = useState<string | null>(null);\n const [toName, setToName] = useState<string | null>(null);\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!host.listSceneFamilyTracks) {\n setLoad({ status: 'error', message: 'This host does not support fades.' });\n return;\n }\n setLoad({ status: 'loading' });\n try {\n const [from, to, fName, tName] = await Promise.all([\n host.listSceneFamilyTracks(fromSceneId),\n host.listSceneFamilyTracks(toSceneId),\n host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),\n host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),\n ]);\n setFromName(fName);\n setToName(tName);\n setLoad({ status: 'ready', from, to });\n } catch (err: unknown) {\n setLoad({ status: 'error', message: err instanceof Error ? err.message : 'Failed to load tracks.' });\n }\n }, [host, fromSceneId, toSceneId]);\n\n // Fetch on open; reset state.\n useEffect(() => {\n if (open) {\n setError(null);\n setIsCreating(false);\n setSelectedDbId('');\n void refresh();\n }\n }, [open, refresh]);\n\n const excludeSet = useMemo(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);\n\n const { fadeOut, fadeIn } = useMemo(\n () =>\n load.status === 'ready'\n ? computeOrphans(load.from, load.to, excludeSet)\n : { fadeOut: [] as SceneFamilyTrack[], fadeIn: [] as SceneFamilyTrack[] },\n [load, excludeSet],\n );\n\n // One flat selection space across both sections (dbIds are unique).\n const allOrphans = useMemo(\n () => [\n ...fadeOut.map((t) => ({ track: t, direction: 'out' as FadeDirection })),\n ...fadeIn.map((t) => ({ track: t, direction: 'in' as FadeDirection })),\n ],\n [fadeOut, fadeIn],\n );\n\n // Keep the selection valid / defaulted to the first orphan.\n useEffect(() => {\n if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {\n setSelectedDbId(allOrphans[0]?.track.dbId ?? '');\n }\n }, [allOrphans, selectedDbId]);\n\n const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;\n const canCreate = !isCreating && !!selected;\n\n const handleClose = useCallback((): void => {\n if (!isCreating) onClose();\n }, [isCreating, onClose]);\n\n const handleCreate = useCallback(async (): Promise<void> => {\n if (!selected) return;\n setIsCreating(true);\n setError(null);\n try {\n await onCreate(\n { dbId: selected.track.dbId, name: selected.track.name, role: selected.track.role },\n selected.direction,\n defaultFadeGesture(selected.track.role),\n );\n onClose();\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Failed to create fade.');\n setIsCreating(false);\n }\n }, [selected, onCreate, onClose]);\n\n const fromLabel = fromName ?? fromSceneName ?? null;\n const toLabel = toName ?? toSceneName ?? null;\n\n if (!open) return null;\n\n const renderSection = (\n heading: string,\n list: SceneFamilyTrack[],\n section: 'out' | 'in',\n ): React.ReactElement | null => {\n if (list.length === 0) return null;\n return (\n <div className=\"block\">\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted\">{heading}</span>\n <div\n role=\"radiogroup\"\n aria-label={heading}\n data-testid={`${testIdPrefix}-${section === 'out' ? 'fade-out' : 'fade-in'}-list`}\n className=\"mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5\"\n >\n {list.map((t) => (\n <OrphanRow\n key={t.dbId}\n track={t}\n gesture={defaultFadeGesture(t.role)}\n selected={t.dbId === selectedDbId}\n disabled={isCreating}\n onSelect={() => setSelectedDbId(t.dbId)}\n testId={`${testIdPrefix}-option-${t.dbId}`}\n />\n ))}\n </div>\n </div>\n );\n };\n\n return (\n <Modal open={open} onClose={handleClose} testIdPrefix={testIdPrefix} initialFocusRef={cancelRef}>\n <div\n className=\"bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3\"\n onClick={(e: React.MouseEvent) => e.stopPropagation()}\n data-testid={`${testIdPrefix}-box`}\n >\n <h3 className=\"text-sm font-bold text-sas-text\">Add fade</h3>\n <p className=\"text-[11px] text-sas-muted leading-relaxed\">\n Tracks with no counterpart between{' '}\n <span className=\"text-sas-text\">{fromLabel ?? 'the origin scene'}</span> and{' '}\n <span className=\"text-sas-text\">{toLabel ?? 'the target scene'}</span> can gracefully fade\n out (leaving) or fade in (entering) across this transition.\n </p>\n\n {load.status === 'loading' && (\n <div className=\"text-xs text-sas-muted py-4 text-center\">Loading tracks…</div>\n )}\n {load.status === 'error' && (\n <div className=\"text-xs text-sas-danger py-4 text-center\">{load.message}</div>\n )}\n {load.status === 'ready' &&\n (allOrphans.length === 0 ? (\n <div className=\"text-xs text-sas-muted py-4 text-center\" data-testid={`${testIdPrefix}-empty`}>\n Every track has a counterpart in the other scene — nothing to fade. Use “+ Crossfade” to\n bridge matching tracks.\n </div>\n ) : (\n <>\n {renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ''}`, fadeOut, 'out')}\n {renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ''}`, fadeIn, 'in')}\n </>\n ))}\n\n {error && (\n <div className=\"text-xs text-sas-danger\" data-testid={`${testIdPrefix}-error`}>\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-2 pt-1\">\n <button\n ref={cancelRef}\n data-testid={`${testIdPrefix}-cancel`}\n onClick={onClose}\n disabled={isCreating}\n className=\"px-3 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-text disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n data-testid={`${testIdPrefix}-confirm`}\n onClick={handleCreate}\n disabled={!canCreate}\n className={`px-3 py-1 text-xs rounded-sm border transition-colors ${\n canCreate\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n >\n {isCreating ? 'Generating fade…' : 'Create fade'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport default FadeModal;\n","/**\n * ImportTrackModal — \"import a track from another scene\" picker (SDK component).\n *\n * Shared by all five generator panels (drums / instruments / synths / loops /\n * stems). Self-fetching: given the scoped `host`, it calls\n * `host.listImportableTracks()` to enumerate candidates (already filtered to\n * the calling panel's type and gate-annotated by the host) and\n * `host.importTrack()` to perform the copy. The UI only renders `importable` +\n * `disabledReason` — it never computes the harmonic/length/tempo gate itself.\n *\n * Two-step picker: choose a source scene, then a track in it. Incompatible\n * tracks render disabled with a reason tooltip (never hidden), per product\n * decision.\n *\n * @since SDK 2.13.0\n */\n\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { Modal } from './Modal';\nimport type {\n PluginHost,\n ImportCandidateScene,\n ImportCandidateTrack,\n PluginTrackHandle,\n} from '../types/plugin-sdk.types';\n\nexport interface ImportTrackModalProps {\n /** Scoped host — the modal calls listImportableTracks / importTrack itself. */\n host: PluginHost;\n /** Controls visibility (the panel owns open/closed from its header button). */\n open: boolean;\n /** Close handler (Escape, backdrop, Cancel, or after a successful import). */\n onClose: () => void;\n /** Fired after a successful import with the new track handle. */\n onImported: (handle: PluginTrackHandle) => void;\n /** Optional modal title (default names the whole-track import). */\n title?: string;\n /** data-testid prefix so each panel's modal is addressable in tests. */\n testIdPrefix?: string;\n /**\n * 'track' (default) imports a whole track via `importTrack`. 'sound' copies\n * ONLY the sound onto an existing track: every candidate is selectable (the\n * contract gate is ignored) and the chosen track is handed back via `onPick`\n * instead of being imported — the panel applies it via `host.getTrackSound`.\n */\n mode?: 'track' | 'sound';\n /** Sound-mode pick handler — required when `mode='sound'`. */\n onPick?: (sel: { sourceTrackDbId: string; trackName: string; sceneName: string }) => void | Promise<void>;\n /**\n * Cross-panel port handler (track mode). When provided, the modal also lists\n * the ACTIVE scene's tracks owned by OTHER panels as a `sameScene` group —\n * shown first and selected by default — and routes a pick there to this\n * callback instead of `importTrack`. The panel re-sounds the part on its own\n * instrument (create track → copy MIDI → load native sound). @since SDK 2.20.0\n */\n onPortTrack?: (sel: { sourceTrackDbId: string; trackName: string; role?: string }) => void | Promise<void>;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; scenes: ImportCandidateScene[] };\n\nexport function ImportTrackModal({\n host,\n open,\n onClose,\n onImported,\n title = 'Import track from scene (must match contract)',\n testIdPrefix = 'import-track',\n mode = 'track',\n onPick,\n onPortTrack,\n}: ImportTrackModalProps): React.ReactElement | null {\n const [load, setLoad] = useState<LoadState>({ status: 'loading' });\n const [selectedSceneId, setSelectedSceneId] = useState<string | null>(null);\n const [importingTrackId, setImportingTrackId] = useState<string | null>(null);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!host.listImportableTracks) {\n setLoad({ status: 'error', message: 'This host does not support importing tracks.' });\n return;\n }\n setLoad({ status: 'loading' });\n try {\n // Track mode with a port handler also wants the \"this scene — other\n // panels\" group (cross-panel re-sound source); plain/sound flows don't.\n const wantsPort = mode === 'track' && !!onPortTrack;\n const scenes = await host.listImportableTracks(wantsPort ? { includeSameScene: true } : undefined);\n setLoad({ status: 'ready', scenes });\n // Default to the same-scene group when present so the user lands on\n // cross-panel tracks (they can ← back to pick another scene).\n const sameScene = scenes.find((s) => s.sameScene);\n if (sameScene) setSelectedSceneId(sameScene.sceneId);\n } catch (err: unknown) {\n setLoad({ status: 'error', message: err instanceof Error ? err.message : 'Failed to load scenes.' });\n }\n }, [host, mode, onPortTrack]);\n\n // Fetch candidates each time the modal opens; reset selection on close.\n useEffect(() => {\n if (open) {\n setSelectedSceneId(null);\n setImportingTrackId(null);\n void refresh();\n }\n }, [open, refresh]);\n\n const handleImport = useCallback(\n async (\n track: ImportCandidateTrack,\n sourceSceneId: string,\n sceneName: string,\n isSameScene: boolean,\n ): Promise<void> => {\n // Same-scene, other-panel pick: re-sound the part on THIS panel's\n // instrument. The panel creates a track, copies the MIDI, and loads its\n // own sound (see onPortTrack) — never a faithful copy / importTrack.\n if (isSameScene && onPortTrack) {\n if (!track.importable) return;\n setImportingTrackId(track.trackId);\n try {\n await onPortTrack({ sourceTrackDbId: track.dbId, trackName: track.name, role: track.role });\n onClose();\n } catch (err: unknown) {\n host.showToast?.('error', err instanceof Error ? err.message : 'Import failed');\n setImportingTrackId(null);\n }\n return;\n }\n // Sound mode: ignore the gate and hand the pick back to the panel, which\n // reads the source sound via host.getTrackSound and applies it itself.\n if (mode === 'sound') {\n setImportingTrackId(track.trackId);\n try {\n await onPick?.({ sourceTrackDbId: track.dbId, trackName: track.name, sceneName });\n onClose();\n } catch (err: unknown) {\n host.showToast?.('error', err instanceof Error ? err.message : 'Import failed');\n setImportingTrackId(null);\n }\n return;\n }\n if (!track.importable || !host.importTrack) return;\n setImportingTrackId(track.trackId);\n try {\n const handle = await host.importTrack({ sourceSceneId, sourceTrackId: track.trackId });\n onImported(handle);\n onClose();\n } catch (err: unknown) {\n host.showToast?.('error', err instanceof Error ? err.message : 'Import failed');\n setImportingTrackId(null);\n }\n },\n [host, onImported, onClose, mode, onPick, onPortTrack],\n );\n\n if (!open) return null;\n\n const scenes = load.status === 'ready' ? load.scenes : [];\n const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;\n\n return (\n <Modal open={open} onClose={onClose} testIdPrefix={testIdPrefix}>\n <div\n className=\"w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl\"\n onClick={(e) => e.stopPropagation()}\n data-testid={`${testIdPrefix}-modal`}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-sas-border\">\n <div className=\"flex items-center gap-2\">\n {selectedScene && (\n <button\n className=\"text-sas-muted hover:text-sas-accent text-xs\"\n onClick={() => setSelectedSceneId(null)}\n data-testid={`${testIdPrefix}-back`}\n >\n ←\n </button>\n )}\n <span className=\"text-sm font-medium text-sas-text\">\n {selectedScene ? selectedScene.sceneName : title}\n </span>\n </div>\n <button\n className=\"text-sas-muted hover:text-sas-accent text-sm\"\n onClick={onClose}\n data-testid={`${testIdPrefix}-close`}\n >\n ✕\n </button>\n </div>\n\n {/* Body */}\n <div className=\"overflow-y-auto p-2 flex-1\">\n {load.status === 'loading' && (\n <div className=\"py-8 text-center text-xs text-sas-muted\" data-testid={`${testIdPrefix}-loading`}>\n Loading scenes…\n </div>\n )}\n\n {load.status === 'error' && (\n <div className=\"py-8 text-center text-xs text-red-400\" data-testid={`${testIdPrefix}-error`}>\n {load.message}\n </div>\n )}\n\n {load.status === 'ready' && scenes.length === 0 && (\n <div className=\"py-8 text-center text-xs text-sas-muted\" data-testid={`${testIdPrefix}-empty`}>\n {mode === 'sound'\n ? 'No other scenes have a sound to import.'\n : 'No other scenes have a compatible track to import.'}\n </div>\n )}\n\n {/* Scene list */}\n {load.status === 'ready' && scenes.length > 0 && !selectedScene && (\n <ul className=\"flex flex-col gap-1\" data-testid={`${testIdPrefix}-scene-list`}>\n {scenes.map((scene) => (\n <li key={scene.sceneId}>\n <button\n className=\"w-full flex items-center justify-between px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt text-left text-xs text-sas-text hover:border-sas-accent hover:text-sas-accent transition-colors\"\n onClick={() => setSelectedSceneId(scene.sceneId)}\n data-testid={`${testIdPrefix}-scene`}\n >\n <span className=\"truncate\">{scene.sceneName}</span>\n <span className=\"text-sas-muted\">{scene.tracks.length} →</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n\n {/* Track list */}\n {selectedScene && (\n <ul className=\"flex flex-col gap-1\" data-testid={`${testIdPrefix}-track-list`}>\n {selectedScene.tracks.map((track) => {\n const busy = importingTrackId === track.trackId;\n // Sound mode ignores the contract gate — every candidate is a\n // valid sound source. Track mode honors `importable`.\n const gated = mode === 'track' && !track.importable;\n const disabled = gated || busy;\n return (\n <li key={track.dbId}>\n <button\n className={`w-full flex items-center justify-between px-2 py-1.5 rounded-sm border text-left text-xs transition-colors ${\n disabled\n ? 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n : 'bg-sas-panel-alt border-sas-border text-sas-text hover:border-sas-accent hover:text-sas-accent'\n }`}\n disabled={disabled}\n title={gated ? track.disabledReason : undefined}\n onClick={() => void handleImport(track, selectedScene.sceneId, selectedScene.sceneName, !!selectedScene.sameScene)}\n data-testid={`${testIdPrefix}-track`}\n data-importable={mode === 'sound' || track.importable ? 'true' : 'false'}\n >\n <span className=\"truncate\">\n {track.name}\n {track.role ? <span className=\"text-sas-muted\"> · {track.role}</span> : null}\n </span>\n {busy ? (\n <span className=\"text-sas-muted\">…</span>\n ) : gated ? (\n <span className=\"text-sas-muted\">⊘</span>\n ) : null}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n </Modal>\n );\n}\n","/**\n * CrossfadeModal — \"add a crossfade track\" picker for a transition scene.\n *\n * Shown only inside a `scene_type='transition'` scene. The user picks an ORIGIN\n * track (from the transition's FROM scene) and a TARGET track (from its TO\n * scene), in ANY order — the only constraint is same plugin/family (the picker is\n * per-panel). A source track already used in a crossfade is hidden (via\n * excludeSourceDbIds), so each source is used at most once.\n *\n * Self-fetching: given the scoped `host`, it calls `host.listSceneFamilyTracks`\n * for both scenes (ungated — a transition deliberately bridges different keys).\n * It does NOT build the pair itself; it hands the two selections to `onCreate`,\n * which the panel implements (create two tracks, generate one shared MIDI clip,\n * copy each preset). `onCreate` should reject on failure so the modal can show\n * it and stay open.\n *\n * @since SDK 2.22.0\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Modal } from './Modal';\nimport type { PluginHost, SceneFamilyTrack } from '../types/plugin-sdk.types';\n\n/** A picked source track handed to `onCreate`. */\nexport interface CrossfadeSelection {\n /** Source track DB id (selector for getTrackSound + crossfade metadata). */\n dbId: string;\n /** Display name (for the row caption). */\n name: string;\n /** Musical role of the source track (the panel uses the TARGET's for generation). */\n role?: string;\n}\n\nexport interface CrossfadeModalProps {\n /** Scoped host — the modal calls listSceneFamilyTracks itself. */\n host: PluginHost;\n /** Controls visibility (the panel owns open/closed from its header button). */\n open: boolean;\n /** DB id of the transition's FROM (origin) scene. */\n fromSceneId: string;\n /** DB id of the transition's TO (target) scene. */\n toSceneId: string;\n /** Display name for the origin scene heading (optional). */\n fromSceneName?: string;\n /** Display name for the target scene heading (optional). */\n toSceneName?: string;\n /**\n * Source-track DB ids already used in a crossfade (origin + target of every\n * existing pair in this panel). Hidden from BOTH dropdowns so each source is\n * used at most once. @since SDK 2.26.0\n */\n excludeSourceDbIds?: readonly string[];\n /** Close handler (Escape, backdrop, Cancel, or after a successful create). */\n onClose: () => void;\n /** Build the crossfade pair. Should reject on failure so the modal shows it. */\n onCreate: (origin: CrossfadeSelection, target: CrossfadeSelection) => Promise<void>;\n /** data-testid prefix. */\n testIdPrefix?: string;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; origin: SceneFamilyTrack[]; target: SceneFamilyTrack[] };\n\n/** Short, recognisable id prefix — the full id lives in the row's title. */\nfunction shortId(dbId: string): string {\n return dbId.length > 8 ? dbId.slice(0, 8) : dbId;\n}\n\n/**\n * One selectable track row. Users recognise tracks by their generation prompt,\n * so the prompt is the prominent line; role + id sit underneath in a smaller,\n * muted font (prompt → role → id order). Falls back to the display name when a\n * track has no prompt (e.g. sample/audio).\n */\nfunction CandidateRow({\n track,\n selected,\n disabled,\n onSelect,\n testId,\n}: {\n track: SceneFamilyTrack;\n selected: boolean;\n disabled: boolean;\n onSelect: () => void;\n testId: string;\n}): React.ReactElement {\n const primary = track.prompt?.trim() || track.name;\n const meta = [track.role, shortId(track.dbId)].filter(Boolean).join(' · ');\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n data-testid={testId}\n data-value={track.dbId}\n onClick={onSelect}\n disabled={disabled}\n className={`w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${\n selected\n ? 'bg-sas-accent/15 border-sas-accent'\n : 'bg-sas-panel border-sas-border hover:border-sas-accent/50'\n }`}\n >\n <div className=\"text-xs text-sas-text truncate\" title={primary}>\n {primary}\n </div>\n {meta && (\n <div className=\"text-[10px] text-sas-muted truncate mt-0.5\" title={track.dbId}>\n {meta}\n </div>\n )}\n </button>\n );\n}\n\nexport function CrossfadeModal({\n host,\n open,\n fromSceneId,\n toSceneId,\n fromSceneName,\n toSceneName,\n excludeSourceDbIds,\n onClose,\n onCreate,\n testIdPrefix = 'crossfade-modal',\n}: CrossfadeModalProps): React.ReactElement | null {\n const [load, setLoad] = useState<LoadState>({ status: 'loading' });\n const [originDbId, setOriginDbId] = useState<string>('');\n const [targetDbId, setTargetDbId] = useState<string>('');\n const [isCreating, setIsCreating] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [fromName, setFromName] = useState<string | null>(null);\n const [toName, setToName] = useState<string | null>(null);\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!host.listSceneFamilyTracks) {\n setLoad({ status: 'error', message: 'This host does not support crossfade tracks.' });\n return;\n }\n setLoad({ status: 'loading' });\n try {\n const [origin, target, fName, tName] = await Promise.all([\n host.listSceneFamilyTracks(fromSceneId),\n host.listSceneFamilyTracks(toSceneId),\n host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),\n host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),\n ]);\n setFromName(fName);\n setToName(tName);\n setLoad({ status: 'ready', origin, target });\n } catch (err: unknown) {\n setLoad({ status: 'error', message: err instanceof Error ? err.message : 'Failed to load tracks.' });\n }\n }, [host, fromSceneId, toSceneId]);\n\n // Fetch on open; reset state.\n useEffect(() => {\n if (open) {\n setError(null);\n setIsCreating(false);\n setOriginDbId('');\n setTargetDbId('');\n void refresh();\n }\n }, [open, refresh]);\n\n // Hide any source track already used in a crossfade (each source used once).\n const excludeSet = useMemo(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);\n\n // The only constraint is same plugin/family (already enforced per-panel), so the\n // two lists are independent — pick in any order, any role.\n const originCandidates = useMemo(\n () => (load.status === 'ready' ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : []),\n [load, excludeSet],\n );\n const targetCandidates = useMemo(\n () => (load.status === 'ready' ? load.target.filter((t) => !excludeSet.has(t.dbId)) : []),\n [load, excludeSet],\n );\n\n // Keep each selection valid / defaulted to its first candidate, independently.\n useEffect(() => {\n if (!originCandidates.some((t) => t.dbId === originDbId)) {\n setOriginDbId(originCandidates[0]?.dbId ?? '');\n }\n }, [originCandidates, originDbId]);\n useEffect(() => {\n if (!targetCandidates.some((t) => t.dbId === targetDbId)) {\n setTargetDbId(targetCandidates[0]?.dbId ?? '');\n }\n }, [targetCandidates, targetDbId]);\n\n const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;\n const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;\n const canCreate = !isCreating && !!originTrack && !!targetTrack;\n\n const handleClose = useCallback((): void => {\n if (!isCreating) onClose();\n }, [isCreating, onClose]);\n\n const handleCreate = useCallback(async (): Promise<void> => {\n if (!originTrack || !targetTrack) return;\n setIsCreating(true);\n setError(null);\n try {\n await onCreate(\n { dbId: originTrack.dbId, name: originTrack.name, role: originTrack.role },\n { dbId: targetTrack.dbId, name: targetTrack.name, role: targetTrack.role },\n );\n onClose();\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Failed to create crossfade.');\n setIsCreating(false);\n }\n }, [originTrack, targetTrack, onCreate, onClose]);\n\n // Prefer the live-fetched scene names; fall back to the optional props.\n const fromLabel = fromName ?? fromSceneName ?? null;\n const toLabel = toName ?? toSceneName ?? null;\n\n if (!open) return null;\n\n return (\n <Modal open={open} onClose={handleClose} testIdPrefix={testIdPrefix} initialFocusRef={cancelRef}>\n <div\n className=\"bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3\"\n onClick={(e: React.MouseEvent) => e.stopPropagation()}\n data-testid={`${testIdPrefix}-box`}\n >\n <h3 className=\"text-sm font-bold text-sas-text\">Add crossfade</h3>\n <p className=\"text-[11px] text-sas-muted leading-relaxed\">\n Bridge a track from{' '}\n <span className=\"text-sas-text\">{fromLabel ?? 'the origin scene'}</span> into one from{' '}\n <span className=\"text-sas-text\">{toLabel ?? 'the target scene'}</span>. Both layers share one\n generated part; each keeps its own preset.\n </p>\n\n {load.status === 'loading' && (\n <div className=\"text-xs text-sas-muted py-4 text-center\">Loading tracks…</div>\n )}\n {load.status === 'error' && (\n <div className=\"text-xs text-sas-danger py-4 text-center\">{load.message}</div>\n )}\n {load.status === 'ready' &&\n (originCandidates.length === 0 ? (\n <div\n className=\"text-xs text-sas-muted py-4 text-center\"\n data-testid={`${testIdPrefix}-empty-origin`}\n >\n No available tracks in {fromLabel ?? 'the origin scene'}. Add one (or free one from another\n crossfade) first.\n </div>\n ) : (\n <>\n <div className=\"block\">\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted\">\n Origin {fromLabel ? `(${fromLabel})` : '(top)'}\n </span>\n <div\n role=\"radiogroup\"\n aria-label=\"Origin track\"\n data-testid={`${testIdPrefix}-origin-list`}\n className=\"mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5\"\n >\n {originCandidates.map((t) => (\n <CandidateRow\n key={t.dbId}\n track={t}\n selected={t.dbId === originDbId}\n disabled={isCreating}\n onSelect={() => setOriginDbId(t.dbId)}\n testId={`${testIdPrefix}-origin-option-${t.dbId}`}\n />\n ))}\n </div>\n </div>\n\n <div className=\"block\">\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted\">\n Target {toLabel ? `(${toLabel})` : '(bottom)'}\n </span>\n {targetCandidates.length === 0 ? (\n <div className=\"text-xs text-sas-danger mt-0.5\" data-testid={`${testIdPrefix}-empty-target`}>\n No available tracks in {toLabel ?? 'the target scene'} to crossfade into.\n </div>\n ) : (\n <div\n role=\"radiogroup\"\n aria-label=\"Target track\"\n data-testid={`${testIdPrefix}-target-list`}\n className=\"mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5\"\n >\n {targetCandidates.map((t) => (\n <CandidateRow\n key={t.dbId}\n track={t}\n selected={t.dbId === targetDbId}\n disabled={isCreating}\n onSelect={() => setTargetDbId(t.dbId)}\n testId={`${testIdPrefix}-target-option-${t.dbId}`}\n />\n ))}\n </div>\n )}\n </div>\n </>\n ))}\n\n {error && (\n <div className=\"text-xs text-sas-danger\" data-testid={`${testIdPrefix}-error`}>\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-2 pt-1\">\n <button\n ref={cancelRef}\n data-testid={`${testIdPrefix}-cancel`}\n onClick={onClose}\n disabled={isCreating}\n className=\"px-3 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-text disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n data-testid={`${testIdPrefix}-confirm`}\n onClick={handleCreate}\n disabled={!canCreate}\n className={`px-3 py-1 text-xs rounded-sm border transition-colors ${\n canCreate\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n >\n {isCreating ? 'Generating bridge…' : 'Create crossfade'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport default CrossfadeModal;\n","/**\n * TransitionDesigner — the per-panel transition staging board, rendered INLINE as\n * a toggled view inside a generator panel (NOT a modal).\n *\n * The multi-row, persistent successor to {@link CrossfadeModal} + {@link FadeModal}:\n * instead of opening a single-pair dialog ~20 times, the panel header gains a\n * Tracks ⇄ Transition toggle; flipping to Transition replaces the panel's track\n * list with this board. Playback is unaffected (the engine keeps playing the\n * scene's tracks — they're just not shown until you toggle back). Shown only\n * inside a `scene_type='transition'` scene and scoped to one panel family (a synth\n * board shows only synth tracks — \"drums can't crossfade to synth\" is enforced\n * structurally because each board asks its own family-scoped host).\n *\n * Two index-aligned, drag-reorderable columns: origin (scene A, left) and target\n * (scene B, right). Row i pairs origin[i] with target[i]; the pairing derives the\n * transition type (both → crossfade, origin-only → fade out, target-only → fade\n * in). Insert a \"gap\" above a cell to push a track so it fades instead of\n * crossfading with whatever sits opposite. The pool per column is the scene's\n * family tracks MINUS sources already consumed by a committed crossfade/fade\n * (`excludeSourceDbIds`).\n *\n * Per-row **Create** reuses the panel's EXISTING orchestration via `onCreateCrossfade`\n * / `onCreateFade`. Creates run CONCURRENTLY — fire several at once, or **Create all**\n * (a bounded pool). Each in-flight row shows its own progress bar and locks just its\n * own cells; the rest of the board stays editable. On success the source leaves the\n * pool (the panel updates `excludeSourceDbIds`) and the row collapses; deleting the\n * committed crossfade/fade on the deck returns the source here. The staged\n * arrangement persists to the transition scene's plugin_data so it survives toggles.\n *\n * @since SDK 2.29.0 (modal); inline toggle view + concurrent creation since 2.30.0.\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { DragEvent } from 'react';\nimport { SorceryProgressBar } from './SorceryProgressBar';\nimport { moveItem } from '../hooks/useTrackReorder';\nimport type { PluginHost, SceneFamilyTrack } from '../types/plugin-sdk.types';\nimport type { CrossfadeSelection } from './CrossfadeModal';\nimport type { FadeSelection } from './FadeModal';\nimport { type FadeDirection, type FadeGesture, defaultFadeGesture } from '../fade-meta';\nimport {\n TRANSITION_DESIGNER_DRAFT_KEY,\n type TransitionRowType,\n type DesignerRowSlots,\n asTransitionDesignerDraft,\n reconcileSlots,\n buildRowSlots,\n normalizeSlots,\n padPair,\n slotsEqual,\n rowKey,\n dbIdsFromKeys,\n type AudioEffect,\n AUDIO_EFFECTS,\n AUDIO_EFFECT_LABEL,\n} from '../transition-designer-meta';\n\ntype Column = 'origin' | 'target';\n\nexport interface TransitionDesignerProps {\n /** Scoped host — the board calls listSceneFamilyTracks / getSceneName itself. */\n host: PluginHost;\n /** DB id of the transition's FROM (origin) scene. */\n fromSceneId: string;\n /** DB id of the transition's TO (target) scene. */\n toSceneId: string;\n /** DB id of the transition scene itself — the staged draft is persisted here. */\n transitionSceneId: string;\n /**\n * Source-track DB ids already consumed by a committed crossfade OR fade in this\n * panel. Hidden from both columns so each source is used at most once; when the\n * deck row is deleted the panel drops the id and the source reappears here.\n */\n excludeSourceDbIds?: readonly string[];\n /**\n * Build a crossfade pair — the panel's existing handler (create two tracks, one\n * morphed clip, copy each preset). Should reject on failure. Safe to call\n * concurrently.\n */\n onCreateCrossfade: (origin: CrossfadeSelection, target: CrossfadeSelection) => Promise<void>;\n /** Build a one-sided fade — the panel's existing handler. Should reject on failure. */\n onCreateFade: (\n selection: FadeSelection,\n direction: FadeDirection,\n gesture: FadeGesture,\n ) => Promise<void>;\n /**\n * Build an AUDIO-only one-sided transition (stutter / chopped / delay). When\n * provided, one-sided rows render an effect selector; absent (MIDI panels) →\n * one-sided rows stay plain fades. @since SDK 2.32.0\n */\n onCreateAudioTransition?: (\n selection: FadeSelection,\n direction: FadeDirection,\n effect: 'stutter' | 'chopped' | 'delay',\n ) => Promise<void>;\n /** Short family label for the heading, e.g. \"Synths\". */\n familyLabel?: string;\n /** data-testid prefix. */\n testIdPrefix?: string;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; origin: SceneFamilyTrack[]; target: SceneFamilyTrack[] };\n\n/** ~time the LLM morph/fade generation takes, for the time-based progress bar. */\nconst CROSSFADE_ESTIMATE_MS = 15000;\nconst FADE_ESTIMATE_MS = 11000;\n/** Bounded fan-out for \"Create all\" (the user wants ~3-5 at once). */\nconst CREATE_ALL_CONCURRENCY = 5;\n\nconst TYPE_LABEL: Record<TransitionRowType, string> = {\n crossfade: 'Crossfade',\n 'fade-out': 'Fade out',\n 'fade-in': 'Fade in',\n};\n\n/** Short, recognisable id prefix — the full id lives in the cell's title. */\nfunction shortId(dbId: string): string {\n return dbId.length > 8 ? dbId.slice(0, 8) : dbId;\n}\n\nexport function TransitionDesigner({\n host,\n fromSceneId,\n toSceneId,\n transitionSceneId,\n excludeSourceDbIds,\n onCreateCrossfade,\n onCreateFade,\n onCreateAudioTransition,\n familyLabel,\n testIdPrefix = 'transition-designer',\n}: TransitionDesignerProps): React.ReactElement {\n const [load, setLoad] = useState<LoadState>({ status: 'loading' });\n const [fromName, setFromName] = useState<string | null>(null);\n const [toName, setToName] = useState<string | null>(null);\n // Columns are kept padded to equal length (aligned rendering + drag); trimmed\n // only at persist time (normalizeSlots).\n const [originSlots, setOriginSlots] = useState<(string | null)[]>([]);\n const [targetSlots, setTargetSlots] = useState<(string | null)[]>([]);\n // In-flight creates, keyed by a STABLE row key (source dbIds, not index) so\n // several can run at once and a reorder mid-create still tracks the right row.\n const [creatingKeys, setCreatingKeys] = useState<Set<string>>(() => new Set());\n const [rowErrors, setRowErrors] = useState<Record<string, string>>({});\n // Per one-sided-row audio effect (keyed by source dbId). Only meaningful when\n // onCreateAudioTransition is provided (audio panels).\n const [rowEffects, setRowEffects] = useState<Record<string, AudioEffect>>({});\n const rowEffectsRef = useRef(rowEffects);\n rowEffectsRef.current = rowEffects;\n const audioEffectsEnabled = !!onCreateAudioTransition;\n\n // Latest props/state read inside effects + handlers without widening deps.\n const excludeRef = useRef(excludeSourceDbIds);\n excludeRef.current = excludeSourceDbIds;\n const originSlotsRef = useRef(originSlots);\n originSlotsRef.current = originSlots;\n const targetSlotsRef = useRef(targetSlots);\n targetSlotsRef.current = targetSlots;\n const creatingKeysRef = useRef(creatingKeys);\n creatingKeysRef.current = creatingKeys;\n\n // Drag state: a ref drives the drop computation (no stale closure); the matching\n // React state drives the dim/highlight visuals.\n const dragRef = useRef<{ col: Column; index: number } | null>(null);\n const [dragging, setDragging] = useState<{ col: Column; index: number } | null>(null);\n const [dragOver, setDragOver] = useState<{ col: Column; index: number } | null>(null);\n\n const excludeSet = useMemo(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);\n const originPool = useMemo(\n () => (load.status === 'ready' ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : []),\n [load, excludeSet],\n );\n const targetPool = useMemo(\n () => (load.status === 'ready' ? load.target.filter((t) => !excludeSet.has(t.dbId)) : []),\n [load, excludeSet],\n );\n const originById = useMemo(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);\n const targetById = useMemo(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);\n const originByIdRef = useRef(originById);\n originByIdRef.current = originById;\n const targetByIdRef = useRef(targetById);\n targetByIdRef.current = targetById;\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!host.listSceneFamilyTracks) {\n setLoad({ status: 'error', message: 'This host does not support transition tracks.' });\n return;\n }\n setLoad({ status: 'loading' });\n try {\n const [origin, target, fName, tName, draftRaw] = await Promise.all([\n host.listSceneFamilyTracks(fromSceneId),\n host.listSceneFamilyTracks(toSceneId),\n host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),\n host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),\n host.getSceneData\n ? host.getSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY)\n : Promise.resolve(null),\n ]);\n const draft = asTransitionDesignerDraft(draftRaw);\n const exSet = new Set(excludeRef.current ?? []);\n const originIds = origin.filter((t) => !exSet.has(t.dbId)).map((t) => t.dbId);\n const targetIds = target.filter((t) => !exSet.has(t.dbId)).map((t) => t.dbId);\n const [po, pt] = padPair(\n reconcileSlots(draft?.originOrder, originIds),\n reconcileSlots(draft?.targetOrder, targetIds),\n );\n setOriginSlots(po);\n setTargetSlots(pt);\n setRowEffects(draft?.rowEffects ?? {});\n setFromName(fName);\n setToName(tName);\n setLoad({ status: 'ready', origin, target });\n } catch (err: unknown) {\n setLoad({\n status: 'error',\n message: err instanceof Error ? err.message : 'Failed to load tracks.',\n });\n }\n }, [host, fromSceneId, toSceneId, transitionSceneId]);\n\n // Fetch on mount (the panel mounts this only when the Transition view is active)\n // and whenever the bridged scenes change.\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n // Keep the columns in sync with the pool: drop sources consumed by a just-created\n // crossfade/fade (excludeSourceDbIds grew) and append any newly-added tracks.\n useEffect(() => {\n if (load.status !== 'ready') return;\n const [po, pt] = padPair(\n reconcileSlots(originSlotsRef.current, originPool.map((t) => t.dbId)),\n reconcileSlots(targetSlotsRef.current, targetPool.map((t) => t.dbId)),\n );\n if (!slotsEqual(po, originSlotsRef.current)) setOriginSlots(po);\n if (!slotsEqual(pt, targetSlotsRef.current)) setTargetSlots(pt);\n }, [originPool, targetPool, load.status]);\n\n // Persist the trimmed draft; re-pad state so rendering stays aligned.\n const mutate = useCallback(\n (nextOrigin: (string | null)[], nextTarget: (string | null)[]): void => {\n const norm = normalizeSlots(nextOrigin, nextTarget);\n const [po, pt] = padPair(norm.originOrder, norm.targetOrder);\n setOriginSlots(po);\n setTargetSlots(pt);\n if (host.setSceneData) {\n host.setSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY, { ...norm, rowEffects: rowEffectsRef.current }).catch(() => {});\n }\n },\n [host, transitionSceneId],\n );\n\n // Change a one-sided row's audio effect; persist alongside the slot draft.\n const setRowEffect = useCallback(\n (sourceDbId: string, effect: AudioEffect): void => {\n setRowEffects((prev) => {\n const next = { ...prev, [sourceDbId]: effect };\n if (host.setSceneData) {\n const norm = normalizeSlots(originSlotsRef.current, targetSlotsRef.current);\n host.setSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY, { ...norm, rowEffects: next }).catch(() => {});\n }\n return next;\n });\n },\n [host, transitionSceneId],\n );\n\n const insertGapAbove = useCallback(\n (col: Column, index: number): void => {\n const slots = col === 'origin' ? originSlots : targetSlots;\n const next = [...slots.slice(0, index), null, ...slots.slice(index)];\n if (col === 'origin') mutate(next, targetSlots);\n else mutate(originSlots, next);\n },\n [originSlots, targetSlots, mutate],\n );\n\n const removeGap = useCallback(\n (col: Column, index: number): void => {\n const slots = col === 'origin' ? originSlots : targetSlots;\n const next = slots.filter((_, i) => i !== index);\n if (col === 'origin') mutate(next, targetSlots);\n else mutate(originSlots, next);\n },\n [originSlots, targetSlots, mutate],\n );\n\n const handleDrop = useCallback(\n (col: Column, to: number): void => {\n const from = dragRef.current;\n dragRef.current = null;\n setDragging(null);\n setDragOver(null);\n if (!from || from.col !== col || from.index === to) return;\n if (col === 'origin') mutate(moveItem(originSlots, from.index, to), targetSlots);\n else mutate(originSlots, moveItem(targetSlots, from.index, to));\n },\n [originSlots, targetSlots, mutate],\n );\n\n const rows = useMemo(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);\n // Source dbIds with a create in flight — their cells lock (no drag / gap edits).\n const creatingDbIds = useMemo(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);\n const eligibleCount = useMemo(\n () => rows.filter((r) => { const k = rowKey(r); return k !== null && !creatingKeys.has(k); }).length,\n [rows, creatingKeys],\n );\n\n // Create ONE row. Concurrency-safe: keyed by source dbIds; reuses the panel's\n // existing crossfade/fade orchestration. Reads latest maps/keys via refs.\n const createRow = useCallback(\n async (row: DesignerRowSlots): Promise<void> => {\n const key = rowKey(row);\n if (!key || !row.type || creatingKeysRef.current.has(key)) return;\n setCreatingKeys((prev) => new Set(prev).add(key));\n setRowErrors((prev) => {\n if (!(key in prev)) return prev;\n const next = { ...prev };\n delete next[key];\n return next;\n });\n try {\n if (row.type === 'crossfade') {\n const o = row.originId ? originByIdRef.current.get(row.originId) : undefined;\n const t = row.targetId ? targetByIdRef.current.get(row.targetId) : undefined;\n if (!o || !t) throw new Error('Track is no longer available — refresh and retry.');\n await onCreateCrossfade(\n { dbId: o.dbId, name: o.name, role: o.role },\n { dbId: t.dbId, name: t.name, role: t.role },\n );\n } else if (row.type === 'fade-out') {\n const o = row.originId ? originByIdRef.current.get(row.originId) : undefined;\n if (!o) throw new Error('Track is no longer available — refresh and retry.');\n const eff = rowEffectsRef.current[o.dbId] ?? 'fade';\n if (eff !== 'fade' && onCreateAudioTransition) {\n await onCreateAudioTransition({ dbId: o.dbId, name: o.name, role: o.role }, 'out', eff);\n } else {\n await onCreateFade({ dbId: o.dbId, name: o.name, role: o.role }, 'out', defaultFadeGesture(o.role));\n }\n } else {\n const t = row.targetId ? targetByIdRef.current.get(row.targetId) : undefined;\n if (!t) throw new Error('Track is no longer available — refresh and retry.');\n const eff = rowEffectsRef.current[t.dbId] ?? 'fade';\n if (eff !== 'fade' && onCreateAudioTransition) {\n await onCreateAudioTransition({ dbId: t.dbId, name: t.name, role: t.role }, 'in', eff);\n } else {\n await onCreateFade({ dbId: t.dbId, name: t.name, role: t.role }, 'in', defaultFadeGesture(t.role));\n }\n }\n } catch (err: unknown) {\n setRowErrors((prev) => ({\n ...prev,\n [key]: err instanceof Error ? err.message : 'Failed to create transition.',\n }));\n } finally {\n setCreatingKeys((prev) => {\n const next = new Set(prev);\n next.delete(key);\n return next;\n });\n }\n },\n [onCreateCrossfade, onCreateFade, onCreateAudioTransition],\n );\n\n // Fire every eligible row through a bounded concurrency pool.\n const createAll = useCallback(async (): Promise<void> => {\n const eligible = buildRowSlots(originSlotsRef.current, targetSlotsRef.current).filter((r) => {\n const k = rowKey(r);\n return k !== null && !creatingKeysRef.current.has(k);\n });\n if (eligible.length === 0) return;\n let cursor = 0;\n const worker = async (): Promise<void> => {\n while (cursor < eligible.length) {\n const row = eligible[cursor];\n cursor += 1;\n await createRow(row);\n }\n };\n await Promise.all(\n Array.from({ length: Math.min(CREATE_ALL_CONCURRENCY, eligible.length) }, () => worker()),\n );\n }, [createRow]);\n\n const fromLabel = fromName ?? 'origin';\n const toLabel = toName ?? 'target';\n\n const cellDragProps = (\n col: Column,\n index: number,\n locked: boolean,\n ): {\n draggable: boolean;\n onDragStart: (e: DragEvent<HTMLElement>) => void;\n onDragEnd: () => void;\n onDragEnter: (e: DragEvent<HTMLElement>) => void;\n onDragOver: (e: DragEvent<HTMLElement>) => void;\n onDragLeave: () => void;\n onDrop: (e: DragEvent<HTMLElement>) => void;\n } => ({\n draggable: !locked,\n onDragStart: (e) => {\n if (locked) return;\n dragRef.current = { col, index };\n setDragging({ col, index });\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n try {\n e.dataTransfer.setData('text/plain', String(index));\n } catch {\n /* some environments disallow setData — drag still works */\n }\n }\n },\n onDragEnd: () => {\n dragRef.current = null;\n setDragging(null);\n setDragOver(null);\n },\n onDragEnter: (e) => {\n const d = dragRef.current;\n if (!d || d.col !== col) return;\n e.preventDefault();\n setDragOver({ col, index });\n },\n onDragOver: (e) => {\n const d = dragRef.current;\n if (!d || d.col !== col) return;\n e.preventDefault();\n if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';\n },\n onDragLeave: () => {\n setDragOver((cur) => (cur && cur.col === col && cur.index === index ? null : cur));\n },\n onDrop: (e) => {\n e.preventDefault();\n handleDrop(col, index);\n },\n });\n\n const renderCell = (col: Column, index: number, slotId: string | null): React.ReactElement => {\n const byId = col === 'origin' ? originById : targetById;\n const track = slotId ? byId.get(slotId) : undefined;\n const locked = slotId !== null && creatingDbIds.has(slotId);\n const isDragging = dragging?.col === col && dragging.index === index;\n const isDragTarget = dragOver?.col === col && dragOver.index === index && !isDragging;\n const base =\n 'group relative rounded-sm border px-2 py-1.5 text-left transition-colors select-none';\n const tone = isDragTarget\n ? 'border-sas-accent bg-sas-accent/10'\n : 'border-sas-border bg-sas-panel';\n\n if (slotId === null) {\n return (\n <div\n {...cellDragProps(col, index, false)}\n data-testid={`${testIdPrefix}-${col}-gap-${index}`}\n className={`${base} ${tone} border-dashed flex items-center justify-between ${\n isDragging ? 'opacity-40' : 'opacity-70'\n }`}\n >\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted\">— gap —</span>\n <button\n type=\"button\"\n data-testid={`${testIdPrefix}-${col}-remove-gap-${index}`}\n onClick={() => removeGap(col, index)}\n title=\"Remove gap\"\n className=\"text-[10px] text-sas-muted hover:text-sas-danger\"\n >\n ✕\n </button>\n </div>\n );\n }\n\n const primary = track ? track.prompt?.trim() || track.name : slotId;\n const meta = track ? [track.role, shortId(track.dbId)].filter(Boolean).join(' · ') : 'missing';\n return (\n <div\n {...cellDragProps(col, index, locked)}\n data-testid={`${testIdPrefix}-${col}-cell-${slotId}`}\n data-value={slotId}\n className={`${base} ${tone} ${isDragging ? 'opacity-40' : ''} ${\n locked ? 'opacity-60' : 'cursor-grab active:cursor-grabbing'\n }`}\n title={track ? track.dbId : 'Track no longer available'}\n >\n <div className=\"flex items-start gap-1\">\n <span className=\"text-sas-muted/60 text-xs leading-tight pt-0.5\" aria-hidden>\n ⠿\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-xs text-sas-text truncate\">{primary}</div>\n {meta && <div className=\"text-[10px] text-sas-muted truncate mt-0.5\">{meta}</div>}\n </div>\n <button\n type=\"button\"\n data-testid={`${testIdPrefix}-${col}-insert-gap-${index}`}\n onClick={() => insertGapAbove(col, index)}\n disabled={locked}\n title=\"Insert a gap above (make this a fade)\"\n className=\"text-[10px] text-sas-muted opacity-0 group-hover:opacity-100 hover:text-sas-accent disabled:opacity-30\"\n >\n +gap\n </button>\n </div>\n </div>\n );\n };\n\n return (\n <div className=\"space-y-2\" data-testid={`${testIdPrefix}-box`}>\n {/* Header: hint + Create all */}\n <div className=\"flex items-center justify-between gap-3 pb-1 border-b border-sas-border\">\n <p className=\"text-[11px] text-sas-muted leading-snug min-w-0\">\n <span className=\"text-sas-text\">{fromLabel}</span> →{' '}\n <span className=\"text-sas-text\">{toLabel}</span>\n {familyLabel ? ` · ${familyLabel}` : ''} · line up a track on each side to crossfade;\n leave one blank (or insert a gap) to fade.\n </p>\n <div className=\"flex items-center gap-2 shrink-0\">\n {creatingKeys.size > 0 && (\n <span className=\"text-[10px] text-sas-accent whitespace-nowrap\" data-testid={`${testIdPrefix}-creating-count`}>\n {creatingKeys.size} creating…\n </span>\n )}\n <button\n type=\"button\"\n data-testid={`${testIdPrefix}-create-all`}\n onClick={createAll}\n disabled={eligibleCount === 0}\n title=\"Create every staged transition at once (runs several concurrently)\"\n className={`px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors whitespace-nowrap ${\n eligibleCount > 0\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n >\n Create all{eligibleCount > 0 ? ` (${eligibleCount})` : ''}\n </button>\n </div>\n </div>\n\n {/* Column headings */}\n <div className=\"grid grid-cols-[1fr_auto_1fr] gap-2\">\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted truncate\">\n Origin ({fromLabel})\n </span>\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted text-center px-2\">\n Transition\n </span>\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted truncate text-right\">\n Target ({toLabel})\n </span>\n </div>\n\n {/* Body */}\n {load.status === 'loading' && (\n <div className=\"text-xs text-sas-muted py-6 text-center\">Loading tracks…</div>\n )}\n {load.status === 'error' && (\n <div className=\"text-xs text-sas-danger py-6 text-center\" data-testid={`${testIdPrefix}-error`}>\n {load.message}\n </div>\n )}\n {load.status === 'ready' &&\n (rows.length === 0 ? (\n <div className=\"text-xs text-sas-muted py-6 text-center\" data-testid={`${testIdPrefix}-empty`}>\n No tracks to arrange in this panel for either scene. Add tracks to {fromLabel} or {toLabel}{' '}\n first (or free one by deleting an existing crossfade/fade).\n </div>\n ) : (\n <div className=\"space-y-2\">\n {rows.map((row, i) => {\n const key = rowKey(row);\n const isCreatingThis = key !== null && creatingKeys.has(key);\n const errMsg = key !== null ? rowErrors[key] : undefined;\n return (\n <div\n key={i}\n data-testid={`${testIdPrefix}-row-${i}`}\n className=\"grid grid-cols-[1fr_auto_1fr] gap-2 items-center\"\n >\n {renderCell('origin', i, row.originId)}\n\n {/* Center: type + create / progress */}\n <div className=\"w-[160px] flex flex-col items-center gap-1\">\n {!row.type ? (\n <span className=\"text-[10px] text-sas-muted/50\">—</span>\n ) : row.type === 'crossfade' ? (\n <span\n data-testid={`${testIdPrefix}-type-${i}`}\n className=\"text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-accent/50 text-sas-accent\"\n >\n {TYPE_LABEL[row.type]}\n </span>\n ) : audioEffectsEnabled ? (\n <div className=\"flex items-center gap-1\" data-testid={`${testIdPrefix}-type-${i}`}>\n <select\n data-testid={`${testIdPrefix}-effect-${i}`}\n value={rowEffects[(row.originId ?? row.targetId) as string] ?? 'fade'}\n onChange={(e) => {\n const id = row.originId ?? row.targetId;\n if (id) setRowEffect(id, e.target.value as AudioEffect);\n }}\n className=\"text-[10px] bg-sas-panel border border-sas-border rounded-sm px-1 py-0.5 text-sas-text\"\n >\n {AUDIO_EFFECTS.map((eff) => (\n <option key={eff} value={eff}>\n {AUDIO_EFFECT_LABEL[eff]}\n </option>\n ))}\n </select>\n <span className=\"text-[9px] text-sas-muted\">{row.type === 'fade-out' ? 'out' : 'in'}</span>\n </div>\n ) : (\n <span\n data-testid={`${testIdPrefix}-type-${i}`}\n className=\"text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-border text-sas-muted\"\n >\n {TYPE_LABEL[row.type]}\n </span>\n )}\n {isCreatingThis ? (\n <div className=\"w-full\">\n <SorceryProgressBar\n isLoading\n heightClass=\"h-5\"\n statusText=\"CREATING\"\n estimatedDurationMs={\n row.type === 'crossfade' ? CROSSFADE_ESTIMATE_MS : FADE_ESTIMATE_MS\n }\n />\n </div>\n ) : (\n <button\n type=\"button\"\n data-testid={`${testIdPrefix}-create-${i}`}\n onClick={() => createRow(row)}\n disabled={!row.type}\n className={`w-full px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors ${\n row.type\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n >\n Create\n </button>\n )}\n {errMsg && (\n <span\n data-testid={`${testIdPrefix}-row-error-${i}`}\n className=\"text-[10px] text-sas-danger text-center leading-tight\"\n >\n {errMsg}\n </span>\n )}\n </div>\n\n {renderCell('target', i, row.targetId)}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n\nexport default TransitionDesigner;\n","/**\n * useTrackReorder — shared drag-and-drop row reordering for generator panels.\n *\n * One hook drives the whole flow so every panel (drums / instruments / synths)\n * behaves identically: HTML5 drag mechanics (zero dependencies), an optimistic\n * local reorder, persistence via {@link PluginHost.reorderTracks}, and an\n * automatic revert if persistence fails. Panels supply their track array + its\n * setter and spread the returned props onto each {@link TrackRow}; the grip\n * handle and drop-target visuals live in TrackRow.\n *\n * Persisted ids should be STABLE (use `getId: t => t.handle.dbId`) — engine\n * track ids are not stable across project reopen.\n */\nimport { useCallback, useRef, useState } from 'react';\nimport type { DragEvent, Dispatch, SetStateAction } from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\n\n/**\n * Props the reorder machinery hands to a single row. Spread `handleProps` on the\n * drag grip and `rowProps` on the row's outer element; `isDragging` /\n * `isDragTarget` drive the visual state.\n */\nexport interface TrackRowDragProps {\n handleProps: {\n draggable: true;\n onDragStart: (e: DragEvent<HTMLElement>) => void;\n onDragEnd: (e: DragEvent<HTMLElement>) => void;\n };\n rowProps: {\n onDragEnter: (e: DragEvent<HTMLElement>) => void;\n onDragOver: (e: DragEvent<HTMLElement>) => void;\n onDragLeave: (e: DragEvent<HTMLElement>) => void;\n onDrop: (e: DragEvent<HTMLElement>) => void;\n };\n /** This row is the one currently being dragged (dim it). */\n isDragging: boolean;\n /** This row is the current drop target (show an insertion accent). */\n isDragTarget: boolean;\n}\n\n/**\n * Pure helper: return a NEW array with the item at `from` moved to `to`.\n * Out-of-range or no-op moves return a shallow copy unchanged. Exported for\n * unit testing the index math without a DOM.\n */\nexport function moveItem<T>(arr: readonly T[], from: number, to: number): T[] {\n const next = arr.slice();\n if (\n from === to ||\n from < 0 ||\n to < 0 ||\n from >= next.length ||\n to >= next.length\n ) {\n return next;\n }\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved);\n return next;\n}\n\nexport interface UseTrackReorderOptions<T> {\n /** Host (only {@link PluginHost.reorderTracks} is used). */\n host: Pick<PluginHost, 'reorderTracks'>;\n /** The panel's current track array (also the render order). */\n items: T[];\n /** The panel's state setter for `items` (used for optimistic update + revert). */\n setItems: Dispatch<SetStateAction<T[]>>;\n /** Stable id for persistence — use the track's dbId, not its engine id. */\n getId: (item: T) => string;\n /** Called if persistence fails, after the optimistic update is reverted. */\n onError?: (err: unknown) => void;\n}\n\nexport interface UseTrackReorderResult {\n /** Build the drag props for the row at `index`; spread onto its TrackRow. */\n dragPropsFor: (index: number) => TrackRowDragProps;\n /** Index of the row being dragged, or null. */\n draggingIndex: number | null;\n /** Index of the current drop-target row, or null. */\n dragOverIndex: number | null;\n}\n\n/**\n * Drag-and-drop reordering for a panel's track list. Dropping a row onto another\n * row moves it into that row's position (everything between shifts); the top and\n * bottom are reachable by dropping on the first/last row.\n */\nexport function useTrackReorder<T>({\n host,\n items,\n setItems,\n getId,\n onError,\n}: UseTrackReorderOptions<T>): UseTrackReorderResult {\n const [draggingIndex, setDraggingIndex] = useState<number | null>(null);\n const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);\n // Source index for the in-flight drag; a ref avoids stale-closure reads in the\n // drop handler. itemsRef keeps the freshest array without re-creating handlers.\n const fromRef = useRef<number | null>(null);\n const itemsRef = useRef(items);\n itemsRef.current = items;\n\n const dragPropsFor = useCallback(\n (index: number): TrackRowDragProps => ({\n handleProps: {\n draggable: true,\n onDragStart: (e) => {\n fromRef.current = index;\n setDraggingIndex(index);\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n // Required by Firefox to start a drag; the value itself is unused.\n try {\n e.dataTransfer.setData('text/plain', String(index));\n } catch {\n /* some environments disallow setData — drag still works */\n }\n }\n },\n onDragEnd: () => {\n fromRef.current = null;\n setDraggingIndex(null);\n setDragOverIndex(null);\n },\n },\n rowProps: {\n onDragEnter: (e) => {\n if (fromRef.current === null) return;\n e.preventDefault();\n setDragOverIndex(index);\n },\n onDragOver: (e) => {\n if (fromRef.current === null) return;\n e.preventDefault(); // allow drop\n if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';\n setDragOverIndex((cur) => (cur === index ? cur : index));\n },\n onDragLeave: () => {\n setDragOverIndex((cur) => (cur === index ? null : cur));\n },\n onDrop: (e) => {\n e.preventDefault();\n const from = fromRef.current;\n fromRef.current = null;\n setDraggingIndex(null);\n setDragOverIndex(null);\n if (from === null || from === index) return;\n\n const prev = itemsRef.current;\n const next = moveItem(prev, from, index);\n setItems(next);\n const ids = next.map(getId);\n Promise.resolve(host.reorderTracks(ids)).catch((err) => {\n // Persistence failed — roll back to the pre-drag order.\n setItems(prev);\n onError?.(err);\n });\n },\n },\n isDragging: draggingIndex === index,\n isDragTarget: dragOverIndex === index && draggingIndex !== index,\n }),\n [host, setItems, getId, onError, draggingIndex, dragOverIndex]\n );\n\n return { dragPropsFor, draggingIndex, dragOverIndex };\n}\n","/**\n * Transition Designer — pure helpers for the per-panel transition staging board.\n *\n * The designer is the multi-row, persistent successor to CrossfadeModal/FadeModal:\n * it lays out ONE panel-family's origin (scene A) and target (scene B) source\n * tracks as two index-aligned, drag-reorderable columns. Row i pairs the origin\n * slot at index i with the target slot at index i, and the pairing DERIVES the\n * transition type:\n * - both filled → crossfade (morph A→B)\n * - origin filled, target blank → fade out (the track leaves)\n * - target filled, origin blank → fade in (the track enters)\n * A slot may be a source-track dbId or `null` (a blank spacer). Blanks let the\n * user open a gap so a mid-list track becomes a fade instead of crossfading with\n * whatever happens to sit opposite it (the CSV-style layout).\n *\n * The \"available pool\" per column is the scene's family tracks MINUS the sources\n * already consumed by a committed crossfade/fade (excludeSourceDbIds). Creating a\n * row reuses the panel's existing crossfade/fade orchestration; deleting the\n * committed crossfade/fade on the deck returns its source to the pool.\n *\n * This module owns only the shape + the pure slot/row math so it can be unit\n * tested without a DOM and can't drift across the three panels. The component\n * (TransitionDesigner.tsx) owns the overlay, drag wiring, and persistence.\n *\n * @since SDK 2.29.0\n */\n\n/**\n * Persisted per-transition-scene draft: the two columns' slot orders. A slot is\n * a source-track dbId, or `null` for a blank spacer (an intentional gap). Stored\n * in the transition scene's plugin_data under {@link TRANSITION_DESIGNER_DRAFT_KEY};\n * because plugin_data is scoped by (plugin_id, sceneId), each panel family keeps\n * its own draft automatically.\n */\nexport interface TransitionDesignerDraft {\n /** Origin (scene A) column order — dbIds or `null` blanks. */\n originOrder: (string | null)[];\n /** Target (scene B) column order — dbIds or `null` blanks. */\n targetOrder: (string | null)[];\n /** Per one-sided-row audio effect, keyed by the source dbId. @since SDK 2.32.0 */\n rowEffects?: Record<string, AudioEffect>;\n}\n\n/** scene-data key (under the transition scene) holding the staged draft. */\nexport const TRANSITION_DESIGNER_DRAFT_KEY = 'transitionDesigner:draft';\n\n/** The transition a single aligned row represents (derived from its two slots). */\nexport type TransitionRowType = 'crossfade' | 'fade-out' | 'fade-in';\n\n/**\n * Audio-only transition gesture for a ONE-SIDED (orphan) loop. `'fade'` is the\n * default level ramp (works for any family); `stutter`/`chopped`/`delay` are\n * audio panels only, surfaced via the row's effect selector when the panel\n * passes `onCreateAudioTransition`. @since SDK 2.32.0\n */\nexport type AudioEffect = 'fade' | 'stutter' | 'chopped' | 'delay';\nexport const AUDIO_EFFECTS: readonly AudioEffect[] = ['fade', 'stutter', 'chopped', 'delay'];\nexport const AUDIO_EFFECT_LABEL: Record<AudioEffect, string> = {\n fade: 'Fade',\n stutter: 'Stutter',\n chopped: 'Chopped',\n delay: 'Delay',\n};\nexport function asAudioEffect(v: unknown): AudioEffect | null {\n return v === 'fade' || v === 'stutter' || v === 'chopped' || v === 'delay' ? v : null;\n}\n\n/** Derive a row's transition type from which slots are filled. `null` = empty row. */\nexport function rowType(hasOrigin: boolean, hasTarget: boolean): TransitionRowType | null {\n if (hasOrigin && hasTarget) return 'crossfade';\n if (hasOrigin) return 'fade-out';\n if (hasTarget) return 'fade-in';\n return null;\n}\n\n/** Narrow an unknown scene-data value to a TransitionDesignerDraft (defensive). */\nexport function asTransitionDesignerDraft(val: unknown): TransitionDesignerDraft | null {\n if (!val || typeof val !== 'object') return null;\n const d = val as Partial<TransitionDesignerDraft>;\n const clean = (a: unknown): (string | null)[] =>\n Array.isArray(a)\n ? (a.filter((x) => x === null || typeof x === 'string') as (string | null)[])\n : [];\n const cleanEffects = (e: unknown): Record<string, AudioEffect> => {\n const out: Record<string, AudioEffect> = {};\n if (e && typeof e === 'object') {\n for (const [k, v] of Object.entries(e as Record<string, unknown>)) {\n const eff = asAudioEffect(v);\n if (eff) out[k] = eff;\n }\n }\n return out;\n };\n return {\n originOrder: clean(d.originOrder),\n targetOrder: clean(d.targetOrder),\n rowEffects: cleanEffects(d.rowEffects),\n };\n}\n\n/**\n * Reconcile a saved slot order against the current pool of available source ids:\n * - keep saved ids still in the pool (in their saved position),\n * - keep `null` blanks,\n * - drop ids no longer in the pool (consumed by a created crossfade/fade, or the\n * source track was deleted) and any duplicates,\n * - append pool ids missing from the saved order (newly added tracks) at the end.\n *\n * Pure; exported for unit testing.\n */\nexport function reconcileSlots(\n saved: readonly (string | null)[] | undefined,\n poolIds: readonly string[],\n): (string | null)[] {\n const pool = new Set(poolIds);\n const seen = new Set<string>();\n const out: (string | null)[] = [];\n for (const slot of saved ?? []) {\n if (slot === null) {\n out.push(null);\n continue;\n }\n if (pool.has(slot) && !seen.has(slot)) {\n out.push(slot);\n seen.add(slot);\n }\n }\n for (const id of poolIds) {\n if (!seen.has(id)) {\n out.push(id);\n seen.add(id);\n }\n }\n return out;\n}\n\n/** One assembled designer row: the two source dbIds (or `null`) + derived type. */\nexport interface DesignerRowSlots {\n originId: string | null;\n targetId: string | null;\n type: TransitionRowType | null;\n}\n\n/** Zip two slot columns into index-aligned rows with their derived type. */\nexport function buildRowSlots(\n originSlots: readonly (string | null)[],\n targetSlots: readonly (string | null)[],\n): DesignerRowSlots[] {\n const n = Math.max(originSlots.length, targetSlots.length);\n const rows: DesignerRowSlots[] = [];\n for (let i = 0; i < n; i++) {\n const originId = originSlots[i] ?? null;\n const targetId = targetSlots[i] ?? null;\n rows.push({ originId, targetId, type: rowType(originId !== null, targetId !== null) });\n }\n return rows;\n}\n\n/**\n * Tidy the columns for persistence: drop rows where BOTH slots are blank (a\n * meaningless gap) and trim trailing blanks per column. Returns clean columns\n * suitable for {@link TransitionDesignerDraft}.\n */\nexport function normalizeSlots(\n originSlots: readonly (string | null)[],\n targetSlots: readonly (string | null)[],\n): TransitionDesignerDraft {\n const rows = buildRowSlots(originSlots, targetSlots).filter(\n (r) => r.originId !== null || r.targetId !== null,\n );\n const trimTrailing = (a: (string | null)[]): (string | null)[] => {\n let end = a.length;\n while (end > 0 && a[end - 1] === null) end--;\n return a.slice(0, end);\n };\n return {\n originOrder: trimTrailing(rows.map((r) => r.originId)),\n targetOrder: trimTrailing(rows.map((r) => r.targetId)),\n };\n}\n\n/** Pad a column with trailing `null`s up to `n` (so both columns render aligned). */\nexport function padSlots(slots: readonly (string | null)[], n: number): (string | null)[] {\n if (slots.length >= n) return slots.slice();\n return [...slots, ...new Array<null>(n - slots.length).fill(null)];\n}\n\n/** Pad both columns to equal length (= the longer column). */\nexport function padPair(\n originSlots: readonly (string | null)[],\n targetSlots: readonly (string | null)[],\n): [(string | null)[], (string | null)[]] {\n const n = Math.max(originSlots.length, targetSlots.length);\n return [padSlots(originSlots, n), padSlots(targetSlots, n)];\n}\n\n/** Shallow element-wise equality for two slot columns. */\nexport function slotsEqual(a: readonly (string | null)[], b: readonly (string | null)[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Stable key identifying an in-flight create, derived from the row's SOURCE dbIds\n * (not its row index) — so reordering or inserting a gap mid-create still maps the\n * progress indicator to the right row, and concurrent creates never collide. dbIds\n * are UUIDs, so `|` is a safe origin/target separator. `null` for an empty row.\n *\n * @since SDK 2.30.0\n */\nexport function rowKey(row: DesignerRowSlots): string | null {\n if (row.type === 'crossfade') return `xf:${row.originId}|${row.targetId}`;\n if (row.type === 'fade-out') return `fo:${row.originId}`;\n if (row.type === 'fade-in') return `fi:${row.targetId}`;\n return null;\n}\n\n/**\n * The set of source dbIds referenced by a collection of in-flight {@link rowKey}s —\n * used to lock those cells (no drag / gap edits) while their create runs.\n *\n * @since SDK 2.30.0\n */\nexport function dbIdsFromKeys(keys: Iterable<string>): Set<string> {\n const out = new Set<string>();\n for (const k of keys) {\n const body = k.slice(3); // strip the 3-char \"xf:\" / \"fo:\" / \"fi:\" tag\n if (k.startsWith('xf:')) {\n const sep = body.indexOf('|');\n out.add(body.slice(0, sep));\n out.add(body.slice(sep + 1));\n } else {\n out.add(body);\n }\n }\n return out;\n}\n","/**\n * DownloadPackButton — versioned-pack download trigger (SDK component).\n *\n * Parameterized by `packId`; drives the download through the host\n * (`host.startSamplePackDownload` / `host.onSamplePackProgress`) so plugins\n * never reach into the app's IPC (`window.electronAPI`). Two display variants:\n * - 'compact' (default) — small uppercase button for panel headers\n * - 'large' — bigger CTA used inside SamplePackCTACard\n *\n * @since SDK 2.8.0 (moved from the app and refactored onto PluginHost).\n */\n\nimport React, { useCallback, useEffect, useState } from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\n\nexport type DownloadPackButtonVariant = 'compact' | 'large';\n\ntype PackDownloadStatus =\n | 'idle'\n | 'downloading'\n | 'verifying'\n | 'extracting'\n | 'installing'\n | 'complete'\n | 'error';\n\nexport interface DownloadPackButtonProps {\n /** Host the plugin received; drives the download + progress. */\n host: PluginHost;\n packId: string;\n /** Pack display name, e.g. 'Drum Sample Library'. Used in tooltips/labels. */\n displayName: string;\n /** Bundle size in bytes (shown in the large-variant label). */\n sizeBytes?: number;\n variant?: DownloadPackButtonVariant;\n /** Called once after the install completes (status === 'complete'). */\n onDownloadComplete?: () => void;\n}\n\n// Base-1024 (GiB/MiB) to match the host's own SamplePackDownloader formatter and\n// the `_pack-version.json` / sample-packs.ts size comments (e.g. a 28.5e9-byte\n// instrument bundle reads as \"26.6 GB\", not the decimal \"28.5 GB\").\nfunction formatSize(bytes?: number): string {\n if (!bytes || bytes <= 0) return '';\n const gb = bytes / 1024 ** 3;\n if (gb >= 1) return `${gb.toFixed(1)} GB`;\n const mb = bytes / 1024 ** 2;\n return `${Math.round(mb)} MB`;\n}\n\nexport const DownloadPackButton: React.FC<DownloadPackButtonProps> = ({\n host,\n packId,\n displayName,\n sizeBytes,\n variant = 'compact',\n onDownloadComplete,\n}) => {\n const [status, setStatus] = useState<PackDownloadStatus>('idle');\n const [progress, setProgress] = useState(0);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n useEffect(() => {\n const unsub = host.onSamplePackProgress(packId, (p) => {\n setStatus(p.status as PackDownloadStatus);\n setProgress(p.progress);\n if (p.status === 'error') {\n setErrorMessage(p.message || 'Download failed');\n } else if (p.status === 'complete') {\n setErrorMessage(null);\n setTimeout(() => onDownloadComplete?.(), 250);\n } else {\n setErrorMessage(null);\n }\n });\n return unsub;\n }, [host, packId, onDownloadComplete]);\n\n const handleClick = useCallback(async (): Promise<void> => {\n if (status !== 'idle' && status !== 'error') return;\n try {\n setStatus('downloading');\n setProgress(0);\n setErrorMessage(null);\n const result = await host.startSamplePackDownload(packId);\n if (!result.success) {\n setStatus('error');\n setErrorMessage(result.error || 'Download failed');\n }\n } catch (err) {\n console.error('[DownloadPackButton] start failed:', err);\n setStatus('error');\n setErrorMessage(err instanceof Error ? err.message : String(err));\n }\n }, [host, packId, status]);\n\n const isWorking =\n status === 'downloading' ||\n status === 'verifying' ||\n status === 'extracting' ||\n status === 'installing';\n const isDisabled = isWorking || status === 'complete';\n\n const buttonLabel = (() => {\n switch (status) {\n case 'downloading':\n return `${progress}%`;\n case 'verifying':\n return 'Verifying...';\n case 'extracting':\n return 'Extracting...';\n case 'installing':\n return 'Installing...';\n case 'complete':\n return 'Done!';\n case 'error':\n return 'Retry';\n default:\n return variant === 'large'\n ? `Download ${displayName}${sizeBytes ? ` (${formatSize(sizeBytes)})` : ''}`\n : 'Download';\n }\n })();\n\n const tooltip = (() => {\n if (status === 'error') return errorMessage || 'Download failed. Click to retry.';\n if (isWorking) return `${buttonLabel} — ${displayName}`;\n if (status === 'complete') return 'Installation complete';\n return `Download ${displayName}${sizeBytes ? ` (${formatSize(sizeBytes)})` : ''}`;\n })();\n\n const baseClasses =\n variant === 'large'\n ? 'px-4 py-2 text-sm font-medium rounded border transition-colors'\n : 'px-2 py-0.5 text-[10px] uppercase tracking-wide rounded-sm border transition-colors';\n\n let className: string;\n if (status === 'error') {\n className = `${baseClasses} text-red-400 border-red-400/50 hover:text-red-300 hover:border-red-300`;\n } else if (status === 'complete') {\n className = `${baseClasses} text-green-400 border-green-400/50`;\n } else if (isDisabled) {\n className = `${baseClasses} text-sas-accent border-sas-accent/50 cursor-wait`;\n } else {\n className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;\n }\n\n return (\n <div>\n <button\n data-testid={`download-pack-button-${packId}`}\n onClick={handleClick}\n disabled={isDisabled}\n className={className}\n title={tooltip}\n >\n {buttonLabel}\n </button>\n {variant === 'large' && status === 'error' && errorMessage && (\n <div className=\"text-xs text-sas-danger mt-2\" data-testid={`download-pack-error-${packId}`}>\n {errorMessage}\n </div>\n )}\n </div>\n );\n};\n\nexport default DownloadPackButton;\n","/**\n * SamplePackCTACard — empty-state card a generator panel renders when its\n * sample pack is missing OR a newer version is available. Wraps\n * DownloadPackButton in a centered card. The completion callback should\n * re-fetch pack status on the parent so the card unmounts and the normal panel\n * UI takes over.\n *\n * @since SDK 2.8.0 (moved from the app; download driven through PluginHost).\n */\n\nimport React from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\nimport { DownloadPackButton } from './DownloadPackButton';\n\nexport type SamplePackCTACardStatus = 'missing' | 'stale' | 'checking';\n\n/** Minimal pack info the card needs. A PackConfig is structurally compatible. */\nexport interface SamplePackCardInfo {\n packId: string;\n displayName: string;\n description: string;\n sizeBytes?: number;\n}\n\nexport interface SamplePackCTACardProps {\n /** Host the plugin received; drives the download. */\n host: PluginHost;\n pack: SamplePackCardInfo;\n status: SamplePackCTACardStatus;\n onDownloadComplete?: () => void;\n}\n\nexport const SamplePackCTACard: React.FC<SamplePackCTACardProps> = ({\n host,\n pack,\n status,\n onDownloadComplete,\n}) => {\n if (status === 'checking') {\n return (\n <div\n data-testid={`sample-pack-cta-checking-${pack.packId}`}\n className=\"flex items-center justify-center py-16 text-sas-muted text-sm\"\n >\n Checking sample library...\n </div>\n );\n }\n\n const headline =\n status === 'stale'\n ? `${pack.displayName} update available`\n : `${pack.displayName} not installed`;\n\n const sublabel =\n status === 'stale'\n ? `A newer version is available for download.`\n : pack.description;\n\n return (\n <div\n data-testid={`sample-pack-cta-${pack.packId}`}\n className=\"flex flex-col items-center justify-center py-12 px-6 text-center\"\n >\n <div className=\"text-sm uppercase tracking-wide text-sas-muted mb-2\">\n {status === 'stale' ? 'Update available' : 'Sample library not installed'}\n </div>\n <div className=\"text-base text-sas-text mb-1\">{headline}</div>\n <div className=\"text-xs text-sas-muted mb-6 max-w-md\">{sublabel}</div>\n <DownloadPackButton\n host={host}\n packId={pack.packId}\n displayName={pack.displayName}\n sizeBytes={pack.sizeBytes}\n variant=\"large\"\n onDownloadComplete={onDownloadComplete}\n />\n </div>\n );\n};\n\nexport default SamplePackCTACard;\n","/**\n * WaveformView — small canvas waveform for an audio file on disk.\n *\n * Reads bytes via `host.getAudioFileBytes`, decodes via\n * `AudioContext.decodeAudioData`, computes peaks, and renders to a\n * canvas. Suitable for take rows, sample previews, or any place a\n * decorative ~40px waveform makes sense.\n *\n * The component is self-contained: it owns the AudioContext and the\n * peak buffer, decodes once per `filePath` change, and tears down on\n * unmount. Failures (file missing, decode error) render as a silent\n * blank canvas — the caller can decide how to surface errors.\n */\n\nimport React, { useEffect, useRef, useState } from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\nimport { computePeaks, drawWaveform, type WaveformPeaks } from './waveform';\n\nexport interface WaveformViewProps {\n host: PluginHost;\n filePath: string;\n /** Number of bins to compute. Default 256 — plenty for ~40px tall rows. */\n bins?: number;\n /** Tailwind / inline className for sizing. Default: w-full h-10. */\n className?: string;\n /** Override the bar fill style (e.g., to match a track color). */\n fillStyle?: string;\n /**\n * If set, the bin range spans `targetSamples` instead of the file's\n * actual length. Bins beyond the audio render as flat silence — used\n * to align a partial recording inside a full-loop-width canvas so\n * every take row has the same time scale.\n */\n targetSamples?: number;\n}\n\nexport const WaveformView: React.FC<WaveformViewProps> = ({\n host,\n filePath,\n bins = 256,\n className,\n fillStyle,\n targetSamples,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [peaks, setPeaks] = useState<WaveformPeaks | null>(null);\n\n // Decode + compute peaks whenever the file changes.\n useEffect(() => {\n let cancelled = false;\n let audioContext: AudioContext | null = null;\n\n (async () => {\n try {\n const bytes = await host.getAudioFileBytes(filePath);\n if (cancelled) return;\n\n // OfflineAudioContext would be cheaper but its constructor needs\n // sampleRate/length up front — we don't know them until decode.\n const ContextCtor: typeof AudioContext =\n (window as unknown as { AudioContext?: typeof AudioContext }).AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext!;\n audioContext = new ContextCtor();\n\n // decodeAudioData mutates / detaches the buffer in some impls,\n // so pass a copy.\n const audioBuffer = await audioContext.decodeAudioData(bytes.slice(0));\n if (cancelled) return;\n\n const computed = computePeaks(audioBuffer, bins, targetSamples);\n setPeaks(computed);\n } catch (err) {\n // Silent: the canvas stays blank. Caller can layer their own\n // error UI on top if needed.\n console.warn('[WaveformView] failed to decode', filePath, err);\n } finally {\n if (audioContext) {\n audioContext.close().catch(() => { /* ignore */ });\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [host, filePath, bins, targetSamples]);\n\n // Repaint whenever peaks update — including layout-driven resizes via\n // ResizeObserver so the canvas stays crisp at the current CSS width.\n useEffect(() => {\n if (!peaks) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n drawWaveform(canvas, peaks, fillStyle ? { fillStyle } : undefined);\n\n const observer = new ResizeObserver(() => {\n drawWaveform(canvas, peaks, fillStyle ? { fillStyle } : undefined);\n });\n observer.observe(canvas);\n return () => observer.disconnect();\n }, [peaks, fillStyle]);\n\n return (\n <canvas\n ref={canvasRef}\n data-testid=\"waveform-view\"\n className={className ?? 'w-full h-10'}\n />\n );\n};\n\nexport default WaveformView;\n","/**\n * Shared waveform peaks + canvas drawer.\n *\n * Originally inlined in `stems/TrimEditorDrawer.tsx`; lifted to\n * this module so the recorder plugin's per-take rows can render the\n * same compact min/max display without duplicating the math.\n *\n * Design:\n * - `computePeaks` reduces an AudioBuffer to `bins` min/max pairs (mono\n * average across channels). Output layout is interleaved\n * `[min0, max0, min1, max1, ...]` so the renderer reads pairs\n * sequentially without index arithmetic.\n * - `drawWaveform` paints one 1px vertical bar per canvas column,\n * dpr-aware so it stays crisp on retina displays.\n *\n * No host or React dependencies — pure functions are safe to use from\n * tests, web workers, or non-React renderers.\n */\n\nexport interface WaveformPeaks {\n /** Sample rate of the source file (used to convert sample → seconds). */\n sampleRate: number;\n /** Total length of the raw file in samples. */\n totalSamples: number;\n /** Min/max pairs per bin (length = bins × 2). */\n peaks: Float32Array;\n}\n\n/**\n * Reduce an AudioBuffer to `bins` min/max pairs. Mono averages across\n * channels. The output buffer is fixed-size (`bins * 2`) for fast canvas\n * traversal.\n *\n * `targetSamples` (optional) extends the bin range to a fixed sample\n * count larger than the buffer's actual length — bins falling beyond\n * the buffer get (0, 0) pairs, which renders as a flat tail. Used by\n * the recorder so a partial last chunk's waveform sits at the start of\n * a full-loop-width canvas instead of being stretched to fill.\n */\nexport function computePeaks(\n audioBuffer: AudioBuffer,\n bins: number,\n targetSamples?: number\n): WaveformPeaks {\n const { length, numberOfChannels, sampleRate } = audioBuffer;\n const channels: Float32Array[] = [];\n for (let c = 0; c < numberOfChannels; c++) {\n channels.push(audioBuffer.getChannelData(c));\n }\n const totalForBinning =\n typeof targetSamples === 'number' && targetSamples > length ? targetSamples : length;\n const samplesPerBin = Math.max(1, Math.floor(totalForBinning / bins));\n const out = new Float32Array(bins * 2);\n for (let i = 0; i < bins; i++) {\n const startIdx = i * samplesPerBin;\n const endIdx = Math.min(length, startIdx + samplesPerBin);\n if (startIdx >= length) {\n // Bin falls entirely past the audio's end — render as silence.\n out[i * 2] = 0;\n out[i * 2 + 1] = 0;\n continue;\n }\n let mn = Infinity;\n let mx = -Infinity;\n for (let j = startIdx; j < endIdx; j++) {\n let v = 0;\n for (let c = 0; c < numberOfChannels; c++) {\n v += channels[c][j];\n }\n v /= numberOfChannels;\n if (v < mn) mn = v;\n if (v > mx) mx = v;\n }\n if (!Number.isFinite(mn)) mn = 0;\n if (!Number.isFinite(mx)) mx = 0;\n out[i * 2] = mn;\n out[i * 2 + 1] = mx;\n }\n return { sampleRate, totalSamples: totalForBinning, peaks: out };\n}\n\n/**\n * Draw min/max peaks to the given canvas. Resizes the canvas backing\n * store to CSS pixels × devicePixelRatio so the result is crisp on\n * retina. Caller controls CSS sizing via the `<canvas>` element's\n * className.\n */\nexport function drawWaveform(\n canvas: HTMLCanvasElement,\n peaks: WaveformPeaks,\n options: { fillStyle?: string } = {}\n): void {\n const dpr = window.devicePixelRatio || 1;\n const cssWidth = canvas.clientWidth;\n const cssHeight = canvas.clientHeight;\n if (cssWidth === 0 || cssHeight === 0) return;\n canvas.width = Math.floor(cssWidth * dpr);\n canvas.height = Math.floor(cssHeight * dpr);\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n ctx.scale(dpr, dpr);\n ctx.clearRect(0, 0, cssWidth, cssHeight);\n ctx.fillStyle = options.fillStyle ?? 'rgba(255, 255, 255, 0.4)';\n\n const bins = peaks.peaks.length / 2;\n const mid = cssHeight / 2;\n for (let x = 0; x < cssWidth; x++) {\n const binIdx = Math.floor((x / cssWidth) * bins);\n const mn = peaks.peaks[binIdx * 2];\n const mx = peaks.peaks[binIdx * 2 + 1];\n const yTop = mid - mx * mid;\n const yBot = mid - mn * mid;\n ctx.fillRect(x, yTop, 1, Math.max(1, yBot - yTop));\n }\n}\n","/**\n * ScrollingWaveform — live waveform during recording (Phase 8.10).\n *\n * Reads the platform's `peakDb` history and renders it as a horizontal\n * bar-graph that scrolls left as new samples arrive. Two halves: top\n * band shows positive amplitude, bottom band mirrors it (matches the\n * static waveform's min/max layout in `WaveformView`).\n *\n * The data source is a function the caller supplies — typically a ref\n * to the `inputLevelDb` value from `AudioRoutingContext` polled at\n * ~30Hz. The component samples that ref via requestAnimationFrame and\n * shifts a fixed-size float ring buffer one column per frame.\n *\n * Pure presentational + animation logic; no IPC. Stops animating\n * when `active` is false (engine isn't running the audio callback).\n */\n\nimport React, { useEffect, useRef } from 'react';\n\nexport interface ScrollingWaveformProps {\n /** Function returning the latest peak in dBFS. Called per RAF. */\n getPeakDb: () => number;\n /** True while the audio callback is running; false freezes the wave. */\n active: boolean;\n /** Number of horizontal columns in the ring buffer. */\n columns?: number;\n /** Optional className for sizing. */\n className?: string;\n /** Highlight color for the wave. */\n fillStyle?: string;\n}\n\nexport const ScrollingWaveform: React.FC<ScrollingWaveformProps> = ({\n getPeakDb,\n active,\n columns = 256,\n className,\n fillStyle,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const ringRef = useRef<Float32Array>(new Float32Array(columns));\n const writeIdxRef = useRef(0);\n const rafRef = useRef<number | null>(null);\n\n // Recreate the ring buffer if `columns` changes — preserve any data\n // that fits.\n useEffect(() => {\n if (ringRef.current.length !== columns) {\n const next = new Float32Array(columns);\n const prev = ringRef.current;\n const copyLen = Math.min(prev.length, columns);\n // Copy the tail of the previous buffer into the head of the new one.\n for (let i = 0; i < copyLen; i++) {\n next[i] = prev[i];\n }\n ringRef.current = next;\n writeIdxRef.current = writeIdxRef.current % columns;\n }\n }, [columns]);\n\n useEffect(() => {\n if (!active) {\n // Freeze the wave but leave the existing buffer on screen.\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n return;\n }\n\n const tick = (): void => {\n const peakDb = getPeakDb();\n // Map dBFS → normalised amplitude [0, 1]. -60dB → 0, 0dB → 1.\n const amp =\n peakDb <= -120\n ? 0\n : Math.max(0, Math.min(1, (peakDb + 60) / 60));\n const ring = ringRef.current;\n ring[writeIdxRef.current] = amp;\n writeIdxRef.current = (writeIdxRef.current + 1) % ring.length;\n\n // Draw.\n const canvas = canvasRef.current;\n if (canvas) {\n const dpr = window.devicePixelRatio || 1;\n const cssW = canvas.clientWidth;\n const cssH = canvas.clientHeight;\n if (cssW > 0 && cssH > 0) {\n if (canvas.width !== Math.floor(cssW * dpr) || canvas.height !== Math.floor(cssH * dpr)) {\n canvas.width = Math.floor(cssW * dpr);\n canvas.height = Math.floor(cssH * dpr);\n }\n const ctx = canvas.getContext('2d');\n if (ctx) {\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, cssW, cssH);\n ctx.fillStyle = fillStyle ?? '#6af2c5';\n const mid = cssH / 2;\n const cols = ring.length;\n const colW = cssW / cols;\n // Read the ring oldest → newest so the wave scrolls left.\n const start = writeIdxRef.current; // oldest sample\n for (let x = 0; x < cols; x++) {\n const ringIdx = (start + x) % cols;\n const a = ring[ringIdx];\n const half = a * mid;\n ctx.fillRect(x * colW, mid - half, Math.max(1, colW), Math.max(1, half * 2));\n }\n }\n }\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n };\n }, [active, getPeakDb, fillStyle]);\n\n return (\n <canvas\n ref={canvasRef}\n data-testid=\"scrolling-waveform\"\n className={className ?? 'w-full h-12'}\n />\n );\n};\n\nexport default ScrollingWaveform;\n","/**\n * OffsetScrubber — manual sample-offset slider for Lyria-generated audio.\n *\n * Renders a thin horizontal track with one tick per detected beat (tall\n * tick on the downbeat) and a draggable thumb. Drag distance maps to a\n * sample offset that is applied to the audio clip via\n * `host.setAudioOffsetSamples(trackId, n)`.\n *\n * Snap behavior:\n * - Default: snap to the nearest beat in `cuePoints.beats`.\n * - Hold Shift: bypass snap (free 1-sample resolution).\n * - Click on a tick mark: jump to that beat exactly.\n *\n * The visible range is one bar (= meter beats) on each side of bar 1.\n * For a 4-bar / 4/4 clip at 44100 Hz, one bar at 120 BPM is 88_200\n * samples — so the slider covers ±88_200 samples, ~2 s either way. That\n * matches the alignment errors we observe from Lyria detection misses\n * (typically <1 beat off).\n *\n * BPM mismatch chip: shown when `cuePoints.detected_bpm` is more than\n * 1 BPM away from the project BPM, since the beat ticks won't line up\n * with the project grid in that case.\n */\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { PluginCuePoints } from '../types/plugin-sdk.types';\n\nconst SLIDER_HEIGHT_PX = 28;\nconst TICK_HEIGHT_PX = 14;\nconst DOWNBEAT_TICK_HEIGHT_PX = 22;\nconst THUMB_WIDTH_PX = 4;\n\nexport interface OffsetScrubberProps {\n /** Detected beat positions + sample rate. Slider is disabled when null. */\n cuePoints: PluginCuePoints | null;\n /** Current offset, in samples (signed). */\n offsetSamples: number;\n /** Project BPM — used to compute the visible range and the mismatch chip. */\n projectBpm: number;\n /** Beats per bar, defaults to 4. */\n meter?: number;\n /** Called on drag-end with the resolved offset (already snapped). */\n onChange: (offsetSamples: number) => void;\n /** Disable interaction (e.g., during generation / split). */\n disabled?: boolean;\n}\n\nexport function OffsetScrubber({\n cuePoints,\n offsetSamples,\n projectBpm,\n meter = 4,\n onChange,\n disabled = false,\n}: OffsetScrubberProps): React.ReactElement {\n const trackRef = useRef<HTMLDivElement | null>(null);\n // Local optimistic offset during drag — committed on mouseup\n const [draftOffset, setDraftOffset] = useState<number>(offsetSamples);\n const [isDragging, setIsDragging] = useState(false);\n\n // Keep the draft synced with the parent prop when not dragging.\n useEffect(() => {\n if (!isDragging) setDraftOffset(offsetSamples);\n }, [offsetSamples, isDragging]);\n\n // Range is ±1 bar of samples around the downbeat.\n // beats are 60 / bpm seconds; bar = meter beats.\n const sampleRate = cuePoints?.sample_rate ?? 44100;\n const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;\n const beatsForRange = useMemo(() => {\n // Use the project BPM for the visible range so the slider scale\n // matches what the user is editing against in the timeline.\n return Math.round((60 / projectBpm) * sampleRate);\n }, [projectBpm, sampleRate]);\n const rangeSamples = beatsForRange * meter; // ±1 bar\n\n // Map a sample offset to a 0..1 position on the slider track.\n const sampleToFraction = useCallback(\n (sample: number): number => {\n const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));\n return (clamped + rangeSamples) / (2 * rangeSamples);\n },\n [rangeSamples],\n );\n\n const fractionToSample = useCallback(\n (fraction: number): number => {\n const clamped = Math.max(0, Math.min(1, fraction));\n return Math.round(clamped * 2 * rangeSamples - rangeSamples);\n },\n [rangeSamples],\n );\n\n // Snap a candidate sample to the nearest detected beat. Beats are\n // CuePoints.beats positions (relative to clip start). Offset slider\n // semantics: positive = shift clip later; we map offset onto the\n // beats array so the user lines up the desired beat with bar 1.\n //\n // Implementation: each beat[i] corresponds to a candidate offset\n // value of `beats[i] - beats[0]` (the relative distance the user has\n // shifted the clip). Snap to the nearest such candidate.\n const snapTargets = useMemo(() => {\n if (!cuePoints || cuePoints.beats.length === 0) return [];\n const downbeat = cuePoints.beats[0];\n // Snap candidates: differences between every beat and the downbeat\n // (positive shifts) plus their negation (negative shifts). De-dup +\n // sort so binary search is cheap if the array gets large.\n const positives = cuePoints.beats.map((b) => b - downbeat);\n const negatives = positives.slice(1).map((p) => -p); // skip 0 to avoid dupe\n return [...negatives, ...positives].sort((a, b) => a - b);\n }, [cuePoints]);\n\n const snapToBeat = useCallback(\n (sample: number): number => {\n if (snapTargets.length === 0) return sample;\n // Linear scan — beats[] is small (≤ 16 for v1). Switch to binary\n // search if we ever generate longer clips.\n let best = snapTargets[0];\n let bestDist = Math.abs(sample - best);\n for (const t of snapTargets) {\n const d = Math.abs(sample - t);\n if (d < bestDist) {\n best = t;\n bestDist = d;\n }\n }\n return best;\n },\n [snapTargets],\n );\n\n // Drag handler — pointer events let us track outside the element.\n const handlePointerDown = useCallback(\n (e: React.PointerEvent<HTMLDivElement>): void => {\n if (disabled || !cuePoints) return;\n e.preventDefault();\n const track = trackRef.current;\n if (!track) return;\n track.setPointerCapture(e.pointerId);\n setIsDragging(true);\n\n const updateFromEvent = (clientX: number, shiftHeld: boolean): number => {\n const rect = track.getBoundingClientRect();\n const fraction = (clientX - rect.left) / rect.width;\n const raw = fractionToSample(fraction);\n return shiftHeld ? raw : snapToBeat(raw);\n };\n\n // Apply the initial click position immediately.\n setDraftOffset(updateFromEvent(e.clientX, e.shiftKey));\n\n const onMove = (ev: PointerEvent): void => {\n setDraftOffset(updateFromEvent(ev.clientX, ev.shiftKey));\n };\n const onUp = (ev: PointerEvent): void => {\n const final = updateFromEvent(ev.clientX, ev.shiftKey);\n track.releasePointerCapture(e.pointerId);\n track.removeEventListener('pointermove', onMove);\n track.removeEventListener('pointerup', onUp);\n track.removeEventListener('pointercancel', onUp);\n setIsDragging(false);\n setDraftOffset(final);\n onChange(final);\n };\n\n track.addEventListener('pointermove', onMove);\n track.addEventListener('pointerup', onUp);\n track.addEventListener('pointercancel', onUp);\n },\n [disabled, cuePoints, fractionToSample, onChange, snapToBeat],\n );\n\n // Reset to 0 (downbeat-aligned) — handy \"snap to bar 1\" button.\n const handleResetToZero = useCallback((): void => {\n if (disabled) return;\n setDraftOffset(0);\n onChange(0);\n }, [disabled, onChange]);\n\n const thumbFraction = sampleToFraction(draftOffset);\n const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;\n\n // BPM mismatch — show a chip when detected BPM diverges from project.\n const bpmMismatch = cuePoints?.detected_bpm != null\n && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;\n\n // Render tick marks for each beat in the snap-target list. Convert\n // sample → fraction → percent for CSS positioning.\n const ticks = useMemo(() => {\n if (!cuePoints) return [];\n const downbeat = cuePoints.beats[0] ?? 0;\n return cuePoints.beats.map((b, i) => {\n const offsetCandidate = b - downbeat;\n const fraction = sampleToFraction(offsetCandidate);\n const isDownbeat = i === 0;\n return { i, fraction, isDownbeat };\n });\n }, [cuePoints, sampleToFraction]);\n\n const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;\n\n return (\n <div data-testid=\"offset-scrubber\" className=\"flex items-center gap-2 w-full\">\n <span className=\"text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0\">\n Align\n </span>\n <div\n ref={trackRef}\n data-testid=\"offset-scrubber-track\"\n onPointerDown={handlePointerDown}\n className={`relative flex-1 min-w-0 rounded-sm select-none ${\n isDisabled\n ? 'bg-sas-panel cursor-not-allowed opacity-40'\n : 'bg-sas-bg cursor-pointer'\n }`}\n style={{ height: SLIDER_HEIGHT_PX }}\n title={\n isDisabled\n ? 'Generate audio first to enable offset alignment'\n : 'Drag to align beat 1. Hold Shift for free, no-snap movement.'\n }\n role=\"slider\"\n aria-label=\"Audio offset alignment\"\n aria-valuemin={-rangeSamples}\n aria-valuemax={rangeSamples}\n aria-valuenow={draftOffset}\n aria-disabled={isDisabled}\n >\n {/* Center marker — bar 1 / beat 1 reference line */}\n <div\n aria-hidden=\"true\"\n className=\"absolute top-0 bottom-0 w-px bg-sas-accent/40\"\n style={{ left: '50%' }}\n />\n {/* Beat ticks */}\n {ticks.map((t) => (\n <div\n key={t.i}\n data-testid={t.isDownbeat ? 'offset-tick-downbeat' : 'offset-tick'}\n aria-hidden=\"true\"\n className={t.isDownbeat ? 'absolute bg-sas-accent' : 'absolute bg-sas-muted/50'}\n style={{\n left: `${(t.fraction * 100).toFixed(2)}%`,\n top: (SLIDER_HEIGHT_PX - (t.isDownbeat ? DOWNBEAT_TICK_HEIGHT_PX : TICK_HEIGHT_PX)) / 2,\n width: 1,\n height: t.isDownbeat ? DOWNBEAT_TICK_HEIGHT_PX : TICK_HEIGHT_PX,\n }}\n />\n ))}\n {/* Thumb */}\n <div\n data-testid=\"offset-scrubber-thumb\"\n aria-hidden=\"true\"\n className={`absolute top-0 bottom-0 rounded-sm ${\n isDragging ? 'bg-sas-accent' : 'bg-sas-accent/80'\n }`}\n style={{\n left: thumbLeftPct,\n width: THUMB_WIDTH_PX,\n transform: 'translateX(-50%)',\n pointerEvents: 'none',\n }}\n />\n </div>\n {/* Numeric readout — samples + millisecond equivalent */}\n <span\n data-testid=\"offset-scrubber-readout\"\n className=\"text-[10px] text-sas-muted/70 tabular-nums flex-shrink-0 min-w-[64px] text-right\"\n >\n {formatOffset(draftOffset, sampleRate)}\n </span>\n {/* Reset button (snap back to 0) */}\n <button\n type=\"button\"\n data-testid=\"offset-scrubber-reset\"\n onClick={handleResetToZero}\n disabled={isDisabled || draftOffset === 0}\n className={`text-[10px] px-1 py-0.5 rounded-sm border transition-colors flex-shrink-0 ${\n isDisabled || draftOffset === 0\n ? 'border-sas-border text-sas-muted/30 cursor-not-allowed'\n : 'border-sas-border text-sas-muted/70 hover:border-sas-accent hover:text-sas-accent'\n }`}\n title=\"Reset offset to 0 (bar 1)\"\n >\n ⌖\n </button>\n {bpmMismatch && (\n <span\n data-testid=\"offset-bpm-mismatch\"\n className=\"text-[9px] px-1 py-0.5 rounded-sm bg-amber-500/15 text-amber-400 border border-amber-500/30 flex-shrink-0\"\n title={`Detected ${detectedBpm.toFixed(1)} BPM — beats may not align with project ${projectBpm} BPM grid`}\n >\n BPM ≠\n </span>\n )}\n </div>\n );\n}\n\n/** Format an offset in samples as `+12345 spl (+279 ms)` for the readout. */\nfunction formatOffset(samples: number, sampleRate: number): string {\n const sign = samples > 0 ? '+' : samples < 0 ? '-' : '';\n const abs = Math.abs(samples);\n const ms = Math.round((abs / sampleRate) * 1000);\n return `${sign}${abs} spl (${sign}${ms} ms)`;\n}\n\nexport default OffsetScrubber;\n","/**\n * WAV peak analyzer (Phase 8.10).\n *\n * Reads a WAV file via the plugin host, decodes it via Web Audio,\n * scans every channel for the absolute maximum sample, and returns\n * peak dBFS + a clipped flag (true when the peak >= -1dBFS, matching\n * the engine's hard-limiter ceiling).\n *\n * Used by the recorder's take rows to surface \"this take peaked at\n * -8dB\" or \"this take CLIPPED\" without the user having to click play.\n */\n\nimport type { PluginHost } from '../types/plugin-sdk.types';\n\nexport interface PeakAnalysis {\n peakLinear: number;\n peakDb: number;\n clipped: boolean;\n}\n\n/** Threshold matching the engine's -1dBFS hard limiter ceiling. */\nconst CLIP_THRESHOLD_LINEAR = 0.891;\n\nexport async function analyzeWavPeak(\n host: PluginHost,\n filePath: string\n): Promise<PeakAnalysis> {\n const bytes = await host.getAudioFileBytes(filePath);\n const ContextCtor: typeof AudioContext =\n (window as unknown as { AudioContext?: typeof AudioContext }).AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext!;\n const audioContext = new ContextCtor();\n try {\n const audioBuffer = await audioContext.decodeAudioData(bytes.slice(0));\n let peak = 0;\n for (let c = 0; c < audioBuffer.numberOfChannels; c++) {\n const data = audioBuffer.getChannelData(c);\n for (let i = 0; i < data.length; i++) {\n const a = Math.abs(data[i]);\n if (a > peak) peak = a;\n }\n }\n const peakDb = peak > 1e-6 ? 20 * Math.log10(peak) : -120;\n return {\n peakLinear: peak,\n peakDb,\n clipped: peak >= CLIP_THRESHOLD_LINEAR - 0.005,\n };\n } finally {\n await audioContext.close().catch(() => { /* ignore */ });\n }\n}\n","/**\n * Synthesize a PluginCuePoints object from raw BPM/sample-rate inputs.\n *\n * The OffsetScrubber consumes PluginCuePoints — a beat grid plus\n * per-beat sample positions, normally produced by Lyria's onset\n * detector. The recorder doesn't have detected cue points (live\n * recordings have no detection pass), but it always knows the project\n * BPM, the engine sample rate, and the loop length in bars. That's\n * enough to construct a synthetic grid where every beat sits on a\n * regular interval — which is exactly what the scrubber needs to\n * provide tick marks + snap behavior for nudging the take's offset.\n */\n\nimport type { PluginCuePoints } from '../types/plugin-sdk.types';\n\nexport interface SynthesizeCuePointsOptions {\n bpm: number;\n sampleRate: number;\n /** Total bars in the clip (e.g. 4 for a 4-bar loop). */\n bars: number;\n /** Beats per bar. Defaults to 4 (4/4). */\n meter?: number;\n}\n\nexport function synthesizeCuePoints({\n bpm,\n sampleRate,\n bars,\n meter = 4,\n}: SynthesizeCuePointsOptions): PluginCuePoints {\n const safeBpm = bpm > 0 ? bpm : 120;\n const safeSampleRate = sampleRate > 0 ? sampleRate : 48000;\n const samplesPerBeat = Math.round((60 / safeBpm) * safeSampleRate);\n const totalBeats = Math.max(1, Math.round(bars * meter));\n const beats: number[] = [];\n for (let i = 0; i < totalBeats; i++) {\n beats.push(i * samplesPerBeat);\n }\n return {\n schema: 1,\n sample_rate: safeSampleRate,\n detected_bpm: safeBpm,\n downbeat_sample: 0,\n beats,\n detected_at: new Date().toISOString(),\n };\n}\n","/**\n * useSceneState — Scene-keyed state hook for plugin developers.\n *\n * Works like `useState`, but maintains separate state per scene.\n * When the user switches scenes, the previous scene's state is preserved\n * and restored when they switch back.\n *\n * Returns `[value, setForCurrentScene, setForScene]`:\n * - `value` — state for the currently active scene\n * - `setForCurrentScene(v)` — updates state for whatever scene is active at call time\n * - `setForScene(sceneId, v)` — updates state for a specific scene (for async callbacks)\n *\n * Both setters support the functional updater pattern: `prev => next`.\n *\n * **Important:** For object/array `initialValue`, hoist to a module-level constant\n * to keep the setter callbacks referentially stable:\n * ```ts\n * const EMPTY: string[] = [];\n * const [items, setItems, setItemsForScene] = useSceneState(activeSceneId, EMPTY);\n * ```\n */\n\nimport { useState, useCallback, useRef } from 'react';\n\ntype SetSceneState<T> = (value: T | ((prev: T) => T)) => void;\ntype SetSceneStateForScene<T> = (sceneId: string, value: T | ((prev: T) => T)) => void;\n\nexport function useSceneState<T>(\n activeSceneId: string | null,\n initialValue: T\n): [T, SetSceneState<T>, SetSceneStateForScene<T>] {\n const [stateMap, setStateMap] = useState<Map<string, T>>(() => new Map());\n const activeSceneIdRef = useRef(activeSceneId);\n activeSceneIdRef.current = activeSceneId;\n\n const currentValue = activeSceneId !== null && stateMap.has(activeSceneId)\n ? stateMap.get(activeSceneId)!\n : initialValue;\n\n const setForCurrentScene = useCallback((value: T | ((prev: T) => T)): void => {\n const sid = activeSceneIdRef.current;\n if (sid === null) return;\n setStateMap(prev => {\n const current = prev.has(sid) ? prev.get(sid)! : initialValue;\n const next = typeof value === 'function' ? (value as (prev: T) => T)(current) : value;\n const newMap = new Map(prev);\n newMap.set(sid, next);\n return newMap;\n });\n }, [initialValue]);\n\n const setForScene = useCallback((sceneId: string, value: T | ((prev: T) => T)): void => {\n setStateMap(prev => {\n const current = prev.has(sceneId) ? prev.get(sceneId)! : initialValue;\n const next = typeof value === 'function' ? (value as (prev: T) => T)(current) : value;\n const newMap = new Map(prev);\n newMap.set(sceneId, next);\n return newMap;\n });\n }, [initialValue]);\n\n return [currentValue, setForCurrentScene, setForScene];\n}\n","/**\n * useAnySolo — reactively reports whether ANY track in the project is soloed.\n *\n * Solo is cross-panel: when the user solos a track in ANY panel, the engine's\n * effective-mute model silences every non-soloed track. A panel uses this flag\n * to DIM its own non-soloed rows without lighting their Mute buttons:\n *\n * ```tsx\n * const anySolo = useAnySolo(host);\n * // ...\n * <TrackRow soloedOut={anySolo && !track.runtimeState.solo} ... />\n * ```\n *\n * Refreshes on mount and on every track-state change. `onTrackStateChange`\n * fires for tracks in ALL panels (not just this plugin's), so a solo toggled in\n * another panel updates this flag too.\n */\n\nimport { useEffect, useState } from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\n\nexport function useAnySolo(\n host: Pick<PluginHost, 'isAnySoloActive' | 'onTrackStateChange'>\n): boolean {\n const [anySolo, setAnySolo] = useState(false);\n\n useEffect(() => {\n let active = true;\n const refresh = (): void => {\n host\n .isAnySoloActive()\n .then((v) => {\n if (active) setAnySolo(v);\n })\n .catch(() => {\n /* engine unreachable — leave the flag as-is rather than flicker */\n });\n };\n refresh();\n const unsub = host.onTrackStateChange(() => refresh());\n return () => {\n active = false;\n unsub();\n };\n }, [host]);\n\n return anySolo;\n}\n","/**\n * useSoundHistory — generic, per-track \"what sounds has this track had?\" stack.\n *\n * Powers the drawer \"History\" tab: restore any earlier sound, star favorites,\n * and (via the host plugin) persist across project reopen. The SDK is ignorant\n * of WHAT a sound is — each plugin records an opaque `descriptor` (a drum sample\n * path / an instrument `{ displayName, zones }` / a synth Surge state blob) plus\n * a human `label`, and supplies `applySound` to re-apply a chosen descriptor.\n *\n * Persistence is the plugin's job: pass `opts.onChange` (called after every\n * mutation with the new state) to save, and call `restore()` on load to seed.\n * Favorited entries are never auto-evicted by the cap.\n *\n * Robustness: `applySound` + `onChange` are read through refs, so the returned\n * object is referentially STABLE regardless of whether the caller memoizes them.\n * Plugins list this object in `loadTracks` deps — an unstable return previously\n * caused a render loop, so keep it stable.\n *\n * @since SDK 2.13.0\n */\n\nimport { useCallback, useMemo, useRef, useState } from 'react';\nimport type { SoundHistoryEntry } from '../types/plugin-sdk.types';\n\nexport type { SoundHistoryEntry };\n\n/** A track's ordered sound history plus the index of the currently-applied sound. */\nexport interface TrackSoundHistory {\n entries: readonly SoundHistoryEntry[];\n /** Index into `entries` of the currently-applied sound; -1 when empty. */\n cursor: number;\n}\n\nexport interface UseSoundHistoryOptions {\n /** Max non-favorited entries kept per track (favorites are never evicted). Default 24. */\n max?: number;\n /**\n * Called after every mutation (record/undo/restoreTo/toggleFavorite/clear) with the\n * track's new state — use it to persist. NOT called by `restore()` (that's a load).\n */\n onChange?: (trackId: string, state: TrackSoundHistory) => void;\n}\n\nexport interface UseSoundHistoryResult {\n /** Remember a sound that was just applied (generation, scene-load, or shuffle). */\n record(trackId: string, descriptor: unknown, label: string): void;\n /** Re-apply the sound one step before the current one. Resolves true if it moved. */\n undo(trackId: string): Promise<boolean>;\n /** Re-apply a specific entry by index. Resolves true if it applied. */\n restoreTo(trackId: string, index: number): Promise<boolean>;\n /** The ordered history + cursor for a track (safe empty default). */\n list(trackId: string): TrackSoundHistory;\n /** Whether there is an earlier sound to step back to. */\n canUndo(trackId: string): boolean;\n /** Forget a track's history (e.g. on regenerate). Persists the cleared state. */\n clear(trackId: string): void;\n /** Forget ALL tracks' history in memory (e.g. before re-seeding on scene load). */\n reset(): void;\n /** Seed a track's full history (e.g. from persistence on load). Does NOT fire onChange. */\n restore(\n trackId: string,\n state: { entries?: readonly SoundHistoryEntry[]; cursor?: number } | null | undefined,\n ): void;\n /** Toggle the favorite flag on an entry (favorites survive cap eviction). */\n toggleFavorite(trackId: string, index: number): void;\n}\n\nconst EMPTY: TrackSoundHistory = { entries: [], cursor: -1 };\n\nfunction sameDescriptor(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n}\n\nexport function useSoundHistory(\n applySound: (trackId: string, descriptor: unknown) => Promise<void>,\n opts: UseSoundHistoryOptions = {},\n): UseSoundHistoryResult {\n const max = Math.max(2, opts.max ?? 24);\n\n // Read callbacks through refs so the returned API stays referentially stable\n // even if the caller passes a fresh closure each render.\n const applyRef = useRef(applySound);\n applyRef.current = applySound;\n const onChangeRef = useRef(opts.onChange);\n onChangeRef.current = opts.onChange;\n\n // Authoritative store in a ref (async callbacks read latest); version forces re-render.\n const dataRef = useRef<Record<string, TrackSoundHistory>>({});\n const [, setVersion] = useState(0);\n const bump = useCallback((): void => setVersion((v) => v + 1), []);\n\n // Single writer: update store, re-render, optionally notify for persistence.\n const commit = useCallback(\n (trackId: string, next: TrackSoundHistory, notify: boolean): void => {\n dataRef.current = { ...dataRef.current, [trackId]: next };\n bump();\n if (notify) onChangeRef.current?.(trackId, next);\n },\n [bump],\n );\n\n const record = useCallback(\n (trackId: string, descriptor: unknown, label: string): void => {\n const h = dataRef.current[trackId];\n const current = h && h.cursor >= 0 ? h.entries[h.cursor] : undefined;\n // Ignore re-applying the same sound (no-op shuffles, scene re-seeds).\n if (current && sameDescriptor(current.descriptor, descriptor)) return;\n const entries: SoundHistoryEntry[] = [...(h ? h.entries : []), { descriptor, label }];\n // Cap: evict the OLDEST NON-FAVORITED entry when over the limit (favorites survive).\n while (entries.length > max) {\n const victim = entries.findIndex((e) => !e.favorite);\n if (victim === -1) break; // everything is favorited — keep it all\n entries.splice(victim, 1);\n }\n commit(trackId, { entries, cursor: entries.length - 1 }, true);\n },\n [max, commit],\n );\n\n const restoreTo = useCallback(\n async (trackId: string, index: number): Promise<boolean> => {\n const h = dataRef.current[trackId];\n if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;\n await applyRef.current(trackId, h.entries[index].descriptor);\n commit(trackId, { entries: h.entries, cursor: index }, true);\n return true;\n },\n [commit],\n );\n\n const undo = useCallback(\n (trackId: string): Promise<boolean> => {\n const h = dataRef.current[trackId];\n if (!h || h.cursor <= 0) return Promise.resolve(false);\n return restoreTo(trackId, h.cursor - 1);\n },\n [restoreTo],\n );\n\n const toggleFavorite = useCallback(\n (trackId: string, index: number): void => {\n const h = dataRef.current[trackId];\n if (!h || index < 0 || index >= h.entries.length) return;\n const entries = h.entries.map((e, i) => (i === index ? { ...e, favorite: !e.favorite } : e));\n commit(trackId, { entries, cursor: h.cursor }, true);\n },\n [commit],\n );\n\n const restore = useCallback(\n (\n trackId: string,\n state: { entries?: readonly SoundHistoryEntry[]; cursor?: number } | null | undefined,\n ): void => {\n const entries: SoundHistoryEntry[] = Array.isArray(state?.entries) ? [...state!.entries] : [];\n const raw = typeof state?.cursor === 'number' ? state!.cursor : entries.length - 1;\n const cursor = entries.length === 0 ? -1 : Math.min(Math.max(raw, 0), entries.length - 1);\n commit(trackId, { entries, cursor }, false);\n },\n [commit],\n );\n\n const list = useCallback(\n (trackId: string): TrackSoundHistory => dataRef.current[trackId] ?? EMPTY,\n [],\n );\n\n const canUndo = useCallback((trackId: string): boolean => {\n const h = dataRef.current[trackId];\n return !!h && h.cursor > 0;\n }, []);\n\n const clear = useCallback(\n (trackId: string): void => {\n if (dataRef.current[trackId]) {\n const next = { ...dataRef.current };\n delete next[trackId];\n dataRef.current = next;\n bump();\n }\n onChangeRef.current?.(trackId, EMPTY); // persist the cleared state\n },\n [bump],\n );\n\n const reset = useCallback((): void => {\n dataRef.current = {};\n bump();\n }, [bump]);\n\n // Stable object so consumers can safely list it in useCallback/useEffect deps.\n return useMemo(\n () => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),\n [record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite],\n );\n}\n","/**\n * Plugin SDK Version\n *\n * Semver version of the plugin SDK contract.\n * Plugins declare minSdkVersion in their manifest.\n * Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)\n * during activation and marks incompatible plugins accordingly.\n */\nexport const PLUGIN_SDK_VERSION = '2.34.0';\n","/**\n * Format the cross-plugin concurrent-track context into a prose block\n * that's safe to drop straight into an LLM user-prompt. Both the synth\n * and drum builtin panels use this so the rendered prompt stays\n * consistent across generators — and so a single change here propagates\n * to every plugin that calls `host.getGenerationContext()`.\n *\n * Per-track payload follows the user's preferred shape (raw note JSON\n * grouped by chord) so the model sees velocity / start-beat /\n * duration / pitch verbatim and can reason about feel + harmony.\n *\n * Returns the empty string when there are no concurrent tracks — call\n * sites can `if (block) push(block)` rather than baking in a placeholder.\n */\n\nimport type {\n PluginGenerationContext,\n PluginChordSegment,\n PluginMidiNote,\n} from '../types/plugin-sdk.types';\n\nexport function formatConcurrentTracks(ctx: PluginGenerationContext): string {\n const tracks = ctx.concurrentTracks;\n if (!tracks || tracks.length === 0) return '';\n\n const lines: string[] = [`Concurrent tracks in scene (already generated):`];\n\n for (const track of tracks) {\n const promptStr = track.prompt\n ? ` prompt=\"${escapeQuotes(track.prompt)}\"`\n : '';\n lines.push(` - role=${track.role ?? 'unknown'}${promptStr}`);\n\n if (track.notesByChord.length === 0) {\n lines.push(` (no notes)`);\n } else {\n for (const segment of track.notesByChord) {\n if (segment.notes.length === 0) continue;\n lines.push(` ${formatChordSegment(segment)}`);\n }\n }\n\n if (track.truncated && typeof track.originalNoteCount === 'number') {\n const dropped = track.originalNoteCount - sumKeptNotes(track.notesByChord);\n if (dropped > 0) {\n lines.push(` … (${dropped} more notes truncated)`);\n }\n }\n }\n\n if (ctx.truncatedTrackCount && ctx.truncatedTrackCount > 0) {\n lines.push(\n ` … (${ctx.truncatedTrackCount} additional track${ctx.truncatedTrackCount === 1 ? '' : 's'} omitted to fit token budget)`,\n );\n }\n\n return lines.join('\\n');\n}\n\nfunction formatChordSegment(segment: PluginChordSegment): string {\n const [start, end] = segment.chordRangeQn;\n const notesJson = JSON.stringify(segment.notes.map(compactNote));\n return `${segment.chord} (beats ${start}-${end}): ${notesJson}`;\n}\n\n/**\n * Strip channel and other rarely-relevant fields so the LLM sees only\n * the four properties that drive perception: pitch, startBeat,\n * durationBeats, velocity.\n */\nfunction compactNote(n: PluginMidiNote): {\n pitch: number;\n startBeat: number;\n durationBeats: number;\n velocity: number;\n} {\n return {\n pitch: n.pitch,\n startBeat: n.startBeat,\n durationBeats: n.durationBeats,\n velocity: n.velocity,\n };\n}\n\nfunction escapeQuotes(s: string): string {\n return s.replace(/\"/g, '\\\\\"');\n}\n\nfunction sumKeptNotes(segments: PluginChordSegment[]): number {\n let total = 0;\n for (const s of segments) total += s.notes.length;\n return total;\n}\n","/**\n * Lightweight, dependency-free semantic matching for sample selection.\n *\n * Sample generators (drums, instruments) ship a short StableAudio text\n * prompt next to every sample (\"tight 909-style kick one shot, hard click\n * transient, short punchy body, dry, no hi hats, no loop\"). When the user\n * asks for \"a 1950s style boom bap kick\" we want to pick the sample whose\n * prompt is closest to that intent — instead of a uniform random draw —\n * while still preserving variety so a vague \"give me a kick\" doesn't return\n * the identical sample every time.\n *\n * Design notes:\n * - Pure functions, no I/O, no SDK-type dependencies → trivially unit\n * testable with an injected `rng`, and safe to call from either the\n * main or renderer process.\n * - Scoring is IDF-weighted query-coverage (a TF-IDF / BM25-lite). The\n * IDF is derived from the candidate pool itself, so it is STRUCTURAL —\n * no hand-maintained synonym tables. Rare, discriminating tokens in the\n * prompts (\"909\", \"dusty\", \"tube\") dominate; corpus-universal filler\n * (\"one\", \"shot\", \"dry\") washes out to ~zero IDF on its own.\n * - The near-universal negative clauses StableAudio prompts carry\n * (\"no hi hats\", \"no loop\", \"no melody\") are stripped before tokenizing;\n * they are pure noise for matching.\n * - Selection is softmax-weighted random among the top-k. Flat scores →\n * ~uniform (≈ the old random behavior); a clear winner → tight\n * convergence. The all-zero (no-signal) case is intentionally left to\n * the caller to fall back to its existing random path over the full\n * pool — see `scorePromptMatch`'s contract below.\n */\n\n/**\n * Function words + a few imperative-request fillers that should never count\n * as matchable intent. Kept deliberately SMALL — IDF already neutralizes\n * corpus-universal words, and query tokens that appear in no candidate are\n * dropped during scoring, so this list only needs the words that would\n * otherwise be both query-frequent AND coincidentally present in prompts.\n */\nconst STOP_WORDS: ReadonlySet<string> = new Set([\n 'a', 'an', 'the', 'and', 'or', 'but', 'with', 'for', 'to', 'of', 'in', 'on',\n 'at', 'by', 'is', 'it', 'this', 'that', 'i', 'my', 'me', 'make', 'please',\n 'give', 'want', 'need', 'some', 'like', 'get', 'something',\n]);\n\n/**\n * Tokenize a prompt or query into matchable lowercase tokens.\n *\n * 1. Drop comma-delimited negative clauses (\"no hi hats\", \"no loop\").\n * 2. Lowercase, split on any non-alphanumeric run.\n * 3. Drop stop-words and 1–2 digit numeric noise (\"01\", \"02\") while\n * keeping meaningful numerics (\"808\", \"909\", \"1950\").\n */\nexport function tokenizePrompt(text: string): string[] {\n if (!text) return [];\n const withoutNegatives = text\n .split(',')\n .map((clause) => clause.trim())\n .filter((clause) => clause.length > 0 && !/^no\\s/i.test(clause))\n .join(' ');\n\n return withoutNegatives\n .toLowerCase()\n .split(/[^a-z0-9]+/u)\n .filter((tok) => {\n if (!tok) return false;\n if (STOP_WORDS.has(tok)) return false;\n if (/^\\d{1,2}$/.test(tok)) return false; // \"01\", \"02\" — sequence noise\n return true;\n });\n}\n\n/**\n * Score each candidate prompt against the query, returning a parallel array\n * of scores in [0, 1] (1 = the candidate covers all of the query's\n * discriminating intent).\n *\n * Contract: a returned max of 0 means the query shares NO matchable token\n * with any candidate (no signal). Callers should treat that as \"fall back to\n * the existing uniform-random pick over the full pool\" so vague queries keep\n * today's variety rather than biasing toward an arbitrary top-k slice.\n */\nexport function scorePromptMatch(\n query: string,\n candidatePrompts: ReadonlyArray<string>,\n): number[] {\n const n = candidatePrompts.length;\n if (n === 0) return [];\n\n const queryTokens = Array.from(new Set(tokenizePrompt(query)));\n if (queryTokens.length === 0) return candidatePrompts.map(() => 0);\n\n const candidateTokenSets = candidatePrompts.map((p) => new Set(tokenizePrompt(p)));\n\n // IDF for each query token, derived from the candidate pool. Tokens that\n // appear in no candidate are unmatchable → excluded from both the score\n // numerator and the normalization denominator.\n const idf = new Map<string, number>();\n for (const token of queryTokens) {\n let df = 0;\n for (const set of candidateTokenSets) {\n if (set.has(token)) df += 1;\n }\n if (df > 0) idf.set(token, Math.log(1 + n / df));\n }\n\n let denominator = 0;\n for (const weight of idf.values()) denominator += weight;\n if (denominator === 0) return candidatePrompts.map(() => 0);\n\n return candidateTokenSets.map((set) => {\n let numerator = 0;\n for (const [token, weight] of idf) {\n if (set.has(token)) numerator += weight;\n }\n return numerator / denominator;\n });\n}\n\n/** One scored candidate. `key` (if present) is what `excludeKeys` matches on. */\nexport interface ScoredCandidate<T> {\n item: T;\n score: number;\n key?: string;\n}\n\nexport interface PickTopKOptions {\n /** Consider only the top-k by score (default 5). */\n k?: number;\n /**\n * Softmax temperature (default 0.3). Lower → sharper preference for the\n * top match; higher → flatter (more variety). Scores are in [0, 1].\n */\n temperature?: number;\n /** Candidate keys to exclude (e.g. shuffle history). */\n excludeKeys?: ReadonlySet<string>;\n /** Injectable RNG in [0, 1) for deterministic tests (default Math.random). */\n rng?: () => number;\n}\n\n/**\n * Pick one candidate via softmax-weighted random selection among the top-k\n * by score. Returns null only when the pool is empty after exclusion.\n *\n * Equal scores → equal weights → uniform pick among the top-k, so this\n * degrades gracefully toward random when the query gives no preference.\n */\nexport function pickTopKWeighted<T>(\n scored: ReadonlyArray<ScoredCandidate<T>>,\n options: PickTopKOptions = {},\n): T | null {\n const { k = 5, temperature = 0.3, excludeKeys, rng = Math.random } = options;\n\n let pool = scored;\n if (excludeKeys && excludeKeys.size > 0) {\n pool = pool.filter((c) => c.key === undefined || !excludeKeys.has(c.key));\n }\n if (pool.length === 0) return null;\n\n const sorted = [...pool].sort((a, b) => b.score - a.score);\n const top = sorted.slice(0, Math.max(1, k));\n\n // Softmax with a max-subtraction for numerical stability.\n const maxScore = top[0].score;\n const safeTemp = Math.max(1e-6, temperature);\n const weights = top.map((c) => Math.exp((c.score - maxScore) / safeTemp));\n const totalWeight = weights.reduce((sum, w) => sum + w, 0);\n\n let threshold = rng() * totalWeight;\n for (let i = 0; i < top.length; i += 1) {\n threshold -= weights[i];\n if (threshold <= 0) return top[i].item;\n }\n return top[top.length - 1].item;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC4mEO,IAAM,cAAN,cAA0B,MAAM;AAAA,EAIrC,YACE,MACA,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AACF;;;AC9mEO,IAAM,gBAAuC;AAAA,EAClD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,iBAA6C;AAAA,EACxD,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACV;AAGO,IAAM,yBAAqD;AAAA,EAChE,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACV;AAGO,IAAM,oBAAgD;AAAA,EAC3D,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACV;AAaO,IAAM,iBAA+B;AAAA,EAC1C,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACV;AAgDO,IAAM,qBAAqB;AAG3B,IAAM,6BAAoD;AAAA,EAC/D,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AACV;AAGO,IAAM,wBAA4C;AAAA,EACvD,IAAI,EAAE,GAAG,2BAA2B;AAAA,EACpC,YAAY,EAAE,GAAG,2BAA2B;AAAA,EAC5C,QAAQ,EAAE,GAAG,2BAA2B;AAAA,EACxC,QAAQ,EAAE,GAAG,2BAA2B;AAAA,EACxC,OAAO,EAAE,GAAG,2BAA2B;AAAA,EACvC,QAAQ,EAAE,GAAG,2BAA2B;AAC1C;;;AC1HA,IAAAA,gBAAkB;AAClB,0BAAuD;;;ACOvD,IAAAC,gBAAyC;;;ACAzC,IAAM,aAA6B;AAAA,EACjC,SAAS;AAAA,IACP;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ;AAAA,QACN,kBAAkB;AAAA,QAAI,kBAAkB;AAAA,QAAG,eAAe;AAAA,QAC1D,cAAc;AAAA,QAAK,cAAc;AAAA,QAAI,WAAW;AAAA,QAChD,cAAc;AAAA,QAAM,cAAc;AAAA,QAAI,WAAW;AAAA,QACjD,mBAAmB;AAAA,QAAO,mBAAmB;AAAA,QAAG,gBAAgB;AAAA,MAClE;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ;AAAA,QACN,kBAAkB;AAAA,QAAK,kBAAkB;AAAA,QAAK,eAAe;AAAA,QAC7D,cAAc;AAAA,QAAM,cAAc;AAAA,QAAG,WAAW;AAAA,QAChD,cAAc;AAAA,QAAM,cAAc;AAAA,QAAI,WAAW;AAAA,QACjD,mBAAmB;AAAA,QAAM,mBAAmB;AAAA,QAAK,gBAAgB;AAAA,MACnE;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ;AAAA,QACN,kBAAkB;AAAA,QAAK,kBAAkB;AAAA,QAAG,eAAe;AAAA,QAC3D,cAAc;AAAA,QAAK,cAAc;AAAA,QAAG,WAAW;AAAA,QAC/C,cAAc;AAAA,QAAM,cAAc;AAAA,QAAG,WAAW;AAAA,QAChD,mBAAmB;AAAA,QAAO,mBAAmB;AAAA,QAAI,gBAAgB;AAAA,MACnE;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ;AAAA,QACN,kBAAkB;AAAA,QAAK,kBAAkB;AAAA,QAAI,eAAe;AAAA,QAC5D,cAAc;AAAA,QAAK,cAAc;AAAA,QAAI,WAAW;AAAA,QAChD,cAAc;AAAA,QAAM,cAAc;AAAA,QAAG,WAAW;AAAA,QAChD,mBAAmB;AAAA,QAAO,mBAAmB;AAAA,QAAG,gBAAgB;AAAA,MAClE;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ;AAAA,QACN,kBAAkB;AAAA,QAAI,kBAAkB;AAAA,QAAG,eAAe;AAAA,QAC1D,cAAc;AAAA,QAAK,cAAc;AAAA,QAAI,WAAW;AAAA,QAChD,cAAc;AAAA,QAAK,cAAc;AAAA,QAAI,WAAW;AAAA,QAChD,mBAAmB;AAAA,QAAO,mBAAmB;AAAA,QAAG,gBAAgB;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,kBAAkB;AACpB;AAMA,IAAM,qBAAqC;AAAA,EACzC,SAAS;AAAA,IACP;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,OAAO,SAAS,KAAK,UAAU,IAAM,WAAW,KAAO,UAAU,EAAI;AAAA,IAC9F;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,KAAO,SAAS,KAAK,UAAU,KAAK,WAAW,KAAO,UAAU,EAAI;AAAA,IAC7F;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,OAAO,SAAS,OAAO,UAAU,IAAM,WAAW,KAAO,UAAU,EAAI;AAAA,IAChG;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,OAAO,SAAS,MAAM,UAAU,IAAM,WAAW,KAAO,UAAU,EAAI;AAAA,IAC/F;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,OAAO,SAAS,GAAK,UAAU,KAAK,WAAW,IAAM,UAAU,EAAI;AAAA,IAC5F;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,kBAAkB;AACpB;AAMA,IAAM,iBAAiC;AAAA,EACrC,SAAS;AAAA,IACP;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,SAAS,KAAK,SAAS,KAAK,OAAO,GAAK,eAAe,IAAI;AAAA,IAC/E;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,SAAS,GAAK,SAAS,KAAK,OAAO,KAAK,eAAe,IAAI;AAAA,IAC/E;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,SAAS,GAAK,SAAS,KAAK,OAAO,KAAK,eAAe,EAAI;AAAA,IAC/E;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,SAAS,GAAK,SAAS,GAAK,OAAO,KAAK,eAAe,IAAI;AAAA,IAC/E;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,SAAS,GAAK,SAAS,KAAK,OAAO,GAAK,eAAe,IAAI;AAAA,IAC/E;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,kBAAkB;AACpB;AAMA,IAAM,iBAAiC;AAAA,EACrC,SAAS;AAAA,IACP;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,OAAO,GAAK,MAAM,KAAK,UAAU,IAAI;AAAA,IACzD;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,OAAO,GAAK,MAAM,GAAK,UAAU,IAAI;AAAA,IACzD;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,OAAO,GAAK,MAAM,KAAK,UAAU,IAAI;AAAA,IACzD;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,OAAO,KAAK,MAAM,GAAK,UAAU,IAAI;AAAA,IACzD;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,gBAAgB,EAAE,OAAO,GAAK,MAAM,MAAM,UAAU,IAAI;AAAA,IAC1D;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,kBAAkB;AACpB;AAMA,IAAM,gBAAgC;AAAA,EACpC,SAAS;AAAA,IACP;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,QAAQ,EAAE,YAAY,KAAO,kBAAkB,KAAK;AAAA,IACtD;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,QAAQ,EAAE,YAAY,IAAM,kBAAkB,KAAK;AAAA,IACrD;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,QAAQ,EAAE,YAAY,KAAO,kBAAkB,IAAI;AAAA,IACrD;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,QAAQ,EAAE,YAAY,MAAM,kBAAkB,IAAI;AAAA,IACpD;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,QAAQ,EAAE,YAAY,IAAM,kBAAkB,IAAI;AAAA,IACpD;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,kBAAkB;AACpB;AAMA,IAAM,iBAAiC;AAAA,EACrC,SAAS;AAAA,IACP;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,KAAK,WAAW,KAAK,aAAa,MAAM,aAAa,KAAK,SAAS,IAAI;AAAA,IAChG;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,KAAK,WAAW,KAAK,aAAa,MAAM,aAAa,KAAK,SAAS,EAAI;AAAA,IAChG;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,GAAK,WAAW,KAAK,aAAa,OAAO,aAAa,KAAK,SAAS,EAAI;AAAA,IACjG;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,MAAM,WAAW,GAAK,aAAa,KAAK,aAAa,KAAK,SAAS,IAAI;AAAA,IAChG;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,QAAQ,EAAE,aAAa,KAAK,WAAW,GAAK,aAAa,KAAK,aAAa,KAAK,SAAS,EAAI;AAAA,IAC/F;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,kBAAkB;AACpB;AAOO,IAAM,oBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACV;;;ACzOY;AAtCZ,IAAM,YAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACV;AAWO,IAAM,cAA0C,CAAC;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AACb,MAAM;AACJ,SACE,4CAAC,SAAI,WAAU,uBAAsB,eAAY,iBAC9C,wBAAc,IAAI,CAAC,aAAyB;AAC3C,UAAM,SAAgC,QAAQ,QAAQ;AACtD,UAAM,WAAW,OAAO;AACxB,UAAM,QAAQ,kBAAkB,QAAQ;AACxC,UAAM,cAAc,UAAU,QAAQ;AACtC,UAAM,SAAS,kBAAkB,QAAQ;AAEzC,WACE,6CAAC,SAAmB,WAAU,6BAE5B;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,eAAa,aAAa,QAAQ;AAAA,UAClC;AAAA,UACA,SAAS,MAAM,SAAS,SAAS,UAAU,CAAC,QAAQ;AAAA,UACpD,WAAW,6GACT,WACI,sDACA,WACE,GAAG,WAAW,gBACd,6EACR;AAAA,UACA,OAAO,GAAG,WAAW,YAAY,QAAQ,IAAI,SAAS,YAAY,CAAC;AAAA,UAElE;AAAA;AAAA,MACH;AAAA,MAGC,OAAO,QAAQ,IAAI,CAAC,QAAQ,QAC3B;AAAA,QAAC;AAAA;AAAA,UAEC,eAAa,aAAa,QAAQ,IAAI,GAAG;AAAA,UACzC,UAAU,YAAY,CAAC;AAAA,UACvB,SAAS,MAAM,eAAe,SAAS,UAAU,GAAG;AAAA,UACpD,WAAW,0FACT,YAAY,CAAC,WACT,sDACA,OAAO,gBAAgB,MACrB,GAAG,WAAW,gBACd,6EACR;AAAA,UACA,OAAO,OAAO;AAAA,UAEb,gBAAM;AAAA;AAAA,QAbF;AAAA,MAcP,CACD;AAAA,MAGD;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,eAAa,aAAa,QAAQ;AAAA,UAClC,KAAI;AAAA,UACJ,KAAI;AAAA,UACJ,OAAO,KAAK,MAAM,OAAO,SAAS,GAAG;AAAA,UACrC,UAAU,YAAY,CAAC;AAAA,UACvB,UAAU,CAAC,MACT,eAAe,SAAS,UAAU,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG;AAAA,UAEhE,WAAU;AAAA,UACV,OAAO,YAAY,KAAK,MAAM,OAAO,SAAS,GAAG,CAAC;AAAA;AAAA,MACpD;AAAA,MACA,6CAAC,UAAK,WAAU,6DACb;AAAA,aAAK,MAAM,OAAO,SAAS,GAAG;AAAA,QAAE;AAAA,SACnC;AAAA,SAtDQ,QAuDV;AAAA,EAEJ,CAAC,GACH;AAEJ;;;ACzFA,mBAA+E;AAwYvE,IAAAC,sBAAA;AAhYD,IAAM,cAAc;AAEpB,IAAM,aAAa;AAEnB,IAAM,WAAW;AAEjB,IAAM,iBAAiB;AAEvB,IAAM,mBAAmB;AAEzB,IAAM,eAAe;AAE5B,IAAM,aAAa,CAAC,KAAK,MAAM,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,GAAG;AACnF,IAAM,aAAa,oBAAI,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;AAE3C,IAAM,cAAsC;AAAA,EAC1C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AACX;AAEA,SAAS,MAAM,GAAW,IAAY,IAAoB;AACxD,SAAO,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC;AACrC;AAEA,SAAS,UAAU,GAAmB;AACpC,SAAO,YAAY,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC;AACvC;AAOO,SAAS,YAAY,OAAuB;AACjD,QAAM,OAAO,YAAa,QAAQ,KAAM,MAAM,EAAE;AAChD,QAAM,SAAS,KAAK,MAAM,QAAQ,EAAE,IAAI;AACxC,SAAO,GAAG,IAAI,GAAG,MAAM;AACzB;AAMO,SAAS,SACd,OACA,WACA,IAC+B;AAC/B,SAAO,EAAE,MAAM,YAAY,aAAa,MAAM,KAAK,SAAS,WAAW;AACzE;AAOO,SAAS,SACd,QACA,QACA,IACA,MACA,MACA,aACsC;AACtC,QAAM,aAAa,OAAO;AAC1B,QAAM,QAAQ,MAAM,KAAK,KAAK,MAAM,SAAS,UAAU,GAAG,GAAG,GAAG;AAChE,QAAM,UAAU,SAAS;AACzB,QAAM,UAAU,KAAK,MAAM,UAAU,IAAI,IAAI;AAC7C,QAAM,YAAY,MAAM,SAAS,GAAG,KAAK,IAAI,GAAG,aAAa,IAAI,CAAC;AAClE,SAAO,EAAE,OAAO,UAAU;AAC5B;AAQO,SAAS,mBACd,WACA,QACA,MACA,MACA,aACQ;AACR,QAAM,aAAa,OAAO;AAC1B,QAAM,aAAa,KAAK,MAAM,SAAS,cAAc,IAAI,IAAI;AAC7D,QAAM,MAAM,MAAM,YAAY,YAAY,MAAM,UAAU;AAC1D,SAAO,MAAM;AACf;AASO,SAAS,gBACd,SACA,IACA,UACA,WACQ;AACR,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,SAAS,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAChD,QAAM,MAAM,KAAK,MAAM,OAAO,SAAS,CAAC;AACxC,QAAM,SAAS,OAAO,SAAS,MAAM,IAAI,OAAO,GAAG,KAAK,OAAO,MAAM,CAAC,IAAI,OAAO,GAAG,KAAK;AAEzF,QAAM,qBAAqB,KAAK,UAAU,aAAa,aAAa;AACpE,QAAM,YAAY,KAAK,IAAI,GAAG,WAAW,aAAa,SAAS;AAC/D,SAAO,MAAM,oBAAoB,YAAY,GAAG,GAAG,SAAS;AAC9D;AAGO,SAAS,eACd,OACA,WACkB;AAClB,SAAO,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM,EAAE,QAAQ,WAAW,GAAG,GAAG,EAAE,EAAE;AAC/E;AA4DO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,OAAO;AAAA,EACP,cAAc,CAAC,GAAG,KAAK,IAAI;AAAA,EAC3B;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV;AAAA,EACA,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX;AAAA,EACA,SAAS;AACX,GAA6C;AAC3C,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,IAAI;AAC/C,QAAM,cAAU,qBAA8B,IAAI;AAClD,QAAM,gBAAY,qBAA8B,IAAI;AACpD,QAAM,cAAU,qBAAyB,IAAI;AAG7C,QAAM,mBAAe,qBAAO,KAAK;AAIjC,QAAM,EAAE,IAAI,GAAG,QAAI,sBAAQ,MAAkC;AAC3D,QAAI,WAAW,MAAM,SAAS,GAAG;AAC/B,YAAM,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK;AACnC,aAAO;AAAA,QACL,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,KAAK,IAAI,GAAG,EAAE,IAAI,CAAC,CAAC;AAAA,QACvD,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,GAAG,EAAE,IAAI,CAAC,CAAC;AAAA,MAC3D;AAAA,IACF;AACA,WAAO,EAAE,IAAI,UAAU,IAAI,SAAS;AAAA,EACtC,GAAG,CAAC,SAAS,OAAO,UAAU,QAAQ,CAAC;AAEvC,QAAM,WAAW,KAAK,KAAK;AAC3B,QAAM,aAAa,OAAO;AAC1B,QAAM,YAAY,aAAa;AAC/B,QAAM,aAAa,WAAW;AAK9B,QAAM,eAAW,qBAAO;AAAA,IACtB;AAAA,IAAO;AAAA,IAAU;AAAA,IAAW;AAAA,IAAI;AAAA,IAAM;AAAA,IAAa;AAAA,IAAiB;AAAA,IAAK;AAAA,IAAgB;AAAA,EAC3F,CAAC;AACD,WAAS,UAAU;AAAA,IACjB;AAAA,IAAO;AAAA,IAAU;AAAA,IAAW;AAAA,IAAI;AAAA,IAAM;AAAA,IAAa;AAAA,IAAiB;AAAA,IAAK;AAAA,IAAgB;AAAA,EAC3F;AAEA,QAAM,kBAAc,0BAAY,CAAC,SAAiB,YAA8C;AAC9F,UAAM,OAAO,QAAQ,SAAS,sBAAsB;AACpD,WAAO,EAAE,GAAG,WAAW,MAAM,QAAQ,IAAI,GAAG,WAAW,MAAM,OAAO,GAAG;AAAA,EACzE,GAAG,CAAC,CAAC;AAEL,QAAM,wBAAoB,0BAAY,CAAC,MAAgD;AACrF,QAAI,SAAS,QAAQ,SAAU;AAC/B,UAAM,SAAS,EAAE;AACjB,UAAM,SAAS,OAAO,QAAQ,6BAA6B;AAC3D,UAAM,UAAU,QAAQ,aAAa,YAAY;AAGjD,UAAM,iBAAiB,WAAW,QAAQ,OAAO,QAAQ,sBAAsB,KAAK;AACpF,YAAQ,UAAU;AAAA,MAChB,MAAM,WAAW,OAAO,gBAAgB,iBAAiB,mBAAmB;AAAA,MAC5E,OAAO,WAAW,OAAO,OAAO,OAAO,IAAI;AAAA,MAC3C,QAAQ,EAAE;AAAA,MACV,QAAQ,EAAE;AAAA,IACZ;AACA,QAAI;AACF,MAAC,EAAE,cAA8B,oBAAoB,EAAE,SAAS;AAAA,IAClE,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,wBAAoB,0BAAY,CAAC,MAAgD;AACrF,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,KAAK,MAAM,EAAE,UAAU,KAAK,QAAQ,EAAE,UAAU,KAAK,MAAM;AACxE,QAAI,OAAO,gBAAgB;AACzB,UAAI,KAAK,SAAS,eAAgB,MAAK,OAAO;AAAA,eACrC,KAAK,SAAS,iBAAkB,MAAK,OAAO;AAAA,IACvD;AACA,UAAM,IAAI,SAAS;AACnB,UAAM,EAAE,GAAG,EAAE,IAAI,YAAY,EAAE,SAAS,EAAE,OAAO;AAEjD,QAAI,KAAK,SAAS,UAAU;AAC1B,YAAM,OAAO,EAAE,MAAM,KAAK,KAAK;AAC/B,UAAI,CAAC,KAAM;AACX,YAAM,gBAAgB,mBAAmB,KAAK,WAAW,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW;AAC9F,UAAI,kBAAkB,KAAK,cAAe;AAC1C,YAAMC,QAAO,EAAE,MAAM,IAAI,CAAC,GAAG,MAAO,MAAM,KAAK,QAAQ,EAAE,GAAG,GAAG,cAAc,IAAI,CAAE;AACnF,QAAE,SAASA,KAAI;AACf;AAAA,IACF;AAEA,QAAI,KAAK,SAAS,OAAQ;AAC1B,UAAM,EAAE,OAAO,UAAU,IAAI,SAAS,GAAG,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW;AACpF,UAAM,OAAO,EAAE,MAAM,IAAI,CAAC,GAAG,MAAO,MAAM,KAAK,QAAQ,EAAE,GAAG,GAAG,OAAO,UAAU,IAAI,CAAE;AACtF,MAAE,SAAS,IAAI;AAAA,EACjB,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,sBAAkB,0BAAY,CAAC,MAAgD;AACnF,UAAM,OAAO,QAAQ;AACrB,YAAQ,UAAU;AAClB,QAAI,CAAC,KAAM;AACX,UAAM,IAAI,SAAS;AACnB,QAAI,EAAE,SAAU;AAEhB,QAAI,KAAK,SAAS,kBAAkB,KAAK,SAAS,kBAAkB;AAGlE,QAAE,SAAS,EAAE,MAAM,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK,KAAK,CAAC;AACrD;AAAA,IACF;AACA,QAAI,KAAK,SAAS,eAAe;AAC/B,YAAM,EAAE,GAAG,EAAE,IAAI,YAAY,EAAE,SAAS,EAAE,OAAO;AACjD,YAAM,EAAE,OAAO,UAAU,IAAI,SAAS,GAAG,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW;AACpF,YAAM,OAAuB;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,eAAe,EAAE;AAAA,QACjB,UAAU,EAAE;AAAA,QACZ,SAAS;AAAA,MACX;AACA,QAAE,SAAS,CAAC,GAAG,EAAE,OAAO,IAAI,CAAC;AAC7B,QAAE,iBAAiB,OAAO,EAAE,iBAAiB,KAAK,IAAI,GAAG,EAAE,aAAa,KAAK,EAAE,OAAO,GAAI,CAAC;AAAA,IAC7F;AAAA,EAEF,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,0BAAsB,0BAAY,MAAY;AAClD,YAAQ,UAAU;AAAA,EACpB,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAe,0BAAY,CAAC,UAAwB;AACxD,UAAM,IAAI,SAAS;AACnB,QAAI,EAAE,SAAU;AAEhB,iBAAa,UAAU;AACvB,MAAE,SAAS,eAAe,EAAE,OAAO,KAAK,CAAC;AAAA,EAC3C,GAAG,CAAC,CAAC;AAEL,QAAM,uBAAmB,0BAAY,CAAC,MAAkD;AACtF,UAAM,IAAI,OAAO,EAAE,OAAO,KAAK;AAC/B,iBAAa,CAAC;AACd,mBAAe,CAAC;AAAA,EAClB,GAAG,CAAC,YAAY,CAAC;AAMjB,oCAAgB,MAAM;AACpB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,QAAI,MAAM,WAAW,GAAG;AACtB,mBAAa,UAAU;AACvB;AAAA,IACF;AACA,QAAI,aAAa,WAAW,QAAQ,QAAS;AAC7C,iBAAa,UAAU;AACvB,UAAM,YAAY,GAAG,gBAAgB;AACrC,OAAG,YAAY;AAAA,MACb,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,GAAG,CAAC,OAAO,IAAI,QAAQ,CAAC;AAGxB,QAAM,WAAO,sBAAQ,MAAgB;AACnC,UAAM,MAAgB,CAAC;AACvB,aAAS,IAAI,IAAI,KAAK,IAAI,IAAK,KAAI,KAAK,CAAC;AACzC,WAAO;AAAA,EACT,GAAG,CAAC,IAAI,EAAE,CAAC;AAIX,QAAM,aAAS,sBAAQ,MAAc;AACnC,UAAM,SAAS;AACf,UAAM,QAAQ,cAAc;AAC5B,WAAO;AAAA,MACL,qDAAqD,SAAS,CAAC,8BAA8B,SAAS,CAAC,MAAM,MAAM;AAAA,MACnH,qDAAqD,QAAQ,CAAC,8BAA8B,QAAQ,CAAC,MAAM,KAAK;AAAA,MAChH,sDAAsD,aAAa,CAAC,8BAA8B,aAAa,CAAC,MAAM,UAAU;AAAA,IAClI,EAAE,KAAK,IAAI;AAAA,EACb,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,iBAAiB,YAAY,MAAM,WAAW;AAEpD,SACE,8CAAC,SAAI,WAAW,uBAAuB,aAAa,EAAE,IAAI,eAAa,QAErE;AAAA,kDAAC,SAAI,WAAU,2BAA0B,eAAY,kBACnD;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,eAAY;AAAA,UACZ,UAAU;AAAA,UACV,SAAS,MAAM,aAAa,GAAG;AAAA,UAC/B,WAAU;AAAA,UACV,OAAM;AAAA,UACP;AAAA;AAAA,MAED;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,eAAY;AAAA,UACZ,UAAU;AAAA,UACV,SAAS,MAAM,aAAa,EAAE;AAAA,UAC9B,WAAU;AAAA,UACV,OAAM;AAAA,UACP;AAAA;AAAA,MAED;AAAA,MACA,8CAAC,WAAM,WAAU,8DAA6D;AAAA;AAAA,QAE5E;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,OAAO;AAAA,YACP;AAAA,YACA,UAAU;AAAA,YACV,WAAU;AAAA,YAET,sBAAY,IAAI,CAAC,MAChB,6CAAC,YAAe,OAAO,GACpB,oBAAU,CAAC,KADD,CAEb,CACD;AAAA;AAAA,QACH;AAAA,SACF;AAAA,MACA,8CAAC,UAAK,WAAU,yCAAwC,eAAY,qBACjE;AAAA,cAAM;AAAA,QAAO;AAAA,QAAE,MAAM,WAAW,IAAI,SAAS;AAAA,SAChD;AAAA,OACF;AAAA,IAGA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,WAAU;AAAA,QACV,OAAO,EAAE,WAAW,aAAa;AAAA,QACjC,eAAY;AAAA,QAEZ,wDAAC,SAAI,WAAU,QAAO,OAAO,EAAE,OAAO,WAAW,UAAU,GAEzD;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,WAAU;AAAA,cACV,OAAO,EAAE,OAAO,SAAS;AAAA,cAExB,eAAK,IAAI,CAAC,MACT;AAAA,gBAAC;AAAA;AAAA,kBAEC,eAAY;AAAA,kBACZ,cAAY;AAAA,kBACZ,WAAW,4FACT,WAAW,KAAM,IAAI,KAAM,MAAM,EAAE,IAC/B,gCACA,mBACN;AAAA,kBACA,OAAO,EAAE,QAAQ,WAAW;AAAA,kBAE3B,cAAI,OAAO,IAAI,YAAY,CAAC,IAAI;AAAA;AAAA,gBAV5B;AAAA,cAWP,CACD;AAAA;AAAA,UACH;AAAA,UAGA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL,eAAY;AAAA,cACZ,WAAU;AAAA,cACV,OAAO;AAAA,gBACL,OAAO;AAAA,gBACP,QAAQ;AAAA,gBACR,iBAAiB;AAAA,gBACjB,QAAQ,WAAW,gBAAgB;AAAA,gBACnC,aAAa;AAAA,cACf;AAAA,cACA,eAAe;AAAA,cACf,eAAe;AAAA,cACf,aAAa;AAAA,cACb,iBAAiB;AAAA,cAEhB;AAAA,sBAAM,IAAI,CAAC,GAAG,MAAM;AACnB,wBAAM,EAAE,MAAM,IAAI,IAAI,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE;AACvD,wBAAM,QAAQ,KAAK,IAAI,GAAG,EAAE,gBAAgB,WAAW;AAGvD,wBAAM,UAAU,KAAK,IAAI,kBAAkB,QAAQ,CAAC;AACpD,yBACE;AAAA,oBAAC;AAAA;AAAA,sBAEC,eAAY;AAAA,sBACZ,cAAY;AAAA,sBACZ,cAAY,EAAE;AAAA,sBACd,mBAAiB,EAAE;AAAA,sBACnB,uBAAqB,EAAE;AAAA,sBACvB,WAAU;AAAA,sBACV,OAAO,EAAE,MAAM,KAAK,OAAO,QAAQ,WAAW;AAAA,sBAC9C,OAAO,GAAG,YAAY,EAAE,KAAK,CAAC,cAAW,EAAE,SAAS,SAAM,EAAE,aAAa,mBAAW,EAAE,QAAQ;AAAA,sBAE7F,WAAC,YACA;AAAA,wBAAC;AAAA;AAAA,0BACC,sBAAmB;AAAA,0BACnB,eAAY;AAAA,0BACZ,WAAU;AAAA,0BACV,OAAO,EAAE,OAAO,SAAS,QAAQ,YAAY;AAAA;AAAA,sBAC/C;AAAA;AAAA,oBAhBG;AAAA,kBAkBP;AAAA,gBAEJ,CAAC;AAAA,gBACA,MAAM,WAAW,KAChB;AAAA,kBAAC;AAAA;AAAA,oBACC,eAAY;AAAA,oBACZ,WAAU;AAAA,oBACX;AAAA;AAAA,gBAED;AAAA;AAAA;AAAA,UAEJ;AAAA,WACF;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;;;AHlVU,IAAAC,sBAAA;AA9KV,IAAM,aAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AACR;AA0EO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,cAAc,CAAC;AAAA,EACf,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyC;AAEvC,QAAM,CAAC,QAAQ,SAAS,QAAI,wBAAS,EAAE;AAEvC,QAAM,YAAY,CAAC,CAAC;AACpB,QAAM,cAAc,CAAC,CAAC;AACtB,QAAM,iBAAiB,CAAC,CAAC;AACzB,QAAM,gBAAgB,CAAC,CAAC;AACxB,QAAM,cAAc,CAAC,CAAC;AAEtB,QAAM,kBAAc,uBAAQ,MAAmB;AAC7C,UAAM,OAAoB,CAAC;AAC3B,QAAI,UAAW,MAAK,KAAK,IAAI;AAC7B,QAAI,YAAa,MAAK,KAAK,MAAM;AACjC,QAAI,eAAgB,MAAK,KAAK,SAAS;AACvC,QAAI,cAAe,MAAK,KAAK,QAAQ;AACrC,QAAI,YAAa,MAAK,KAAK,MAAM;AACjC,WAAO;AAAA,EACT,GAAG,CAAC,WAAW,aAAa,gBAAgB,eAAe,WAAW,CAAC;AAGvE,QAAM,sBAAsB;AAI5B,QAAM,eAAW,uBAAQ,MAA8B;AACrD,QAAI,MAAM,YAAY,OAAO,CAAC,MAA4B,EAAE,SAAS,UAAU;AAC/E,QAAI,OAAO,KAAK,GAAG;AACjB,YAAM,IAAI,OAAO,YAAY;AAC7B,YAAM,IAAI;AAAA,QACR,CAAC,MACC,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,KAAK,EAAE,aAAa,YAAY,EAAE,SAAS,CAAC;AAAA,MAC/E;AAAA,IACF;AACA,QAAI,iBAAiB;AACnB,YAAM,cAAc,IAAI,UAAU,CAAC,MAA4B,EAAE,aAAa,eAAe;AAC7F,UAAI,cAAc,GAAG;AACnB,cAAM,CAAC,QAAQ,IAAI,IAAI,OAAO,aAAa,CAAC;AAC5C,YAAI,QAAQ,QAAQ;AAAA,MACtB;AAAA,IACF;AACA,WAAO;AAAA,EACT,GAAG,CAAC,aAAa,QAAQ,eAAe,CAAC;AAGzC,QAAM,UAAU,gBAAgB,CAAC;AACjC,QAAM,eAA0B,YAAY,SAAS,SAAS,IAC1D,YACA,YAAY,CAAC,KAAK;AAEtB,QAAM,WAAW,CAAC,WAChB,oDACE,SAAS,iDAAiD,sCAC5D;AAIF,QAAM,QACJ,YAAY,SAAS,IACnB;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,eAAY;AAAA,MAEX,sBAAY,IAAI,CAAC,QAChB;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,eAAa,kBAAkB,GAAG;AAAA,UAClC,SAAS,MAAM,cAAc,GAAG;AAAA,UAChC,WAAW,SAAS,iBAAiB,GAAG;AAAA,UAEvC,kBAAQ,aAAa,QAAQ,SAAS,IACnC,YAAY,QAAQ,MAAM,MAC1B,WAAW,GAAG;AAAA;AAAA,QARb;AAAA,MASP,CACD;AAAA;AAAA,EACH,IACE;AAGN,QAAM,eACJ,sBAAsB,KAAK,qBAAqB,QAAQ,SACpD,QAAQ,kBAAkB,EAAE,QAC5B;AAEN,QAAM,SACJ,SAAS,eACP,8CAAC,SAAI,WAAU,uBAAsB,eAAY,qBAC9C;AAAA;AAAA,IACA,gBACC;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO;AAAA,QAEN;AAAA;AAAA,IACH;AAAA,KAEJ,IACE;AAGN,MAAI,iBAAiB,QAAQ;AAC3B,WACE,8CAAC,SAAI,WAAU,uBAAsB,eAAY,mBAC9C;AAAA;AAAA,MACD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,aAAa,CAAC;AAAA,UACrB,UAAU,kBAAkB,MAAY;AAAA,UAAC;AAAA,UACzC,MAAM,YAAY;AAAA,UAClB,KAAK,WAAW;AAAA,UAChB,MAAM;AAAA,UACN;AAAA;AAAA,MACF;AAAA,OACF;AAAA,EAEJ;AAGA,MAAI,iBAAiB,MAAM;AACzB,WACE,8CAAC,SAAI,WAAU,uBAAsB,eAAY,iBAC9C;AAAA;AAAA,MACD;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA,UAAU,CAAC,IAAY,UAAsB,YAC3C,aAAa,UAAU,OAAO;AAAA,UAEhC,gBAAgB,CAAC,IAAY,UAAsB,gBACjD,mBAAmB,UAAU,WAAW;AAAA,UAE1C,gBAAgB,CAAC,IAAY,UAAsB,UACjD,mBAAmB,UAAU,KAAK;AAAA,UAEpC,UAAU;AAAA;AAAA,MACZ;AAAA,OACF;AAAA,EAEJ;AAGA,MAAI,iBAAiB,UAAU;AAC7B,UAAM,YAAY,UAAU,KAAK,oBAAoB,EAAE,IACnD,WACA,UAAU,KAAK,oBAAoB,EAAE,IACnC,WACA;AACN,WACE,8CAAC,SAAI,WAAU,uBAAsB,eAAY,qBAC9C;AAAA;AAAA,MACD,8CAAC,OAAE,WAAU,8CAA6C;AAAA;AAAA,QAC0B;AAAA,QACjF;AAAA,QAAU;AAAA,SACb;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,eAAY;AAAA,UACZ,SAAS;AAAA,UACT,WAAU;AAAA,UACV,OAAM;AAAA,UACP;AAAA;AAAA,YACI,oBAAoB;AAAA;AAAA;AAAA,MACzB;AAAA,OACF;AAAA,EAEJ;AAGA,MAAI,iBAAiB,WAAW;AAC9B,UAAM,QAAQ,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,QAAQ;AAC/C,WACE,8CAAC,SAAI,WAAU,uBACZ;AAAA;AAAA,MACA,QAAQ,WAAW,IAClB;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,eAAY;AAAA,UACb;AAAA;AAAA,MAED,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,eAAY;AAAA,UAEX,gBAAM,IAAI,CAAC,MAAM;AAChB,kBAAM,QAAQ,QAAQ,CAAC;AACvB,kBAAM,YAAY,MAAM;AACxB,mBACE,8CAAC,QAAW,WAAU,2BACpB;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,eAAY;AAAA,kBACZ,UAAU;AAAA,kBACV,SAAS,MAAM,iBAAiB,CAAC;AAAA,kBACjC,WAAW,sHACT,YACI,sEACA,iGACN;AAAA,kBACA,OAAO,YAAY,kBAAkB,YAAY,MAAM,KAAK;AAAA,kBAE5D;AAAA,iEAAC,UAAK,WAAU,YAAY,gBAAM,OAAM;AAAA,oBACxC,6CAAC,UAAK,WAAU,oDACb,sBAAY,mBAAc,WAC7B;AAAA;AAAA;AAAA,cACF;AAAA,cACC,oBACC;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,eAAY;AAAA,kBACZ,SAAS,MAAM,iBAAiB,CAAC;AAAA,kBACjC,WAAW,oEACT,MAAM,WACF,oBACA,yCACN;AAAA,kBACA,OAAO,MAAM,WAAW,eAAe;AAAA,kBAEtC,gBAAM,WAAW,WAAM;AAAA;AAAA,cAC1B;AAAA,iBA/BK,CAiCT;AAAA,UAEJ,CAAC;AAAA;AAAA,MACH;AAAA,OAEJ;AAAA,EAEJ;AAGA,MAAI,iBAAiB,UAAU,aAAa;AAC1C,WACE,8CAAC,SAAI,WAAU,uBACZ;AAAA;AAAA,MACD,8CAAC,SAAI,WAAU,2BACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,MAAM,sBAAsB;AAAA,YACrC,WAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,QACA,6CAAC,UAAK,WAAU,sDACb,oCAA0B,UAC7B;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,eAAe;AAAA,UAC9B,WAAU;AAAA,UACX;AAAA;AAAA,MAED;AAAA,OACF;AAAA,EAEJ;AAGA,QAAM,oBAAoB,oBAAoB;AAC9C,QAAM,aAAa,CAAC,aAA8B,aAAa;AAE/D,SACE,8CAAC,SAAI,WAAU,uBACZ;AAAA;AAAA,IAED,8CAAC,SAAI,WAAU,2BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAA2C,UAAU,EAAE,OAAO,KAAK;AAAA,UAC9E,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,YAAY;AAAA,UAC3B,UAAU;AAAA,UACV,WAAU;AAAA,UACV,OAAM;AAAA,UAEL,sBAAY,QAAQ;AAAA;AAAA,MACvB;AAAA,OACF;AAAA,IAGC,aAAa,YAAY,WAAW,IACnC,6CAAC,SAAI,WAAU,8CAA6C,iCAAmB,IAE/E,8CAAC,SAAI,WAAU,wDAEb;AAAA;AAAA,QAAC;AAAA;AAAA,UAEC,SAAS,MAAM,WAAW,mBAAmB;AAAA,UAC7C,WAAW,uFACT,oBACI,uDACA,iGACN;AAAA,UACA,OAAM;AAAA,UAEN;AAAA,0DAAC,UAAK,WAAU,uCACb;AAAA,mCAAqB;AAAA,cAAK;AAAA,eAC7B;AAAA,YACA,6CAAC,UAAK,WAAU,gDAA+C,qBAAO;AAAA;AAAA;AAAA,QAZlE;AAAA,MAaN;AAAA,MAEC,SAAS,IAAI,CAAC,SAA+B;AAC5C,cAAM,WAAW,WAAW,KAAK,QAAQ;AACzC,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,SAAS,MAAM,WAAW,KAAK,QAAQ;AAAA,YACvC,WAAW,uFACT,WACI,uDACA,KAAK,UACH,8EACA,iGACR;AAAA,YACA,OAAO,GAAG,KAAK,IAAI,OAAO,KAAK,YAAY,KAAK,KAAK,KAAK,YAAY,CAAC,IAAI,KAAK,UAAU,oBAAe,EAAE;AAAA,YAE3G;AAAA,4DAAC,UAAK,WAAU,uCACb;AAAA,4BAAY;AAAA,gBACZ,KAAK;AAAA,iBACR;AAAA,cACA,6CAAC,UAAK,WAAU,gDACb,eAAK,gBAAgB,KAAK,KAAK,YAAY,GAC9C;AAAA;AAAA;AAAA,UAjBK,KAAK;AAAA,QAkBZ;AAAA,MAEJ,CAAC;AAAA,MACA,SAAS,WAAW,KACnB,6CAAC,SAAI,WAAU,yDACZ,iBAAO,KAAK,IAAI,eAAe,0BAClC;AAAA,OAEJ;AAAA,KAEJ;AAEJ;;;AIndA,IAAAC,gBAA8B;;;ACI9B,IAAAC,gBAAiC;AACjC,uBAA6B;AA6CzB,IAAAC,sBAAA;AA1BG,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB;AACF,GAA0C;AAExC,+BAAU,MAAM;AACd,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,QAAQ,CAAC,MAA2B;AACxC,UAAI,iBAAiB,EAAE,QAAQ,UAAU;AACvC,UAAE,eAAe;AACjB,gBAAQ;AAAA,MACV;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,KAAK;AACxC,qBAAiB,SAAS,MAAM;AAChC,WAAO,MAAM,OAAO,oBAAoB,WAAW,KAAK;AAAA,EAC1D,GAAG,CAAC,MAAM,SAAS,eAAe,eAAe,CAAC;AAElD,MAAI,CAAC,KAAM,QAAO;AAElB,aAAO;AAAA,IACL;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,eAAa,GAAG,YAAY;AAAA,QAC5B,SAAS,kBAAkB,UAAU;AAAA,QAEpC;AAAA;AAAA,IACH;AAAA,IACA,SAAS;AAAA,EACX;AACF;;;ADTU,IAAAC,sBAAA;AA1BH,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,eAAe;AACjB,GAAkD;AAChD,QAAM,gBAAY,sBAA0B,IAAI;AAGhD,SACE,6CAAC,SAAM,MAAY,SAAS,UAAU,cAA4B,iBAAiB,WACjF;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,MAClC,MAAK;AAAA,MACL,cAAW;AAAA,MACX,cAAY;AAAA,MACZ,eAAa,GAAG,YAAY;AAAA,MAG5B;AAAA,qDAAC,SAAI,WAAU,wCACb,uDAAC,UAAK,WAAU,qCAAoC,eAAa,GAAG,YAAY,UAC7E,iBACH,GACF;AAAA,QAGA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAa,GAAG,YAAY;AAAA,YAE3B;AAAA;AAAA,QACH;AAAA,QAGA,8CAAC,SAAI,WAAU,+DACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS;AAAA,cACT,eAAa,GAAG,YAAY;AAAA,cAE3B;AAAA;AAAA,UACH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAW,qEACT,cACI,6FACA,0FACN;AAAA,cACA,SAAS;AAAA,cACT,eAAa,GAAG,YAAY;AAAA,cAE3B;AAAA;AAAA,UACH;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;;;AEVM,IAAAC,sBAAA;AAvEN,IAAM,cAAc;AACpB,IAAM,eAAe;AACrB,IAAM,YAAY;AAClB,IAAM,iBAAiB;AACvB,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB;AAC1B,IAAM,aAAa;AAInB,IAAM,iBAAiB,0BAA0B,WAAW,QAAQ,WAAW,SAAS,YAAY,SAAS,SAAS,SAAS,SAAS;AAGxI,IAAM,WAAW;AACjB,IAAM,iBAAiB;AAGvB,SAAS,QAAQ,IAAoB;AACnC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,MAAO,KAAK,MAAM,KAAM,GAAG,CAAC;AAC1D;AA2BO,IAAM,aAAwC,CAAC;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,eAAe;AACjB,MAAM;AACJ,QAAM,KAAK,UAAU;AACrB,QAAM,WAAW,SAAS,QAAQ,MAAM,IAAI;AAC5C,QAAM,WAAW,cAAc,QAAQ,UAAU,aAAa;AAC9D,QAAM,cAAc,WAAW,QAAQ,UAAW,IAAI;AAEtD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,mBAAmB,aAAa,EAAE;AAAA,MAC7C,eAAa;AAAA,MACb,OAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,KAAK,UAAU,IAAI;AAAA,MACrB;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,UAAU;AAAA,cACV,MAAM;AAAA,cACN,QAAQ,UAAU,IAAI;AAAA,cACtB,YAAY;AAAA,cACZ,QAAQ,aAAa,kBAAkB;AAAA,cACvC,cAAc;AAAA,cACd,UAAU;AAAA,cACV,UAAU,UAAU,IAAI;AAAA,YAC1B;AAAA,YAGA;AAAA,2DAAC,SAAI,OAAO,EAAE,UAAU,YAAY,OAAO,GAAG,YAAY,eAAe,GAAG;AAAA,cAG5E;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAO;AAAA,oBACL,UAAU;AAAA,oBACV,KAAK;AAAA,oBACL,QAAQ;AAAA,oBACR,MAAM,GAAG,QAAQ;AAAA,oBACjB,OAAO;AAAA,oBACP,YAAY;AAAA,oBACZ,YAAY;AAAA,kBACd;AAAA;AAAA,cACF;AAAA,cAGA;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAa,GAAG,EAAE;AAAA,kBAClB,OAAO;AAAA,oBACL,UAAU;AAAA,oBACV,OAAO;AAAA,oBACP,eAAe;AAAA,oBACf,iBAAiB,iEAAiE,cAAc,QAAQ,iBAAiB,gBAAgB,cAAc,QAAQ,iBAAiB;AAAA,oBAChL,gBAAgB,eAAe,QAAQ;AAAA,kBACzC;AAAA;AAAA,cACF;AAAA,cAGC,YACC;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAa,GAAG,EAAE;AAAA,kBAClB,OAAO;AAAA,oBACL,UAAU;AAAA,oBACV,KAAK;AAAA,oBACL,QAAQ;AAAA,oBACR,MAAM,GAAG,WAAW;AAAA,oBACpB,OAAO;AAAA,oBACP,YAAY;AAAA,oBACZ,YAAY;AAAA,oBACZ,WAAW;AAAA,oBACX,YAAY;AAAA,kBACd;AAAA,kBACA,OAAM;AAAA;AAAA,cACR;AAAA;AAAA;AAAA,QAEJ;AAAA,QAEC,CAAC,WACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,UAAU;AAAA,cACV,OAAO;AAAA,cACP,oBAAoB;AAAA,cACpB,UAAU;AAAA,cACV,WAAW;AAAA,YACb;AAAA,YAEC,oBAAU,SAAS,OAAO,GAAG,OAAO,QAAQ,CAAC,CAAC,QAAQ;AAAA;AAAA,QACzD;AAAA,QAED,WACC;AAAA,UAAC;AAAA;AAAA,YACC,eAAa,GAAG,EAAE;AAAA,YAClB,SAAS;AAAA,YACT,OAAO;AAAA,cACL,SAAS;AAAA,cACT,UAAU;AAAA,cACV,YAAY;AAAA,cACZ,YAAY;AAAA,cACZ,OAAO;AAAA,cACP,cAAc;AAAA,cACd,QAAQ,cAAc,YAAY;AAAA,cAClC,YAAY,UAAU,IAAI;AAAA,YAC5B;AAAA,YACA,OAAO,cAAc,kCAA6B;AAAA,YACnD;AAAA;AAAA,QAED;AAAA;AAAA;AAAA,EAEJ;AAEJ;;;ACnKA,IAAAC,gBAA4C;AAI5C,IAAM,iBAAiB,oBAAI,IAAoB;AAG/C,IAAM,mBAAmB;AAEzB,IAAM,oBAAoB;AAG1B,IAAM,iBAAiB;AAEvB,IAAM,eAAe;AAErB,IAAM,wBAAwB;AAc9B,SAAS,WAAoB;AAC3B,SAAO,OAAO,aAAa,eAAe,SAAS,WAAW;AAChE;AAQO,SAAS,eACd,MACA,UAAmB,MACA;AACnB,QAAM,aAAS,sBAAsC,oBAAI,IAAI,CAAC;AAC9D,QAAM,mBAAe,sBAAwB,oBAAI,IAAI,CAAC;AAGtD,QAAM,gBAAY,sBAAiC,IAAI;AACvD,MAAI,UAAU,YAAY,MAAM;AAC9B,cAAU,UAAU;AAAA,MAClB,UAAU,CAAC,YAAoB,OAAO,QAAQ,IAAI,OAAO,KAAK;AAAA,MAC9D,WAAW,CAAC,aAAyB;AACnC,qBAAa,QAAQ,IAAI,QAAQ;AACjC,eAAO,MAAM;AACX,uBAAa,QAAQ,OAAO,QAAQ;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,+BAAU,MAAM;AACd,UAAM,SAAS,MAAY;AACzB,mBAAa,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;AAAA,IACzC;AAEA,UAAM,cAAc,MAAY;AAC9B,UAAI,OAAO,QAAQ,OAAO,GAAG;AAC3B,eAAO,QAAQ,MAAM;AACrB,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,UACJ,WAAW,CAAC,CAAC,QAAQ,OAAO,KAAK,mBAAmB;AAEtD,QAAI,CAAC,SAAS;AACZ,kBAAY;AACZ;AAAA,IACF;AAEA,QAAI,UAAU;AACd,QAAI,QAA8C;AAElD,UAAM,WAAW,CAAC,UAAwB;AACxC,UAAI,QAAS;AACb,cAAQ,WAAW,MAAM,KAAK;AAAA,IAChC;AAEA,UAAM,OAAO,YAA2B;AACtC,UAAI,QAAS;AAIb,UAAI,SAAS,GAAG;AACd,iBAAS,iBAAiB;AAC1B;AAAA,MACF;AAEA,UAAI;AACF,cAAM,SAAS,MAAM,KAAM,eAAgB;AAC3C,YAAI,QAAS;AAGb,cAAM,OAAO,oBAAI,IAAY;AAC7B,mBAAW,OAAO,QAAQ;AACxB,iBAAO,QAAQ,IAAI,IAAI,SAAS,GAAG;AACnC,eAAK,IAAI,IAAI,OAAO;AAAA,QACtB;AACA,mBAAW,OAAO,MAAM,KAAK,OAAO,QAAQ,KAAK,CAAC,GAAG;AACnD,cAAI,CAAC,KAAK,IAAI,GAAG,EAAG,QAAO,QAAQ,OAAO,GAAG;AAAA,QAC/C;AACA,eAAO;AAAA,MACT,QAAQ;AAAA,MAER;AAGA,eAAS,gBAAgB;AAAA,IAC3B;AAEA,UAAM,eAAe,MAAY;AAC/B,UAAI,QAAS;AACb,UAAI,CAAC,SAAS,GAAG;AAEf,YAAI,MAAO,cAAa,KAAK;AAC7B,aAAK,KAAK;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,OAAO,aAAa,aAAa;AACnC,eAAS,iBAAiB,oBAAoB,YAAY;AAAA,IAC5D;AAEA,SAAK,KAAK;AAEV,WAAO,MAAM;AACX,gBAAU;AACV,UAAI,MAAO,cAAa,KAAK;AAC7B,UAAI,OAAO,aAAa,aAAa;AACnC,iBAAS,oBAAoB,oBAAoB,YAAY;AAAA,MAC/D;AAAA,IAEF;AAAA,EACF,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,SAAO,UAAU;AACnB;AAGA,SAAS,UACP,GACA,GACS;AACT,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO;AACrC,SAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE;AAClD;AAOO,SAAS,cACd,QACA,SACyB;AACzB,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAkC,IAAI;AAEhE,+BAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,eAAS,IAAI;AACb;AAAA,IACF;AACA,UAAM,SAAS,MAAY;AACzB,YAAM,OAAO,OAAO,SAAS,OAAO;AACpC,eAAS,CAAC,SAAU,UAAU,MAAM,IAAI,IAAI,OAAO,IAAK;AAAA,IAC1D;AACA,WAAO;AACP,WAAO,OAAO,UAAU,MAAM;AAAA,EAChC,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB,SAAO;AACT;AAgBA,IAAM,kBAAkC;AAAA,EACtC,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,QAAQ;AACV;AAKA,SAAS,UAAU,GAAmB,GAA4B;AAChE,SACE,EAAE,WAAW,EAAE,UACf,EAAE,YAAY,EAAE,WAChB,EAAE,WAAW,EAAE,UACf,KAAK,MAAM,EAAE,aAAa,CAAC,MAAM,KAAK,MAAM,EAAE,aAAa,CAAC;AAEhE;AAUO,SAAS,cACd,QACA,SACgB;AAChB,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAyB,eAAe;AAIhE,QAAM,gBAAY,sBAAO,cAAc;AACvC,QAAM,gBAAY,sBAAO,CAAC;AAC1B,QAAM,kBAAc,sBAAO,CAAC;AAE5B,+BAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,gBAAU,UAAU;AACpB,kBAAY,UAAU;AACtB,cAAQ,eAAe;AACvB;AAAA,IACF;AAEA,UAAM,SAAS,MAAY;AACzB,YAAM,QAAQ,OAAO,SAAS,OAAO;AAIrC,YAAM,OAAO,KAAK,IAAI;AACtB,WAAK,eAAe,IAAI,OAAO,KAAK,KAAK,OAAO,KAAM;AACpD,uBAAe,IAAI,SAAS,IAAI;AAEhC,gBAAQ,IAAI,+BAA+B,OAAO,WAAM,UAAU,OAAO,gCAAgC,KAAK,EAAE;AAAA,MAClH;AACA,YAAM,MAAM,YAAY,IAAI;AAC5B,YAAM,QAAQ,YAAY,UAAU,KAAK,IAAI,IAAI,MAAM,YAAY,WAAW,GAAI,IAAI;AACtF,kBAAY,UAAU;AAEtB,UAAI,UAAU,MAAM;AAElB,kBAAU,UAAU;AACpB,gBAAQ,CAAC,SAAU,UAAU,MAAM,eAAe,IAAI,OAAO,eAAgB;AAC7E;AAAA,MACF;AAEA,YAAM,IAAI,MAAM;AAChB,UAAI,KAAK,UAAU,SAAS;AAE1B,kBAAU,UAAU;AACpB,kBAAU,UAAU;AAAA,MACtB,WAAW,MAAM,UAAU,UAAU,cAAc;AAEjD,kBAAU,UAAU,KAAK,IAAI,GAAG,UAAU,UAAU,wBAAwB,KAAK;AAAA,MACnF;AAGA,YAAM,OAAuB;AAAA,QAC3B,QAAQ;AAAA,QACR,YAAY,UAAU;AAAA,QACtB,SAAS,MAAM;AAAA,QACf,QAAQ;AAAA,MACV;AACA,cAAQ,CAAC,SAAU,UAAU,MAAM,IAAI,IAAI,OAAO,IAAK;AAAA,IACzD;AAEA,WAAO;AACP,WAAO,OAAO,UAAU,MAAM;AAAA,EAChC,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB,SAAO;AACT;AAOO,SAAS,oBAAoB,MAA8C;AAChF,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAS,KAAK;AAE5C,+BAAU,MAAM;AACd,QAAI,CAAC,MAAM;AACT,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,QAAI,YAAY;AAEhB,SACG,kBAAkB,EAClB,KAAK,CAAC,UAAU;AACf,UAAI,CAAC,UAAW,YAAW,CAAC,CAAC,MAAM,SAAS;AAAA,IAC9C,CAAC,EACA,MAAM,MAAM;AAAA,IAEb,CAAC;AAEH,UAAM,QAAQ,KAAK,mBAAmB,CAAC,QAAQ;AAC7C,UAAI,OAAO,IAAI,cAAc,WAAW;AACtC,mBAAW,IAAI,SAAS;AAAA,MAC1B,WAAW,IAAI,SAAS,QAAQ;AAC9B,mBAAW,IAAI;AAAA,MACjB,WAAW,IAAI,SAAS,UAAU,IAAI,SAAS,SAAS;AACtD,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AACZ,cAAQ;AAAA,IACV;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AACT;;;ACpUM,IAAAC,sBAAA;AAbC,IAAM,kBAAkD,CAAC;AAAA,EAC9D;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AACF,MAAM;AACJ,QAAM,QAAQ,cAAc,QAAQ,OAAO;AAE3C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAY;AAAA,MACZ,WAAW,yEAAyE,cAAc,iBAAiB,EAAE,IAAI,aAAa,EAAE;AAAA,MAExI;AAAA,QAAC;AAAA;AAAA,UACC,SAAO;AAAA,UACP,QAAQ,MAAM;AAAA,UACd,QAAQ,MAAM;AAAA,UACd,YAAY,MAAM;AAAA,UAClB,SAAS,MAAM;AAAA,UACf,eAAa,uBAAuB,OAAO;AAAA;AAAA,MAC7C;AAAA;AAAA,EACF;AAEJ;;;AC1CA,IAAAC,gBAAgE;;;ACQzD,IAAM,eAAe;AAGrB,IAAM,SAAS;AAGf,IAAM,SAAS;AAQtB,IAAM,WACJ,KAAK,IAAI,KAAK,IAAI,IAAI,SAAS,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,YAAY;AAQ1D,SAAS,WAAW,QAAwB;AACjD,MAAI,UAAU,EAAG,QAAO;AACxB,QAAM,OAAO,KAAK,IAAI,SAAS,cAAc,QAAQ;AACrD,QAAM,KAAK,KAAK,KAAK,MAAM,IAAI;AAC/B,SAAO,KAAK,IAAI,QAAQ,KAAK,IAAI,QAAQ,EAAE,CAAC;AAC9C;AASO,SAAS,WAAW,IAAoB;AAC7C,MAAI,MAAM,OAAQ,QAAO;AACzB,MAAI,MAAM,OAAQ,QAAO;AACzB,QAAM,OAAO,KAAK,IAAI,IAAI,KAAK,EAAE;AACjC,QAAM,SAAS,eAAe,KAAK,IAAI,MAAM,IAAI,QAAQ;AACzD,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC;AACxC;;;ADwDM,IAAAC,sBAAA;AA1FN,SAAS,SAAS,OAAuB;AACvC,QAAM,KAAK,WAAW,KAAK;AAC3B,MAAI,MAAM,IAAK,QAAO;AACtB,QAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,SAAO,GAAG,IAAI,GAAG,GAAG,QAAQ,CAAC,CAAC;AAChC;AAKA,SAAS,qBACP,UACA,OACG;AACH,QAAM,iBAAa,sBAA6C,IAAI;AACpE,QAAM,kBAAc,sBAAO,QAAQ;AAGnC,+BAAU,MAAM;AACd,gBAAY,UAAU;AAAA,EACxB,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,wBAAoB;AAAA,IACxB,IAAI,SAAwB;AAC1B,UAAI,WAAW,SAAS;AACtB,qBAAa,WAAW,OAAO;AAAA,MACjC;AACA,iBAAW,UAAU,WAAW,MAAM;AACpC,oBAAY,QAAQ,GAAG,IAAI;AAAA,MAC7B,GAAG,KAAK;AAAA,IACV;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AAGA,+BAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,SAAS;AACtB,qBAAa,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;AAEO,IAAM,eAA4C,CAAC;AAAA,EACxD;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AACd,MAAM;AAEJ,QAAM,CAAC,YAAY,aAAa,QAAI,wBAAS,KAAK;AAClD,QAAM,CAAC,YAAY,aAAa,QAAI,wBAAS,KAAK;AAGlD,+BAAU,MAAM;AACd,QAAI,CAAC,YAAY;AACf,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,OAAO,UAAU,CAAC;AAGtB,QAAM,oBAAoB,qBAAqB,UAAU,EAAE;AAE3D,QAAM,mBAAe;AAAA,IACnB,CAAC,MAA2C;AAC1C,YAAM,WAAW,WAAW,EAAE,OAAO,KAAK;AAC1C,oBAAc,QAAQ;AACtB,wBAAkB,QAAQ;AAAA,IAC5B;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,sBAAkB,2BAAY,MAAM;AACxC,kBAAc,IAAI;AAAA,EACpB,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAgB,2BAAY,MAAM;AACtC,kBAAc,KAAK;AAEnB,aAAS,UAAU;AAAA,EACrB,GAAG,CAAC,YAAY,QAAQ,CAAC;AAEzB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,qBAAqB,SAAS;AAAA,MACzC,OAAO,WAAW,SAAS,UAAU,CAAC;AAAA,MAEtC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,KAAI;AAAA,UACJ,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,UACV,aAAa;AAAA,UACb,WAAW;AAAA,UACX,cAAc;AAAA,UACd,YAAY;AAAA,UACZ;AAAA,UACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAmBb;AAAA;AAAA,EACF;AAEJ;;;AE5IA,IAAAC,gBAAgE;AA+G1D,IAAAC,sBAAA;AA/FN,SAAS,aAAa,OAAuB;AAC3C,MAAI,KAAK,IAAI,KAAK,IAAI,MAAM;AAC1B,WAAO;AAAA,EACT;AACA,QAAM,UAAU,KAAK,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC;AAChD,SAAO,QAAQ,IAAI,IAAI,OAAO,KAAK,IAAI,OAAO;AAChD;AAKA,SAASC,sBACP,UACA,OACG;AACH,QAAM,iBAAa,sBAA6C,IAAI;AACpE,QAAM,kBAAc,sBAAO,QAAQ;AAEnC,+BAAU,MAAM;AACd,gBAAY,UAAU;AAAA,EACxB,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,wBAAoB;AAAA,IACxB,IAAI,SAAwB;AAC1B,UAAI,WAAW,SAAS;AACtB,qBAAa,WAAW,OAAO;AAAA,MACjC;AACA,iBAAW,UAAU,WAAW,MAAM;AACpC,oBAAY,QAAQ,GAAG,IAAI;AAAA,MAC7B,GAAG,KAAK;AAAA,IACV;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AAEA,+BAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,SAAS;AACtB,qBAAa,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;AAEO,IAAM,YAAsC,CAAC;AAAA,EAClD;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AACd,MAAM;AAEJ,QAAM,CAAC,YAAY,aAAa,QAAI,wBAAS,KAAK;AAClD,QAAM,CAAC,YAAY,aAAa,QAAI,wBAAS,KAAK;AAGlD,+BAAU,MAAM;AACd,QAAI,CAAC,YAAY;AACf,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,OAAO,UAAU,CAAC;AAGtB,QAAM,oBAAoBA,sBAAqB,UAAU,EAAE;AAE3D,QAAM,mBAAe;AAAA,IACnB,CAAC,MAA2C;AAC1C,YAAM,WAAW,WAAW,EAAE,OAAO,KAAK;AAC1C,oBAAc,QAAQ;AACtB,wBAAkB,QAAQ;AAAA,IAC5B;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,sBAAkB,2BAAY,MAAM;AACxC,kBAAc,IAAI;AAAA,EACpB,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAgB,2BAAY,MAAM;AACtC,kBAAc,KAAK;AAEnB,aAAS,UAAU;AAAA,EACrB,GAAG,CAAC,YAAY,QAAQ,CAAC;AAGzB,QAAM,wBAAoB,2BAAY,MAAM;AAC1C,kBAAc,CAAC;AACf,aAAS,CAAC;AAAA,EACZ,GAAG,CAAC,QAAQ,CAAC;AAEb,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,qBAAqB,SAAS;AAAA,MACzC,OAAO,QAAQ,aAAa,UAAU,CAAC;AAAA,MAEvC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,KAAI;AAAA,UACJ,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,UACV,aAAa;AAAA,UACb,WAAW;AAAA,UACX,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,eAAe;AAAA,UACf;AAAA,UACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAmBb;AAAA;AAAA,EACF;AAEJ;;;ACxIA,IAAAC,gBAAmD;AAuN7C,IAAAC,uBAAA;AArLC,SAAS,yBAAyB,WAAmB,qBAAqC;AAC/F,QAAM,IAAI,YAAY;AACtB,MAAI,KAAK,EAAG,QAAO;AAEnB,MAAI,KAAK,GAAK;AAEZ,WAAO,MAAM,IAAI,KAAK,IAAI,IAAI,GAAG,GAAG;AAAA,EACtC;AAGA,QAAM,kBAAkB,YAAY,uBAAuB;AAC3D,SAAO,KAAK,KAAK,IAAI,KAAK,IAAI,CAAC,iBAAiB,CAAC;AACnD;AASA,SAAS,sBAAsB,iBAAiC;AAC9D,MAAI,kBAAkB,IAAI;AACxB,WAAO,kBAAkB,KAAK,OAAO,IAAI,KAAK;AAAA,EAChD;AACA,MAAI,kBAAkB,IAAI;AACxB,WAAO,kBAAkB,KAAK,OAAO,IAAI,IAAI;AAAA,EAC/C;AACA,MAAI,kBAAkB,IAAI;AACxB,UAAM,YAAY,KAAK;AACvB,UAAM,YAAY,aAAa,KAAK,OAAO,IAAI,MAAM;AACrD,WAAO,kBAAkB,KAAK,IAAI,WAAW,GAAG;AAAA,EAClD;AACA,SAAO;AACT;AAKA,SAAS,0BAA0B,UAA0B;AAC3D,MAAI,WAAW,IAAI;AACjB,WAAO,KAAK,OAAO,IAAI,MAAM;AAAA,EAC/B;AACA,MAAI,WAAW,IAAI;AACjB,WAAO,KAAK,OAAO,IAAI,MAAM;AAAA,EAC/B;AACA,SAAO,KAAK,OAAO,IAAI,MAAM;AAC/B;AAGA,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB;AAKvB,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,aAAa;AAAA,EACb,eAAe;AAAA,EACf;AAAA,EACA,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB;AAAA,EACA;AACF,GAAuD;AACrD,QAAM,CAAC,UAAU,WAAW,QAAI,wBAAiB,eAAe;AAChE,QAAM,eAAW,sBAA6C,IAAI;AAElE,QAAM,mBAAe,sBAAgB,KAAK;AAC1C,QAAM,oBAAgB,sBAAgB,KAAK;AAC3C,QAAM,mBAAe,sBAAe,CAAC;AAGrC,QAAM,0BAAsB,sBAAO,gBAAgB;AACnD,QAAM,oBAAgB,sBAAO,UAAU;AACvC,sBAAoB,UAAU;AAC9B,gBAAc,UAAU;AAGxB,QAAM,yBAAqB,sBAAO,eAAe;AACjD,qBAAmB,UAAU;AAC7B,QAAM,6BAAyB,sBAAO,mBAAmB;AACzD,yBAAuB,UAAU;AAGjC,+BAAU,MAAM;AACd,UAAM,aAAa,aAAa;AAChC,iBAAa,UAAU;AAEvB,QAAI,aAAa,CAAC,YAAY;AAE5B,oBAAc,UAAU;AACxB,mBAAa,UAAU,KAAK,IAAI;AAGhC,YAAM,gBAAgB,mBAAmB,UAAU,IAAI,mBAAmB,UAAU;AACpF,kBAAY,aAAa;AAEzB,YAAM,WAAW,uBAAuB;AAExC,UAAI,YAAY,WAAW,GAAG;AAE5B,cAAM,OAAO,MAAY;AACvB,sBAAY,CAAC,SAAS;AACpB,kBAAM,UAAU,KAAK,IAAI,IAAI,aAAa;AAC1C,kBAAM,SAAS,yBAAyB,SAAS,QAAQ;AAGzD,kBAAM,UAAU,KAAK,OAAO,IAAI,OAAO;AAEvC,kBAAM,OAAO,KAAK,IAAI,KAAK,IAAI,SAAS,QAAQ,OAAO,IAAI,GAAG,EAAE;AAEhE,gCAAoB,UAAU,IAAI;AAClC,qBAAS,UAAU,WAAW,MAAM,sBAAsB,KAAK,OAAO,IAAI,qBAAqB;AAC/F,mBAAO;AAAA,UACT,CAAC;AAAA,QACH;AAEA,iBAAS,UAAU,WAAW,MAAM,mBAAmB;AAAA,MACzD,OAAO;AAEL,cAAM,OAAO,MAAY;AACvB,sBAAY,CAAC,SAAS;AACpB,gBAAI,QAAQ,IAAI;AACd,uBAAS,UAAU,WAAW,MAAM,GAAI;AACxC,qBAAO;AAAA,YACT;AAEA,kBAAM,OAAO,KAAK,IAAI,sBAAsB,IAAI,GAAG,EAAE;AACrD,gCAAoB,UAAU,IAAI;AAElC,kBAAM,WAAW,0BAA0B,IAAI;AAC/C,qBAAS,UAAU,WAAW,MAAM,QAAQ;AAE5C,mBAAO;AAAA,UACT,CAAC;AAAA,QACH;AAEA,cAAM,gBAAgB,0BAA0B,aAAa;AAC7D,iBAAS,UAAU,WAAW,MAAM,aAAa;AAAA,MACnD;AAAA,IACF,WAAW,CAAC,aAAa,cAAc,cAAc,SAAS;AAE5D,UAAI,SAAS,SAAS;AACpB,qBAAa,SAAS,OAAO;AAC7B,iBAAS,UAAU;AAAA,MACrB;AACA,kBAAY,GAAG;AACf,0BAAoB,UAAU,GAAG;AACjC,oBAAc,UAAU;AACxB,oBAAc,UAAU;AAAA,IAC1B;AAGA,WAAO,MAAM;AACX,UAAI,SAAS,SAAS;AACpB,qBAAa,SAAS,OAAO;AAC7B,iBAAS,UAAU;AAAA,MACrB;AAAA,IACF;AAAA,EAGF,GAAG,CAAC,SAAS,CAAC;AAGd,MAAI,CAAC,aAAa,aAAa,GAAG;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,KAAK,MAAM,QAAQ;AAC3C,QAAM,aAAa,CAAC,aAAa,aAAa;AAG9C,QAAM,qBAAqB,WAAW,KAAK,UAAU,WAAW,KAAK,UAAU;AAE/E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,mBAAmB,WAAW;AAAA,MAGzC;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMP,WAAW,KAAK,2BAA2B,EAAE;AAAA;AAAA;AAAA,YAGjD,OAAO;AAAA,cACL,OAAO,GAAG,QAAQ;AAAA,cAClB;AAAA,YACF;AAAA;AAAA,QACF;AAAA,QAGA,8CAAC,SAAI,WAAU,qDACZ,uBAAa,WAAW,MACvB,+CAAC,UAAK,WAAU,6EACb;AAAA;AAAA,UAAW;AAAA,UAAE;AAAA,UAAgB;AAAA,WAChC,IACE,aACF,8CAAC,UAAK,WAAU,2EACb,wBACH,IACE,MACN;AAAA,QAGA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAOnB;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ;;;AbPY,IAAAC,uBAAA;AAhHL,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB;AAAA,EACA,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,wBAAwB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyC;AACvC,QAAM,EAAE,OAAO,SAAS,MAAM,UAAU,QAAQ,eAAe,KAAK,WAAW,IAAI;AAInF,QAAM,CAAC,eAAe,gBAAgB,IAAI,cAAAC,QAAM,SAAS,KAAK;AAG9D,QAAM,kBAAkB,CAAC,EAAE,QAAQ,KAAK,KAAK,CAAC,WAAW,CAAC;AAE1D,QAAM,cAAc,OAAO,OAAO,aAAa,EAAE;AAAA,IAC/C,CAAC,MAA4B,EAAE;AAAA,EACjC;AAIA,QAAM,YAAY,cAAc,cAAc;AAC9C,QAAM,eAAe,cAAc,cAAc;AAEjD,QAAM,gBAAgB,CAAC,MAAmD;AACxE,QAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,YAAY,YAAY;AAClD,QAAE,eAAe;AACjB,iBAAW;AAAA,IACb;AAAA,EACF;AAGA,QAAM,mBAAmB,kBACrB,SACA;AAEJ,QAAM,cAAc,kBAChB,mCACA;AAEJ,SACE,+CAAC,SAAI,eAAY,yBAAwB,WAAU,UAAU,GAAI,MAAM,YAAY,CAAC,GAClF;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,eAAY;AAAA,QACZ,WAAW,yCAAyC,SAAS,iBAAiB,YAAY,kCAAkC,WAAW,qBAAqB,MAAM,aAAa,eAAe,EAAE,IAAI,MAAM,eAAe,sCAAsC,EAAE;AAAA,QACjQ,OAAO;AAAA,UACL,iBAAiB,kBAAkB,YAAY;AAAA,UAC/C,iBAAiB;AAAA,QACnB;AAAA,QAKC;AAAA,kBACC;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACX,GAAG,KAAK;AAAA,cACT,WAAU;AAAA,cACV,OAAM;AAAA,cACN,cAAW;AAAA,cAEX,wDAAC,oCAAa,WAAU,eAAc,aAAa,GAAG;AAAA;AAAA,UACxD;AAAA,UAID,gBACC,8CAAC,SAAI,WAAU,gDACb;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,cACX,YAAW;AAAA,cACX,aAAY;AAAA,cACZ,iBAAiB;AAAA,cACjB;AAAA,cACA,qBAAqB;AAAA;AAAA,UACvB,GACF;AAAA,UAMF;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,WAAW,iEAAiE,YAAY,eAAe,EAAE;AAAA,cACzG,OAAO,YAAY,4CAAuC;AAAA,cAEzD;AAAA,8BAAc,cAAc,iBAC3B;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,eAAY;AAAA,oBACZ,OAAO,UAAU;AAAA,oBACjB,UAAU,CAAC,MAA2C,eAAe,EAAE,OAAO,KAAK;AAAA,oBACnF,WAAW;AAAA,oBACX,aAAY;AAAA,oBACZ,UAAU;AAAA,oBACV,WAAU;AAAA;AAAA,gBACZ,IACE;AAAA,gBAEJ,+CAAC,SAAI,WAAU,gCACZ;AAAA,wBAAM,QACL,8CAAC,UAAK,WAAU,0EAAyE,OAAO,MAAM,MACnG,gBAAM,MACT;AAAA,kBAEF,8CAAC,UAAK,WAAU,8CAA6C,kBAAI;AAAA,kBACjE;AAAA,oBAAC;AAAA;AAAA,sBACC,OAAO;AAAA,sBACP,UAAU;AAAA,sBACV,UAAU;AAAA,sBACV,WAAU;AAAA;AAAA,kBACZ;AAAA,kBACA,8CAAC,UAAK,WAAU,8CAA6C,kBAAI;AAAA,kBACjE;AAAA,oBAAC;AAAA;AAAA,sBACC,OAAO;AAAA,sBACP,UAAU;AAAA,sBACV,UAAU;AAAA,sBACV,WAAU;AAAA;AAAA,kBACZ;AAAA,mBACF;AAAA;AAAA;AAAA,UACF;AAAA,UAGC,SACC;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,WAAU;AAAA,cACV,OAAO;AAAA,cAEP,yDAAC,SAAI,WAAU,YACb;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAU;AAAA,oBACV,aAAa;AAAA;AAAA,gBACf;AAAA,gBAEA,8CAAC,SAAI,WAAU,6OACZ,iBACH;AAAA,iBACF;AAAA;AAAA,UACF;AAAA,UAIF,+CAAC,SAAI,WAAU,oEAEb;AAAA,2DAAC,SAAI,WAAU,2BACZ;AAAA,4BACC;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU,CAAC,mBAAmB,gBAAgB,CAAC,QAAQ,KAAK;AAAA,kBAC5D,WAAW,uEACT,CAAC,mBAAmB,eAChB,wEACA,kBACE,uGACA,QAAQ,KAAK,IACX,6FACA,qEACV;AAAA,kBACA,OAAO,CAAC,kBAAkB,kBAAkB,eAAe,kBAAkB;AAAA,kBAC9E;AAAA;AAAA,cAED;AAAA,cAED,UACC;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU,CAAC,WAAW;AAAA,kBACtB,WAAW,uEACT,CAAC,WAAW,eACR,wEACA,iGACN;AAAA,kBACA,OAAO,UAAU,0CAA0C;AAAA,kBAC5D;AAAA;AAAA,cAED;AAAA,cAKF;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,WAAW,6DACT,UACI,0BACA,qDACN;AAAA,kBACA,OAAO,UAAU,iBAAiB;AAAA,kBACnC;AAAA;AAAA,cAED;AAAA,cACC,YACC;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAY;AAAA,kBACZ,SAAS,MAAM,iBAAiB,IAAI;AAAA,kBACpC,WAAU;AAAA,kBACV,OAAM;AAAA,kBACP;AAAA;AAAA,cAED;AAAA,eAEJ;AAAA,YAEA,+CAAC,SAAI,WAAU,2BACZ;AAAA,2BACC;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU,CAAC,WAAW,gBAAgB,CAAC,CAAC;AAAA,kBACxC,WAAW,uEACT,CAAC,WAAW,gBAAgB,CAAC,CAAC,4BAC1B,wEACA,iGACN;AAAA,kBACA,OACE,4BACI,6CACA,UACE,8BACA;AAAA,kBAET;AAAA;AAAA,cAED;AAAA,cAED,oBACC;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU;AAAA,kBACV,WAAW,uEACT,eACI,wEACA,YACE,gDACA,cACE,6FACA,iGACV;AAAA,kBACA,OAAO,YAAY,qBAAqB;AAAA,kBACzC;AAAA;AAAA,cAED;AAAA,cAEF;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU;AAAA,kBACV,WAAW,6DACT,eACI,sDACA,WACE,6BACA,qDACR;AAAA,kBACA,OAAO,WAAW,iBAAiB;AAAA,kBACpC;AAAA;AAAA,cAED;AAAA,cACC,kBACC;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU;AAAA,kBACV,WAAW,6DACT,eACI,sDACA,eACE,gDACA,oBACE,yDACA,qDACV;AAAA,kBACA,OAAO,iCAA4B,oBAAoB,0BAA0B,EAAE;AAAA,kBAEnF,wDAAC,mCAAY,WAAU,WAAU,aAAa,KAAK;AAAA;AAAA,cACrD;AAAA,eAEJ;AAAA,aACF;AAAA;AAAA;AAAA,IACF;AAAA,IAKC,UACC,8CAAC,mBAAgB,QAAgB,SAAS,MAAM,IAAI,aAAa,CAAC,YAAY;AAAA,IAM/E,cACC;AAAA,MAAC;AAAA;AAAA,QACC,eAAY;AAAA,QACZ,WAAU;AAAA,QAEV;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,YACX;AAAA,YACA,SAAS,MAAM;AAAA,YACf,SAAS;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA,YAAY;AAAA,YACZ,aAAa;AAAA,YACb,iBAAiB,6BAA6B;AAAA,YAC9C,WAAW,sBAAsB;AAAA,YACjC,UAAU;AAAA,YACV,WAAW;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,YACA,wBAAwB;AAAA,YACxB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IAGF;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,OAAM;AAAA,QACN,SACE,gFACE;AAAA,wDAAC,UAAK,WAAU,iBAAiB,gBAAM,MAAM,KAAK,KAAK,cAAa;AAAA,UAAO;AAAA,WAE7E;AAAA,QAEF,cAAa;AAAA,QACb,WAAW,MAAM;AACf,2BAAiB,KAAK;AACtB,qBAAW;AAAA,QACb;AAAA,QACA,UAAU,MAAM,iBAAiB,KAAK;AAAA,QACtC,cAAa;AAAA;AAAA,IACf;AAAA,KACF;AAEJ;;;AcliBA,IAAAC,iBAAkB;AAmDZ,IAAAC,uBAAA;AAHN,SAAS,aAAa,EAAE,KAAK,MAAM,GAA+D;AAChG,SACE,+CAAC,SAAI,WAAU,iDACb;AAAA,kDAAC,UAAK,WAAU,8EAA8E,eAAI;AAAA,IAClG,8CAAC,UAAK,WAAU,sCAAqC,OAAO,MAAM,cAAc,MAAM,MACnF,gBAAM,cAAc,MAAM,MAC7B;AAAA,IACC,MAAM,cACL,+CAAC,UAAK,WAAU,uDAAsD,OAAO,MAAM,YAAY;AAAA;AAAA,MAC1F,MAAM;AAAA,OACX;AAAA,KAEJ;AAEJ;AAEO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAChB,GAA+C;AAC7C,QAAM,CAAC,eAAe,gBAAgB,IAAI,eAAAC,QAAM,SAAS,KAAK;AAO9D,QAAM,cAAc,CAAC,OAAuB,MAAqB,QAC/D;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,MAAM,MAAM,KAAK;AAAA,MACvD,cAAc,MAAM;AAAA,MACpB,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,WAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,aAAa,8CAAC,gBAAa,KAAU,OAAc;AAAA,MACnD;AAAA,MACA;AAAA,MACA,gBAAgB,CAAC,MAAc,eAAe,MAAM,CAAC;AAAA,MACrD,aAAa,CAAC,MAAc,YAAY,MAAM,CAAC;AAAA;AAAA,EACjD;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAY;AAAA,MACZ,WAAU;AAAA,MACV,OAAO,EAAE,iBAAiB,aAAa,iBAAiB,MAAM;AAAA,MAG9D;AAAA,uDAAC,SAAI,WAAU,mEACb;AAAA,wDAAC,UAAK,WAAU,iDAAgD,OAAO,EAAE,OAAO,YAAY,GAAG,8BAE/F;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,SAAS,MAAM,iBAAiB,IAAI;AAAA,cACpC,WAAU;AAAA,cACV,OAAM;AAAA,cACN,cAAW;AAAA,cACZ;AAAA;AAAA,UAED;AAAA,WACF;AAAA,QAEC,YAAY,QAAQ,UAAU,QAAQ;AAAA,QAIvC,+CAAC,SAAI,WAAU,uCAAsC,eAAY,wBAC/D;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,OAAO,cAAc,OAAO;AAAA,cAElC,iBAAO,cAAc,OAAO;AAAA;AAAA,UAC/B;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,eAAY;AAAA,cACZ,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,OAAO;AAAA,cACP,UAAU,CAAC;AAAA,cACX,UACE,iBACI,CAAC,MAA2C,eAAe,OAAO,EAAE,OAAO,KAAK,CAAC,IACjF;AAAA,cAEN,OAAO,EAAE,YAAY;AAAA,cACrB,WAAU;AAAA,cACV,cAAW;AAAA;AAAA,UACb;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,OAAO,cAAc,OAAO;AAAA,cAElC,iBAAO,cAAc,OAAO;AAAA;AAAA,UAC/B;AAAA,WACF;AAAA,QAEC,YAAY,QAAQ,UAAU,QAAQ;AAAA,QAEvC;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,OAAM;AAAA,YACN,SACE,+EAAE,mHAGF;AAAA,YAEF,cAAa;AAAA,YACb,WAAW,MAAM;AACf,+BAAiB,KAAK;AACtB,uBAAS;AAAA,YACX;AAAA,YACA,UAAU,MAAM,iBAAiB,KAAK;AAAA,YACtC,cAAa;AAAA;AAAA,QACf;AAAA;AAAA;AAAA,EACF;AAEJ;;;AC/KO,SAAS,WAAW,GAAmB;AAC5C,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,MAAO,KAAK,KAAK,IAAK,EAAE,WAAW,CAAC,IAAK;AAC5E,UAAQ,MAAM,GAAG,SAAS,EAAE;AAC9B;AACO,SAAS,cAAc,MAAqD;AACjF,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,KAAK,SAAS,SAAU,QAAO,KAAK,WAAW,KAAK,KAAK,CAAC;AAC9D,MAAI,KAAK,SAAS,SAAU,QAAO,KAAK,KAAK,UAAU;AACvD,MAAI,KAAK,SAAS,aAAc,QAAO,KAAK,KAAK,gBAAgB,EAAE,IAAI,WAAW,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC;AAC7G,SAAO;AACT;AAUO,IAAM,mBAAmB;AAwCzB,SAAS,gBAAgB,KAAoC;AAClE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,YAAY,YAAa,EAAE,SAAS,YAAY,EAAE,SAAS,SAAW,QAAO;AAC1F,MAAI,OAAO,EAAE,gBAAgB,SAAU,QAAO;AAC9C,SAAO;AAAA,IACL,SAAS,EAAE;AAAA,IACX,MAAM,EAAE;AAAA,IACR,aAAa,EAAE;AAAA,IACf,iBAAiB,OAAO,EAAE,oBAAoB,WAAW,EAAE,kBAAkB;AAAA,IAC7E,eAAe,OAAO,EAAE,kBAAkB,WAAW,EAAE,gBAAgB;AAAA,IACvE,YAAY,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AAAA,IAC9D,YAAY,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AAAA,IAC9D,WAAW,OAAO,EAAE,cAAc,WAAW,EAAE,YAAY;AAAA,EAC7D;AACF;AAQO,SAAS,oBAAoB,WAAyD;AAC3F,QAAM,SAAS,oBAAI,IAGjB;AACF,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,SAAS,GAAG;AAClD,UAAM,QAAQ,yBAAyB,KAAK,GAAG;AAC/C,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,gBAAgB,GAAG;AAChC,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,IAAI,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AACvC,QAAI,KAAK,SAAS,SAAU,GAAE,SAAS,EAAE,MAAM,KAAK;AAAA,QAC/C,GAAE,SAAS,EAAE,MAAM,KAAK;AAC7B,WAAO,IAAI,KAAK,SAAS,CAAC;AAAA,EAC5B;AACA,QAAM,QAA6B,CAAC;AACpC,aAAW,CAAC,SAAS,CAAC,KAAK,QAAQ;AACjC,QAAI,CAAC,EAAE,UAAU,CAAC,EAAE,OAAQ;AAC5B,UAAM,KAAK;AAAA,MACT;AAAA,MACA,WAAW,EAAE,OAAO,KAAK;AAAA,MACzB,YAAY,EAAE,OAAO;AAAA,MACrB,YAAY,EAAE,OAAO;AAAA,MACrB,kBAAkB,EAAE,OAAO,KAAK;AAAA,MAChC,kBAAkB,EAAE,OAAO,KAAK;AAAA,MAChC,kBAAkB,EAAE,OAAO,KAAK;AAAA,MAChC,kBAAkB,EAAE,OAAO,KAAK;AAAA,MAChC,kBAAkB,EAAE,OAAO,KAAK;AAAA,MAChC,kBAAkB,EAAE,OAAO,KAAK;AAAA,IAClC,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAoBO,IAAM,gBAAgB;AAEtB,SAAS,SAAS,MAAsB;AAC7C,SAAO,QAAQ,OAAO,gBAAgB,KAAK,IAAI,eAAe,KAAK,KAAK,MAAM,IAAI,CAAC;AACrF;AAaO,SAAS,2BACd,MACA,KACA,WACA,QAAQ,IACe;AACvB,QAAM,kBAAmB,OAAO,IAAI,KAAM,KAAK,IAAI,GAAG,GAAG;AAEzD,QAAM,IAAI,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,SAAS,CAAC;AAClD,QAAM,QAAQ,CAAC,MAAsB,KAAK,MAAM,IAAI,GAAI,IAAI;AAC5D,QAAM,SAAkC,CAAC;AACzC,QAAM,SAAkC,CAAC;AACzC,WAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,UAAM,IAAI,IAAI;AACd,UAAM,OAAO,MAAM,IAAI,eAAe;AAEtC,UAAM,QAAQ,KAAK,IAAK,IAAI,KAAM,KAAK,KAAK,KAAK,KAAK,KAAK,KAAM,IAAI,MAAM,IAAI,MAAO,KAAK,KAAK;AAChG,WAAO,KAAK,EAAE,MAAM,IAAI,KAAK,MAAM,SAAS,KAAK,IAAI,KAAK,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC;AAC3E,WAAO,KAAK,EAAE,MAAM,IAAI,KAAK,MAAM,SAAS,KAAK,IAAI,KAAK,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC;AAAA,EAC7E;AACA,SAAO,EAAE,QAAQ,OAAO;AAC1B;;;AChJA,IAAM,cAAc,CAAC,KAAK,MAAM,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,GAAG;AAGpF,SAAS,OAAO,GAAmB;AACjC,SAAO,KAAK,MAAM,IAAI,GAAI,IAAI;AAChC;AAGA,SAAS,UAAU,GAAmB;AACpC,SAAO,GAAG,aAAc,IAAI,KAAM,MAAM,EAAE,CAAC,GAAG,KAAK,MAAM,IAAI,EAAE,IAAI,CAAC;AACtE;AAGA,SAAS,YAAY,GAAkG;AACrH,SAAO,EAAE,OAAO,EAAE,OAAO,WAAW,OAAO,EAAE,SAAS,GAAG,eAAe,OAAO,EAAE,aAAa,GAAG,UAAU,EAAE,SAAS;AACxH;AAGA,SAAS,UAAU,OAAkC,YAA6B;AAChF,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAO,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;AAChF,MAAI,WAAY,QAAO,GAAG,MAAM,MAAM,iBAAiB,IAAI;AAC3D,QAAM,UAAU,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK;AACxC,SAAO,GAAG,MAAM,MAAM,WAAW,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC,SAAI,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC,YAAY,IAAI;AACrH;AAGA,SAAS,MAAM,OAAkC,YAA6B;AAC5E,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAClE,QAAM,SAAS,KAAK,IAAI,GAAG,OAAO,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,aAAa,CAAC;AAC3E,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC;AAC9C,QAAM,QAAkB,CAAC;AACzB,WAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,UAAM,QAAQ,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI,KAAK,EAAE,aAAa,IAAI,KAAK,CAAC;AACpF,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,OAAO,aACT,MAAM,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,EAAE,KAAK,GAAG,IACnE,MAAM,IAAI,CAAC,MAAM,GAAG,UAAU,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,GAAG;AAC7E,UAAM,KAAK,WAAW,IAAI,CAAC,KAAK,IAAI,EAAE;AAAA,EACxC;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,aACP,OACA,MACA,KACA,OACA,YACQ;AACR,QAAM,WAAW,MAAM,OAAO,GAAG,KAAK;AACtC,QAAM,SAAS,GAAG,KAAK,YAAO,IAAI,IAAI,QAAQ,KAAK,UAAU,OAAO,UAAU,CAAC;AAC/E,MAAI,MAAM,WAAW,EAAG,QAAO,GAAG,MAAM;AAAA;AACxC,SAAO,GAAG,MAAM;AAAA,EAAK,MAAM,OAAO,UAAU,CAAC;AAAA,kBAAqB,KAAK,UAAU,MAAM,IAAI,WAAW,CAAC,CAAC;AAC1G;AAOO,SAAS,4BAA4B,OAAsC;AAChF,QAAM,EAAE,MAAM,MAAM,YAAY,YAAY,WAAW,WAAW,aAAa,YAAY,IAAI;AAC/F,QAAM,aAAa,MAAM,cAAc;AACvC,QAAM,OAAO,SAAS,aAAa,SAAS;AAC5C,QAAM,aACJ,aAAa,YACT,cAAc,YACZ,YAAY,SAAS,KACrB,kBAAkB,SAAS,WAAW,SAAS,KACjD;AAEN,QAAM,QAAkB;AAAA,IACtB;AAAA,IACA,aAAa,IAAI,gFAAgF,IAAI;AAAA,IACrG,mFAA8E,UAAU;AAAA,IACxF;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,4CAA4C,YAAY,WAAW,aAAa,UAAU;AAAA,IACvG;AAAA,IACA,aAAa,iDAAiD,YAAY,WAAW,aAAa,UAAU;AAAA,IAC5G;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,YAAY,WAAW,KAAK,YAAY,WAAW,GAAG;AACxD,UAAM;AAAA,MACJ;AAAA,MACA,YAAY,WAAW,KAAK,YAAY,WAAW,IAC/C,oDAA+C,IAAI,oCACnD,YAAY,WAAW,IACrB,wEACA;AAAA,IACR;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACjGO,SAAS,WAAW,KAA+B;AACxD,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AACV,MAAI,EAAE,cAAc,QAAQ,EAAE,cAAc,MAAO,QAAO;AAC1D,MAAI,EAAE,YAAY,YAAY,EAAE,YAAY,QAAS,QAAO;AAC5D,QAAM,SACJ,EAAE,WAAW,aAAa,EAAE,WAAW,aAAa,EAAE,WAAW,WAAW,EAAE,WAAW,SACrF,EAAE,SACF;AACN,SAAO;AAAA,IACL,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX;AAAA,IACA,iBAAiB,OAAO,EAAE,oBAAoB,WAAW,EAAE,kBAAkB;AAAA,IAC7E,eAAe,OAAO,EAAE,kBAAkB,WAAW,EAAE,gBAAgB;AAAA,IACvE,YAAY,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AAAA,IAC9D,YAAY,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AAAA,IAC9D,WAAW,OAAO,EAAE,cAAc,WAAW,EAAE,YAAY;AAAA,EAC7D;AACF;AAOO,SAAS,WAAW,WAAiD;AAC1E,QAAM,MAAmB,CAAC;AAC1B,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,SAAS,GAAG;AAClD,UAAM,QAAQ,oBAAoB,KAAK,GAAG;AAC1C,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,WAAW,GAAG;AAC3B,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,EAAE,MAAM,MAAM,CAAC,GAAG,KAAK,CAAC;AAAA,EACnC;AACA,SAAO;AACT;AAiBO,SAAS,qBACd,MACA,KACA,WACA,WACA,SACA,QAAQ,IACiB;AACzB,QAAM,kBAAmB,OAAO,IAAI,KAAM,KAAK,IAAI,GAAG,GAAG;AAGzD,MAAI,YAAY,SAAS;AACvB,WAAO;AAAA,MACL,EAAE,MAAM,GAAG,IAAI,EAAE;AAAA,MACjB,EAAE,MAAM,KAAK,MAAM,kBAAkB,GAAI,IAAI,KAAM,IAAI,EAAE;AAAA,IAC3D;AAAA,EACF;AAGA,QAAM,IAAI,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,SAAS,CAAC;AAClD,QAAM,QAAQ,CAAC,MAAsB,KAAK,MAAM,IAAI,GAAI,IAAI;AAC5D,QAAM,SAAkC,CAAC;AACzC,WAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,UAAM,IAAI,IAAI;AACd,UAAM,OAAO,MAAM,IAAI,eAAe;AAEtC,UAAM,QAAQ,KAAK,IAAK,IAAI,KAAM,KAAK,KAAK,KAAK,KAAK,KAAK,KAAM,IAAI,MAAM,IAAI,MAAO,KAAK,KAAK;AAEhG,UAAM,OAAO,cAAc,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK;AACnE,WAAO,KAAK,EAAE,MAAM,IAAI,KAAK,MAAM,SAAS,IAAI,IAAI,GAAG,IAAI,IAAI,CAAC;AAAA,EAClE;AACA,SAAO;AACT;AAcO,IAAM,iBAAsC,oBAAI,IAAY;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,mBAAmB,MAA8C;AAC/E,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,KAAK,YAAY,EAAE,QAAQ,YAAY,GAAG,EAAE,KAAK;AAC9D,MAAI,eAAe,IAAI,IAAI,EAAG,QAAO;AACrC,aAAW,SAAS,KAAK,MAAM,GAAG,GAAG;AACnC,QAAI,eAAe,IAAI,KAAK,EAAG,QAAO;AAAA,EACxC;AACA,SAAO;AACT;;;ACxKA,IAAAC,iBAAkB;AAgEZ,IAAAC,uBAAA;AAZN,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,GAIuB;AACrB,QAAM,MAAM,cAAc,OAAO,YAAY;AAC7C,SACE,+CAAC,SAAI,WAAU,iDACb;AAAA,kDAAC,UAAK,WAAU,8EAA8E,eAAI;AAAA,IAClG,8CAAC,UAAK,WAAU,sCAAqC,OAAO,MAAM,cAAc,MAAM,MACnF,gBAAM,cAAc,MAAM,MAC7B;AAAA,IACC,MAAM,cACL,+CAAC,UAAK,WAAU,uDAAsD,OAAO,MAAM,YAAY;AAAA;AAAA,MAC1F,MAAM;AAAA,OACX;AAAA,IAEF,+CAAC,UAAK,WAAU,8CAA6C,OAAO,iBAAiB,OAAO,IAAI;AAAA;AAAA,MAC3F;AAAA,OACL;AAAA,KACF;AAEJ;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAChB,GAA0C;AACxC,QAAM,CAAC,eAAe,gBAAgB,IAAI,eAAAC,QAAM,SAAS,KAAK;AAG9D,QAAM,YAAY,cAAc,OAAO,aAAc,MAAM,cAAc,MAAM;AAC/E,QAAM,aAAa,cAAc,OAAQ,MAAM,cAAc,MAAM,OAAQ;AAC3E,QAAM,OAAO,UAAU,WAAW,SAAS,OAAO,OAAO,CAAC,EAAE,YAAY,IAAI,OAAO,MAAM,CAAC,IAAI;AAC9F,QAAM,QAAQ,cAAc,OAAO,UAAK,IAAI,QAAQ,UAAK,IAAI;AAE7D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAY;AAAA,MACZ,WAAU;AAAA,MACV,OAAO,EAAE,iBAAiB,aAAa,iBAAiB,MAAM;AAAA,MAG9D;AAAA,uDAAC,SAAI,WAAU,mEACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,WAAU;AAAA,cACV,OAAO,EAAE,OAAO,YAAY;AAAA,cAE3B;AAAA;AAAA,UACH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,SAAS,MAAM,iBAAiB,IAAI;AAAA,cACpC,WAAU;AAAA,cACV,OAAM;AAAA,cACN,cAAW;AAAA,cACZ;AAAA;AAAA,UAED;AAAA,WACF;AAAA,QAIA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,MAAM,MAAM,KAAK;AAAA,YACvD,cAAc,MAAM;AAAA,YACpB,eAAe;AAAA,YACf,YAAY;AAAA,YACZ,WAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA,aAAa,8CAAC,eAAY,OAAc,WAAsB,SAAkB;AAAA,YAChF;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF;AAAA,QAGA,+CAAC,SAAI,WAAU,uCAAsC,eAAY,mBAC/D;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO;AAAA,cAEN;AAAA;AAAA,UACH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,eAAY;AAAA,cACZ,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,OAAO;AAAA,cACP,UAAU,CAAC;AAAA,cACX,UACE,iBACI,CAAC,MAA2C,eAAe,OAAO,EAAE,OAAO,KAAK,CAAC,IACjF;AAAA,cAEN,OAAO,EAAE,YAAY;AAAA,cACrB,WAAU;AAAA,cACV,cAAW;AAAA;AAAA,UACb;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO;AAAA,cAEN;AAAA;AAAA,UACH;AAAA,WACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,OAAM;AAAA,YACN,SAAS,+EAAE,iGAAmF;AAAA,YAC9F,cAAa;AAAA,YACb,WAAW,MAAM;AACf,+BAAiB,KAAK;AACtB,uBAAS;AAAA,YACX;AAAA,YACA,UAAU,MAAM,iBAAiB,KAAK;AAAA,YACtC,cAAa;AAAA;AAAA,QACf;AAAA;AAAA;AAAA,EACF;AAEJ;;;AC3LA,IAAAC,iBAAyE;AA6GrE,IAAAC,uBAAA;AAjEJ,SAAS,QAAQ,MAAsB;AACrC,SAAO,KAAK,SAAS,IAAI,KAAK,MAAM,GAAG,CAAC,IAAI;AAC9C;AAEA,IAAM,WAAW,CAAC,OAAmC,KAAK,IAAI,YAAY,EAAE,KAAK;AAMjF,SAAS,eACP,MACA,IACA,YAC6D;AAC7D,QAAM,SAAS,CAAC,SAA8D;AAC5E,UAAM,IAAI,oBAAI,IAAgC;AAC9C,eAAW,KAAK,MAAM;AACpB,YAAM,IAAI,SAAS,EAAE,IAAI;AACzB,YAAM,MAAM,EAAE,IAAI,CAAC;AACnB,UAAI,IAAK,KAAI,KAAK,CAAC;AAAA,UACd,GAAE,IAAI,GAAG,CAAC,CAAC,CAAC;AAAA,IACnB;AACA,WAAO;AAAA,EACT;AACA,QAAM,aAAa,OAAO,IAAI;AAC9B,QAAM,WAAW,OAAO,EAAE;AAC1B,QAAM,QAAQ,oBAAI,IAAY,CAAC,GAAG,WAAW,KAAK,GAAG,GAAG,SAAS,KAAK,CAAC,CAAC;AACxE,QAAM,UAA8B,CAAC;AACrC,QAAM,SAA6B,CAAC;AACpC,aAAW,QAAQ,OAAO;AACxB,UAAM,IAAI,WAAW,IAAI,IAAI,KAAK,CAAC;AACnC,UAAM,IAAI,SAAS,IAAI,IAAI,KAAK,CAAC;AACjC,UAAM,SAAS,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AAC1C,YAAQ,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAC/B,WAAO,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAAA,EAChC;AACA,SAAO;AAAA,IACL,SAAS,QAAQ,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,IAAI,CAAC;AAAA,IACtD,QAAQ,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,IAAI,CAAC;AAAA,EACtD;AACF;AAMA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOuB;AACrB,QAAM,UAAU,MAAM,QAAQ,KAAK,KAAK,MAAM;AAC9C,QAAM,OAAO,CAAC,MAAM,MAAM,QAAQ,MAAM,IAAI,GAAG,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,QAAK;AAClF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,MAAK;AAAA,MACL,gBAAc;AAAA,MACd,eAAa;AAAA,MACb,cAAY,MAAM;AAAA,MAClB,SAAS;AAAA,MACT;AAAA,MACA,WAAW,wFACT,WACI,uCACA,2DACN;AAAA,MAEA;AAAA,sDAAC,SAAI,WAAU,kCAAiC,OAAO,SACpD,mBACH;AAAA,QACC,QACC,8CAAC,SAAI,WAAU,8CAA6C,OAAO,MAAM,MACtE,gBACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AACjB,GAA8C;AAC5C,QAAM,CAAC,MAAM,OAAO,QAAI,yBAAoB,EAAE,QAAQ,UAAU,CAAC;AACjE,QAAM,CAAC,cAAc,eAAe,QAAI,yBAAiB,EAAE;AAC3D,QAAM,CAAC,YAAY,aAAa,QAAI,yBAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,QAAI,yBAAwB,IAAI;AACtD,QAAM,CAAC,UAAU,WAAW,QAAI,yBAAwB,IAAI;AAC5D,QAAM,CAAC,QAAQ,SAAS,QAAI,yBAAwB,IAAI;AACxD,QAAM,gBAAY,uBAA0B,IAAI;AAEhD,QAAM,cAAU,4BAAY,YAA2B;AACrD,QAAI,CAAC,KAAK,uBAAuB;AAC/B,cAAQ,EAAE,QAAQ,SAAS,SAAS,oCAAoC,CAAC;AACzE;AAAA,IACF;AACA,YAAQ,EAAE,QAAQ,UAAU,CAAC;AAC7B,QAAI;AACF,YAAM,CAAC,MAAM,IAAI,OAAO,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,QACjD,KAAK,sBAAsB,WAAW;AAAA,QACtC,KAAK,sBAAsB,SAAS;AAAA,QACpC,KAAK,eAAe,KAAK,aAAa,WAAW,IAAI,QAAQ,QAAQ,IAAI;AAAA,QACzE,KAAK,eAAe,KAAK,aAAa,SAAS,IAAI,QAAQ,QAAQ,IAAI;AAAA,MACzE,CAAC;AACD,kBAAY,KAAK;AACjB,gBAAU,KAAK;AACf,cAAQ,EAAE,QAAQ,SAAS,MAAM,GAAG,CAAC;AAAA,IACvC,SAAS,KAAc;AACrB,cAAQ,EAAE,QAAQ,SAAS,SAAS,eAAe,QAAQ,IAAI,UAAU,yBAAyB,CAAC;AAAA,IACrG;AAAA,EACF,GAAG,CAAC,MAAM,aAAa,SAAS,CAAC;AAGjC,gCAAU,MAAM;AACd,QAAI,MAAM;AACR,eAAS,IAAI;AACb,oBAAc,KAAK;AACnB,sBAAgB,EAAE;AAClB,WAAK,QAAQ;AAAA,IACf;AAAA,EACF,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,QAAM,iBAAa,wBAAQ,MAAM,IAAI,IAAI,sBAAsB,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAExF,QAAM,EAAE,SAAS,OAAO,QAAI;AAAA,IAC1B,MACE,KAAK,WAAW,UACZ,eAAe,KAAK,MAAM,KAAK,IAAI,UAAU,IAC7C,EAAE,SAAS,CAAC,GAAyB,QAAQ,CAAC,EAAwB;AAAA,IAC5E,CAAC,MAAM,UAAU;AAAA,EACnB;AAGA,QAAM,iBAAa;AAAA,IACjB,MAAM;AAAA,MACJ,GAAG,QAAQ,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,MAAuB,EAAE;AAAA,MACvE,GAAG,OAAO,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,KAAsB,EAAE;AAAA,IACvE;AAAA,IACA,CAAC,SAAS,MAAM;AAAA,EAClB;AAGA,gCAAU,MAAM;AACd,QAAI,CAAC,WAAW,KAAK,CAAC,MAAM,EAAE,MAAM,SAAS,YAAY,GAAG;AAC1D,sBAAgB,WAAW,CAAC,GAAG,MAAM,QAAQ,EAAE;AAAA,IACjD;AAAA,EACF,GAAG,CAAC,YAAY,YAAY,CAAC;AAE7B,QAAM,WAAW,WAAW,KAAK,CAAC,MAAM,EAAE,MAAM,SAAS,YAAY,KAAK;AAC1E,QAAM,YAAY,CAAC,cAAc,CAAC,CAAC;AAEnC,QAAM,kBAAc,4BAAY,MAAY;AAC1C,QAAI,CAAC,WAAY,SAAQ;AAAA,EAC3B,GAAG,CAAC,YAAY,OAAO,CAAC;AAExB,QAAM,mBAAe,4BAAY,YAA2B;AAC1D,QAAI,CAAC,SAAU;AACf,kBAAc,IAAI;AAClB,aAAS,IAAI;AACb,QAAI;AACF,YAAM;AAAA,QACJ,EAAE,MAAM,SAAS,MAAM,MAAM,MAAM,SAAS,MAAM,MAAM,MAAM,SAAS,MAAM,KAAK;AAAA,QAClF,SAAS;AAAA,QACT,mBAAmB,SAAS,MAAM,IAAI;AAAA,MACxC;AACA,cAAQ;AAAA,IACV,SAAS,KAAc;AACrB,eAAS,eAAe,QAAQ,IAAI,UAAU,wBAAwB;AACtE,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,UAAU,UAAU,OAAO,CAAC;AAEhC,QAAM,YAAY,YAAY,iBAAiB;AAC/C,QAAM,UAAU,UAAU,eAAe;AAEzC,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,gBAAgB,CACpB,SACA,MACA,YAC8B;AAC9B,QAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,WACE,+CAAC,SAAI,WAAU,SACb;AAAA,oDAAC,UAAK,WAAU,sDAAsD,mBAAQ;AAAA,MAC9E;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,eAAa,GAAG,YAAY,IAAI,YAAY,QAAQ,aAAa,SAAS;AAAA,UAC1E,WAAU;AAAA,UAET,eAAK,IAAI,CAAC,MACT;AAAA,YAAC;AAAA;AAAA,cAEC,OAAO;AAAA,cACP,SAAS,mBAAmB,EAAE,IAAI;AAAA,cAClC,UAAU,EAAE,SAAS;AAAA,cACrB,UAAU;AAAA,cACV,UAAU,MAAM,gBAAgB,EAAE,IAAI;AAAA,cACtC,QAAQ,GAAG,YAAY,WAAW,EAAE,IAAI;AAAA;AAAA,YANnC,EAAE;AAAA,UAOT,CACD;AAAA;AAAA,MACH;AAAA,OACF;AAAA,EAEJ;AAEA,SACE,8CAAC,SAAM,MAAY,SAAS,aAAa,cAA4B,iBAAiB,WACpF;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAS,CAAC,MAAwB,EAAE,gBAAgB;AAAA,MACpD,eAAa,GAAG,YAAY;AAAA,MAE5B;AAAA,sDAAC,QAAG,WAAU,mCAAkC,sBAAQ;AAAA,QACxD,+CAAC,OAAE,WAAU,8CAA6C;AAAA;AAAA,UACrB;AAAA,UACnC,8CAAC,UAAK,WAAU,iBAAiB,uBAAa,oBAAmB;AAAA,UAAO;AAAA,UAAK;AAAA,UAC7E,8CAAC,UAAK,WAAU,iBAAiB,qBAAW,oBAAmB;AAAA,UAAO;AAAA,WAExE;AAAA,QAEC,KAAK,WAAW,aACf,8CAAC,SAAI,WAAU,2CAA0C,kCAAe;AAAA,QAEzE,KAAK,WAAW,WACf,8CAAC,SAAI,WAAU,4CAA4C,eAAK,SAAQ;AAAA,QAEzE,KAAK,WAAW,YACd,WAAW,WAAW,IACrB,8CAAC,SAAI,WAAU,2CAA0C,eAAa,GAAG,YAAY,UAAU,6IAG/F,IAEA,gFACG;AAAA,wBAAc,WAAW,YAAY,UAAU,SAAS,MAAM,EAAE,IAAI,SAAS,KAAK;AAAA,UAClF,cAAc,UAAU,UAAU,QAAQ,OAAO,MAAM,EAAE,IAAI,QAAQ,IAAI;AAAA,WAC5E;AAAA,QAGH,SACC,8CAAC,SAAI,WAAU,2BAA0B,eAAa,GAAG,YAAY,UAClE,iBACH;AAAA,QAGF,+CAAC,SAAI,WAAU,+BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL,eAAa,GAAG,YAAY;AAAA,cAC5B,SAAS;AAAA,cACT,UAAU;AAAA,cACV,WAAU;AAAA,cACX;AAAA;AAAA,UAED;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,eAAa,GAAG,YAAY;AAAA,cAC5B,SAAS;AAAA,cACT,UAAU,CAAC;AAAA,cACX,WAAW,yDACT,YACI,6FACA,qEACN;AAAA,cAEC,uBAAa,0BAAqB;AAAA;AAAA,UACrC;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;;;ACvVA,IAAAC,iBAAwD;AA0J9C,IAAAC,uBAAA;AA5GH,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,OAAO;AAAA,EACP;AAAA,EACA;AACF,GAAqD;AACnD,QAAM,CAAC,MAAM,OAAO,QAAI,yBAAoB,EAAE,QAAQ,UAAU,CAAC;AACjE,QAAM,CAAC,iBAAiB,kBAAkB,QAAI,yBAAwB,IAAI;AAC1E,QAAM,CAAC,kBAAkB,mBAAmB,QAAI,yBAAwB,IAAI;AAE5E,QAAM,cAAU,4BAAY,YAA2B;AACrD,QAAI,CAAC,KAAK,sBAAsB;AAC9B,cAAQ,EAAE,QAAQ,SAAS,SAAS,+CAA+C,CAAC;AACpF;AAAA,IACF;AACA,YAAQ,EAAE,QAAQ,UAAU,CAAC;AAC7B,QAAI;AAGF,YAAM,YAAY,SAAS,WAAW,CAAC,CAAC;AACxC,YAAMC,UAAS,MAAM,KAAK,qBAAqB,YAAY,EAAE,kBAAkB,KAAK,IAAI,MAAS;AACjG,cAAQ,EAAE,QAAQ,SAAS,QAAAA,QAAO,CAAC;AAGnC,YAAM,YAAYA,QAAO,KAAK,CAAC,MAAM,EAAE,SAAS;AAChD,UAAI,UAAW,oBAAmB,UAAU,OAAO;AAAA,IACrD,SAAS,KAAc;AACrB,cAAQ,EAAE,QAAQ,SAAS,SAAS,eAAe,QAAQ,IAAI,UAAU,yBAAyB,CAAC;AAAA,IACrG;AAAA,EACF,GAAG,CAAC,MAAM,MAAM,WAAW,CAAC;AAG5B,gCAAU,MAAM;AACd,QAAI,MAAM;AACR,yBAAmB,IAAI;AACvB,0BAAoB,IAAI;AACxB,WAAK,QAAQ;AAAA,IACf;AAAA,EACF,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,QAAM,mBAAe;AAAA,IACnB,OACE,OACA,eACA,WACA,gBACkB;AAIlB,UAAI,eAAe,aAAa;AAC9B,YAAI,CAAC,MAAM,WAAY;AACvB,4BAAoB,MAAM,OAAO;AACjC,YAAI;AACF,gBAAM,YAAY,EAAE,iBAAiB,MAAM,MAAM,WAAW,MAAM,MAAM,MAAM,MAAM,KAAK,CAAC;AAC1F,kBAAQ;AAAA,QACV,SAAS,KAAc;AACrB,eAAK,YAAY,SAAS,eAAe,QAAQ,IAAI,UAAU,eAAe;AAC9E,8BAAoB,IAAI;AAAA,QAC1B;AACA;AAAA,MACF;AAGA,UAAI,SAAS,SAAS;AACpB,4BAAoB,MAAM,OAAO;AACjC,YAAI;AACF,gBAAM,SAAS,EAAE,iBAAiB,MAAM,MAAM,WAAW,MAAM,MAAM,UAAU,CAAC;AAChF,kBAAQ;AAAA,QACV,SAAS,KAAc;AACrB,eAAK,YAAY,SAAS,eAAe,QAAQ,IAAI,UAAU,eAAe;AAC9E,8BAAoB,IAAI;AAAA,QAC1B;AACA;AAAA,MACF;AACA,UAAI,CAAC,MAAM,cAAc,CAAC,KAAK,YAAa;AAC5C,0BAAoB,MAAM,OAAO;AACjC,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,YAAY,EAAE,eAAe,eAAe,MAAM,QAAQ,CAAC;AACrF,mBAAW,MAAM;AACjB,gBAAQ;AAAA,MACV,SAAS,KAAc;AACrB,aAAK,YAAY,SAAS,eAAe,QAAQ,IAAI,UAAU,eAAe;AAC9E,4BAAoB,IAAI;AAAA,MAC1B;AAAA,IACF;AAAA,IACA,CAAC,MAAM,YAAY,SAAS,MAAM,QAAQ,WAAW;AAAA,EACvD;AAEA,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,SAAS,KAAK,WAAW,UAAU,KAAK,SAAS,CAAC;AACxD,QAAM,gBAAgB,OAAO,KAAK,CAAC,MAAM,EAAE,YAAY,eAAe,KAAK;AAE3E,SACE,8CAAC,SAAM,MAAY,SAAkB,cACnC;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,MAClC,eAAa,GAAG,YAAY;AAAA,MAG5B;AAAA,uDAAC,SAAI,WAAU,0EACb;AAAA,yDAAC,SAAI,WAAU,2BACZ;AAAA,6BACC;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,SAAS,MAAM,mBAAmB,IAAI;AAAA,gBACtC,eAAa,GAAG,YAAY;AAAA,gBAC7B;AAAA;AAAA,YAED;AAAA,YAEF,8CAAC,UAAK,WAAU,qCACb,0BAAgB,cAAc,YAAY,OAC7C;AAAA,aACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS;AAAA,cACT,eAAa,GAAG,YAAY;AAAA,cAC7B;AAAA;AAAA,UAED;AAAA,WACF;AAAA,QAGA,+CAAC,SAAI,WAAU,8BACZ;AAAA,eAAK,WAAW,aACf,8CAAC,SAAI,WAAU,2CAA0C,eAAa,GAAG,YAAY,YAAY,kCAEjG;AAAA,UAGD,KAAK,WAAW,WACf,8CAAC,SAAI,WAAU,yCAAwC,eAAa,GAAG,YAAY,UAChF,eAAK,SACR;AAAA,UAGD,KAAK,WAAW,WAAW,OAAO,WAAW,KAC5C,8CAAC,SAAI,WAAU,2CAA0C,eAAa,GAAG,YAAY,UAClF,mBAAS,UACN,4CACA,sDACN;AAAA,UAID,KAAK,WAAW,WAAW,OAAO,SAAS,KAAK,CAAC,iBAChD,8CAAC,QAAG,WAAU,uBAAsB,eAAa,GAAG,YAAY,eAC7D,iBAAO,IAAI,CAAC,UACX,8CAAC,QACC;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,MAAM,mBAAmB,MAAM,OAAO;AAAA,cAC/C,eAAa,GAAG,YAAY;AAAA,cAE5B;AAAA,8DAAC,UAAK,WAAU,YAAY,gBAAM,WAAU;AAAA,gBAC5C,+CAAC,UAAK,WAAU,kBAAkB;AAAA,wBAAM,OAAO;AAAA,kBAAO;AAAA,mBAAE;AAAA;AAAA;AAAA,UAC1D,KARO,MAAM,OASf,CACD,GACH;AAAA,UAID,iBACC,8CAAC,QAAG,WAAU,uBAAsB,eAAa,GAAG,YAAY,eAC7D,wBAAc,OAAO,IAAI,CAAC,UAAU;AACnC,kBAAM,OAAO,qBAAqB,MAAM;AAGxC,kBAAM,QAAQ,SAAS,WAAW,CAAC,MAAM;AACzC,kBAAM,WAAW,SAAS;AAC1B,mBACE,8CAAC,QACC;AAAA,cAAC;AAAA;AAAA,gBACC,WAAW,8GACT,WACI,wEACA,gGACN;AAAA,gBACA;AAAA,gBACA,OAAO,QAAQ,MAAM,iBAAiB;AAAA,gBACtC,SAAS,MAAM,KAAK,aAAa,OAAO,cAAc,SAAS,cAAc,WAAW,CAAC,CAAC,cAAc,SAAS;AAAA,gBACjH,eAAa,GAAG,YAAY;AAAA,gBAC5B,mBAAiB,SAAS,WAAW,MAAM,aAAa,SAAS;AAAA,gBAEjE;AAAA,iEAAC,UAAK,WAAU,YACb;AAAA,0BAAM;AAAA,oBACN,MAAM,OAAO,+CAAC,UAAK,WAAU,kBAAiB;AAAA;AAAA,sBAAI,MAAM;AAAA,uBAAK,IAAU;AAAA,qBAC1E;AAAA,kBACC,OACC,8CAAC,UAAK,WAAU,kBAAiB,oBAAC,IAChC,QACF,8CAAC,UAAK,WAAU,kBAAiB,oBAAC,IAChC;AAAA;AAAA;AAAA,YACN,KAtBO,MAAM,IAuBf;AAAA,UAEJ,CAAC,GACH;AAAA,WAEJ;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;;;ACjQA,IAAAC,iBAAyE;AAyErE,IAAAC,uBAAA;AA1BJ,SAASC,SAAQ,MAAsB;AACrC,SAAO,KAAK,SAAS,IAAI,KAAK,MAAM,GAAG,CAAC,IAAI;AAC9C;AAQA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMuB;AACrB,QAAM,UAAU,MAAM,QAAQ,KAAK,KAAK,MAAM;AAC9C,QAAM,OAAO,CAAC,MAAM,MAAMA,SAAQ,MAAM,IAAI,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,QAAK;AACzE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,MAAK;AAAA,MACL,gBAAc;AAAA,MACd,eAAa;AAAA,MACb,cAAY,MAAM;AAAA,MAClB,SAAS;AAAA,MACT;AAAA,MACA,WAAW,wFACT,WACI,uCACA,2DACN;AAAA,MAEA;AAAA,sDAAC,SAAI,WAAU,kCAAiC,OAAO,SACpD,mBACH;AAAA,QACC,QACC,8CAAC,SAAI,WAAU,8CAA6C,OAAO,MAAM,MACtE,gBACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AACjB,GAAmD;AACjD,QAAM,CAAC,MAAM,OAAO,QAAI,yBAAoB,EAAE,QAAQ,UAAU,CAAC;AACjE,QAAM,CAAC,YAAY,aAAa,QAAI,yBAAiB,EAAE;AACvD,QAAM,CAAC,YAAY,aAAa,QAAI,yBAAiB,EAAE;AACvD,QAAM,CAAC,YAAY,aAAa,QAAI,yBAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,QAAI,yBAAwB,IAAI;AACtD,QAAM,CAAC,UAAU,WAAW,QAAI,yBAAwB,IAAI;AAC5D,QAAM,CAAC,QAAQ,SAAS,QAAI,yBAAwB,IAAI;AACxD,QAAM,gBAAY,uBAA0B,IAAI;AAEhD,QAAM,cAAU,4BAAY,YAA2B;AACrD,QAAI,CAAC,KAAK,uBAAuB;AAC/B,cAAQ,EAAE,QAAQ,SAAS,SAAS,+CAA+C,CAAC;AACpF;AAAA,IACF;AACA,YAAQ,EAAE,QAAQ,UAAU,CAAC;AAC7B,QAAI;AACF,YAAM,CAAC,QAAQ,QAAQ,OAAO,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,QACvD,KAAK,sBAAsB,WAAW;AAAA,QACtC,KAAK,sBAAsB,SAAS;AAAA,QACpC,KAAK,eAAe,KAAK,aAAa,WAAW,IAAI,QAAQ,QAAQ,IAAI;AAAA,QACzE,KAAK,eAAe,KAAK,aAAa,SAAS,IAAI,QAAQ,QAAQ,IAAI;AAAA,MACzE,CAAC;AACD,kBAAY,KAAK;AACjB,gBAAU,KAAK;AACf,cAAQ,EAAE,QAAQ,SAAS,QAAQ,OAAO,CAAC;AAAA,IAC7C,SAAS,KAAc;AACrB,cAAQ,EAAE,QAAQ,SAAS,SAAS,eAAe,QAAQ,IAAI,UAAU,yBAAyB,CAAC;AAAA,IACrG;AAAA,EACF,GAAG,CAAC,MAAM,aAAa,SAAS,CAAC;AAGjC,gCAAU,MAAM;AACd,QAAI,MAAM;AACR,eAAS,IAAI;AACb,oBAAc,KAAK;AACnB,oBAAc,EAAE;AAChB,oBAAc,EAAE;AAChB,WAAK,QAAQ;AAAA,IACf;AAAA,EACF,GAAG,CAAC,MAAM,OAAO,CAAC;AAGlB,QAAM,iBAAa,wBAAQ,MAAM,IAAI,IAAI,sBAAsB,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAIxF,QAAM,uBAAmB;AAAA,IACvB,MAAO,KAAK,WAAW,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;AAAA,IACvF,CAAC,MAAM,UAAU;AAAA,EACnB;AACA,QAAM,uBAAmB;AAAA,IACvB,MAAO,KAAK,WAAW,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;AAAA,IACvF,CAAC,MAAM,UAAU;AAAA,EACnB;AAGA,gCAAU,MAAM;AACd,QAAI,CAAC,iBAAiB,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,GAAG;AACxD,oBAAc,iBAAiB,CAAC,GAAG,QAAQ,EAAE;AAAA,IAC/C;AAAA,EACF,GAAG,CAAC,kBAAkB,UAAU,CAAC;AACjC,gCAAU,MAAM;AACd,QAAI,CAAC,iBAAiB,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,GAAG;AACxD,oBAAc,iBAAiB,CAAC,GAAG,QAAQ,EAAE;AAAA,IAC/C;AAAA,EACF,GAAG,CAAC,kBAAkB,UAAU,CAAC;AAEjC,QAAM,cAAc,iBAAiB,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,KAAK;AAC3E,QAAM,cAAc,iBAAiB,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,KAAK;AAC3E,QAAM,YAAY,CAAC,cAAc,CAAC,CAAC,eAAe,CAAC,CAAC;AAEpD,QAAM,kBAAc,4BAAY,MAAY;AAC1C,QAAI,CAAC,WAAY,SAAQ;AAAA,EAC3B,GAAG,CAAC,YAAY,OAAO,CAAC;AAExB,QAAM,mBAAe,4BAAY,YAA2B;AAC1D,QAAI,CAAC,eAAe,CAAC,YAAa;AAClC,kBAAc,IAAI;AAClB,aAAS,IAAI;AACb,QAAI;AACF,YAAM;AAAA,QACJ,EAAE,MAAM,YAAY,MAAM,MAAM,YAAY,MAAM,MAAM,YAAY,KAAK;AAAA,QACzE,EAAE,MAAM,YAAY,MAAM,MAAM,YAAY,MAAM,MAAM,YAAY,KAAK;AAAA,MAC3E;AACA,cAAQ;AAAA,IACV,SAAS,KAAc;AACrB,eAAS,eAAe,QAAQ,IAAI,UAAU,6BAA6B;AAC3E,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,aAAa,aAAa,UAAU,OAAO,CAAC;AAGhD,QAAM,YAAY,YAAY,iBAAiB;AAC/C,QAAM,UAAU,UAAU,eAAe;AAEzC,MAAI,CAAC,KAAM,QAAO;AAElB,SACE,8CAAC,SAAM,MAAY,SAAS,aAAa,cAA4B,iBAAiB,WACpF;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAS,CAAC,MAAwB,EAAE,gBAAgB;AAAA,MACpD,eAAa,GAAG,YAAY;AAAA,MAE5B;AAAA,sDAAC,QAAG,WAAU,mCAAkC,2BAAa;AAAA,QAC7D,+CAAC,OAAE,WAAU,8CAA6C;AAAA;AAAA,UACpC;AAAA,UACpB,8CAAC,UAAK,WAAU,iBAAiB,uBAAa,oBAAmB;AAAA,UAAO;AAAA,UAAe;AAAA,UACvF,8CAAC,UAAK,WAAU,iBAAiB,qBAAW,oBAAmB;AAAA,UAAO;AAAA,WAExE;AAAA,QAEC,KAAK,WAAW,aACf,8CAAC,SAAI,WAAU,2CAA0C,kCAAe;AAAA,QAEzE,KAAK,WAAW,WACf,8CAAC,SAAI,WAAU,4CAA4C,eAAK,SAAQ;AAAA,QAEzE,KAAK,WAAW,YACd,iBAAiB,WAAW,IAC3B;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAa,GAAG,YAAY;AAAA,YAC7B;AAAA;AAAA,cACyB,aAAa;AAAA,cAAmB;AAAA;AAAA;AAAA,QAE1D,IAEA,gFACE;AAAA,yDAAC,SAAI,WAAU,SACb;AAAA,2DAAC,UAAK,WAAU,sDAAqD;AAAA;AAAA,cAC3D,YAAY,IAAI,SAAS,MAAM;AAAA,eACzC;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,cAAW;AAAA,gBACX,eAAa,GAAG,YAAY;AAAA,gBAC5B,WAAU;AAAA,gBAET,2BAAiB,IAAI,CAAC,MACrB;AAAA,kBAAC;AAAA;AAAA,oBAEC,OAAO;AAAA,oBACP,UAAU,EAAE,SAAS;AAAA,oBACrB,UAAU;AAAA,oBACV,UAAU,MAAM,cAAc,EAAE,IAAI;AAAA,oBACpC,QAAQ,GAAG,YAAY,kBAAkB,EAAE,IAAI;AAAA;AAAA,kBAL1C,EAAE;AAAA,gBAMT,CACD;AAAA;AAAA,YACH;AAAA,aACF;AAAA,UAEA,+CAAC,SAAI,WAAU,SACb;AAAA,2DAAC,UAAK,WAAU,sDAAqD;AAAA;AAAA,cAC3D,UAAU,IAAI,OAAO,MAAM;AAAA,eACrC;AAAA,YACC,iBAAiB,WAAW,IAC3B,+CAAC,SAAI,WAAU,kCAAiC,eAAa,GAAG,YAAY,iBAAiB;AAAA;AAAA,cACnE,WAAW;AAAA,cAAmB;AAAA,eACxD,IAEA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,cAAW;AAAA,gBACX,eAAa,GAAG,YAAY;AAAA,gBAC5B,WAAU;AAAA,gBAET,2BAAiB,IAAI,CAAC,MACrB;AAAA,kBAAC;AAAA;AAAA,oBAEC,OAAO;AAAA,oBACP,UAAU,EAAE,SAAS;AAAA,oBACrB,UAAU;AAAA,oBACV,UAAU,MAAM,cAAc,EAAE,IAAI;AAAA,oBACpC,QAAQ,GAAG,YAAY,kBAAkB,EAAE,IAAI;AAAA;AAAA,kBAL1C,EAAE;AAAA,gBAMT,CACD;AAAA;AAAA,YACH;AAAA,aAEJ;AAAA,WACF;AAAA,QAGH,SACC,8CAAC,SAAI,WAAU,2BAA0B,eAAa,GAAG,YAAY,UAClE,iBACH;AAAA,QAGF,+CAAC,SAAI,WAAU,+BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL,eAAa,GAAG,YAAY;AAAA,cAC5B,SAAS;AAAA,cACT,UAAU;AAAA,cACV,WAAU;AAAA,cACX;AAAA;AAAA,UAED;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,eAAa,GAAG,YAAY;AAAA,cAC5B,SAAS;AAAA,cACT,UAAU,CAAC;AAAA,cACX,WAAW,yDACT,YACI,6FACA,qEACN;AAAA,cAEC,uBAAa,4BAAuB;AAAA;AAAA,UACvC;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;;;ACzTA,IAAAC,iBAAyE;;;ACnBzE,IAAAC,iBAA8C;AAgCvC,SAAS,SAAY,KAAmB,MAAc,IAAiB;AAC5E,QAAM,OAAO,IAAI,MAAM;AACvB,MACE,SAAS,MACT,OAAO,KACP,KAAK,KACL,QAAQ,KAAK,UACb,MAAM,KAAK,QACX;AACA,WAAO;AAAA,EACT;AACA,QAAM,CAAC,KAAK,IAAI,KAAK,OAAO,MAAM,CAAC;AACnC,OAAK,OAAO,IAAI,GAAG,KAAK;AACxB,SAAO;AACT;AA6BO,SAAS,gBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqD;AACnD,QAAM,CAAC,eAAe,gBAAgB,QAAI,yBAAwB,IAAI;AACtE,QAAM,CAAC,eAAe,gBAAgB,QAAI,yBAAwB,IAAI;AAGtE,QAAM,cAAU,uBAAsB,IAAI;AAC1C,QAAM,eAAW,uBAAO,KAAK;AAC7B,WAAS,UAAU;AAEnB,QAAM,mBAAe;AAAA,IACnB,CAAC,WAAsC;AAAA,MACrC,aAAa;AAAA,QACX,WAAW;AAAA,QACX,aAAa,CAAC,MAAM;AAClB,kBAAQ,UAAU;AAClB,2BAAiB,KAAK;AACtB,cAAI,EAAE,cAAc;AAClB,cAAE,aAAa,gBAAgB;AAE/B,gBAAI;AACF,gBAAE,aAAa,QAAQ,cAAc,OAAO,KAAK,CAAC;AAAA,YACpD,QAAQ;AAAA,YAER;AAAA,UACF;AAAA,QACF;AAAA,QACA,WAAW,MAAM;AACf,kBAAQ,UAAU;AAClB,2BAAiB,IAAI;AACrB,2BAAiB,IAAI;AAAA,QACvB;AAAA,MACF;AAAA,MACA,UAAU;AAAA,QACR,aAAa,CAAC,MAAM;AAClB,cAAI,QAAQ,YAAY,KAAM;AAC9B,YAAE,eAAe;AACjB,2BAAiB,KAAK;AAAA,QACxB;AAAA,QACA,YAAY,CAAC,MAAM;AACjB,cAAI,QAAQ,YAAY,KAAM;AAC9B,YAAE,eAAe;AACjB,cAAI,EAAE,aAAc,GAAE,aAAa,aAAa;AAChD,2BAAiB,CAAC,QAAS,QAAQ,QAAQ,MAAM,KAAM;AAAA,QACzD;AAAA,QACA,aAAa,MAAM;AACjB,2BAAiB,CAAC,QAAS,QAAQ,QAAQ,OAAO,GAAI;AAAA,QACxD;AAAA,QACA,QAAQ,CAAC,MAAM;AACb,YAAE,eAAe;AACjB,gBAAM,OAAO,QAAQ;AACrB,kBAAQ,UAAU;AAClB,2BAAiB,IAAI;AACrB,2BAAiB,IAAI;AACrB,cAAI,SAAS,QAAQ,SAAS,MAAO;AAErC,gBAAM,OAAO,SAAS;AACtB,gBAAM,OAAO,SAAS,MAAM,MAAM,KAAK;AACvC,mBAAS,IAAI;AACb,gBAAM,MAAM,KAAK,IAAI,KAAK;AAC1B,kBAAQ,QAAQ,KAAK,cAAc,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ;AAEtD,qBAAS,IAAI;AACb,sBAAU,GAAG;AAAA,UACf,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MACA,YAAY,kBAAkB;AAAA,MAC9B,cAAc,kBAAkB,SAAS,kBAAkB;AAAA,IAC7D;AAAA,IACA,CAAC,MAAM,UAAU,OAAO,SAAS,eAAe,aAAa;AAAA,EAC/D;AAEA,SAAO,EAAE,cAAc,eAAe,cAAc;AACtD;;;AC3HO,IAAM,gCAAgC;AAYtC,IAAM,gBAAwC,CAAC,QAAQ,WAAW,WAAW,OAAO;AACpF,IAAM,qBAAkD;AAAA,EAC7D,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AACT;AACO,SAAS,cAAc,GAAgC;AAC5D,SAAO,MAAM,UAAU,MAAM,aAAa,MAAM,aAAa,MAAM,UAAU,IAAI;AACnF;AAGO,SAAS,QAAQ,WAAoB,WAA8C;AACxF,MAAI,aAAa,UAAW,QAAO;AACnC,MAAI,UAAW,QAAO;AACtB,MAAI,UAAW,QAAO;AACtB,SAAO;AACT;AAGO,SAAS,0BAA0B,KAA8C;AACtF,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AACV,QAAM,QAAQ,CAAC,MACb,MAAM,QAAQ,CAAC,IACV,EAAE,OAAO,CAAC,MAAM,MAAM,QAAQ,OAAO,MAAM,QAAQ,IACpD,CAAC;AACP,QAAM,eAAe,CAAC,MAA4C;AAChE,UAAM,MAAmC,CAAC;AAC1C,QAAI,KAAK,OAAO,MAAM,UAAU;AAC9B,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,CAA4B,GAAG;AACjE,cAAM,MAAM,cAAc,CAAC;AAC3B,YAAI,IAAK,KAAI,CAAC,IAAI;AAAA,MACpB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,aAAa,MAAM,EAAE,WAAW;AAAA,IAChC,aAAa,MAAM,EAAE,WAAW;AAAA,IAChC,YAAY,aAAa,EAAE,UAAU;AAAA,EACvC;AACF;AAYO,SAAS,eACd,OACA,SACmB;AACnB,QAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAyB,CAAC;AAChC,aAAW,QAAQ,SAAS,CAAC,GAAG;AAC9B,QAAI,SAAS,MAAM;AACjB,UAAI,KAAK,IAAI;AACb;AAAA,IACF;AACA,QAAI,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI,GAAG;AACrC,UAAI,KAAK,IAAI;AACb,WAAK,IAAI,IAAI;AAAA,IACf;AAAA,EACF;AACA,aAAW,MAAM,SAAS;AACxB,QAAI,CAAC,KAAK,IAAI,EAAE,GAAG;AACjB,UAAI,KAAK,EAAE;AACX,WAAK,IAAI,EAAE;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;AAUO,SAAS,cACd,aACA,aACoB;AACpB,QAAM,IAAI,KAAK,IAAI,YAAY,QAAQ,YAAY,MAAM;AACzD,QAAM,OAA2B,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,WAAW,YAAY,CAAC,KAAK;AACnC,UAAM,WAAW,YAAY,CAAC,KAAK;AACnC,SAAK,KAAK,EAAE,UAAU,UAAU,MAAM,QAAQ,aAAa,MAAM,aAAa,IAAI,EAAE,CAAC;AAAA,EACvF;AACA,SAAO;AACT;AAOO,SAAS,eACd,aACA,aACyB;AACzB,QAAM,OAAO,cAAc,aAAa,WAAW,EAAE;AAAA,IACnD,CAAC,MAAM,EAAE,aAAa,QAAQ,EAAE,aAAa;AAAA,EAC/C;AACA,QAAM,eAAe,CAAC,MAA4C;AAChE,QAAI,MAAM,EAAE;AACZ,WAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,KAAM;AACvC,WAAO,EAAE,MAAM,GAAG,GAAG;AAAA,EACvB;AACA,SAAO;AAAA,IACL,aAAa,aAAa,KAAK,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAAA,IACrD,aAAa,aAAa,KAAK,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAAA,EACvD;AACF;AAGO,SAAS,SAAS,OAAmC,GAA8B;AACxF,MAAI,MAAM,UAAU,EAAG,QAAO,MAAM,MAAM;AAC1C,SAAO,CAAC,GAAG,OAAO,GAAG,IAAI,MAAY,IAAI,MAAM,MAAM,EAAE,KAAK,IAAI,CAAC;AACnE;AAGO,SAAS,QACd,aACA,aACwC;AACxC,QAAM,IAAI,KAAK,IAAI,YAAY,QAAQ,YAAY,MAAM;AACzD,SAAO,CAAC,SAAS,aAAa,CAAC,GAAG,SAAS,aAAa,CAAC,CAAC;AAC5D;AAGO,SAAS,WAAW,GAA+B,GAAwC;AAChG,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,QAAI,EAAE,CAAC,MAAM,EAAE,CAAC,EAAG,QAAO;AAAA,EAC5B;AACA,SAAO;AACT;AAUO,SAAS,OAAO,KAAsC;AAC3D,MAAI,IAAI,SAAS,YAAa,QAAO,MAAM,IAAI,QAAQ,IAAI,IAAI,QAAQ;AACvE,MAAI,IAAI,SAAS,WAAY,QAAO,MAAM,IAAI,QAAQ;AACtD,MAAI,IAAI,SAAS,UAAW,QAAO,MAAM,IAAI,QAAQ;AACrD,SAAO;AACT;AAQO,SAAS,cAAc,MAAqC;AACjE,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,MAAM;AACpB,UAAM,OAAO,EAAE,MAAM,CAAC;AACtB,QAAI,EAAE,WAAW,KAAK,GAAG;AACvB,YAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,UAAI,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC;AAC1B,UAAI,IAAI,KAAK,MAAM,MAAM,CAAC,CAAC;AAAA,IAC7B,OAAO;AACL,UAAI,IAAI,IAAI;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;;;AF4NQ,IAAAC,uBAAA;AA/VR,IAAM,wBAAwB;AAC9B,IAAM,mBAAmB;AAEzB,IAAM,yBAAyB;AAE/B,IAAM,aAAgD;AAAA,EACpD,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,WAAW;AACb;AAGA,SAASC,SAAQ,MAAsB;AACrC,SAAO,KAAK,SAAS,IAAI,KAAK,MAAM,GAAG,CAAC,IAAI;AAC9C;AAEO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AACjB,GAAgD;AAC9C,QAAM,CAAC,MAAM,OAAO,QAAI,yBAAoB,EAAE,QAAQ,UAAU,CAAC;AACjE,QAAM,CAAC,UAAU,WAAW,QAAI,yBAAwB,IAAI;AAC5D,QAAM,CAAC,QAAQ,SAAS,QAAI,yBAAwB,IAAI;AAGxD,QAAM,CAAC,aAAa,cAAc,QAAI,yBAA4B,CAAC,CAAC;AACpE,QAAM,CAAC,aAAa,cAAc,QAAI,yBAA4B,CAAC,CAAC;AAGpE,QAAM,CAAC,cAAc,eAAe,QAAI,yBAAsB,MAAM,oBAAI,IAAI,CAAC;AAC7E,QAAM,CAAC,WAAW,YAAY,QAAI,yBAAiC,CAAC,CAAC;AAGrE,QAAM,CAAC,YAAY,aAAa,QAAI,yBAAsC,CAAC,CAAC;AAC5E,QAAM,oBAAgB,uBAAO,UAAU;AACvC,gBAAc,UAAU;AACxB,QAAM,sBAAsB,CAAC,CAAC;AAG9B,QAAM,iBAAa,uBAAO,kBAAkB;AAC5C,aAAW,UAAU;AACrB,QAAM,qBAAiB,uBAAO,WAAW;AACzC,iBAAe,UAAU;AACzB,QAAM,qBAAiB,uBAAO,WAAW;AACzC,iBAAe,UAAU;AACzB,QAAM,sBAAkB,uBAAO,YAAY;AAC3C,kBAAgB,UAAU;AAI1B,QAAM,cAAU,uBAA8C,IAAI;AAClE,QAAM,CAAC,UAAU,WAAW,QAAI,yBAAgD,IAAI;AACpF,QAAM,CAAC,UAAU,WAAW,QAAI,yBAAgD,IAAI;AAEpF,QAAM,iBAAa,wBAAQ,MAAM,IAAI,IAAI,sBAAsB,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC;AACxF,QAAM,iBAAa;AAAA,IACjB,MAAO,KAAK,WAAW,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;AAAA,IACvF,CAAC,MAAM,UAAU;AAAA,EACnB;AACA,QAAM,iBAAa;AAAA,IACjB,MAAO,KAAK,WAAW,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;AAAA,IACvF,CAAC,MAAM,UAAU;AAAA,EACnB;AACA,QAAM,iBAAa,wBAAQ,MAAM,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC;AAC1F,QAAM,iBAAa,wBAAQ,MAAM,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC;AAC1F,QAAM,oBAAgB,uBAAO,UAAU;AACvC,gBAAc,UAAU;AACxB,QAAM,oBAAgB,uBAAO,UAAU;AACvC,gBAAc,UAAU;AAExB,QAAM,cAAU,4BAAY,YAA2B;AACrD,QAAI,CAAC,KAAK,uBAAuB;AAC/B,cAAQ,EAAE,QAAQ,SAAS,SAAS,gDAAgD,CAAC;AACrF;AAAA,IACF;AACA,YAAQ,EAAE,QAAQ,UAAU,CAAC;AAC7B,QAAI;AACF,YAAM,CAAC,QAAQ,QAAQ,OAAO,OAAO,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,QACjE,KAAK,sBAAsB,WAAW;AAAA,QACtC,KAAK,sBAAsB,SAAS;AAAA,QACpC,KAAK,eAAe,KAAK,aAAa,WAAW,IAAI,QAAQ,QAAQ,IAAI;AAAA,QACzE,KAAK,eAAe,KAAK,aAAa,SAAS,IAAI,QAAQ,QAAQ,IAAI;AAAA,QACvE,KAAK,eACD,KAAK,aAAa,mBAAmB,6BAA6B,IAClE,QAAQ,QAAQ,IAAI;AAAA,MAC1B,CAAC;AACD,YAAM,QAAQ,0BAA0B,QAAQ;AAChD,YAAM,QAAQ,IAAI,IAAI,WAAW,WAAW,CAAC,CAAC;AAC9C,YAAM,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AAC5E,YAAM,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AAC5E,YAAM,CAAC,IAAI,EAAE,IAAI;AAAA,QACf,eAAe,OAAO,aAAa,SAAS;AAAA,QAC5C,eAAe,OAAO,aAAa,SAAS;AAAA,MAC9C;AACA,qBAAe,EAAE;AACjB,qBAAe,EAAE;AACjB,oBAAc,OAAO,cAAc,CAAC,CAAC;AACrC,kBAAY,KAAK;AACjB,gBAAU,KAAK;AACf,cAAQ,EAAE,QAAQ,SAAS,QAAQ,OAAO,CAAC;AAAA,IAC7C,SAAS,KAAc;AACrB,cAAQ;AAAA,QACN,QAAQ;AAAA,QACR,SAAS,eAAe,QAAQ,IAAI,UAAU;AAAA,MAChD,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,MAAM,aAAa,WAAW,iBAAiB,CAAC;AAIpD,gCAAU,MAAM;AACd,SAAK,QAAQ;AAAA,EACf,GAAG,CAAC,OAAO,CAAC;AAIZ,gCAAU,MAAM;AACd,QAAI,KAAK,WAAW,QAAS;AAC7B,UAAM,CAAC,IAAI,EAAE,IAAI;AAAA,MACf,eAAe,eAAe,SAAS,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,MACpE,eAAe,eAAe,SAAS,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,IACtE;AACA,QAAI,CAAC,WAAW,IAAI,eAAe,OAAO,EAAG,gBAAe,EAAE;AAC9D,QAAI,CAAC,WAAW,IAAI,eAAe,OAAO,EAAG,gBAAe,EAAE;AAAA,EAChE,GAAG,CAAC,YAAY,YAAY,KAAK,MAAM,CAAC;AAGxC,QAAM,aAAS;AAAA,IACb,CAAC,YAA+B,eAAwC;AACtE,YAAM,OAAO,eAAe,YAAY,UAAU;AAClD,YAAM,CAAC,IAAI,EAAE,IAAI,QAAQ,KAAK,aAAa,KAAK,WAAW;AAC3D,qBAAe,EAAE;AACjB,qBAAe,EAAE;AACjB,UAAI,KAAK,cAAc;AACrB,aAAK,aAAa,mBAAmB,+BAA+B,EAAE,GAAG,MAAM,YAAY,cAAc,QAAQ,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACpI;AAAA,IACF;AAAA,IACA,CAAC,MAAM,iBAAiB;AAAA,EAC1B;AAGA,QAAM,mBAAe;AAAA,IACnB,CAAC,YAAoB,WAA8B;AACjD,oBAAc,CAAC,SAAS;AACtB,cAAM,OAAO,EAAE,GAAG,MAAM,CAAC,UAAU,GAAG,OAAO;AAC7C,YAAI,KAAK,cAAc;AACrB,gBAAM,OAAO,eAAe,eAAe,SAAS,eAAe,OAAO;AAC1E,eAAK,aAAa,mBAAmB,+BAA+B,EAAE,GAAG,MAAM,YAAY,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACnH;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,CAAC,MAAM,iBAAiB;AAAA,EAC1B;AAEA,QAAM,qBAAiB;AAAA,IACrB,CAAC,KAAa,UAAwB;AACpC,YAAM,QAAQ,QAAQ,WAAW,cAAc;AAC/C,YAAM,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,MAAM,KAAK,CAAC;AACnE,UAAI,QAAQ,SAAU,QAAO,MAAM,WAAW;AAAA,UACzC,QAAO,aAAa,IAAI;AAAA,IAC/B;AAAA,IACA,CAAC,aAAa,aAAa,MAAM;AAAA,EACnC;AAEA,QAAM,gBAAY;AAAA,IAChB,CAAC,KAAa,UAAwB;AACpC,YAAM,QAAQ,QAAQ,WAAW,cAAc;AAC/C,YAAM,OAAO,MAAM,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK;AAC/C,UAAI,QAAQ,SAAU,QAAO,MAAM,WAAW;AAAA,UACzC,QAAO,aAAa,IAAI;AAAA,IAC/B;AAAA,IACA,CAAC,aAAa,aAAa,MAAM;AAAA,EACnC;AAEA,QAAM,iBAAa;AAAA,IACjB,CAAC,KAAa,OAAqB;AACjC,YAAM,OAAO,QAAQ;AACrB,cAAQ,UAAU;AAClB,kBAAY,IAAI;AAChB,kBAAY,IAAI;AAChB,UAAI,CAAC,QAAQ,KAAK,QAAQ,OAAO,KAAK,UAAU,GAAI;AACpD,UAAI,QAAQ,SAAU,QAAO,SAAS,aAAa,KAAK,OAAO,EAAE,GAAG,WAAW;AAAA,UAC1E,QAAO,aAAa,SAAS,aAAa,KAAK,OAAO,EAAE,CAAC;AAAA,IAChE;AAAA,IACA,CAAC,aAAa,aAAa,MAAM;AAAA,EACnC;AAEA,QAAM,WAAO,wBAAQ,MAAM,cAAc,aAAa,WAAW,GAAG,CAAC,aAAa,WAAW,CAAC;AAE9F,QAAM,oBAAgB,wBAAQ,MAAM,cAAc,YAAY,GAAG,CAAC,YAAY,CAAC;AAC/E,QAAM,oBAAgB;AAAA,IACpB,MAAM,KAAK,OAAO,CAAC,MAAM;AAAE,YAAM,IAAI,OAAO,CAAC;AAAG,aAAO,MAAM,QAAQ,CAAC,aAAa,IAAI,CAAC;AAAA,IAAG,CAAC,EAAE;AAAA,IAC9F,CAAC,MAAM,YAAY;AAAA,EACrB;AAIA,QAAM,gBAAY;AAAA,IAChB,OAAO,QAAyC;AAC9C,YAAM,MAAM,OAAO,GAAG;AACtB,UAAI,CAAC,OAAO,CAAC,IAAI,QAAQ,gBAAgB,QAAQ,IAAI,GAAG,EAAG;AAC3D,sBAAgB,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC;AAChD,mBAAa,CAAC,SAAS;AACrB,YAAI,EAAE,OAAO,MAAO,QAAO;AAC3B,cAAM,OAAO,EAAE,GAAG,KAAK;AACvB,eAAO,KAAK,GAAG;AACf,eAAO;AAAA,MACT,CAAC;AACD,UAAI;AACF,YAAI,IAAI,SAAS,aAAa;AAC5B,gBAAM,IAAI,IAAI,WAAW,cAAc,QAAQ,IAAI,IAAI,QAAQ,IAAI;AACnE,gBAAM,IAAI,IAAI,WAAW,cAAc,QAAQ,IAAI,IAAI,QAAQ,IAAI;AACnE,cAAI,CAAC,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,wDAAmD;AACjF,gBAAM;AAAA,YACJ,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK;AAAA,YAC3C,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK;AAAA,UAC7C;AAAA,QACF,WAAW,IAAI,SAAS,YAAY;AAClC,gBAAM,IAAI,IAAI,WAAW,cAAc,QAAQ,IAAI,IAAI,QAAQ,IAAI;AACnE,cAAI,CAAC,EAAG,OAAM,IAAI,MAAM,wDAAmD;AAC3E,gBAAM,MAAM,cAAc,QAAQ,EAAE,IAAI,KAAK;AAC7C,cAAI,QAAQ,UAAU,yBAAyB;AAC7C,kBAAM,wBAAwB,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG;AAAA,UACxF,OAAO;AACL,kBAAM,aAAa,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,GAAG,OAAO,mBAAmB,EAAE,IAAI,CAAC;AAAA,UACpG;AAAA,QACF,OAAO;AACL,gBAAM,IAAI,IAAI,WAAW,cAAc,QAAQ,IAAI,IAAI,QAAQ,IAAI;AACnE,cAAI,CAAC,EAAG,OAAM,IAAI,MAAM,wDAAmD;AAC3E,gBAAM,MAAM,cAAc,QAAQ,EAAE,IAAI,KAAK;AAC7C,cAAI,QAAQ,UAAU,yBAAyB;AAC7C,kBAAM,wBAAwB,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG;AAAA,UACvF,OAAO;AACL,kBAAM,aAAa,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,GAAG,MAAM,mBAAmB,EAAE,IAAI,CAAC;AAAA,UACnG;AAAA,QACF;AAAA,MACF,SAAS,KAAc;AACrB,qBAAa,CAAC,UAAU;AAAA,UACtB,GAAG;AAAA,UACH,CAAC,GAAG,GAAG,eAAe,QAAQ,IAAI,UAAU;AAAA,QAC9C,EAAE;AAAA,MACJ,UAAE;AACA,wBAAgB,CAAC,SAAS;AACxB,gBAAM,OAAO,IAAI,IAAI,IAAI;AACzB,eAAK,OAAO,GAAG;AACf,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,CAAC,mBAAmB,cAAc,uBAAuB;AAAA,EAC3D;AAGA,QAAM,gBAAY,4BAAY,YAA2B;AACvD,UAAM,WAAW,cAAc,eAAe,SAAS,eAAe,OAAO,EAAE,OAAO,CAAC,MAAM;AAC3F,YAAM,IAAI,OAAO,CAAC;AAClB,aAAO,MAAM,QAAQ,CAAC,gBAAgB,QAAQ,IAAI,CAAC;AAAA,IACrD,CAAC;AACD,QAAI,SAAS,WAAW,EAAG;AAC3B,QAAI,SAAS;AACb,UAAM,SAAS,YAA2B;AACxC,aAAO,SAAS,SAAS,QAAQ;AAC/B,cAAM,MAAM,SAAS,MAAM;AAC3B,kBAAU;AACV,cAAM,UAAU,GAAG;AAAA,MACrB;AAAA,IACF;AACA,UAAM,QAAQ;AAAA,MACZ,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,wBAAwB,SAAS,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC;AAAA,IAC1F;AAAA,EACF,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,YAAY,YAAY;AAC9B,QAAM,UAAU,UAAU;AAE1B,QAAM,gBAAgB,CACpB,KACA,OACA,YASI;AAAA,IACJ,WAAW,CAAC;AAAA,IACZ,aAAa,CAAC,MAAM;AAClB,UAAI,OAAQ;AACZ,cAAQ,UAAU,EAAE,KAAK,MAAM;AAC/B,kBAAY,EAAE,KAAK,MAAM,CAAC;AAC1B,UAAI,EAAE,cAAc;AAClB,UAAE,aAAa,gBAAgB;AAC/B,YAAI;AACF,YAAE,aAAa,QAAQ,cAAc,OAAO,KAAK,CAAC;AAAA,QACpD,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW,MAAM;AACf,cAAQ,UAAU;AAClB,kBAAY,IAAI;AAChB,kBAAY,IAAI;AAAA,IAClB;AAAA,IACA,aAAa,CAAC,MAAM;AAClB,YAAM,IAAI,QAAQ;AAClB,UAAI,CAAC,KAAK,EAAE,QAAQ,IAAK;AACzB,QAAE,eAAe;AACjB,kBAAY,EAAE,KAAK,MAAM,CAAC;AAAA,IAC5B;AAAA,IACA,YAAY,CAAC,MAAM;AACjB,YAAM,IAAI,QAAQ;AAClB,UAAI,CAAC,KAAK,EAAE,QAAQ,IAAK;AACzB,QAAE,eAAe;AACjB,UAAI,EAAE,aAAc,GAAE,aAAa,aAAa;AAAA,IAClD;AAAA,IACA,aAAa,MAAM;AACjB,kBAAY,CAAC,QAAS,OAAO,IAAI,QAAQ,OAAO,IAAI,UAAU,QAAQ,OAAO,GAAI;AAAA,IACnF;AAAA,IACA,QAAQ,CAAC,MAAM;AACb,QAAE,eAAe;AACjB,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,aAAa,CAAC,KAAa,OAAe,WAA8C;AAC5F,UAAM,OAAO,QAAQ,WAAW,aAAa;AAC7C,UAAM,QAAQ,SAAS,KAAK,IAAI,MAAM,IAAI;AAC1C,UAAM,SAAS,WAAW,QAAQ,cAAc,IAAI,MAAM;AAC1D,UAAM,aAAa,UAAU,QAAQ,OAAO,SAAS,UAAU;AAC/D,UAAM,eAAe,UAAU,QAAQ,OAAO,SAAS,UAAU,SAAS,CAAC;AAC3E,UAAM,OACJ;AACF,UAAM,OAAO,eACT,uCACA;AAEJ,QAAI,WAAW,MAAM;AACnB,aACE;AAAA,QAAC;AAAA;AAAA,UACE,GAAG,cAAc,KAAK,OAAO,KAAK;AAAA,UACnC,eAAa,GAAG,YAAY,IAAI,GAAG,QAAQ,KAAK;AAAA,UAChD,WAAW,GAAG,IAAI,IAAI,IAAI,oDACxB,aAAa,eAAe,YAC9B;AAAA,UAEA;AAAA,0DAAC,UAAK,WAAU,sDAAqD,+BAAO;AAAA,YAC5E;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,eAAa,GAAG,YAAY,IAAI,GAAG,eAAe,KAAK;AAAA,gBACvD,SAAS,MAAM,UAAU,KAAK,KAAK;AAAA,gBACnC,OAAM;AAAA,gBACN,WAAU;AAAA,gBACX;AAAA;AAAA,YAED;AAAA;AAAA;AAAA,MACF;AAAA,IAEJ;AAEA,UAAM,UAAU,QAAQ,MAAM,QAAQ,KAAK,KAAK,MAAM,OAAO;AAC7D,UAAM,OAAO,QAAQ,CAAC,MAAM,MAAMA,SAAQ,MAAM,IAAI,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,QAAK,IAAI;AACrF,WACE;AAAA,MAAC;AAAA;AAAA,QACE,GAAG,cAAc,KAAK,OAAO,MAAM;AAAA,QACpC,eAAa,GAAG,YAAY,IAAI,GAAG,SAAS,MAAM;AAAA,QAClD,cAAY;AAAA,QACZ,WAAW,GAAG,IAAI,IAAI,IAAI,IAAI,aAAa,eAAe,EAAE,IAC1D,SAAS,eAAe,oCAC1B;AAAA,QACA,OAAO,QAAQ,MAAM,OAAO;AAAA,QAE5B,yDAAC,SAAI,WAAU,0BACb;AAAA,wDAAC,UAAK,WAAU,kDAAiD,eAAW,MAAC,oBAE7E;AAAA,UACA,+CAAC,SAAI,WAAU,kBACb;AAAA,0DAAC,SAAI,WAAU,kCAAkC,mBAAQ;AAAA,YACxD,QAAQ,8CAAC,SAAI,WAAU,8CAA8C,gBAAK;AAAA,aAC7E;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,eAAa,GAAG,YAAY,IAAI,GAAG,eAAe,KAAK;AAAA,cACvD,SAAS,MAAM,eAAe,KAAK,KAAK;AAAA,cACxC,UAAU;AAAA,cACV,OAAM;AAAA,cACN,WAAU;AAAA,cACX;AAAA;AAAA,UAED;AAAA,WACF;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,SACE,+CAAC,SAAI,WAAU,aAAY,eAAa,GAAG,YAAY,QAErD;AAAA,mDAAC,SAAI,WAAU,2EACb;AAAA,qDAAC,OAAE,WAAU,mDACX;AAAA,sDAAC,UAAK,WAAU,iBAAiB,qBAAU;AAAA,QAAO;AAAA,QAAG;AAAA,QACrD,8CAAC,UAAK,WAAU,iBAAiB,mBAAQ;AAAA,QACxC,cAAc,SAAM,WAAW,KAAK;AAAA,QAAG;AAAA,SAE1C;AAAA,MACA,+CAAC,SAAI,WAAU,oCACZ;AAAA,qBAAa,OAAO,KACnB,+CAAC,UAAK,WAAU,iDAAgD,eAAa,GAAG,YAAY,mBACzF;AAAA,uBAAa;AAAA,UAAK;AAAA,WACrB;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,eAAa,GAAG,YAAY;AAAA,YAC5B,SAAS;AAAA,YACT,UAAU,kBAAkB;AAAA,YAC5B,OAAM;AAAA,YACN,WAAW,6FACT,gBAAgB,IACZ,6FACA,qEACN;AAAA,YACD;AAAA;AAAA,cACY,gBAAgB,IAAI,KAAK,aAAa,MAAM;AAAA;AAAA;AAAA,QACzD;AAAA,SACF;AAAA,OACF;AAAA,IAGA,+CAAC,SAAI,WAAU,uCACb;AAAA,qDAAC,UAAK,WAAU,+DAA8D;AAAA;AAAA,QACnE;AAAA,QAAU;AAAA,SACrB;AAAA,MACA,8CAAC,UAAK,WAAU,uEAAsE,wBAEtF;AAAA,MACA,+CAAC,UAAK,WAAU,0EAAyE;AAAA;AAAA,QAC9E;AAAA,QAAQ;AAAA,SACnB;AAAA,OACF;AAAA,IAGC,KAAK,WAAW,aACf,8CAAC,SAAI,WAAU,2CAA0C,kCAAe;AAAA,IAEzE,KAAK,WAAW,WACf,8CAAC,SAAI,WAAU,4CAA2C,eAAa,GAAG,YAAY,UACnF,eAAK,SACR;AAAA,IAED,KAAK,WAAW,YACd,KAAK,WAAW,IACf,+CAAC,SAAI,WAAU,2CAA0C,eAAa,GAAG,YAAY,UAAU;AAAA;AAAA,MACzB;AAAA,MAAU;AAAA,MAAK;AAAA,MAAS;AAAA,MAAI;AAAA,OAElG,IAEA,8CAAC,SAAI,WAAU,aACZ,eAAK,IAAI,CAAC,KAAK,MAAM;AACpB,YAAM,MAAM,OAAO,GAAG;AACtB,YAAM,iBAAiB,QAAQ,QAAQ,aAAa,IAAI,GAAG;AAC3D,YAAM,SAAS,QAAQ,OAAO,UAAU,GAAG,IAAI;AAC/C,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,eAAa,GAAG,YAAY,QAAQ,CAAC;AAAA,UACrC,WAAU;AAAA,UAET;AAAA,uBAAW,UAAU,GAAG,IAAI,QAAQ;AAAA,YAGrC,+CAAC,SAAI,WAAU,8CACZ;AAAA,eAAC,IAAI,OACJ,8CAAC,UAAK,WAAU,iCAAgC,oBAAC,IAC/C,IAAI,SAAS,cACf;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAa,GAAG,YAAY,SAAS,CAAC;AAAA,kBACtC,WAAU;AAAA,kBAET,qBAAW,IAAI,IAAI;AAAA;AAAA,cACtB,IACE,sBACF,+CAAC,SAAI,WAAU,2BAA0B,eAAa,GAAG,YAAY,SAAS,CAAC,IAC7E;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,eAAa,GAAG,YAAY,WAAW,CAAC;AAAA,oBACxC,OAAO,WAAY,IAAI,YAAY,IAAI,QAAmB,KAAK;AAAA,oBAC/D,UAAU,CAAC,MAAM;AACf,4BAAM,KAAK,IAAI,YAAY,IAAI;AAC/B,0BAAI,GAAI,cAAa,IAAI,EAAE,OAAO,KAAoB;AAAA,oBACxD;AAAA,oBACA,WAAU;AAAA,oBAET,wBAAc,IAAI,CAAC,QAClB,8CAAC,YAAiB,OAAO,KACtB,6BAAmB,GAAG,KADZ,GAEb,CACD;AAAA;AAAA,gBACH;AAAA,gBACA,8CAAC,UAAK,WAAU,6BAA6B,cAAI,SAAS,aAAa,QAAQ,MAAK;AAAA,iBACtF,IAEA;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAa,GAAG,YAAY,SAAS,CAAC;AAAA,kBACtC,WAAU;AAAA,kBAET,qBAAW,IAAI,IAAI;AAAA;AAAA,cACtB;AAAA,cAED,iBACC,8CAAC,SAAI,WAAU,UACb;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAS;AAAA,kBACT,aAAY;AAAA,kBACZ,YAAW;AAAA,kBACX,qBACE,IAAI,SAAS,cAAc,wBAAwB;AAAA;AAAA,cAEvD,GACF,IAEA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,eAAa,GAAG,YAAY,WAAW,CAAC;AAAA,kBACxC,SAAS,MAAM,UAAU,GAAG;AAAA,kBAC5B,UAAU,CAAC,IAAI;AAAA,kBACf,WAAW,kFACT,IAAI,OACA,6FACA,qEACN;AAAA,kBACD;AAAA;AAAA,cAED;AAAA,cAED,UACC;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAa,GAAG,YAAY,cAAc,CAAC;AAAA,kBAC3C,WAAU;AAAA,kBAET;AAAA;AAAA,cACH;AAAA,eAEJ;AAAA,YAEC,WAAW,UAAU,GAAG,IAAI,QAAQ;AAAA;AAAA;AAAA,QAhFhC;AAAA,MAiFP;AAAA,IAEJ,CAAC,GACH;AAAA,KAEN;AAEJ;;;AGppBA,IAAAC,iBAAwD;AAwIpD,IAAAC,uBAAA;AA1GJ,SAAS,WAAW,OAAwB;AAC1C,MAAI,CAAC,SAAS,SAAS,EAAG,QAAO;AACjC,QAAM,KAAK,QAAQ,QAAQ;AAC3B,MAAI,MAAM,EAAG,QAAO,GAAG,GAAG,QAAQ,CAAC,CAAC;AACpC,QAAM,KAAK,QAAQ,QAAQ;AAC3B,SAAO,GAAG,KAAK,MAAM,EAAE,CAAC;AAC1B;AAEO,IAAM,qBAAwD,CAAC;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AACF,MAAM;AACJ,QAAM,CAAC,QAAQ,SAAS,QAAI,yBAA6B,MAAM;AAC/D,QAAM,CAAC,UAAU,WAAW,QAAI,yBAAS,CAAC;AAC1C,QAAM,CAAC,cAAc,eAAe,QAAI,yBAAwB,IAAI;AAEpE,gCAAU,MAAM;AACd,UAAM,QAAQ,KAAK,qBAAqB,QAAQ,CAAC,MAAM;AACrD,gBAAU,EAAE,MAA4B;AACxC,kBAAY,EAAE,QAAQ;AACtB,UAAI,EAAE,WAAW,SAAS;AACxB,wBAAgB,EAAE,WAAW,iBAAiB;AAAA,MAChD,WAAW,EAAE,WAAW,YAAY;AAClC,wBAAgB,IAAI;AACpB,mBAAW,MAAM,qBAAqB,GAAG,GAAG;AAAA,MAC9C,OAAO;AACL,wBAAgB,IAAI;AAAA,MACtB;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,QAAQ,kBAAkB,CAAC;AAErC,QAAM,kBAAc,4BAAY,YAA2B;AACzD,QAAI,WAAW,UAAU,WAAW,QAAS;AAC7C,QAAI;AACF,gBAAU,aAAa;AACvB,kBAAY,CAAC;AACb,sBAAgB,IAAI;AACpB,YAAM,SAAS,MAAM,KAAK,wBAAwB,MAAM;AACxD,UAAI,CAAC,OAAO,SAAS;AACnB,kBAAU,OAAO;AACjB,wBAAgB,OAAO,SAAS,iBAAiB;AAAA,MACnD;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,sCAAsC,GAAG;AACvD,gBAAU,OAAO;AACjB,sBAAgB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAClE;AAAA,EACF,GAAG,CAAC,MAAM,QAAQ,MAAM,CAAC;AAEzB,QAAM,YACJ,WAAW,iBACX,WAAW,eACX,WAAW,gBACX,WAAW;AACb,QAAM,aAAa,aAAa,WAAW;AAE3C,QAAM,eAAe,MAAM;AACzB,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,GAAG,QAAQ;AAAA,MACpB,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO,YAAY,UACf,YAAY,WAAW,GAAG,YAAY,KAAK,WAAW,SAAS,CAAC,MAAM,EAAE,KACxE;AAAA,IACR;AAAA,EACF,GAAG;AAEH,QAAM,WAAW,MAAM;AACrB,QAAI,WAAW,QAAS,QAAO,gBAAgB;AAC/C,QAAI,UAAW,QAAO,GAAG,WAAW,WAAM,WAAW;AACrD,QAAI,WAAW,WAAY,QAAO;AAClC,WAAO,YAAY,WAAW,GAAG,YAAY,KAAK,WAAW,SAAS,CAAC,MAAM,EAAE;AAAA,EACjF,GAAG;AAEH,QAAM,cACJ,YAAY,UACR,mEACA;AAEN,MAAI;AACJ,MAAI,WAAW,SAAS;AACtB,gBAAY,GAAG,WAAW;AAAA,EAC5B,WAAW,WAAW,YAAY;AAChC,gBAAY,GAAG,WAAW;AAAA,EAC5B,WAAW,YAAY;AACrB,gBAAY,GAAG,WAAW;AAAA,EAC5B,OAAO;AACL,gBAAY,GAAG,WAAW;AAAA,EAC5B;AAEA,SACE,+CAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,eAAa,wBAAwB,MAAM;AAAA,QAC3C,SAAS;AAAA,QACT,UAAU;AAAA,QACV;AAAA,QACA,OAAO;AAAA,QAEN;AAAA;AAAA,IACH;AAAA,IACC,YAAY,WAAW,WAAW,WAAW,gBAC5C,8CAAC,SAAI,WAAU,gCAA+B,eAAa,uBAAuB,MAAM,IACrF,wBACH;AAAA,KAEJ;AAEJ;;;AC7HM,IAAAC,uBAAA;AARC,IAAM,oBAAsD,CAAC;AAAA,EAClE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,MAAI,WAAW,YAAY;AACzB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,eAAa,4BAA4B,KAAK,MAAM;AAAA,QACpD,WAAU;AAAA,QACX;AAAA;AAAA,IAED;AAAA,EAEJ;AAEA,QAAM,WACJ,WAAW,UACP,GAAG,KAAK,WAAW,sBACnB,GAAG,KAAK,WAAW;AAEzB,QAAM,WACJ,WAAW,UACP,+CACA,KAAK;AAEX,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAa,mBAAmB,KAAK,MAAM;AAAA,MAC3C,WAAU;AAAA,MAEV;AAAA,sDAAC,SAAI,WAAU,uDACZ,qBAAW,UAAU,qBAAqB,gCAC7C;AAAA,QACA,8CAAC,SAAI,WAAU,gCAAgC,oBAAS;AAAA,QACxD,8CAAC,SAAI,WAAU,wCAAwC,oBAAS;AAAA,QAChE;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,QAAQ,KAAK;AAAA,YACb,aAAa,KAAK;AAAA,YAClB,WAAW,KAAK;AAAA,YAChB,SAAQ;AAAA,YACR;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ;;;ACjEA,IAAAC,iBAAmD;;;ACyB5C,SAAS,aACd,aACA,MACA,eACe;AACf,QAAM,EAAE,QAAQ,kBAAkB,WAAW,IAAI;AACjD,QAAM,WAA2B,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,kBAAkB,KAAK;AACzC,aAAS,KAAK,YAAY,eAAe,CAAC,CAAC;AAAA,EAC7C;AACA,QAAM,kBACJ,OAAO,kBAAkB,YAAY,gBAAgB,SAAS,gBAAgB;AAChF,QAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,kBAAkB,IAAI,CAAC;AACpE,QAAM,MAAM,IAAI,aAAa,OAAO,CAAC;AACrC,WAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,UAAM,WAAW,IAAI;AACrB,UAAM,SAAS,KAAK,IAAI,QAAQ,WAAW,aAAa;AACxD,QAAI,YAAY,QAAQ;AAEtB,UAAI,IAAI,CAAC,IAAI;AACb,UAAI,IAAI,IAAI,CAAC,IAAI;AACjB;AAAA,IACF;AACA,QAAI,KAAK;AACT,QAAI,KAAK;AACT,aAAS,IAAI,UAAU,IAAI,QAAQ,KAAK;AACtC,UAAI,IAAI;AACR,eAAS,IAAI,GAAG,IAAI,kBAAkB,KAAK;AACzC,aAAK,SAAS,CAAC,EAAE,CAAC;AAAA,MACpB;AACA,WAAK;AACL,UAAI,IAAI,GAAI,MAAK;AACjB,UAAI,IAAI,GAAI,MAAK;AAAA,IACnB;AACA,QAAI,CAAC,OAAO,SAAS,EAAE,EAAG,MAAK;AAC/B,QAAI,CAAC,OAAO,SAAS,EAAE,EAAG,MAAK;AAC/B,QAAI,IAAI,CAAC,IAAI;AACb,QAAI,IAAI,IAAI,CAAC,IAAI;AAAA,EACnB;AACA,SAAO,EAAE,YAAY,cAAc,iBAAiB,OAAO,IAAI;AACjE;AAQO,SAAS,aACd,QACA,OACA,UAAkC,CAAC,GAC7B;AACN,QAAM,MAAM,OAAO,oBAAoB;AACvC,QAAM,WAAW,OAAO;AACxB,QAAM,YAAY,OAAO;AACzB,MAAI,aAAa,KAAK,cAAc,EAAG;AACvC,SAAO,QAAQ,KAAK,MAAM,WAAW,GAAG;AACxC,SAAO,SAAS,KAAK,MAAM,YAAY,GAAG;AAC1C,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,IAAK;AACV,MAAI,MAAM,KAAK,GAAG;AAClB,MAAI,UAAU,GAAG,GAAG,UAAU,SAAS;AACvC,MAAI,YAAY,QAAQ,aAAa;AAErC,QAAM,OAAO,MAAM,MAAM,SAAS;AAClC,QAAM,MAAM,YAAY;AACxB,WAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,UAAM,SAAS,KAAK,MAAO,IAAI,WAAY,IAAI;AAC/C,UAAM,KAAK,MAAM,MAAM,SAAS,CAAC;AACjC,UAAM,KAAK,MAAM,MAAM,SAAS,IAAI,CAAC;AACrC,UAAM,OAAO,MAAM,KAAK;AACxB,UAAM,OAAO,MAAM,KAAK;AACxB,QAAI,SAAS,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG,OAAO,IAAI,CAAC;AAAA,EACnD;AACF;;;ADXI,IAAAC,uBAAA;AAnEG,IAAM,eAA4C,CAAC;AAAA,EACxD;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,gBAAY,uBAA0B,IAAI;AAChD,QAAM,CAAC,OAAO,QAAQ,QAAI,yBAA+B,IAAI;AAG7D,gCAAU,MAAM;AACd,QAAI,YAAY;AAChB,QAAI,eAAoC;AAExC,KAAC,YAAY;AACX,UAAI;AACF,cAAM,QAAQ,MAAM,KAAK,kBAAkB,QAAQ;AACnD,YAAI,UAAW;AAIf,cAAM,cACH,OAA6D,gBAC7D,OAAmE;AACtE,uBAAe,IAAI,YAAY;AAI/B,cAAM,cAAc,MAAM,aAAa,gBAAgB,MAAM,MAAM,CAAC,CAAC;AACrE,YAAI,UAAW;AAEf,cAAM,WAAW,aAAa,aAAa,MAAM,aAAa;AAC9D,iBAAS,QAAQ;AAAA,MACnB,SAAS,KAAK;AAGZ,gBAAQ,KAAK,mCAAmC,UAAU,GAAG;AAAA,MAC/D,UAAE;AACA,YAAI,cAAc;AAChB,uBAAa,MAAM,EAAE,MAAM,MAAM;AAAA,UAAe,CAAC;AAAA,QACnD;AAAA,MACF;AAAA,IACF,GAAG;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,UAAU,MAAM,aAAa,CAAC;AAIxC,gCAAU,MAAM;AACd,QAAI,CAAC,MAAO;AACZ,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ;AACb,iBAAa,QAAQ,OAAO,YAAY,EAAE,UAAU,IAAI,MAAS;AAEjE,UAAM,WAAW,IAAI,eAAe,MAAM;AACxC,mBAAa,QAAQ,OAAO,YAAY,EAAE,UAAU,IAAI,MAAS;AAAA,IACnE,CAAC;AACD,aAAS,QAAQ,MAAM;AACvB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,OAAO,SAAS,CAAC;AAErB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,eAAY;AAAA,MACZ,WAAW,aAAa;AAAA;AAAA,EAC1B;AAEJ;;;AE5FA,IAAAC,iBAAyC;AA2GrC,IAAAC,uBAAA;AA5FG,IAAM,oBAAsD,CAAC;AAAA,EAClE;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AACF,MAAM;AACJ,QAAM,gBAAY,uBAA0B,IAAI;AAChD,QAAM,cAAU,uBAAqB,IAAI,aAAa,OAAO,CAAC;AAC9D,QAAM,kBAAc,uBAAO,CAAC;AAC5B,QAAM,aAAS,uBAAsB,IAAI;AAIzC,gCAAU,MAAM;AACd,QAAI,QAAQ,QAAQ,WAAW,SAAS;AACtC,YAAM,OAAO,IAAI,aAAa,OAAO;AACrC,YAAM,OAAO,QAAQ;AACrB,YAAM,UAAU,KAAK,IAAI,KAAK,QAAQ,OAAO;AAE7C,eAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,aAAK,CAAC,IAAI,KAAK,CAAC;AAAA,MAClB;AACA,cAAQ,UAAU;AAClB,kBAAY,UAAU,YAAY,UAAU;AAAA,IAC9C;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,gCAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AAEX,UAAI,OAAO,YAAY,MAAM;AAC3B,6BAAqB,OAAO,OAAO;AACnC,eAAO,UAAU;AAAA,MACnB;AACA;AAAA,IACF;AAEA,UAAM,OAAO,MAAY;AACvB,YAAM,SAAS,UAAU;AAEzB,YAAM,MACJ,UAAU,OACN,IACA,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,SAAS,MAAM,EAAE,CAAC;AACjD,YAAM,OAAO,QAAQ;AACrB,WAAK,YAAY,OAAO,IAAI;AAC5B,kBAAY,WAAW,YAAY,UAAU,KAAK,KAAK;AAGvD,YAAM,SAAS,UAAU;AACzB,UAAI,QAAQ;AACV,cAAM,MAAM,OAAO,oBAAoB;AACvC,cAAM,OAAO,OAAO;AACpB,cAAM,OAAO,OAAO;AACpB,YAAI,OAAO,KAAK,OAAO,GAAG;AACxB,cAAI,OAAO,UAAU,KAAK,MAAM,OAAO,GAAG,KAAK,OAAO,WAAW,KAAK,MAAM,OAAO,GAAG,GAAG;AACvF,mBAAO,QAAQ,KAAK,MAAM,OAAO,GAAG;AACpC,mBAAO,SAAS,KAAK,MAAM,OAAO,GAAG;AAAA,UACvC;AACA,gBAAM,MAAM,OAAO,WAAW,IAAI;AAClC,cAAI,KAAK;AACP,gBAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;AACrC,gBAAI,UAAU,GAAG,GAAG,MAAM,IAAI;AAC9B,gBAAI,YAAY,aAAa;AAC7B,kBAAM,MAAM,OAAO;AACnB,kBAAM,OAAO,KAAK;AAClB,kBAAM,OAAO,OAAO;AAEpB,kBAAM,QAAQ,YAAY;AAC1B,qBAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,oBAAM,WAAW,QAAQ,KAAK;AAC9B,oBAAM,IAAI,KAAK,OAAO;AACtB,oBAAM,OAAO,IAAI;AACjB,kBAAI,SAAS,IAAI,MAAM,MAAM,MAAM,KAAK,IAAI,GAAG,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,YAC7E;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,aAAO,UAAU,sBAAsB,IAAI;AAAA,IAC7C;AACA,WAAO,UAAU,sBAAsB,IAAI;AAE3C,WAAO,MAAM;AACX,UAAI,OAAO,YAAY,MAAM;AAC3B,6BAAqB,OAAO,OAAO;AACnC,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,WAAW,SAAS,CAAC;AAEjC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,eAAY;AAAA,MACZ,WAAW,aAAa;AAAA;AAAA,EAC1B;AAEJ;;;AC3GA,IAAAC,iBAAyE;AAmLnE,IAAAC,uBAAA;AAhLN,IAAM,mBAAmB;AACzB,IAAM,iBAAiB;AACvB,IAAM,0BAA0B;AAChC,IAAM,iBAAiB;AAiBhB,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,WAAW;AACb,GAA4C;AAC1C,QAAM,eAAW,uBAA8B,IAAI;AAEnD,QAAM,CAAC,aAAa,cAAc,QAAI,yBAAiB,aAAa;AACpE,QAAM,CAAC,YAAY,aAAa,QAAI,yBAAS,KAAK;AAGlD,gCAAU,MAAM;AACd,QAAI,CAAC,WAAY,gBAAe,aAAa;AAAA,EAC/C,GAAG,CAAC,eAAe,UAAU,CAAC;AAI9B,QAAM,aAAa,WAAW,eAAe;AAC7C,QAAM,cAAc,WAAW,gBAAgB;AAC/C,QAAM,oBAAgB,wBAAQ,MAAM;AAGlC,WAAO,KAAK,MAAO,KAAK,aAAc,UAAU;AAAA,EAClD,GAAG,CAAC,YAAY,UAAU,CAAC;AAC3B,QAAM,eAAe,gBAAgB;AAGrC,QAAM,uBAAmB;AAAA,IACvB,CAAC,WAA2B;AAC1B,YAAM,UAAU,KAAK,IAAI,CAAC,cAAc,KAAK,IAAI,cAAc,MAAM,CAAC;AACtE,cAAQ,UAAU,iBAAiB,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,QAAM,uBAAmB;AAAA,IACvB,CAAC,aAA6B;AAC5B,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AACjD,aAAO,KAAK,MAAM,UAAU,IAAI,eAAe,YAAY;AAAA,IAC7D;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAUA,QAAM,kBAAc,wBAAQ,MAAM;AAChC,QAAI,CAAC,aAAa,UAAU,MAAM,WAAW,EAAG,QAAO,CAAC;AACxD,UAAM,WAAW,UAAU,MAAM,CAAC;AAIlC,UAAM,YAAY,UAAU,MAAM,IAAI,CAAC,MAAM,IAAI,QAAQ;AACzD,UAAM,YAAY,UAAU,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;AAClD,WAAO,CAAC,GAAG,WAAW,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,EAC1D,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,iBAAa;AAAA,IACjB,CAAC,WAA2B;AAC1B,UAAI,YAAY,WAAW,EAAG,QAAO;AAGrC,UAAI,OAAO,YAAY,CAAC;AACxB,UAAI,WAAW,KAAK,IAAI,SAAS,IAAI;AACrC,iBAAW,KAAK,aAAa;AAC3B,cAAM,IAAI,KAAK,IAAI,SAAS,CAAC;AAC7B,YAAI,IAAI,UAAU;AAChB,iBAAO;AACP,qBAAW;AAAA,QACb;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,CAAC,WAAW;AAAA,EACd;AAGA,QAAM,wBAAoB;AAAA,IACxB,CAAC,MAAgD;AAC/C,UAAI,YAAY,CAAC,UAAW;AAC5B,QAAE,eAAe;AACjB,YAAM,QAAQ,SAAS;AACvB,UAAI,CAAC,MAAO;AACZ,YAAM,kBAAkB,EAAE,SAAS;AACnC,oBAAc,IAAI;AAElB,YAAM,kBAAkB,CAAC,SAAiB,cAA+B;AACvE,cAAM,OAAO,MAAM,sBAAsB;AACzC,cAAM,YAAY,UAAU,KAAK,QAAQ,KAAK;AAC9C,cAAM,MAAM,iBAAiB,QAAQ;AACrC,eAAO,YAAY,MAAM,WAAW,GAAG;AAAA,MACzC;AAGA,qBAAe,gBAAgB,EAAE,SAAS,EAAE,QAAQ,CAAC;AAErD,YAAM,SAAS,CAAC,OAA2B;AACzC,uBAAe,gBAAgB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAAA,MACzD;AACA,YAAM,OAAO,CAAC,OAA2B;AACvC,cAAM,QAAQ,gBAAgB,GAAG,SAAS,GAAG,QAAQ;AACrD,cAAM,sBAAsB,EAAE,SAAS;AACvC,cAAM,oBAAoB,eAAe,MAAM;AAC/C,cAAM,oBAAoB,aAAa,IAAI;AAC3C,cAAM,oBAAoB,iBAAiB,IAAI;AAC/C,sBAAc,KAAK;AACnB,uBAAe,KAAK;AACpB,iBAAS,KAAK;AAAA,MAChB;AAEA,YAAM,iBAAiB,eAAe,MAAM;AAC5C,YAAM,iBAAiB,aAAa,IAAI;AACxC,YAAM,iBAAiB,iBAAiB,IAAI;AAAA,IAC9C;AAAA,IACA,CAAC,UAAU,WAAW,kBAAkB,UAAU,UAAU;AAAA,EAC9D;AAGA,QAAM,wBAAoB,4BAAY,MAAY;AAChD,QAAI,SAAU;AACd,mBAAe,CAAC;AAChB,aAAS,CAAC;AAAA,EACZ,GAAG,CAAC,UAAU,QAAQ,CAAC;AAEvB,QAAM,gBAAgB,iBAAiB,WAAW;AAClD,QAAM,eAAe,IAAI,gBAAgB,KAAK,QAAQ,CAAC,CAAC;AAGxD,QAAM,cAAc,WAAW,gBAAgB,QAC1C,KAAK,IAAI,UAAU,eAAe,UAAU,IAAI;AAIrD,QAAM,YAAQ,wBAAQ,MAAM;AAC1B,QAAI,CAAC,UAAW,QAAO,CAAC;AACxB,UAAM,WAAW,UAAU,MAAM,CAAC,KAAK;AACvC,WAAO,UAAU,MAAM,IAAI,CAAC,GAAG,MAAM;AACnC,YAAM,kBAAkB,IAAI;AAC5B,YAAM,WAAW,iBAAiB,eAAe;AACjD,YAAM,aAAa,MAAM;AACzB,aAAO,EAAE,GAAG,UAAU,WAAW;AAAA,IACnC,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,gBAAgB,CAAC;AAEhC,QAAM,aAAa,YAAY,CAAC,aAAa,UAAU,MAAM,WAAW;AAExE,SACE,+CAAC,SAAI,eAAY,mBAAkB,WAAU,kCAC3C;AAAA,kDAAC,UAAK,WAAU,sEAAqE,mBAErF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,eAAY;AAAA,QACZ,eAAe;AAAA,QACf,WAAW,kDACT,aACI,+CACA,0BACN;AAAA,QACA,OAAO,EAAE,QAAQ,iBAAiB;AAAA,QAClC,OACE,aACI,oDACA;AAAA,QAEN,MAAK;AAAA,QACL,cAAW;AAAA,QACX,iBAAe,CAAC;AAAA,QAChB,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,iBAAe;AAAA,QAGf;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,WAAU;AAAA,cACV,OAAO,EAAE,MAAM,MAAM;AAAA;AAAA,UACvB;AAAA,UAEC,MAAM,IAAI,CAAC,MACV;AAAA,YAAC;AAAA;AAAA,cAEC,eAAa,EAAE,aAAa,yBAAyB;AAAA,cACrD,eAAY;AAAA,cACZ,WAAW,EAAE,aAAa,2BAA2B;AAAA,cACrD,OAAO;AAAA,gBACL,MAAM,IAAI,EAAE,WAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,gBACtC,MAAM,oBAAoB,EAAE,aAAa,0BAA0B,mBAAmB;AAAA,gBACtF,OAAO;AAAA,gBACP,QAAQ,EAAE,aAAa,0BAA0B;AAAA,cACnD;AAAA;AAAA,YATK,EAAE;AAAA,UAUT,CACD;AAAA,UAED;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,eAAY;AAAA,cACZ,WAAW,sCACT,aAAa,kBAAkB,kBACjC;AAAA,cACA,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,WAAW;AAAA,gBACX,eAAe;AAAA,cACjB;AAAA;AAAA,UACF;AAAA;AAAA;AAAA,IACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,eAAY;AAAA,QACZ,WAAU;AAAA,QAET,uBAAa,aAAa,UAAU;AAAA;AAAA,IACvC;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,eAAY;AAAA,QACZ,SAAS;AAAA,QACT,UAAU,cAAc,gBAAgB;AAAA,QACxC,WAAW,6EACT,cAAc,gBAAgB,IAC1B,2DACA,mFACN;AAAA,QACA,OAAM;AAAA,QACP;AAAA;AAAA,IAED;AAAA,IACC,eACC;AAAA,MAAC;AAAA;AAAA,QACC,eAAY;AAAA,QACZ,WAAU;AAAA,QACV,OAAO,YAAY,YAAY,QAAQ,CAAC,CAAC,gDAA2C,UAAU;AAAA,QAC/F;AAAA;AAAA,IAED;AAAA,KAEJ;AAEJ;AAGA,SAAS,aAAa,SAAiB,YAA4B;AACjE,QAAM,OAAO,UAAU,IAAI,MAAM,UAAU,IAAI,MAAM;AACrD,QAAM,MAAM,KAAK,IAAI,OAAO;AAC5B,QAAM,KAAK,KAAK,MAAO,MAAM,aAAc,GAAI;AAC/C,SAAO,GAAG,IAAI,GAAG,GAAG,SAAS,IAAI,GAAG,EAAE;AACxC;;;AC3RA,IAAM,wBAAwB;AAE9B,eAAsB,eACpB,MACA,UACuB;AACvB,QAAM,QAAQ,MAAM,KAAK,kBAAkB,QAAQ;AACnD,QAAM,cACH,OAA6D,gBAC7D,OAAmE;AACtE,QAAM,eAAe,IAAI,YAAY;AACrC,MAAI;AACF,UAAM,cAAc,MAAM,aAAa,gBAAgB,MAAM,MAAM,CAAC,CAAC;AACrE,QAAI,OAAO;AACX,aAAS,IAAI,GAAG,IAAI,YAAY,kBAAkB,KAAK;AACrD,YAAM,OAAO,YAAY,eAAe,CAAC;AACzC,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,cAAM,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC;AAC1B,YAAI,IAAI,KAAM,QAAO;AAAA,MACvB;AAAA,IACF;AACA,UAAM,SAAS,OAAO,OAAO,KAAK,KAAK,MAAM,IAAI,IAAI;AACrD,WAAO;AAAA,MACL,YAAY;AAAA,MACZ;AAAA,MACA,SAAS,QAAQ,wBAAwB;AAAA,IAC3C;AAAA,EACF,UAAE;AACA,UAAM,aAAa,MAAM,EAAE,MAAM,MAAM;AAAA,IAAe,CAAC;AAAA,EACzD;AACF;;;AC3BO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAAgD;AAC9C,QAAM,UAAU,MAAM,IAAI,MAAM;AAChC,QAAM,iBAAiB,aAAa,IAAI,aAAa;AACrD,QAAM,iBAAiB,KAAK,MAAO,KAAK,UAAW,cAAc;AACjE,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,KAAK,CAAC;AACvD,QAAM,QAAkB,CAAC;AACzB,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,UAAM,KAAK,IAAI,cAAc;AAAA,EAC/B;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB;AAAA,IACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtC;AACF;;;ACxBA,IAAAC,iBAA8C;AAKvC,SAAS,cACd,eACA,cACiD;AACjD,QAAM,CAAC,UAAU,WAAW,QAAI,yBAAyB,MAAM,oBAAI,IAAI,CAAC;AACxE,QAAM,uBAAmB,uBAAO,aAAa;AAC7C,mBAAiB,UAAU;AAE3B,QAAM,eAAe,kBAAkB,QAAQ,SAAS,IAAI,aAAa,IACrE,SAAS,IAAI,aAAa,IAC1B;AAEJ,QAAM,yBAAqB,4BAAY,CAAC,UAAsC;AAC5E,UAAM,MAAM,iBAAiB;AAC7B,QAAI,QAAQ,KAAM;AAClB,gBAAY,UAAQ;AAClB,YAAM,UAAU,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,IAAK;AACjD,YAAM,OAAO,OAAO,UAAU,aAAc,MAAyB,OAAO,IAAI;AAChF,YAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,aAAO,IAAI,KAAK,IAAI;AACpB,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,kBAAc,4BAAY,CAAC,SAAiB,UAAsC;AACtF,gBAAY,UAAQ;AAClB,YAAM,UAAU,KAAK,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,IAAK;AACzD,YAAM,OAAO,OAAO,UAAU,aAAc,MAAyB,OAAO,IAAI;AAChF,YAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,aAAO,IAAI,SAAS,IAAI;AACxB,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO,CAAC,cAAc,oBAAoB,WAAW;AACvD;;;AC5CA,IAAAC,iBAAoC;AAG7B,SAAS,WACd,MACS;AACT,QAAM,CAAC,SAAS,UAAU,QAAI,yBAAS,KAAK;AAE5C,gCAAU,MAAM;AACd,QAAI,SAAS;AACb,UAAM,UAAU,MAAY;AAC1B,WACG,gBAAgB,EAChB,KAAK,CAAC,MAAM;AACX,YAAI,OAAQ,YAAW,CAAC;AAAA,MAC1B,CAAC,EACA,MAAM,MAAM;AAAA,MAEb,CAAC;AAAA,IACL;AACA,YAAQ;AACR,UAAM,QAAQ,KAAK,mBAAmB,MAAM,QAAQ,CAAC;AACrD,WAAO,MAAM;AACX,eAAS;AACT,YAAM;AAAA,IACR;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AACT;;;AC1BA,IAAAC,iBAAuD;AA8CvD,IAAM,QAA2B,EAAE,SAAS,CAAC,GAAG,QAAQ,GAAG;AAE3D,SAAS,eAAe,GAAY,GAAqB;AACvD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI;AACF,WAAO,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,gBACd,YACA,OAA+B,CAAC,GACT;AACvB,QAAM,MAAM,KAAK,IAAI,GAAG,KAAK,OAAO,EAAE;AAItC,QAAM,eAAW,uBAAO,UAAU;AAClC,WAAS,UAAU;AACnB,QAAM,kBAAc,uBAAO,KAAK,QAAQ;AACxC,cAAY,UAAU,KAAK;AAG3B,QAAM,cAAU,uBAA0C,CAAC,CAAC;AAC5D,QAAM,CAAC,EAAE,UAAU,QAAI,yBAAS,CAAC;AACjC,QAAM,WAAO,4BAAY,MAAY,WAAW,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;AAGjE,QAAM,aAAS;AAAA,IACb,CAAC,SAAiB,MAAyB,WAA0B;AACnE,cAAQ,UAAU,EAAE,GAAG,QAAQ,SAAS,CAAC,OAAO,GAAG,KAAK;AACxD,WAAK;AACL,UAAI,OAAQ,aAAY,UAAU,SAAS,IAAI;AAAA,IACjD;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AAEA,QAAM,aAAS;AAAA,IACb,CAAC,SAAiB,YAAqB,UAAwB;AAC7D,YAAM,IAAI,QAAQ,QAAQ,OAAO;AACjC,YAAM,UAAU,KAAK,EAAE,UAAU,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI;AAE3D,UAAI,WAAW,eAAe,QAAQ,YAAY,UAAU,EAAG;AAC/D,YAAM,UAA+B,CAAC,GAAI,IAAI,EAAE,UAAU,CAAC,GAAI,EAAE,YAAY,MAAM,CAAC;AAEpF,aAAO,QAAQ,SAAS,KAAK;AAC3B,cAAM,SAAS,QAAQ,UAAU,CAAC,MAAM,CAAC,EAAE,QAAQ;AACnD,YAAI,WAAW,GAAI;AACnB,gBAAQ,OAAO,QAAQ,CAAC;AAAA,MAC1B;AACA,aAAO,SAAS,EAAE,SAAS,QAAQ,QAAQ,SAAS,EAAE,GAAG,IAAI;AAAA,IAC/D;AAAA,IACA,CAAC,KAAK,MAAM;AAAA,EACd;AAEA,QAAM,gBAAY;AAAA,IAChB,OAAO,SAAiB,UAAoC;AAC1D,YAAM,IAAI,QAAQ,QAAQ,OAAO;AACjC,UAAI,CAAC,KAAK,QAAQ,KAAK,SAAS,EAAE,QAAQ,UAAU,UAAU,EAAE,OAAQ,QAAO;AAC/E,YAAM,SAAS,QAAQ,SAAS,EAAE,QAAQ,KAAK,EAAE,UAAU;AAC3D,aAAO,SAAS,EAAE,SAAS,EAAE,SAAS,QAAQ,MAAM,GAAG,IAAI;AAC3D,aAAO;AAAA,IACT;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,WAAO;AAAA,IACX,CAAC,YAAsC;AACrC,YAAM,IAAI,QAAQ,QAAQ,OAAO;AACjC,UAAI,CAAC,KAAK,EAAE,UAAU,EAAG,QAAO,QAAQ,QAAQ,KAAK;AACrD,aAAO,UAAU,SAAS,EAAE,SAAS,CAAC;AAAA,IACxC;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,qBAAiB;AAAA,IACrB,CAAC,SAAiB,UAAwB;AACxC,YAAM,IAAI,QAAQ,QAAQ,OAAO;AACjC,UAAI,CAAC,KAAK,QAAQ,KAAK,SAAS,EAAE,QAAQ,OAAQ;AAClD,YAAM,UAAU,EAAE,QAAQ,IAAI,CAAC,GAAG,MAAO,MAAM,QAAQ,EAAE,GAAG,GAAG,UAAU,CAAC,EAAE,SAAS,IAAI,CAAE;AAC3F,aAAO,SAAS,EAAE,SAAS,QAAQ,EAAE,OAAO,GAAG,IAAI;AAAA,IACrD;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,cAAU;AAAA,IACd,CACE,SACA,UACS;AACT,YAAM,UAA+B,MAAM,QAAQ,OAAO,OAAO,IAAI,CAAC,GAAG,MAAO,OAAO,IAAI,CAAC;AAC5F,YAAM,MAAM,OAAO,OAAO,WAAW,WAAW,MAAO,SAAS,QAAQ,SAAS;AACjF,YAAM,SAAS,QAAQ,WAAW,IAAI,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,QAAQ,SAAS,CAAC;AACxF,aAAO,SAAS,EAAE,SAAS,OAAO,GAAG,KAAK;AAAA,IAC5C;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,WAAO;AAAA,IACX,CAAC,YAAuC,QAAQ,QAAQ,OAAO,KAAK;AAAA,IACpE,CAAC;AAAA,EACH;AAEA,QAAM,cAAU,4BAAY,CAAC,YAA6B;AACxD,UAAM,IAAI,QAAQ,QAAQ,OAAO;AACjC,WAAO,CAAC,CAAC,KAAK,EAAE,SAAS;AAAA,EAC3B,GAAG,CAAC,CAAC;AAEL,QAAM,YAAQ;AAAA,IACZ,CAAC,YAA0B;AACzB,UAAI,QAAQ,QAAQ,OAAO,GAAG;AAC5B,cAAM,OAAO,EAAE,GAAG,QAAQ,QAAQ;AAClC,eAAO,KAAK,OAAO;AACnB,gBAAQ,UAAU;AAClB,aAAK;AAAA,MACP;AACA,kBAAY,UAAU,SAAS,KAAK;AAAA,IACtC;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AAEA,QAAM,YAAQ,4BAAY,MAAY;AACpC,YAAQ,UAAU,CAAC;AACnB,SAAK;AAAA,EACP,GAAG,CAAC,IAAI,CAAC;AAGT,aAAO;AAAA,IACL,OAAO,EAAE,QAAQ,MAAM,WAAW,MAAM,SAAS,OAAO,OAAO,SAAS,eAAe;AAAA,IACvF,CAAC,QAAQ,MAAM,WAAW,MAAM,SAAS,OAAO,OAAO,SAAS,cAAc;AAAA,EAChF;AACF;;;AChMO,IAAM,qBAAqB;;;ACa3B,SAAS,uBAAuB,KAAsC;AAC3E,QAAM,SAAS,IAAI;AACnB,MAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;AAE3C,QAAM,QAAkB,CAAC,iDAAiD;AAE1E,aAAW,SAAS,QAAQ;AAC1B,UAAM,YAAY,MAAM,SACpB,YAAY,aAAa,MAAM,MAAM,CAAC,MACtC;AACJ,UAAM,KAAK,YAAY,MAAM,QAAQ,SAAS,GAAG,SAAS,EAAE;AAE5D,QAAI,MAAM,aAAa,WAAW,GAAG;AACnC,YAAM,KAAK,gBAAgB;AAAA,IAC7B,OAAO;AACL,iBAAW,WAAW,MAAM,cAAc;AACxC,YAAI,QAAQ,MAAM,WAAW,EAAG;AAChC,cAAM,KAAK,OAAO,mBAAmB,OAAO,CAAC,EAAE;AAAA,MACjD;AAAA,IACF;AAEA,QAAI,MAAM,aAAa,OAAO,MAAM,sBAAsB,UAAU;AAClE,YAAM,UAAU,MAAM,oBAAoB,aAAa,MAAM,YAAY;AACzE,UAAI,UAAU,GAAG;AACf,cAAM,KAAK,eAAU,OAAO,wBAAwB;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,IAAI,uBAAuB,IAAI,sBAAsB,GAAG;AAC1D,UAAM;AAAA,MACJ,aAAQ,IAAI,mBAAmB,oBAAoB,IAAI,wBAAwB,IAAI,KAAK,GAAG;AAAA,IAC7F;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,mBAAmB,SAAqC;AAC/D,QAAM,CAAC,OAAO,GAAG,IAAI,QAAQ;AAC7B,QAAM,YAAY,KAAK,UAAU,QAAQ,MAAM,IAAIC,YAAW,CAAC;AAC/D,SAAO,GAAG,QAAQ,KAAK,WAAW,KAAK,IAAI,GAAG,MAAM,SAAS;AAC/D;AAOA,SAASA,aAAY,GAKnB;AACA,SAAO;AAAA,IACL,OAAO,EAAE;AAAA,IACT,WAAW,EAAE;AAAA,IACb,eAAe,EAAE;AAAA,IACjB,UAAU,EAAE;AAAA,EACd;AACF;AAEA,SAAS,aAAa,GAAmB;AACvC,SAAO,EAAE,QAAQ,MAAM,KAAK;AAC9B;AAEA,SAAS,aAAa,UAAwC;AAC5D,MAAI,QAAQ;AACZ,aAAW,KAAK,SAAU,UAAS,EAAE,MAAM;AAC3C,SAAO;AACT;;;ACvDA,IAAM,aAAkC,oBAAI,IAAI;AAAA,EAC9C;AAAA,EAAK;AAAA,EAAM;AAAA,EAAO;AAAA,EAAO;AAAA,EAAM;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EACvE;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAK;AAAA,EAAM;AAAA,EAAM;AAAA,EAAQ;AAAA,EACjE;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AACjD,CAAC;AAUM,SAAS,eAAe,MAAwB;AACrD,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,QAAM,mBAAmB,KACtB,MAAM,GAAG,EACT,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC,EAC7B,OAAO,CAAC,WAAW,OAAO,SAAS,KAAK,CAAC,SAAS,KAAK,MAAM,CAAC,EAC9D,KAAK,GAAG;AAEX,SAAO,iBACJ,YAAY,EACZ,MAAM,aAAa,EACnB,OAAO,CAAC,QAAQ;AACf,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI,WAAW,IAAI,GAAG,EAAG,QAAO;AAChC,QAAI,YAAY,KAAK,GAAG,EAAG,QAAO;AAClC,WAAO;AAAA,EACT,CAAC;AACL;AAYO,SAAS,iBACd,OACA,kBACU;AACV,QAAM,IAAI,iBAAiB;AAC3B,MAAI,MAAM,EAAG,QAAO,CAAC;AAErB,QAAM,cAAc,MAAM,KAAK,IAAI,IAAI,eAAe,KAAK,CAAC,CAAC;AAC7D,MAAI,YAAY,WAAW,EAAG,QAAO,iBAAiB,IAAI,MAAM,CAAC;AAEjE,QAAM,qBAAqB,iBAAiB,IAAI,CAAC,MAAM,IAAI,IAAI,eAAe,CAAC,CAAC,CAAC;AAKjF,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,SAAS,aAAa;AAC/B,QAAI,KAAK;AACT,eAAW,OAAO,oBAAoB;AACpC,UAAI,IAAI,IAAI,KAAK,EAAG,OAAM;AAAA,IAC5B;AACA,QAAI,KAAK,EAAG,KAAI,IAAI,OAAO,KAAK,IAAI,IAAI,IAAI,EAAE,CAAC;AAAA,EACjD;AAEA,MAAI,cAAc;AAClB,aAAW,UAAU,IAAI,OAAO,EAAG,gBAAe;AAClD,MAAI,gBAAgB,EAAG,QAAO,iBAAiB,IAAI,MAAM,CAAC;AAE1D,SAAO,mBAAmB,IAAI,CAAC,QAAQ;AACrC,QAAI,YAAY;AAChB,eAAW,CAAC,OAAO,MAAM,KAAK,KAAK;AACjC,UAAI,IAAI,IAAI,KAAK,EAAG,cAAa;AAAA,IACnC;AACA,WAAO,YAAY;AAAA,EACrB,CAAC;AACH;AA8BO,SAAS,iBACd,QACA,UAA2B,CAAC,GAClB;AACV,QAAM,EAAE,IAAI,GAAG,cAAc,KAAK,aAAa,MAAM,KAAK,OAAO,IAAI;AAErE,MAAI,OAAO;AACX,MAAI,eAAe,YAAY,OAAO,GAAG;AACvC,WAAO,KAAK,OAAO,CAAC,MAAM,EAAE,QAAQ,UAAa,CAAC,YAAY,IAAI,EAAE,GAAG,CAAC;AAAA,EAC1E;AACA,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACzD,QAAM,MAAM,OAAO,MAAM,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AAG1C,QAAM,WAAW,IAAI,CAAC,EAAE;AACxB,QAAM,WAAW,KAAK,IAAI,MAAM,WAAW;AAC3C,QAAM,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,EAAE,QAAQ,YAAY,QAAQ,CAAC;AACxE,QAAM,cAAc,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC;AAEzD,MAAI,YAAY,IAAI,IAAI;AACxB,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,GAAG;AACtC,iBAAa,QAAQ,CAAC;AACtB,QAAI,aAAa,EAAG,QAAO,IAAI,CAAC,EAAE;AAAA,EACpC;AACA,SAAO,IAAI,IAAI,SAAS,CAAC,EAAE;AAC7B;","names":["import_react","import_react","import_jsx_runtime","next","import_jsx_runtime","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","useDebouncedCallback","import_react","import_jsx_runtime","import_jsx_runtime","React","import_react","import_jsx_runtime","React","import_react","import_jsx_runtime","React","import_react","import_jsx_runtime","import_react","import_jsx_runtime","scenes","import_react","import_jsx_runtime","shortId","import_react","import_react","import_jsx_runtime","shortId","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_react","import_react","compactNote"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types/plugin-sdk.types.ts","../src/types/fx-toggle.types.ts","../src/components/TrackRow.tsx","../src/components/TrackDrawer.tsx","../src/constants/fx-presets.ts","../src/components/FxToggleBar.tsx","../src/components/PianoRollEditor.tsx","../src/components/ConfirmDialog.tsx","../src/components/Modal.tsx","../src/components/LevelMeter.tsx","../src/hooks/useTrackLevels.ts","../src/components/TrackMeterStrip.tsx","../src/components/VolumeSlider.tsx","../src/utils/volume-conversion.ts","../src/components/PanSlider.tsx","../src/components/SorceryProgressBar.tsx","../src/components/CrossfadeTrackRow.tsx","../src/crossfade-meta.ts","../src/crossfade-inpaint.ts","../src/fade-meta.ts","../src/components/FadeTrackRow.tsx","../src/components/FadeModal.tsx","../src/components/ImportTrackModal.tsx","../src/components/CrossfadeModal.tsx","../src/components/TransitionDesigner.tsx","../src/hooks/useTrackReorder.ts","../src/transition-designer-meta.ts","../src/components/DownloadPackButton.tsx","../src/components/SamplePackCTACard.tsx","../src/components/WaveformView.tsx","../src/components/waveform.ts","../src/components/ScrollingWaveform.tsx","../src/components/OffsetScrubber.tsx","../src/components/wavPeakAnalyzer.ts","../src/components/synthesizeCuePoints.ts","../src/panel-core/useGeneratorPanelCore.tsx","../src/hooks/useSceneState.ts","../src/hooks/useAnySolo.ts","../src/hooks/useSoundHistory.ts","../src/panel-core/track-state.ts","../src/panel-core/panel-helpers.ts","../src/panel-core/group-meta.ts","../src/panel-core/useTransitionOps.ts","../src/panel-core/GeneratorPanelShell.tsx","../src/panel-core/surge-sound-adapter.ts","../src/constants/sdk-version.ts","../src/utils/format-concurrent-tracks.ts","../src/utils/semantic-match.ts"],"sourcesContent":["/**\n * @sas/plugin-sdk — Public API\n *\n * Everything an external plugin author needs to build a generator plugin\n * for Signals & Sorcery.\n */\n\n// ============================================================================\n// Types — Core plugin contract\n// ============================================================================\n\nexport type {\n GeneratorType,\n InstrumentDescriptor,\n GeneratorPlugin,\n PluginUIProps,\n PluginHost,\n ExportedPluginData,\n CreateTrackOptions,\n PluginTrackHandle,\n PluginTrackInfo,\n ImportCandidateTrack,\n ImportCandidateScene,\n SceneFamilyTrack,\n TrackSoundSnapshot,\n ListImportableTracksOptions,\n PluginSynthInfo,\n PluginTrackRuntimeState,\n TrackStateChangeListener,\n PluginFxCategoryDetailState,\n PluginTrackFxDetailState,\n MidiClipData,\n PluginMidiNote,\n MidiWriteResult,\n ReadMidiClip,\n ReadMidiResult,\n ExportMidiBundleOptions,\n ExportMidiBundleResult,\n PostProcessOptions,\n MusicalContext,\n PluginChordTiming,\n PluginGenerationContext,\n PluginConcurrentTrackInfo,\n PluginChordSegment,\n TransportEvent,\n DeckBoundaryEvent,\n PluginTransportState,\n PluginTrackLevel,\n PluginSceneInfo,\n PluginSceneContext,\n BulkAddPlaceholderTrack,\n TransportEventListener,\n DeckBoundaryListener,\n SceneChangeListener,\n UnsubscribeFn,\n LLMGenerationRequest,\n LLMGenerationResult,\n // Tool-use LLM types — agentic plugins (chat panel, etc.) use these via\n // `host.generateWithLLMTools` to drive a Claude-Code-style loop. SDK 2.4.0+.\n LLMPart,\n LLMContent,\n LLMFunctionDeclaration,\n LLMTool,\n LLMGenerationConfig,\n LLMSystemInstruction,\n LLMToolUseRequest,\n LLMUsageMetadata,\n LLMCandidate,\n LLMToolUseResponse,\n PluginPresetData,\n ShufflePresetResult,\n SoundHistoryEntry,\n PluginSettingsSchema,\n SettingDefinition,\n PluginSettingsStore,\n // AI skill surface — lets plugins declare LLM-callable actions\n // registered as namespaced tools (plugin:<id>:<skill>). Required for\n // plugins that expose a `chat` or similar agent-delegation skill.\n PluginSkill,\n PluginSkillInputSchema,\n PluginErrorCode,\n PluginManifest,\n PluginCapabilities,\n PluginFileDialogOptions,\n PluginDownloadOptions,\n PluginHttpRequestOptions,\n PluginHttpResponse,\n PluginSampleFilter,\n PluginSampleInfo,\n PluginSampleImportResult,\n PluginSampleTrackInfo,\n PluginAudioTextureRequest,\n PluginAudioTextureResult,\n PluginCuePoints,\n PluginTrimWindow,\n ComposeSceneOptions,\n ComposeSceneResult,\n ComposeProgressListener,\n ComposeProgressEvent,\n PluginPresetInfo,\n SavePluginPresetOptions,\n PluginAppTool,\n PluginAppToolInputSchema,\n PluginAppToolResult,\n PluginStatus,\n PluginRegistration,\n StemType,\n PluginStemSplitResult,\n PluginStemTrackInfo,\n // Audio recording (since SDK 2.1.0)\n AudioInputDevice,\n RecordingTargetInfo,\n RecordingChunkFinalizedEvent,\n // Drum sampler (since SDK 1.2.0)\n DrumKit,\n // Pitched instrument sampler (since SDK 1.3.0)\n InstrumentZone,\n InstrumentSampler,\n ListAudioFilesOptions,\n} from './types/plugin-sdk.types';\n\nexport { PluginError } from './types/plugin-sdk.types';\n\n// ============================================================================\n// Types — FX toggle system\n// ============================================================================\n\nexport type {\n FxCategory,\n FxPreset,\n MixInterpolation,\n FxPresetConfig,\n FxCategoryDetailState,\n TrackFxDetailState,\n TrackFxState,\n FxPresetDataEntry,\n FxPresetData,\n} from './types/fx-toggle.types';\n\nexport {\n FX_CATEGORIES,\n FX_CHAIN_ORDER,\n FX_ENGINE_PLUGIN_NAMES,\n FX_DISPLAY_LABELS,\n EMPTY_FX_STATE,\n DEFAULT_FX_DRY_WET,\n DEFAULT_FX_CATEGORY_DETAIL,\n EMPTY_FX_DETAIL_STATE,\n} from './types/fx-toggle.types';\n\n// ============================================================================\n// Components\n// ============================================================================\n\nexport { TrackRow, type SDKTrackRowProps } from './components/TrackRow';\nexport {\n CrossfadeTrackRow,\n type CrossfadeTrackRowProps,\n type CrossfadeLayer,\n} from './components/CrossfadeTrackRow';\nexport {\n EQUAL_POWER_GAIN,\n parseCrossfadePairs,\n asCrossfadeMeta,\n soundIdentity,\n hashString,\n buildCrossfadeVolumeCurves,\n type CrossfadeSlot,\n type CrossfadeMeta,\n type CrossfadePairMeta,\n type VolumeAutomationPoint,\n type CrossfadeVolumeCurves,\n} from './crossfade-meta';\nexport { buildCrossfadeInpaintPrompt, type CrossfadeInpaintInput } from './crossfade-inpaint';\nexport {\n parseFades,\n asFadeMeta,\n buildFadeVolumeCurve,\n defaultFadeGesture,\n TEXTURAL_ROLES,\n type FadeDirection,\n type FadeGesture,\n type FadeMeta,\n type FadeEntry,\n} from './fade-meta';\nexport { FadeTrackRow, type FadeTrackRowProps, type FadeLayer } from './components/FadeTrackRow';\nexport { FadeModal, type FadeModalProps, type FadeSelection } from './components/FadeModal';\nexport { ImportTrackModal, type ImportTrackModalProps } from './components/ImportTrackModal';\nexport {\n CrossfadeModal,\n type CrossfadeModalProps,\n type CrossfadeSelection,\n} from './components/CrossfadeModal';\n// Transition Designer — the multi-row, persistent successor to CrossfadeModal +\n// FadeModal: a per-panel staging board that lays out every A→B pairing at once.\n// Reuses the panel's existing create/delete orchestration. Since 2.29.0.\nexport {\n TransitionDesigner,\n type TransitionDesignerProps,\n} from './components/TransitionDesigner';\nexport {\n TRANSITION_DESIGNER_DRAFT_KEY,\n rowType,\n asTransitionDesignerDraft,\n reconcileSlots,\n buildRowSlots,\n normalizeSlots,\n padSlots,\n padPair,\n slotsEqual,\n rowKey,\n dbIdsFromKeys,\n AUDIO_EFFECTS,\n AUDIO_EFFECT_LABEL,\n asAudioEffect,\n type TransitionDesignerDraft,\n type TransitionRowType,\n type DesignerRowSlots,\n type AudioEffect,\n} from './transition-designer-meta';\nexport { ConfirmDialog, type ConfirmDialogProps } from './components/ConfirmDialog';\nexport { Modal, type ModalProps } from './components/Modal';\nexport {\n TrackDrawer,\n type TrackDrawerProps,\n type DrawerTab,\n // Backwards-compatible aliases — the drawer was `InstrumentDrawer` before it\n // grew an FX tab + Import tab and became the unified per-track drawer.\n InstrumentDrawer,\n type TrackDrawerProps as InstrumentDrawerProps,\n} from './components/TrackDrawer';\nexport {\n PianoRollEditor,\n type PianoRollEditorProps,\n PX_PER_BEAT,\n ROW_HEIGHT,\n GUTTER_W,\n DRAG_DEAD_ZONE,\n RESIZE_HANDLE_PX,\n pxToCell,\n cellToPx,\n resizeNoteDuration,\n centerScrollTop,\n transposeNotes,\n pitchToName,\n} from './components/PianoRollEditor';\nexport { VolumeSlider } from './components/VolumeSlider';\nexport { PanSlider } from './components/PanSlider';\nexport { FxToggleBar, type FxToggleBarProps } from './components/FxToggleBar';\nexport { SorceryProgressBar, calculateTimeBasedTarget } from './components/SorceryProgressBar';\nexport { DownloadPackButton, type DownloadPackButtonProps, type DownloadPackButtonVariant } from './components/DownloadPackButton';\nexport {\n SamplePackCTACard,\n type SamplePackCTACardProps,\n type SamplePackCTACardStatus,\n type SamplePackCardInfo,\n} from './components/SamplePackCTACard';\n\n// Waveform / audio-clip UI toolkit — shared by audio-oriented plugins (stems,\n// recorder). Promoted from the app's src/plugins/shared (W9 — so extracted\n// plugins reach it through the SDK, not a relative app path). Since 2.10.0.\nexport { WaveformView, type WaveformViewProps } from './components/WaveformView';\nexport { LevelMeter, type LevelMeterProps } from './components/LevelMeter';\nexport { TrackMeterStrip, type TrackMeterStripProps } from './components/TrackMeterStrip';\nexport { ScrollingWaveform, type ScrollingWaveformProps } from './components/ScrollingWaveform';\nexport { OffsetScrubber, type OffsetScrubberProps } from './components/OffsetScrubber';\nexport { computePeaks, drawWaveform, type WaveformPeaks } from './components/waveform';\nexport { analyzeWavPeak, type PeakAnalysis } from './components/wavPeakAnalyzer';\nexport { synthesizeCuePoints, type SynthesizeCuePointsOptions } from './components/synthesizeCuePoints';\n\n// ============================================================================\n// Panel core — the shared generator-panel engine (hook + shell + adapter).\n// Panels supply a GeneratorPanelAdapter; the core owns the duplicated ~85%\n// (load/reconcile, events, prompts, mixer/FX, drawer, piano roll, transition\n// machinery, render skeleton). Since 2.35.0.\n// ============================================================================\n\nexport * from './panel-core';\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\nexport { useSceneState } from './hooks/useSceneState';\nexport { useAnySolo } from './hooks/useAnySolo';\nexport {\n useSoundHistory,\n type UseSoundHistoryResult,\n type UseSoundHistoryOptions,\n type TrackSoundHistory,\n} from './hooks/useSoundHistory';\nexport {\n useTrackReorder,\n moveItem,\n type UseTrackReorderOptions,\n type UseTrackReorderResult,\n type TrackRowDragProps,\n} from './hooks/useTrackReorder';\nexport {\n useTrackLevels,\n useTrackLevel,\n useTrackMeter,\n useTransportPlaying,\n type TrackLevelsHandle,\n type TrackMeterView,\n} from './hooks/useTrackLevels';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n// VALID_INSTRUMENT_ROLES (SDK 1.x) removed in 2.0.0 — external plugins now\n// call `host.getValidRoles()` on PluginHost at runtime. The canonical list\n// lives in the assistant (src/music-engine/constants/instrument-classification.ts)\n// and is exposed via that accessor.\nexport { PLUGIN_SDK_VERSION } from './constants/sdk-version';\nexport { FX_PRESET_CONFIGS } from './constants/fx-presets';\n\n// ============================================================================\n// Utils\n// ============================================================================\n\nexport { sliderToDb, dbToSlider, SLIDER_UNITY, DB_MAX, DB_MIN } from './utils/volume-conversion';\nexport { formatConcurrentTracks } from './utils/format-concurrent-tracks';\n\n// Semantic sample matching — pick the closest sample to a text intent by\n// scoring against each sample's StableAudio prompt, with variety-preserving\n// top-k weighted selection. Shared by the drum + instrument resolvers. Since 2.11.0.\nexport {\n tokenizePrompt,\n scorePromptMatch,\n pickTopKWeighted,\n type ScoredCandidate,\n type PickTopKOptions,\n} from './utils/semantic-match';\n","/**\n * Plugin SDK Type Definitions\n *\n * Complete type system for the generator plugin architecture.\n * Plugins implement GeneratorPlugin and interact with the host via PluginHost.\n * All plugin output flows through TracktionEngine (MIDI or audio clips).\n */\n\nimport type { ComponentType, ReactNode } from 'react';\n\n// ============================================================================\n// Core Plugin Interface\n// ============================================================================\n\n/** What kind of Tracktion content this plugin creates */\nexport type GeneratorType = 'midi' | 'audio' | 'sample' | 'hybrid';\n\n/**\n * Drum-kit configuration for `host.setTrackDrumKit`. Prototype shape carries\n * a single sample; future multi-slot kits will extend this with a `notes`\n * map (`Record<midiNote, samplePath>`) for GM-style drum maps.\n */\nexport interface DrumKit {\n /** Absolute path to the sample (WAV, AIFF, FLAC). Triggered on every note-on. */\n samplePath: string;\n}\n\n/**\n * One key-mapped sample zone in a pitched, polyphonic instrument.\n * Used by `host.setTrackInstrumentSampler`.\n *\n * Zones in an InstrumentSampler MUST be disjoint and ordered low to\n * high by rootKey — the engine rejects overlap because Tracktion would\n * otherwise double-trigger every matching sound on each note-on.\n */\nexport interface InstrumentZone {\n /** Absolute path to the zone's sample (WAV, FLAC, AIFF). */\n samplePath: string;\n /** MIDI note this sample sounds at unshifted (0-127). */\n rootKey: number;\n /** Inclusive low end of the key range that triggers this zone (0-127). */\n minKey: number;\n /** Inclusive high end of the key range that triggers this zone (0-127). */\n maxKey: number;\n /**\n * If true, the sampler plays the sample for the duration the note is\n * held and stops on note-off (good for sustaining pads, organs, etc.,\n * whose source has been pre-trimmed to a steady-state region).\n * If false, the sampler plays the sample through to its end ignoring\n * note-off (good for plucks, mallets, percussion).\n */\n openEnded: boolean;\n}\n\n/**\n * Pitched instrument configuration for `host.setTrackInstrumentSampler`.\n * Parallel to `DrumKit` but multi-zone and pitch-aware. A manifest\n * authored by the pitched-sample pipeline reduces to one of these.\n *\n * NOTE: This is distinct from `host.setTrackInstrument(trackId, pluginId)`\n * which loads a VST3/AU synth plugin. `setTrackInstrumentSampler` loads\n * the built-in Tracktion sampler with N pre-rendered zones.\n */\nexport interface InstrumentSampler {\n /** Display name (e.g. \"Bright Warm Pluck\"). Used for diagnostics. */\n name: string;\n /** Disjoint zones, ordered low->high by rootKey. At least one required. */\n zones: ReadonlyArray<InstrumentZone>;\n}\n\n/** Options for `host.listAudioFiles`. */\nexport interface ListAudioFilesOptions {\n /**\n * File extensions to include (dot-prefixed, lowercase). Defaults to\n * `['.wav']`. Other audio formats (`.aif`, `.flac`, `.mp3`) are passed\n * through verbatim; the host does not transcode.\n */\n extensions?: string[];\n /** Walk subdirectories. Defaults to `false`. */\n recursive?: boolean;\n}\n\n/** Describes an available instrument plugin (VST3/AU synth) on the system. */\nexport interface InstrumentDescriptor {\n /** Stable plugin identifier for loading (VST3 TUID or AU component ID) */\n pluginId: string;\n /** Display name */\n name: string;\n /** Plugin manufacturer */\n manufacturer: string;\n /** Plugin format */\n type: 'vst3' | 'au' | 'vst' | 'internal';\n /** Plugin category (from scan) */\n category: string;\n /** Whether this plugin is currently installed/available */\n missing?: boolean;\n}\n\n/** Every generator plugin must implement this interface. */\nexport interface GeneratorPlugin {\n /** Unique ID, npm-style scope: '@sas/synth-generator', '@user/my-plugin' */\n readonly id: string;\n /** Human-readable name shown in accordion header */\n readonly displayName: string;\n /** Semver version string */\n readonly version: string;\n /** Short description for settings/marketplace */\n readonly description: string;\n /** 24x24 icon — data URL, relative path from plugin dir, or undefined */\n readonly icon?: string;\n /** What kind of Tracktion content this plugin creates */\n readonly generatorType: GeneratorType;\n /** Minimum host SDK version this plugin requires */\n readonly minHostVersion?: string;\n\n /**\n * Called once when plugin is loaded. Receives the PluginHost API.\n * If this throws, plugin is marked as failed and not rendered.\n */\n activate(host: PluginHost): Promise<void>;\n\n /**\n * Called when plugin is being unloaded (disable, uninstall, app quit).\n * Must complete within 5 seconds or host force-kills.\n */\n deactivate(): Promise<void>;\n\n /**\n * Return the React component rendered inside the accordion section.\n * Component receives PluginUIProps from the host.\n */\n getUIComponent(): ComponentType<PluginUIProps>;\n\n /**\n * Return JSON Schema for plugin-specific settings.\n * Host auto-renders a settings form. Return null if no settings.\n */\n getSettingsSchema(): PluginSettingsSchema | null;\n\n /**\n * Optional: Called when the active scene changes.\n */\n onSceneChanged?(sceneId: string | null): Promise<void>;\n\n /**\n * Optional: Called when the generation context changes\n * (chords updated, tracks added/removed, BPM changed).\n */\n onContextChanged?(context: MusicalContext): void;\n\n /**\n * Optional: Declare LLM-callable skills this plugin provides.\n * Skills are registered as namespaced tools (plugin:<pluginId>:<skillId>)\n * and become available to AI agents for orchestration.\n *\n * Example: the chat-panel plugin declares a `chat` skill so external\n * agents (Claude Code, OpenClaw) can delegate scene-scoped natural\n * language work to the in-app agent via a single call.\n */\n getSkills?(): PluginSkill[];\n}\n\n// ============================================================================\n// Plugin Skills (AI Harness)\n// ============================================================================\n\n/** An LLM-callable action declared by a plugin. */\nexport interface PluginSkill {\n /** Unique skill id within this plugin (e.g., 'chat', 'generate_bassline') */\n id: string;\n /** Human-readable description — drives LLM tool selection */\n description: string;\n /** JSON Schema for the skill's input parameters */\n inputSchema: PluginSkillInputSchema;\n /** Whether this skill only reads state (no mutations). Default: false */\n isReadOnly?: boolean;\n}\n\n/** JSON Schema shape for skill input parameters. */\nexport interface PluginSkillInputSchema {\n type: 'object';\n properties?: Record<string, unknown>;\n required?: string[];\n}\n\n// ============================================================================\n// Plugin UI Props\n// ============================================================================\n\n/** Props passed to every plugin's React component by the host */\nexport interface PluginUIProps {\n /** The scoped PluginHost API instance for this plugin */\n host: PluginHost;\n /** Currently active scene ID (null if none selected) */\n activeSceneId: string | null;\n /** Whether the user is authenticated (for LLM access) */\n isAuthenticated: boolean;\n /** Whether all systems are connected (engine, gateway) */\n isConnected: boolean;\n /** Which workstation deck this is rendered in */\n deckId?: 'left' | 'right';\n /** Plugin calls this to set/clear header buttons. Pass null to clear. */\n onHeaderContent?: (content: ReactNode | null) => void;\n /** Plugin calls this to show/hide the loading spinner in the header. */\n onLoading?: (loading: boolean) => void;\n /** Scene-level context: contract state, chords, BPM, etc. Null if no scene. */\n sceneContext?: PluginSceneContext | null;\n /** Callback to open the scene selector (Scenes accordion section). */\n onSelectScene?: (() => void) | null;\n /** Callback to open the contract/chords section (for \"Generate a Contract\" CTA). */\n onOpenContract?: (() => void) | null;\n /** Callback to expand this plugin's own accordion section. */\n onExpandSelf?: (() => void) | null;\n /**\n * Whether the host's accordion section for this plugin is currently expanded.\n * Plugin UIs can watch transitions to take focus, refresh data, etc. The host\n * keeps the plugin mounted across collapse/expand to preserve state, so this\n * prop (not mount/unmount) is the signal that the user is actively viewing.\n */\n isExpanded?: boolean;\n}\n\n// ============================================================================\n// PluginHost API\n// ============================================================================\n\n/**\n * Canonical display metadata for a distributable sample pack, sourced from the\n * HOST's pack registry (the same source it uses to download + version-check the\n * bundle). Returned by `host.getSamplePackInfo` so a plugin's download CTA can\n * show the live name / description / size instead of a hardcoded copy that\n * drifts when a new pack version ships. Structurally compatible with\n * `SamplePackCardInfo` (the CTA card prop).\n *\n * @since SDK 2.12.0\n */\nexport interface SamplePackPublicInfo {\n /** Stable pack identifier, e.g. `'sas-instrument-pack'`. */\n packId: string;\n /** Human-readable pack name for the CTA headline. */\n displayName: string;\n /** One-line description of the pack's contents. */\n description: string;\n /** Size in bytes of the default download variant. */\n sizeBytes: number;\n}\n\n/** Scoped API surface that plugins interact with. Plugins NEVER get direct TracktionEngine access. */\nexport interface PluginHost {\n // --- Track Management (ownership-scoped) ---\n\n /** Create a new track in the active scene. Host enforces ownership and scene routing. */\n createTrack(options: CreateTrackOptions): Promise<PluginTrackHandle>;\n\n /** Delete a track previously created by THIS plugin. */\n deleteTrack(trackId: string): Promise<void>;\n\n /** Get all tracks this plugin owns in the active scene. */\n getPluginTracks(): Promise<PluginTrackHandle[]>;\n\n /** Adopt unowned tracks in the active scene matching this plugin's generator type. */\n adoptSceneTracks(): Promise<PluginTrackHandle[]>;\n\n /** Get info about a specific owned track. */\n getTrackInfo(trackId: string): Promise<PluginTrackInfo>;\n\n /** Set track mute state. Only works on owned tracks. */\n setTrackMute(trackId: string, muted: boolean): Promise<void>;\n\n /** Set track volume (linear 0.0 - 1.0). Only works on owned tracks. */\n setTrackVolume(trackId: string, volume: number): Promise<void>;\n /**\n * Set/replace a time-based volume automation curve (a fade envelope) on a track,\n * or clear it with an empty array. Points are {time: seconds, db}; linear between\n * points. Used by crossfade tracks to fade origin↔target across the looped\n * transition. Optional — callers MUST null-check. @since SDK 2.25.0\n */\n setTrackVolumeAutomation?(trackId: string, points: Array<{ time: number; db: number }>): Promise<void>;\n\n /** Set track pan (-1.0 left to 1.0 right). Only works on owned tracks. */\n setTrackPan(trackId: string, pan: number): Promise<void>;\n\n /** Set track solo state. Only works on owned tracks. */\n setTrackSolo(trackId: string, solo: boolean): Promise<void>;\n\n /** Whether ANY track in the project is currently soloed (across all panels).\n * Lets a panel dim its non-soloed rows (the engine silences them via the\n * effective-mute model). Read-only; not ownership-scoped. */\n isAnySoloActive(): Promise<boolean>;\n\n /** Rename a track. Only works on owned tracks. */\n setTrackName(trackId: string, name: string): Promise<void>;\n\n /**\n * Persist a track's musical role to the `tracks.role` column. Call this\n * after an LLM generation classifies the track (e.g. `'bass'`, `'lead'`,\n * `'pad'`, `'fx'`, `'kicks'`) so downstream features — especially the v1\n * transition generator's layer classifier — can see the role.\n *\n * Canonical values understood by the transition classifier include\n * `bass`, `drums`, `lead`, `chords`, `pad`, `arp`, `fx`, `kicks`,\n * `snares`, `hats`, `clap`, `perc`, `riser`, `impact`. Anything else is\n * stored verbatim but won't match the neutral-role set.\n *\n * Only works on owned tracks.\n */\n setTrackRole(trackId: string, role: string): Promise<void>;\n\n /** Shuffle preset: keep MIDI, apply a random preset from the same category. Only works on owned tracks. */\n /**\n * Shuffle preset: keep MIDI, apply a random preset from the same category.\n * `excludeNames` (since SDK 1.5.0) filters preset names out of the random\n * pool; the current preset is always implicitly excluded. Use this to\n * implement a \"no-repeat until full cycle\" shuffle: the panel accumulates\n * the history and resets when shufflePreset throws \"no presets available\".\n */\n shufflePreset(trackId: string, excludeNames?: readonly string[]): Promise<ShufflePresetResult>;\n\n /** Duplicate track: copy MIDI + role to a new track with a different preset. Only works on owned tracks. */\n duplicateTrack(trackId: string): Promise<PluginTrackHandle>;\n\n /**\n * Persist this plugin's track row order for the active scene. Pass the stable\n * track dbIds ({@link PluginTrackHandle.dbId}) in the desired top-to-bottom\n * order. Reload-safe — {@link getPluginTracks} returns tracks in this order\n * across scene switches and project reopen.\n *\n * Per-panel and decoupled from the engine-synced global track order, so\n * reordering one panel never disturbs other plugins' tracks. Tracks omitted\n * from the list (e.g. newly added or duplicated) keep their natural order at\n * the end. Pairs with the {@link useTrackReorder} hook, which drives the\n * drag-and-drop UI and calls this on drop.\n *\n * @since SDK 2.16.0\n */\n reorderTracks(orderedTrackIds: readonly string[]): Promise<void>;\n\n /**\n * Return the canonical list of valid role tokens that the host's\n * classifier and UI understand. Plugins should use this list when\n * building LLM prompts or validating role values before calling\n * {@link setTrackRole}.\n *\n * The assistant owns the canonical taxonomy — plugins MUST NOT ship\n * their own hardcoded list, which would drift from the host. Pair with\n * {@link setTrackRole} to persist a classified role.\n *\n * @since SDK 2.0.0\n */\n getValidRoles(): readonly string[];\n\n // --- FX Operations (ownership-scoped) ---\n\n /** Get detailed FX state for a track (enabled, preset, dry/wet per category). */\n getTrackFxState(trackId: string): Promise<PluginTrackFxDetailState>;\n\n /** Toggle an FX category on/off for a track. */\n toggleTrackFx(trackId: string, category: string, enabled: boolean): Promise<void>;\n\n /** Set FX preset for a track. Returns the new dry/wet value if applicable. */\n setTrackFxPreset(trackId: string, category: string, presetIndex: number): Promise<{ dryWet?: number }>;\n\n /** Set FX dry/wet level for a track. */\n setTrackFxDryWet(trackId: string, category: string, value: number): Promise<void>;\n\n // --- Real-time Track State ---\n\n /** Subscribe to real-time track state changes (mute, solo, volume, pan). Returns unsubscribe fn. */\n onTrackStateChange(listener: TrackStateChangeListener): UnsubscribeFn;\n\n // --- MIDI Operations ---\n\n /** Write MIDI notes to a track this plugin owns. Replaces existing MIDI. */\n writeMidiClip(trackId: string, clip: MidiClipData): Promise<MidiWriteResult>;\n\n /** Clear all MIDI from a track this plugin owns. */\n clearMidi(trackId: string): Promise<void>;\n\n /**\n * Export all tracks owned by this plugin in the active scene as a ZIP bundle\n * of Standard MIDI Files (one .mid per track, named after each track with\n * collision-avoidance suffixes). Prompts the user for a save location.\n *\n * Tracks with no MIDI data are skipped. Returns the path written, or\n * `{ canceled: true }` if the user dismissed the save dialog.\n *\n * @since SDK 1.1.0\n */\n exportTracksAsMidiBundle(\n options?: ExportMidiBundleOptions\n ): Promise<ExportMidiBundleResult>;\n\n /**\n * Run the host's MIDI post-processing pipeline on raw notes.\n * Wraps MidiProcessor: quantize -> swing -> scale -> register -> overlaps -> humanize.\n */\n postProcessMidi(notes: PluginMidiNote[], options: PostProcessOptions): Promise<PluginMidiNote[]>;\n\n /**\n * Read a track's current MIDI notes for in-place editing (e.g. a piano\n * roll). Returns the track's clips with beat-based notes; an empty `clips`\n * array means the track has no MIDI. Reads LIVE engine state (NOT the DB),\n * so it reflects unsaved generator output too and needs no project_id\n * scoping — do not \"fix\" this into a DB query.\n *\n * Ownership-gated like {@link writeMidiClip}. Optional so a plugin built\n * against this SDK still loads on an older host — callers MUST null-check.\n * @since SDK 2.15.0\n */\n readMidiNotes?(trackId: string): Promise<ReadMidiResult>;\n\n // --- Audio Operations ---\n\n /** Place an audio file on a track this plugin owns. */\n writeAudioClip(trackId: string, filePath: string, position?: number): Promise<void>;\n\n /**\n * Render a single track to a temporary WAV file and return its path.\n * Only works on owned tracks. For MIDI/synth tracks the host mutes siblings\n * and renders the scene. For single-clip audio tracks the host MAY take a\n * copy-source fast path.\n * @since SDK 1.2.0\n */\n exportTrackAudio?(trackId: string): Promise<ExportTrackAudioResult>;\n\n /**\n * Run a chain of audio operations on an input WAV via the bundled\n * sas-audio-processor binary. Unsupported ops throw NOT_IMPLEMENTED.\n * @since SDK 1.2.0\n */\n processAudio?(\n inputPath: string,\n operations: AudioProcessingOp[]\n ): Promise<ProcessAudioResult>;\n\n /**\n * Replace a track's audio content. For audio tracks, clears clips and\n * adds the new audio. For MIDI/synth tracks, the original row is stashed\n * in plugin_data and a new audio_tracks row is inserted (MIDI is lost).\n * @since SDK 1.2.0\n */\n replaceTrackAudio?(trackId: string, audioPath: string): Promise<void>;\n\n // --- Plugin/Synth Operations ---\n\n /** Load a VST3/AU plugin onto a track this plugin owns. */\n loadSynthPlugin(trackId: string, pluginName: string): Promise<number>;\n\n /** Set plugin state (base64-encoded preset data). */\n setPluginState(trackId: string, pluginIndex: number, stateBase64: string): Promise<void>;\n\n /** Get current plugin state (base64-encoded). */\n getPluginState(trackId: string, pluginIndex: number): Promise<string>;\n\n /**\n * Set a plugin's RAW VST3/AU state — the plugin's own getStateInformation\n * format, bypassing Tracktion's ValueTree wrapper. Use for third-party\n * instruments (u-he Diva, Serum, …) whose patches the ValueTree round-trip\n * does not faithfully preserve. Default Surge XT presets use setPluginState.\n * @since SDK 2.15.0\n */\n setRawPluginState(trackId: string, pluginIndex: number, stateBase64: string): Promise<void>;\n\n /** Get a plugin's RAW VST3/AU state (see setRawPluginState). @since SDK 2.15.0 */\n getRawPluginState(trackId: string, pluginIndex: number): Promise<string>;\n\n /**\n * Persist a preset as the track's durable sound identity (DB `preset_state`,\n * the shape getTrackSound reads back). setPluginState/setRawPluginState are\n * engine-only — a copied sound that is never persisted has no identity, so\n * drift checks (e.g. the transition-designer preset re-sync) can never\n * observe convergence. Call after applying a copied state to a layer track.\n * @since SDK 2.34.0\n */\n persistTrackPresetState?(\n trackId: string,\n preset: { state: string; stateType: 'raw' | 'valuetree'; name?: string }\n ): Promise<void>;\n\n /** List plugins currently loaded on a track. */\n getTrackPlugins(trackId: string): Promise<PluginSynthInfo[]>;\n\n /** Remove a plugin from a track. */\n removePlugin(trackId: string, pluginIndex: number): Promise<void>;\n\n /** Check if a specific VST/AU plugin is available on the system. */\n isPluginAvailable(pluginName: string): Promise<boolean>;\n\n // --- Instrument Plugin Selection ---\n\n /** Get available instrument plugins (VST3/AU synths) scanned by the engine. */\n getAvailableInstruments(): Promise<InstrumentDescriptor[]>;\n\n /** Get the instrument plugin currently loaded on a track. Null = default (Surge XT). */\n getTrackInstrument(trackId: string): Promise<InstrumentDescriptor | null>;\n\n /** Change the instrument plugin on a track. Preserves MIDI data. */\n setTrackInstrument(trackId: string, pluginId: string): Promise<void>;\n\n /** Open the instrument plugin's native editor GUI as a floating window. */\n showInstrumentEditor(trackId: string): Promise<void>;\n\n /** Close the instrument plugin's editor window. */\n hideInstrumentEditor(trackId: string): Promise<void>;\n\n // --- Drum Sampler ---\n\n /**\n * Load the engine's built-in sampler on the track (if not already\n * present) and configure it with a single one-shot sound. Every MIDI\n * note triggers the loaded sample regardless of pitch — used by the\n * drum-generator plugin where the LLM's emitted pitch is advisory.\n *\n * Idempotent: calling repeatedly on the same track swaps the loaded\n * sample without stacking more sampler instances. The sampler counts\n * as the track's instrument; mixing it with `setTrackInstrument` on\n * the same track is undefined behaviour for now.\n *\n * @since SDK 1.2.0\n */\n setTrackDrumKit(trackId: string, kit: DrumKit): Promise<void>;\n\n /**\n * Load the engine's built-in sampler on the track (if not already\n * present) and configure it with a pitched, polyphonic, multi-zone\n * instrument. Each MIDI note triggers the zone whose [minKey,maxKey]\n * range contains it; the zone is played back pitch-shifted relative\n * to its rootKey. Polyphony is handled by the Tracktion sampler's\n * voice allocator.\n *\n * Used by the instrument-generator plugin to load a pre-rendered\n * pitched-sample manifest. Mutually exclusive with `setTrackDrumKit`\n * on the same track (both occupy the sampler slot) and with\n * `setTrackInstrument(pluginId)` (which loads a VST synth instead).\n *\n * Idempotent: calling repeatedly on the same track swaps the loaded\n * zones without stacking sampler instances.\n *\n * @since SDK 1.3.0\n */\n setTrackInstrumentSampler(trackId: string, instrument: InstrumentSampler): Promise<void>;\n\n // --- Filesystem (sample library scanning) ---\n\n /**\n * List audio files (by default `.wav`) under `rootPath`. Returns\n * absolute file paths. `recursive` defaults to false; pass `true` to\n * walk subdirectories. The drum-generator plugin uses this to\n * lazily discover available samples without round-tripping each\n * folder through `getSamples`.\n *\n * Plugins MUST NOT use this to read paths outside their declared\n * sample roots — the host may add path validation in a later release.\n *\n * @since SDK 1.2.0\n */\n listAudioFiles(rootPath: string, options?: ListAudioFilesOptions): Promise<string[]>;\n\n /**\n * Read a text file's contents from the host filesystem (UTF-8). Returns\n * `null` on any read error (missing file, permission, etc.) — the\n * caller does not need to wrap the call in try/catch.\n *\n * Intended for plugin sample-library metadata: instrument manifest\n * JSON (`<instrument-id>/manifest.json`) and prompt-sibling text\n * (`<id>.txt`). Plugins parse the returned string themselves so the\n * host stays content-agnostic.\n *\n * Plugins MUST NOT use this to read paths outside their declared\n * sample roots — the host may add path validation in a later release.\n *\n * @since SDK 1.4.0\n */\n readTextFile(absolutePath: string): Promise<string | null>;\n\n // --- Scene Context (read-only) ---\n\n /** Get the FULL generation context for the active scene. */\n getGenerationContext(excludeTrackId?: string): Promise<PluginGenerationContext>;\n\n /** Get lightweight musical context (no concurrent track MIDI data). */\n getMusicalContext(): Promise<MusicalContext>;\n\n /** Get the active scene ID. Null if no scene is active. */\n getActiveSceneId(): string | null;\n\n /**\n * Get the bound project's DB id. Null when no project is bound.\n * Optional — older hosts and the renderer-side host proxy may omit it;\n * callers MUST feature-check. Used e.g. to detect project switches for\n * per-project conversation persistence.\n * @since SDK 2.18.0\n */\n getProjectId?(): string | null;\n\n /** Get list of all scenes in the project. */\n getSceneList(): Promise<PluginSceneInfo[]>;\n\n /**\n * Enumerate importable track candidates from OTHER scenes, scoped to this\n * plugin's track type (derived from the plugin id). Each candidate is\n * annotated with `importable` + `disabledReason` — the host computes the\n * harmonic/length/tempo gate so the UI only renders it. By default the active\n * scene is excluded; pass `includeSameScene` to also surface the active\n * scene's MIDI tracks owned by OTHER panels (the cross-panel re-sound source).\n * Scenes with no candidate of this type are omitted.\n *\n * Optional so a plugin built against this SDK still loads on an older host —\n * callers MUST null-check and hide the affordance when absent.\n * @since SDK 2.13.0\n */\n listImportableTracks?(opts?: ListImportableTracksOptions): Promise<ImportCandidateScene[]>;\n\n /**\n * Import a source track (from another scene) into the active scene as a\n * faithful, independent copy, delegating to the `import_track_from_scene`\n * tool. Returns the new track's handle so the panel can append a row.\n * Throws on a gate violation — call only for candidates with `importable`.\n * Optional — callers MUST null-check (see `listImportableTracks`).\n * @since SDK 2.13.0\n */\n importTrack?(opts: { sourceSceneId: string; sourceTrackId: string }): Promise<PluginTrackHandle>;\n\n /**\n * Read a source track's CURRENT sound — sample path (drums), sampler zones\n * (instruments), or Surge preset state (synths) — so a panel can copy just\n * the sound onto another track, IGNORING the contract gate that `importTrack`\n * enforces (\"different contract, same preset\"). Read-only: applies nothing.\n * The selector is the source track's DB row id (`ImportCandidateTrack.dbId`).\n * Returns null when the track has no stored sound. Optional — callers MUST\n * null-check (see `listImportableTracks`).\n * @since SDK 2.14.0\n */\n getTrackSound?(sourceTrackDbId: string): Promise<TrackSoundSnapshot | null>;\n\n /**\n * Read a source track's persisted MIDI by its DB row id — the cross-panel\n * READ half of \"re-sound a part on a different instrument\". Unlike\n * `readMidiNotes` (engine-read, ownership-gated), this reads the DB and is\n * NOT ownership-gated, so a panel can pull a part out of a track owned by a\n * DIFFERENT panel in the same scene (the selector is\n * `ImportCandidateTrack.dbId`, e.g. a `sameScene` candidate). Notes are\n * beat-based, identical shape to `readMidiNotes`; the loop span comes from the\n * source scene. Returns `{ clips: [] }` when the track has no MIDI. Optional —\n * callers MUST null-check (see `listImportableTracks`).\n * @since SDK 2.20.0\n */\n readImportableTrackMidi?(sourceTrackDbId: string): Promise<ReadMidiResult>;\n\n /**\n * List THIS panel's family tracks in a specific scene (by DB id), WITHOUT the\n * import key/length/tempo gate that `listImportableTracks` applies. Powers the\n * crossfade picker: the origin (from) and target (to) scenes of a transition\n * deliberately differ in key, so gating would wrongly hide valid candidates.\n * Project-scoped, read-only. Returns [] for an unknown/empty scene. Optional —\n * callers MUST null-check (see `listImportableTracks`).\n * @since SDK 2.22.0\n */\n listSceneFamilyTracks?(sceneDbId: string): Promise<SceneFamilyTrack[]>;\n\n /**\n * Read a specific scene's musical key (tonic + mode) by db id. Labels the\n * SOURCE keys of a crossfade's origin/target patterns — the active-scene\n * musical context only carries the transition scene's key. Optional — callers\n * MUST null-check. @since SDK 2.24.0\n */\n getSceneKey?(sceneDbId: string): Promise<{ key: string; mode: string } | null>;\n /**\n * Read a scene's human display name by db id (for labelling a crossfade's\n * origin/target scenes). Optional — callers MUST null-check. @since SDK 2.26.0\n */\n getSceneName?(sceneDbId: string): Promise<string | null>;\n\n // --- Transport & Playback Events ---\n\n /** Subscribe to transport state changes. Returns unsubscribe function. */\n onTransportEvent(listener: TransportEventListener): UnsubscribeFn;\n\n /** Subscribe to deck boundary events. Returns unsubscribe function. */\n onDeckBoundary(listener: DeckBoundaryListener): UnsubscribeFn;\n\n /** Subscribe to scene change events. Returns unsubscribe function. */\n onSceneChange(listener: SceneChangeListener): UnsubscribeFn;\n\n /** Get current transport state (one-shot). */\n getTransportState(): Promise<PluginTransportState>;\n\n /**\n * One-shot mono peak level for every track this plugin owns. Drives the\n * cosmetic per-track strip meters; poll at ~30Hz while the transport is\n * playing. The host scopes the result to this plugin's tracks and coalesces\n * the underlying engine read, so a busy engine yields a STALE meter rather\n * than a backlog (playback always wins over the GUI). Optional: guard with\n * `typeof host.getTrackLevels === 'function'` for older hosts.\n * @since SDK 2.21.0\n */\n getTrackLevels?(): Promise<PluginTrackLevel[]>;\n\n // --- LLM Access (metered, authenticated) ---\n\n /** Generate text/JSON via the host's authenticated LLM service. */\n generateWithLLM(request: LLMGenerationRequest): Promise<LLMGenerationResult>;\n\n /**\n * Generate with native tool-use (function calling). Used by agentic plugins\n * (chat panel, etc.) to drive an iterative loop where the model calls tools,\n * observes results, and decides next steps — same loop class as Claude Code\n * or VS Code agent mode.\n *\n * Shape mirrors Gemini's `generateContent` REST surface; the host forwards\n * verbatim to the gateway's Gemini-native passthrough endpoint, which adds\n * the central Google API key. Plugins never see provider credentials.\n *\n * Available since SDK 2.4.0.\n */\n generateWithLLMTools(request: LLMToolUseRequest): Promise<LLMToolUseResponse>;\n\n /**\n * Resolve absolute paths for spawning the bundled `sas` CLI as a subprocess.\n * Used by agentic plugins that drive the CLI as their tool surface (chat\n * panel, etc.). Returns `null` when called from a renderer-side host or\n * when the CLI isn't accessible.\n *\n * Available since SDK 2.4.0.\n */\n getCliPaths(): { appExe: string; cliEntry: string } | null;\n\n /**\n * Resolve the absolute path to a bundled resource directory shipped with\n * the app via `extraResources` (e.g. `'drum-samples'`,\n * `'tracktion-presets'`). In dev, resolves to\n * `<projectRoot>/resources/<name>`. In packaged builds, resolves to\n * `<process.resourcesPath>/<name>`.\n *\n * Returns `null` if the host cannot resolve paths in this context\n * (e.g. Electron mocked out in unit tests). Plugins MUST null-check and\n * either degrade gracefully or fall back to a known dev path.\n *\n * Async by design: the renderer-side host proxy round-trips through IPC.\n *\n * @since SDK 2.7.0\n */\n getBundledResourcePath(name: string): Promise<string | null>;\n\n /** Check if LLM access is available (user authenticated + gateway reachable). */\n isLLMAvailable(): Promise<boolean>;\n\n // --- App Tool Bridge ---\n\n /**\n * List the host's registered app tools. Used by plugins (e.g. the chat\n * panel) that want to expose the same surface external AI agents have.\n *\n * `opts.scope` filters by scope tag — scene-scoped consumers pass\n * `'scene'` to hide project-level tools they shouldn't call. When omitted,\n * every tool regardless of scope is returned.\n *\n * `opts.includeDeferred` (since SDK 2.18.0) opts in to tools flagged with\n * `deferLoading` (progressive disclosure). Default `false` mirrors\n * `/api/v1/actions` — the curated core surface. Used by curation layers\n * that promote specific deferred/project tools onto an agent's default\n * declaration set.\n *\n * @since SDK 1.2.0\n */\n listAppTools(opts?: {\n scope?: 'scene' | 'project';\n includeDeferred?: boolean;\n }): Promise<PluginAppTool[]>;\n\n /**\n * Execute a host app tool by name. Delegates to the in-process\n * ToolRegistry — every call (including this one) broadcasts to the\n * UI's `mutations:tool-executed` channel so renderer state stays\n * fresh whether the call mutates or is read-only. Read-only callers\n * pay zero extra cost since the renderer debounces and skips\n * redundant reloads.\n *\n * For scene-scoped tools tagged with `autoBindSceneId`, the host\n * overrides the caller's `sceneId` param with the currently-active\n * scene. That keeps a scene-bound caller from accidentally targeting\n * another scene.\n *\n * `opts.provenance` (since SDK 2.18.0) stamps the originating actor onto\n * every domain event this call emits — pass `'agent'` from autonomous\n * agent loops so the UI orchestrator can gate auto-navigation, `'user'`\n * when proxying a direct user gesture. Omitted = `'system'`.\n *\n * @since SDK 1.2.0\n */\n executeAppTool(\n name: string,\n params: Record<string, unknown>,\n opts?: { provenance?: 'agent' | 'user' }\n ): Promise<PluginAppToolResult>;\n\n /**\n * Monotonic counter that increments on every state mutation\n * (`broadcastMutation('tool-executed', ...)`). Use as a cache key for\n * derived state that depends on the project: when the counter changes,\n * something mutated; when it doesn't, your cache is still valid.\n *\n * Mostly aimed at performance-sensitive callers like ambient-context\n * builders that want to skip re-querying state when nothing has\n * changed. The counter is process-local — it resets on app restart\n * and is not durable across sessions.\n *\n * Implementation detail: the counter is bumped by `mutation-broadcaster`\n * before the broadcaster fires, so a synchronous `getMutationSeq()`\n * call from inside a mutation listener will see the post-bump value.\n *\n * @since SDK 2.6.0\n */\n getMutationSeq(): number;\n\n // --- Preset System ---\n\n /** Get available preset categories for a synth plugin. */\n getPresetCategories(pluginName: string): Promise<string[]>;\n\n /** Get a random preset from a category. */\n getRandomPreset(category: string): Promise<PluginPresetData | null>;\n\n /** Get a specific preset by name from a category. */\n getPresetByName(category: string, name: string): Promise<PluginPresetData | null>;\n\n /** Use LLM to classify a text description into a preset category. */\n classifyPresetCategory(description: string): Promise<string>;\n\n // --- Storage & Settings ---\n\n /** Get absolute path to this plugin's isolated data directory. */\n getDataDirectory(): string;\n\n /** Persisted key-value settings store. */\n settings: PluginSettingsStore;\n\n // --- Sample Pack Distribution ---\n\n /**\n * Return the absolute path to an installed sample pack's root directory,\n * or `null` if the pack is missing OR its installed version doesn't match\n * what the current app build expects.\n *\n * Plugins should treat `null` as \"show the download CTA\"; do NOT fall back\n * to a hardcoded path. The host owns where samples live (currently\n * `<userData>/samples/<installSubdir>/`).\n *\n * Stable packIds: `'sas-drum-pack'`, `'sas-instrument-pack'`. Both packs\n * are downloaded on demand via the host's pack-download flow; see\n * `host.isSamplePackCurrent` and the renderer-side `DownloadPackButton`.\n *\n * @since SDK 2.7.0\n */\n getSamplePackRoot(packId: string): Promise<string | null>;\n\n /**\n * True if the installed version of `packId` matches the version this app\n * build expects. False if the pack is missing OR the installed version\n * differs (older or newer).\n *\n * Plugins call this on activate to decide between rendering their normal\n * UI vs the \"Sample library not installed / Update available\" CTA.\n *\n * @since SDK 2.7.0\n */\n isSamplePackCurrent(packId: string): Promise<boolean>;\n\n /**\n * Return the currently-installed version string for `packId` (e.g. `'1'`,\n * `'2'`), or `null` if the pack is not installed at all. Reads the\n * `_pack-version.json` marker inside the pack's install dir.\n *\n * Useful for distinguishing the \"missing\" CTA from the \"stale, update\n * available\" CTA — plugins can call this when `isSamplePackCurrent`\n * returns false to pick the right empty-state message.\n *\n * @since SDK 2.7.0\n */\n getSamplePackInstalledVersion(packId: string): Promise<string | null>;\n\n /**\n * Trigger a download + install of `packId` via the host's pack system (the\n * same flow `getSamplePackRoot` / `isSamplePackCurrent` report on). Resolves\n * when the install completes or fails. Plugins call this from a \"download\n * library\" CTA instead of reaching into the app's IPC (`window.electronAPI`)\n * directly.\n *\n * @since SDK 2.8.0\n */\n startSamplePackDownload(\n packId: string\n ): Promise<{ success: boolean; error?: string }>;\n\n /**\n * Subscribe to download/install progress for `packId`. Returns an unsubscribe\n * fn. `status` mirrors the host's pack-download states (e.g. `'downloading' |\n * 'extracting' | 'installing' | 'complete' | 'error'`); `progress` is 0-100.\n *\n * @since SDK 2.8.0\n */\n onSamplePackProgress(\n packId: string,\n listener: (progress: {\n packId?: string;\n status: string;\n progress: number;\n message?: string;\n }) => void\n ): UnsubscribeFn;\n\n /**\n * Return the canonical display metadata (`displayName`, `description`,\n * `sizeBytes`) for `packId` from the host's pack registry — the SAME source\n * the host uses to download + version-check the pack. A plugin's download CTA\n * should prefer this over a hardcoded copy so the size/description stay in\n * sync with whatever bundle the host actually ships (no per-version drift).\n * Resolves `null` for an unknown packId.\n *\n * Optional so a plugin built against this SDK still runs on an older host:\n * callers should fall back to their own static copy when it is absent or\n * returns `null`.\n *\n * @since SDK 2.12.0\n */\n getSamplePackInfo?(packId: string): Promise<SamplePackPublicInfo | null>;\n\n /**\n * Per-pack roots of the USER's imported sample packs for `kind`. Each root\n * is laid out exactly like the corresponding stock pack (drums:\n * `<root>/<role>/<file>.wav` + `.txt` sidecars; instruments:\n * `<root>/<category>/<id>/manifest.json`), so resolvers scan them as\n * additional roots alongside `getSamplePackRoot`. `[]` when nothing is\n * imported. User content lives under `<userData>/user-samples/` — strictly\n * separate on disk; stock pack installs never touch it.\n *\n * Optional for older-host compat: feature-check\n * (`host.getUserSampleRoots?.(...)`) and treat absence as `[]`.\n *\n * @since SDK 2.20.0\n */\n getUserSampleRoots?(kind: 'drums' | 'instruments'): Promise<string[]>;\n\n /**\n * Ask the host app to open its sample-import wizard targeting `kind`.\n * Fire-and-forget; renderer-hosted plugins only (the wizard is an app-level\n * modal — the main-process host no-ops). Library changes land as\n * `onSamplePackProgress` events with packId `user:<kind>` and\n * `status: 'complete'`, so subscribe to that to refresh.\n *\n * @since SDK 2.20.0\n */\n openSampleImportWizard?(kind: 'drums' | 'instruments'): void;\n\n // --- Deck playback ---\n //\n // The two playback decks: `'loop-a'` (composition / cue, headphones) and\n // `'loop-b'` (performance / main). These route through the SAME host path\n // the workstation UI uses, so the deck mutual-exclusivity rules\n // (PlaybackRuleEngine) are enforced identically — a plugin cannot bypass\n // them. Used by playback-driven plugins (e.g. the recorder, which starts\n // loop-a so a take has a backing loop). Available on renderer-hosted plugins.\n\n /**\n * Start a deck playing the given scene/transition. Mirrors the workstation's\n * transport play. `contentType` defaults to `'scene'`.\n *\n * @since SDK 2.9.0\n */\n deckPlay(\n deckId: string,\n contentId?: string,\n contentType?: 'scene' | 'transition'\n ): Promise<{ success: boolean; error?: string; code?: string }>;\n\n /**\n * Stop a deck.\n *\n * @since SDK 2.9.0\n */\n deckStop(deckId: string): Promise<{ success: boolean; error?: string }>;\n\n /**\n * Subscribe to per-deck state changes. Each event carries the `deckId`, the\n * `property` that changed (e.g. `'playing'`), and its new `value`. Returns an\n * unsubscribe fn.\n *\n * @since SDK 2.9.0\n */\n onDeckStateChanged(\n listener: (event: { deckId: string; property: string; value: unknown }) => void\n ): UnsubscribeFn;\n\n /**\n * Subscribe to the \"all decks stopped\" engine event (e.g. global transport\n * stop). Returns an unsubscribe fn.\n *\n * @since SDK 2.9.0\n */\n onAllDecksStopped(listener: () => void): UnsubscribeFn;\n\n // --- Scoped Data API ---\n\n /** Get a value from scene-scoped plugin data. */\n getSceneData<T = unknown>(sceneId: string, key: string): Promise<T | null>;\n\n /** Set a value in scene-scoped plugin data. */\n setSceneData(sceneId: string, key: string, value: unknown): Promise<void>;\n\n /** Get all key-value pairs for a scene. */\n getAllSceneData(sceneId: string): Promise<Record<string, unknown>>;\n\n /** Delete a key from scene-scoped plugin data. */\n deleteSceneData(sceneId: string, key: string): Promise<void>;\n\n /** Get the full project-scoped state object. */\n getProjectData<T = unknown>(key: string): Promise<T | null>;\n\n /** Set a project-scoped data value. */\n setProjectData(key: string, value: unknown): Promise<void>;\n\n // --- Notifications & Progress ---\n\n /** Show a toast notification to the user. */\n showToast(type: 'info' | 'success' | 'warning' | 'error', title: string, message?: string): void;\n\n /** Set progress indicator on a specific track. -1 to hide. */\n setProgress(trackId: string, progress: number): void;\n\n /** Set a global status message in the plugin's accordion header. */\n setStatusMessage(message: string | null): void;\n\n /** Request user confirmation via a modal dialog. */\n confirmAction(title: string, message: string): Promise<boolean>;\n\n // --- File System (Phase 2) ---\n\n /** Show a native file open dialog. Requires 'fileDialog' capability. */\n showOpenDialog(options: PluginFileDialogOptions): Promise<string[] | null>;\n\n /** Show a native file save dialog. Requires 'fileDialog' capability. */\n showSaveDialog(options: PluginFileDialogOptions): Promise<string | null>;\n\n /** Download a file to the plugin's data directory. */\n downloadFile(url: string, filename: string, options?: PluginDownloadOptions): Promise<string>;\n\n /** Copy a file into the plugin's data directory. */\n importFile(sourcePath: string, destFilename: string): Promise<string>;\n\n // --- Network (Phase 2, capability-gated) ---\n\n /** Make an HTTP request. Requires 'network' capability with allowedHosts. */\n httpRequest(options: PluginHttpRequestOptions): Promise<PluginHttpResponse>;\n\n // --- Secure Storage (Phase 2) ---\n\n /** Store a secret in the OS keychain (plugin-scoped). */\n storeSecret(key: string, value: string): Promise<void>;\n\n /** Retrieve a secret from the OS keychain (plugin-scoped). */\n getSecret(key: string): Promise<string | null>;\n\n /** Delete a secret from the OS keychain (plugin-scoped). */\n deleteSecret(key: string): Promise<void>;\n\n // --- Sample Library (Phase 2) ---\n\n /** Query the sample library with optional filters. */\n getSamples(filter?: PluginSampleFilter): Promise<PluginSampleInfo[]>;\n\n /** Get a single sample by ID. */\n getSampleById(id: string): Promise<PluginSampleInfo | null>;\n\n /** Import audio files into the sample library. */\n importSamples(filePaths: string[]): Promise<PluginSampleImportResult>;\n\n /** Create a sample track in the active scene. */\n createSampleTrack(sampleId: string, options?: { name?: string }): Promise<PluginTrackHandle>;\n\n /** Delete a sample track. */\n deleteSampleTrack(trackId: string): Promise<void>;\n\n /** Get all sample tracks in the active scene. Re-establishes ownership. */\n getPluginSampleTracks(): Promise<PluginSampleTrackInfo[]>;\n\n /**\n * Resolve a sample-track row id (as returned by `listSceneFamilyTracks`) back\n * to its library `sampleId` + metadata, so a loop can be re-created in another\n * scene (transition crossfade/fade). Returns null if not a sample track.\n * @since SDK 2.31.0\n */\n getSampleTrackInfo?(dbId: string): Promise<{ sampleId: string; fileName?: string; bpm?: number; key?: string } | null>;\n\n /**\n * Render an audio transition effect onto a sample (offline DSP via the audio\n * tool), returning a NEW library sample to place. Used for stutter / chopped\n * loop transitions. @since SDK 2.32.0\n */\n renderSampleEffect?(\n sampleId: string,\n spec: { effect: 'stutter' | 'chopped'; bars: number; bpm: number; repeats?: number; slices?: number },\n ): Promise<PluginSampleInfo>;\n\n /** Time-stretch a sample to a target BPM. Returns the new sample info. */\n timeStretchSample(sampleId: string, targetBpm: number): Promise<PluginSampleInfo>;\n\n /**\n * Fit a sample to the active scene's `(bpm, length_bars)`. Composes:\n * 1. Time-stretch to scene BPM (no-op if already matching).\n * 2. Chop / loop-stitch / passthrough so the resulting clip's duration\n * equals exactly `length_bars × 4 × (60 / bpm)` seconds.\n *\n * Required because the deck loops the clip at the scene's bar boundary —\n * a 4-bar sample dropped into a 2-bar scene used to over-run; a 4-bar\n * sample dropped into an 8-bar scene used to leave 4 bars of silence.\n *\n * The fitted sample is cached in the library by content hash, so\n * subsequent calls for the same `(sample, bpm, bars)` return instantly.\n */\n fitSampleToScene(sampleId: string): Promise<PluginSampleInfo>;\n\n /**\n * Lightweight one-shot sample audition through the cue (headphone) output.\n *\n * Plays the file via a dedicated SimpleLoopPlayer instance in the audio\n * engine — no Tracktion track or clip is created, no BPM matching, no\n * sync. Calling previewSample again with a different file replaces the\n * current preview cleanly. Independent of loop-b: starting/stopping a\n * preview never affects the performance deck and vice versa.\n */\n previewSample(filePath: string): Promise<void>;\n\n /**\n * Stop any in-flight sample preview started by previewSample(). Safe to\n * call when no preview is active — never throws.\n */\n stopPreview(): Promise<void>;\n\n // --- Audio Generation (Phase 2) ---\n\n /** Invoke the host's audio texture generation pipeline. */\n generateAudioTexture(request: PluginAudioTextureRequest): Promise<PluginAudioTextureResult>;\n\n // --- Audio Cue Points + Offset (Migration 060) ---\n\n /**\n * Persist cue points (detected beat positions) for an audio track.\n * Called once after `writeAudioClip` to remember the trim metadata so the\n * UI can later draw beat ticks and snap-to-beat the manual offset.\n *\n * Pass `null` to clear cue points. Throws OWNERSHIP_VIOLATION if the\n * track wasn't created by this plugin.\n */\n setCuePoints(trackId: string, cues: PluginCuePoints | null): Promise<void>;\n\n /** Read cue points previously written by `setCuePoints`. Returns null when none stored. */\n getCuePoints(trackId: string): Promise<PluginCuePoints | null>;\n\n /**\n * Set the manual sample-offset applied to the track's audio clip during\n * playback. Positive shifts later, negative shifts earlier with head\n * silence. Throws OWNERSHIP_VIOLATION if not owned by this plugin.\n */\n setAudioOffsetSamples(trackId: string, offsetSamples: number): Promise<void>;\n\n /** Read the current manual offset (0 if never set). */\n getAudioOffsetSamples(trackId: string): Promise<number>;\n\n // --- Raw / pre-trim audio metadata (stems trim editor) ---\n\n /**\n * Read raw bytes of an audio file written by the host. The path may be\n * `~app/`-relative or project-relative — the host resolves it using the\n * same logic as `writeAudioClip`. Throws FILE_NOT_FOUND if the path\n * can't be resolved or doesn't exist on disk.\n */\n getAudioFileBytes(filePath: string): Promise<ArrayBuffer>;\n\n /** Persist the original (raw, un-trimmed) audio file path for a track. */\n setRawAudioFilePath(trackId: string, filePath: string | null): Promise<void>;\n\n /** Read the raw audio file path persisted via `setRawAudioFilePath`. */\n getRawAudioFilePath(trackId: string): Promise<string | null>;\n\n /**\n * Persist the cue-points detected in the raw (un-trimmed) audio file.\n * Sample positions are in input-file coordinates.\n */\n setRawCuePoints(trackId: string, cues: PluginCuePoints | null): Promise<void>;\n\n /** Read raw-domain cue points persisted via `setRawCuePoints`. */\n getRawCuePoints(trackId: string): Promise<PluginCuePoints | null>;\n\n /** Persist the current trim window inside the raw audio file. */\n setTrimWindow(trackId: string, window: PluginTrimWindow | null): Promise<void>;\n\n /** Read the current trim window persisted via `setTrimWindow`. */\n getTrimWindow(trackId: string): Promise<PluginTrimWindow | null>;\n\n /**\n * Re-trim the raw audio file at the given sample offset and replace the\n * track's audio clip with the new slice. Persists updated trimmed-domain\n * cue points and the new trim window.\n */\n commitTrimWindow(\n trackId: string,\n startSample: number,\n durationSamples: number,\n ): Promise<{ filePath: string; cuePoints: PluginCuePoints | null }>;\n\n // --- Scene Composition ---\n\n /** Trigger bulk composition for the active scene (LLM plans arrangement, creates tracks, generates MIDI). */\n composeScene(options: ComposeSceneOptions): Promise<ComposeSceneResult>;\n\n /** Subscribe to composition progress events (planning, generating, complete, error). */\n onComposeProgress(listener: ComposeProgressListener): UnsubscribeFn;\n\n /** Subscribe to engine ready events (fires when the engine finishes loading tracks after a scene change). */\n onEngineReady(listener: () => void): UnsubscribeFn;\n\n /**\n * Subscribe to external state mutations (CLI, MCP, or HTTP-API tool calls\n * that bypass plugin-host methods). Fires after such a tool finishes,\n * signalling that scene/track DB state may have changed underneath the\n * plugin's local cache. Use it to refresh state that the plugin doesn't\n * own — e.g. re-running adoptSceneTracks() so AI-created tracks become\n * visible without requiring the user to switch scenes.\n *\n * Optional: only the renderer-side host implements this. Main-side\n * plugins should subscribe to the typed domain-event bus instead.\n */\n onAfterAgentMutation?(listener: () => void): UnsubscribeFn;\n\n // --- MIDI Extensions (Phase 2) ---\n\n /** Audition a single note on a track (fire-and-forget preview). */\n auditionNote(trackId: string, pitch: number, velocity: number, durationMs: number): Promise<void>;\n\n // --- Plugin Presets (Phase 2) ---\n\n /** Get presets for this plugin, optionally filtered by category. */\n getPluginPresets(category?: string): Promise<PluginPresetInfo[]>;\n\n /** Save a new preset for this plugin. */\n savePluginPreset(options: SavePluginPresetOptions): Promise<PluginPresetInfo>;\n\n /** Delete a plugin preset by ID. */\n deletePluginPreset(id: string): Promise<void>;\n\n // --- Performance / Logging (Phase 2) ---\n\n /** Log a performance metric. */\n logMetric(name: string, durationMs: number, metadata?: Record<string, unknown>): void;\n\n /** Start a timer. Returns a stop function that logs the duration. */\n startTimer(name: string): () => void;\n\n // --- Stem Splitting ---\n\n /** Split an audio track into stems (vocals, drums, bass, other). Creates new muted tracks. */\n splitStems(trackId: string): Promise<PluginStemSplitResult>;\n\n /** Check if the stem splitter binary is available. */\n isStemSplitterAvailable(): Promise<boolean>;\n\n // --- Audio Recording (capability-gated, since SDK 2.1.0) ---\n\n /**\n * Enumerate audio input devices visible to the engine. Empty list means\n * no input device is available (or the OS denied permission). Requires\n * `audioCapture` capability.\n * @since SDK 2.1.0\n */\n getAudioInputDevices(): Promise<AudioInputDevice[]>;\n\n /**\n * Snapshot of engine state needed to start a recording session. Reads\n * the engine sample rate, the active scene id, the transition-render\n * lock state, and current BPM/bars. Requires `audioCapture`.\n * @since SDK 2.1.0\n */\n getRecordingTargetInfo(): Promise<RecordingTargetInfo>;\n\n /**\n * Begin a recording session. Engine writes integer-PCM WAV chunks to\n * disk; one chunk per call to `markRecordingChunkBoundary`. Each\n * finalized chunk fires a `RecordingChunkFinalizedEvent` to\n * subscribers of `onRecordingChunkFinalized`. Throws\n * AUDIO_CAPTURE_DENIED on permission failure or if no device is\n * available.\n *\n * Pass `deviceId` to override the platform-configured input (rare —\n * only useful for tests or workflows that need a specific device).\n * Omit it to use the platform's selected input from\n * `AudioRoutingConfig.inputDeviceId` — this is the recommended path\n * for plugins post-SDK-2.2.0.\n *\n * @since SDK 2.1.0 (deviceId required) — 2.2.0 made it optional.\n */\n startTrackRecording(deviceId?: string): Promise<void>;\n\n /**\n * Mark the boundary between two recording chunks. The engine closes the\n * currently-open WAV writer and opens a new one; the closed file fires\n * a `RecordingChunkFinalizedEvent` once flush completes. No-op if no\n * recording session is active.\n *\n * Pass `boundaryHostTimeNs` from `DeckBoundaryEvent.boundaryHostTimeNs`\n * for sample-perfect take alignment (Path 2). The engine then splits\n * the chunk at the EXACT recorder-sample that corresponds to that\n * host-time, eliminating the ~5–50 ms of jitter introduced by the\n * legacy \"split wherever the writer is\" path. Required for any\n * workflow that overlays multiple takes (vocalist comping, layered\n * dubs); optional for single-take captures.\n *\n * @since SDK 2.1.0 — 2.4.0 added optional boundaryHostTimeNs.\n */\n markRecordingChunkBoundary(boundaryHostTimeNs?: number): Promise<void>;\n\n /**\n * Stop the active recording session. The final chunk is closed and\n * finalized; its `RecordingChunkFinalizedEvent` fires before this\n * promise resolves. Returns the path of the final chunk (also delivered\n * via the event for symmetry).\n * @since SDK 2.1.0\n */\n stopTrackRecording(): Promise<{ finalChunkPath: string; durationMs: number }>;\n\n /**\n * Subscribe to chunk-finalized events for this plugin's active recording\n * session. Auto-unsubscribed on `deactivate`. Returns unsubscribe fn.\n * @since SDK 2.1.0\n */\n onRecordingChunkFinalized(\n listener: (event: RecordingChunkFinalizedEvent) => void\n ): UnsubscribeFn;\n\n /**\n * Get the platform-configured audio input device, or null when no\n * device is set. Read-only; configured via the assistant's\n * AudioRoutingPanel. Plugins use this to display the current input\n * to the user without exposing their own picker.\n *\n * @since SDK 2.2.0\n */\n getCurrentInputDevice(): Promise<AudioInputDevice | null>;\n\n /**\n * Subscribe to input-device changes (user picks a new mic in the\n * Audio Routing panel). Listeners should refetch via\n * `getCurrentInputDevice()`. Returns an unsubscribe fn.\n *\n * @since SDK 2.4.0\n */\n onInputDeviceChange(listener: () => void): UnsubscribeFn;\n\n /**\n * Get the platform's mic-to-output round-trip latency offset in\n * samples. 0 = uncalibrated. Plugins recording audio apply this via\n * `setAudioOffsetSamples` so takes line up with the source loop.\n *\n * @since SDK 2.2.0\n */\n getRecordingLatencyOffsetSamples(): Promise<number>;\n\n /**\n * Snapshot of the input level for the most recent audio block.\n * Renderer polls at ~30Hz to drive a level meter / scrolling\n * waveform without an event-channel subscription.\n *\n * @since SDK 2.3.0\n */\n getRecordingInputLevel(): Promise<{\n peakDb: number;\n peakLinear: number;\n clipped: boolean;\n active: boolean;\n }>;\n\n /**\n * Reset the latched clip indicator. Safe regardless of whether\n * monitoring or recording is active.\n *\n * @since SDK 2.3.0\n */\n clearRecordingInputClipIndicator(): Promise<void>;\n}\n\n// ============================================================================\n// Stem Splitting Types\n// ============================================================================\n\n/** Stem type identifiers */\nexport type StemType = 'vocals' | 'drums' | 'bass' | 'other';\n\n/** Result of splitting an audio track into stems */\nexport interface PluginStemSplitResult {\n /** Created stem tracks with audio loaded (all auto-muted) */\n stems: PluginStemTrackInfo[];\n}\n\n/** Information about a single stem track created by stem splitting */\nexport interface PluginStemTrackInfo {\n /** The stem type (vocals, drums, bass, other) */\n stemType: StemType;\n /** Track handle for the new stem track */\n track: PluginTrackHandle;\n}\n\n// ============================================================================\n// Exported Plugin Data Types (for .sasproj portability)\n// ============================================================================\n\nexport interface ExportedPluginData {\n pluginId: string;\n scope: 'project' | 'scene' | 'global';\n scopeId: string | null;\n key: string;\n value: string; // JSON-serialized\n}\n\n// ============================================================================\n// Track Types\n// ============================================================================\n\nexport interface CreateTrackOptions {\n /** Display name for the track. Auto-generated if omitted. */\n name?: string;\n /** Musical role hint: 'bass', 'drums', 'lead', 'chords', 'pad', 'arp', 'fx' */\n role?: string;\n /** Load a synth plugin immediately (default: false) */\n loadSynth?: boolean;\n /** Which synth to load (default: 'Surge XT'). Ignored if loadSynth=false. */\n synthName?: string;\n /**\n * Stable plugin identifier for a custom instrument (VST3 TUID or AU component ID).\n * If provided with loadSynth=true, loads this plugin instead of synthName.\n * Null/undefined = use default (Surge XT).\n */\n instrumentPluginId?: string | null;\n /** Metadata stored in DB. Plugins can use this for plugin-specific data. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface PluginTrackHandle {\n /** Tracktion engine track ID (stable, GUID-based) */\n id: string;\n /** Display name */\n name: string;\n /** Database row ID */\n dbId: string;\n /** Musical role (if set) */\n role?: string;\n /** Prompt from tracks table (fallback when plugin_data not yet populated) */\n prompt?: string;\n /** Custom instrument plugin ID (null = default Surge XT) */\n instrumentPluginId?: string | null;\n /** Custom instrument display name (null = Surge XT) */\n instrumentName?: string | null;\n}\n\n/**\n * One source track offered by `listImportableTracks`, already filtered to the\n * calling panel's type. The host computes the gate; the UI only renders it.\n * @since SDK 2.13.0\n */\nexport interface ImportCandidateTrack {\n /** Source track's engine track id (the selector passed back to importTrack). */\n trackId: string;\n /** Source track's DB row id (globally unique; good React key). */\n dbId: string;\n /** Display name shown in the modal row. */\n name: string;\n /** Musical role if set (drives the row icon). */\n role?: string;\n /** True when this track can be copied into the active scene as-is. */\n importable: boolean;\n /** Why the track is disabled (shown as a tooltip). Present iff `!importable`. */\n disabledReason?: string;\n}\n\n/**\n * One track in a specific scene, returned by `host.listSceneFamilyTracks`,\n * already narrowed to the calling panel's family. Unlike `ImportCandidateTrack`\n * it carries NO import gate — the crossfade picker lists every same-family track\n * in the origin/target scene regardless of key/length. @since SDK 2.22.0\n */\nexport interface SceneFamilyTrack {\n /** Track's DB row id — the selector for getTrackSound + crossfade metadata. */\n dbId: string;\n /** Display name shown in the picker. */\n name: string;\n /** Musical role if set — used to enforce same-role crossfade pairing. */\n role?: string;\n /** Generation prompt, when the track was AI-generated. The crossfade picker\n * shows this as the primary label (users recognise tracks by prompt, not id).\n * @since SDK 2.27.0 */\n prompt?: string;\n}\n\n/**\n * One OTHER scene and its candidate tracks (already type-filtered). Scenes with\n * zero candidates of the panel's type are omitted by the host.\n * @since SDK 2.13.0\n */\nexport interface ImportCandidateScene {\n /** Source scene's engine scene id. */\n sceneId: string;\n /** Source scene's display name. */\n sceneName: string;\n /** Candidate tracks of this panel's type (may include disabled ones). */\n tracks: ImportCandidateTrack[];\n /**\n * True for the synthetic \"this scene — other panels\" entry: the ACTIVE\n * scene's MIDI tracks owned by OTHER panels. Importing one re-sounds the part\n * on the calling panel's instrument (via `readImportableTrackMidi` +\n * `writeMidiClip`) rather than faithfully copying it. Absent/false for\n * ordinary cross-scene entries. @since SDK 2.20.0\n */\n sameScene?: boolean;\n}\n\n/**\n * A source track's current sound, as returned by `host.getTrackSound`. The\n * discriminant matches the panel that reads it: drums → 'sample', instruments →\n * 'instrument', synths → 'preset'. `label` is the human name for the History row.\n * @since SDK 2.14.0\n */\n/**\n * How a synth `state` blob is serialized. `valuetree` is Tracktion's wrapped\n * format (default Surge XT presets); `raw` is the plugin's own\n * getStateInformation format (third-party instruments). Absent ⇒ `valuetree`,\n * for backward compatibility with history recorded before SDK 2.15.0.\n * @since SDK 2.15.0\n */\nexport type SynthStateType = 'raw' | 'valuetree';\n\nexport type TrackSoundSnapshot =\n | { kind: 'sample'; samplePath: string; label: string }\n | { kind: 'instrument'; displayName: string; instrumentId: string | null; zones: InstrumentZone[]; label: string }\n | { kind: 'preset'; state: string; label: string; stateType?: SynthStateType };\n\n/** Options for `PluginHost.listImportableTracks`. @since SDK 2.13.0 */\nexport interface ListImportableTracksOptions {\n /**\n * Coarse content family. 'midi' = synth/drum/instrument, 'audio' = stems,\n * 'sample' = loops. Defaults are derived from the calling plugin id, so\n * panels normally pass nothing.\n */\n family?: 'midi' | 'audio' | 'sample';\n /**\n * When true, prepend the active scene's MIDI tracks owned by OTHER panels as a\n * `sameScene` entry (the cross-panel re-sound source). Off by default so the\n * plain cross-scene import is unchanged. MIDI panels only. @since SDK 2.20.0\n */\n includeSameScene?: boolean;\n}\n\nexport interface PluginTrackInfo extends PluginTrackHandle {\n /** Is track muted? */\n muted: boolean;\n /** Is track soloed? */\n soloed: boolean;\n /** Volume (linear 0-1) */\n volume: number;\n /** Pan (-1 to 1) */\n pan: number;\n /** Loaded plugins on this track */\n plugins: PluginSynthInfo[];\n /** Has MIDI clips? */\n hasMidi: boolean;\n /** Has audio clips? */\n hasAudio: boolean;\n}\n\nexport interface PluginSynthInfo {\n index: number;\n name: string;\n type: string; // 'VST3' | 'AudioUnit' | 'Internal'\n enabled: boolean;\n}\n\n// ============================================================================\n// Real-time Track State Types\n// ============================================================================\n\n/** Real-time runtime state of a track (pushed from engine) */\nexport interface PluginTrackRuntimeState {\n id: string;\n muted: boolean;\n solo: boolean;\n volume: number;\n pan: number;\n}\n\n/** Listener for real-time track state changes */\nexport type TrackStateChangeListener = (trackId: string, state: PluginTrackRuntimeState) => void;\n\n// ============================================================================\n// FX Detail Types (SDK-friendly re-export)\n// ============================================================================\n\n/** Per-category FX detail state */\nexport interface PluginFxCategoryDetailState {\n enabled: boolean;\n presetIndex: number; // 0-4\n dryWet: number; // 0.0-1.0\n}\n\n/** Full FX detail state for a track — one entry per FX category */\nexport type PluginTrackFxDetailState = Record<string, PluginFxCategoryDetailState>;\n\n// ============================================================================\n// MIDI Types\n// ============================================================================\n\nexport interface MidiClipData {\n /** Start time in seconds */\n startTime: number;\n /** End time in seconds */\n endTime: number;\n /** BPM for beat<->time conversion */\n tempo: number;\n /** MIDI notes */\n notes: PluginMidiNote[];\n}\n\nexport interface PluginMidiNote {\n /** MIDI pitch 0-127 */\n pitch: number;\n /** Start position in quarter-note beats (0 = beginning of clip) */\n startBeat: number;\n /** Duration in quarter-note beats */\n durationBeats: number;\n /** Velocity 1-127 */\n velocity: number;\n /** MIDI channel 0-15 (default: 0) */\n channel?: number;\n}\n\nexport interface MidiWriteResult {\n /** Number of notes written */\n notesInserted: number;\n /** Actual bars covered */\n bars: number;\n}\n\n/**\n * One clip returned by {@link PluginHost.readMidiNotes}. `endTime - startTime`\n * (seconds) is the clip's loop span; round-trip it back into\n * {@link MidiClipData} on save so an edit never changes the clip length.\n * @since SDK 2.15.0\n */\nexport interface ReadMidiClip {\n /** Clip start in seconds (engine timeline). */\n startTime: number;\n /** Clip end in seconds. Loop span = endTime - startTime. */\n endTime: number;\n /** Beat-based notes, identical shape to {@link MidiClipData.notes}. */\n notes: PluginMidiNote[];\n}\n\n/**\n * Result of {@link PluginHost.readMidiNotes}: every clip on the track. Drum /\n * instrument / synth tracks are single-clip, so callers normally use\n * `clips[0]`; the array form mirrors the engine and is future-proof.\n * @since SDK 2.15.0\n */\nexport interface ReadMidiResult {\n clips: ReadMidiClip[];\n}\n\n/**\n * Options for {@link PluginHost.exportTracksAsMidiBundle}.\n * @since SDK 1.1.0\n */\nexport interface ExportMidiBundleOptions {\n /** Default ZIP filename suggested in the save dialog (without extension). */\n defaultName?: string;\n}\n\n/**\n * Result of {@link PluginHost.exportTracksAsMidiBundle}.\n * @since SDK 1.1.0\n */\nexport type ExportMidiBundleResult =\n | { success: true; filePath: string; trackCount: number; skippedCount: number }\n | { success: false; canceled: true }\n | { success: false; canceled?: false; error: string };\n\n// ============================================================================\n// Audio Processing Bridge (SDK 1.2.0 — see ai-orchestration-design.md §16)\n// ============================================================================\n\n/** @since SDK 1.2.0 */\nexport interface ExportTrackAudioResult {\n path: string;\n bpm: number;\n durationMs: number;\n fromCopyFastPath?: boolean;\n}\n\n/** @since SDK 1.2.0 */\nexport interface ProcessAudioResult {\n outputPath: string;\n}\n\n/** @since SDK 1.2.0 */\nexport type AudioProcessingOp =\n | { tool: 'normalize' }\n | { tool: 'compress'; params?: { threshold?: number; ratio?: number } }\n | { tool: 'eq'; params?: { low_gain?: number; mid_gain?: number; high_gain?: number } }\n | { tool: 'reverb'; params?: { room_size?: number; dry_wet?: number } }\n | { tool: 'pitch-shift'; params: { semitones: number } }\n | { tool: 'time-stretch'; params: { target_bpm: number } }\n | { tool: 'filter'; params: { type: 'lowpass' | 'highpass'; cutoff: number } }\n | { tool: 'gain'; params: { db: number } }\n | { tool: 'limit' }\n | { tool: 'trim'; params?: { start?: number; end?: number } };\n\nexport interface PostProcessOptions {\n /** Snap notes to grid (default: true) */\n quantize?: boolean;\n /** Grid size: '1/4', '1/8', '1/16', '1/32', '1/8T', '1/16T' (default: '1/16') */\n quantizeGrid?: string;\n /** Quantize strength 0-100 (default: 75) */\n quantizeStrength?: number;\n /** Swing amount 0-100 (default: 0) */\n swing?: number;\n /** Humanize timing/velocity variation 0-100 (default: 0) */\n humanize?: number;\n /** Enforce diatonic scale (default: false). Uses scene key/mode. */\n enforceScale?: boolean;\n /** Clamp notes to pitch range [low, high] */\n clampRegister?: [number, number];\n /** Remove overlapping notes on same pitch/channel (default: true) */\n removeOverlaps?: boolean;\n}\n\n// ============================================================================\n// Context Types\n// ============================================================================\n\nexport interface MusicalContext {\n key: string; // 'C', 'D', 'Eb', 'F#', etc.\n mode: string; // 'major', 'minor', 'dorian', 'mixolydian', etc.\n bpm: number; // 20-960\n bars: number; // Scene length in bars\n genre: string | null; // 'Drum & Bass', 'Lo-fi Hip Hop', etc.\n timeSignature: string; // '4/4', '3/4', '6/8'\n chordProgression: PluginChordTiming[];\n /**\n * The scene's natural-language contract prompt (e.g. \"dark psytrance,\n * driving 130 BPM, claustrophobic\"). Null when the scene has no\n * contract set yet. Auto-prefixed to the LLM by `host.generateWithLLM`\n * so every per-track generation sees the scene-level intent without\n * each plugin having to plumb it through manually.\n * @since SDK 1.2.0\n */\n contractPrompt: string | null;\n}\n\nexport interface PluginChordTiming {\n /** Chord symbol: 'Cm7', 'G', 'Fmaj7', etc. */\n symbol: string;\n /** Start position in quarter notes */\n startQn: number;\n /** End position in quarter notes */\n endQn: number;\n}\n\n/** Full generation context — includes concurrent track MIDI data */\nexport interface PluginGenerationContext {\n chordProgression: {\n key: { tonic: string; mode: string };\n chordsWithTiming: PluginChordTiming[];\n genre: string | null;\n };\n concurrentTracks: PluginConcurrentTrackInfo[];\n /**\n * Count of tracks the host had to drop entirely from `concurrentTracks`\n * because their notes pushed the running total past the cross-track\n * budget. Panels should disclose this to the LLM (e.g. \"… N additional\n * tracks omitted to fit token budget\") so the model knows it is\n * working with partial context.\n * @since SDK 1.2.0\n */\n truncatedTrackCount?: number;\n}\n\nexport interface PluginConcurrentTrackInfo {\n trackId: string;\n role: string | undefined;\n presetCategory: string | null;\n /** Notes organized by which chord they fall under */\n notesByChord: PluginChordSegment[];\n /**\n * The user-typed prompt that produced this track's MIDI (from\n * `tracks.prompt`). Lets the LLM see *intent* alongside the notes —\n * \"punchy 909 kick\" carries more meaning than the kick MIDI alone.\n * @since SDK 1.2.0\n */\n prompt?: string;\n /**\n * True when the host capped this track's notes (per-track budget).\n * The `notesByChord` payload is a prefix of the real content; the\n * total dropped count is `originalNoteCount - sum(notesByChord.notes.length)`.\n * @since SDK 1.2.0\n */\n truncated?: boolean;\n /** The track's full note count before per-track truncation. */\n originalNoteCount?: number;\n}\n\nexport interface PluginChordSegment {\n chord: string;\n chordRangeQn: [number, number];\n notes: PluginMidiNote[];\n}\n\n// ============================================================================\n// Transport Types\n// ============================================================================\n\nexport interface TransportEvent {\n type: 'play' | 'stop' | 'pause' | 'bpmChange' | 'positionChange';\n bpm?: number;\n position?: number; // in seconds\n isPlaying?: boolean;\n}\n\nexport interface DeckBoundaryEvent {\n deckId: string; // 'loop-a', 'loop-b'\n bar: number; // Current bar number (1-based)\n beat: number; // Current beat within bar (1-based)\n loopCount: number; // How many loops completed\n /**\n * Stream-time sample index at which the loop wrap was detected in the\n * audio thread (engine's AudioBoundaryProbe). Undefined when the\n * audio-thread anchor was unavailable. @since SDK 2.4.0\n */\n boundaryAudioSamplePosition?: number;\n /**\n * Monotonic host-time (nanoseconds) at the audio block in which the\n * loop wrap was detected. Same clock as\n * `juce::AudioIODeviceCallbackContext::hostTimeNs`. Pair with\n * `markRecordingChunkBoundary(boundaryHostTimeNs)` for sample-perfect\n * take alignment. @since SDK 2.4.0\n */\n boundaryHostTimeNs?: number;\n}\n\nexport interface PluginTransportState {\n isPlaying: boolean;\n isPaused: boolean;\n bpm: number;\n position: number; // in seconds\n timeSignature: string;\n}\n\n/**\n * Mono peak level for a single track, as reported by `getTrackLevels()`.\n * Drives the cosmetic per-track strip meters. `peakDb` is the max of the\n * L/R channels, floored at -120 (the \"no signal\" sentinel).\n * @since SDK 2.21.0\n */\nexport interface PluginTrackLevel {\n /** Tracktion engine track id — matches `PluginTrackHandle.id`. */\n trackId: string;\n /** Mono peak in dBFS (max of L/R), floored at -120. */\n peakDb: number;\n /** Latched overload since the last poll. */\n clipped: boolean;\n}\n\nexport interface PluginSceneInfo {\n id: string;\n name: string;\n isMuted: boolean;\n}\n\n/** Scene-level contract/context state passed to plugin UIs as a prop */\nexport interface PluginSceneContext {\n /** Whether a contract has been generated (genre or contractPrompt exists AND chords exist) */\n hasContract: boolean;\n /** Original user prompt text (e.g., \"dark psytrance\"). Null if none. */\n contractPrompt: string | null;\n /** Extracted genre. Null if none. */\n genre: string | null;\n /** Musical key. Null if no chord progression. */\n key: { tonic: string; mode: string } | null;\n /** Chord symbols (e.g., [\"Cm\", \"Fm\", \"G\"]). Empty if no chords. */\n chords: string[];\n /** BPM from project tempo */\n bpm: number;\n /** Scene length in bars */\n bars: number;\n /** Whether any synth tracks exist in this scene */\n hasTracks: boolean;\n /** Whether bulk generation is currently in progress */\n isBulkGenerating: boolean;\n /**\n * Scene kind. A 'transition' scene bridges two other scenes (the\n * transition-as-scene feature) and unlocks the crossfade-track UI in the\n * instrument panels; ordinary scenes are 'scene'. Absent on older hosts.\n * @since SDK 2.22.0\n */\n sceneType?: 'scene' | 'transition';\n /** For a transition scene, the DB id of the scene it bridges FROM (origin). Null otherwise. @since SDK 2.22.0 */\n transitionFromSceneId?: string | null;\n /** For a transition scene, the DB id of the scene it bridges TO (target). Null otherwise. @since SDK 2.22.0 */\n transitionToSceneId?: string | null;\n}\n\n/** Placeholder track state for the progressive bulk-add UX */\nexport interface BulkAddPlaceholderTrack {\n id: string;\n planIndex: number;\n role: string;\n description: string;\n status: 'planned' | 'creating' | 'completed' | 'failed';\n error?: string;\n}\n\nexport type TransportEventListener = (event: TransportEvent) => void;\nexport type DeckBoundaryListener = (event: DeckBoundaryEvent) => void;\nexport type SceneChangeListener = (sceneId: string | null) => void;\nexport type UnsubscribeFn = () => void;\n\n// ============================================================================\n// LLM Types\n// ============================================================================\n\nexport interface LLMGenerationRequest {\n /** System prompt (instructions, role, output format) */\n system: string;\n /** User prompt (the actual request) */\n user: string;\n /** Max tokens for response (host may cap this) */\n maxTokens?: number;\n /** Expected response format hint */\n responseFormat?: 'text' | 'json';\n /**\n * If true, the host will NOT auto-prefix the user prompt with musical\n * context (key, BPM, chords, genre, etc.). Default: false (context IS\n * prefixed automatically).\n */\n skipContextPrefix?: boolean;\n}\n\nexport interface LLMGenerationResult {\n /** Raw response text */\n content: string;\n /** Tokens consumed */\n tokensUsed: number;\n /** Model that generated the response */\n model: string;\n}\n\n// ----------------------------------------------------------------------------\n// Tool-use LLM types (Gemini-native shape, since SDK 2.4.0)\n// ----------------------------------------------------------------------------\n//\n// Plugins that want a Claude-Code / VS-Code-agent-mode loop call\n// `host.generateWithLLMTools(...)` with these shapes. The host forwards to\n// the gateway's Gemini-native passthrough endpoint, where Google's API key\n// is added centrally — plugins never see the raw key. Token usage is\n// tracked by the gateway just like `generateWithLLM`.\n//\n// Shapes mirror Gemini's REST `generateContent` surface deliberately. We do\n// not pull in `@google/genai` as a dependency: with the gateway as a\n// passthrough and the host owning auth, an SDK adds no value over typed\n// JSON, and we keep tighter control of breaking changes.\n\n/** A single part of a Gemini-style content block. */\nexport interface LLMPart {\n /** Plain text. Mutually exclusive with functionCall / functionResponse. */\n text?: string;\n /** A tool/function the model is asking the host to invoke. */\n functionCall?: {\n name: string;\n args: Record<string, unknown>;\n /**\n * Opaque signature returned by Gemini 3+ tool-use models. Must be echoed\n * verbatim when the assistant turn is replayed on a later iteration, or\n * the API rejects the request with a 400 (\"Function call is missing a\n * thought_signature in functionCall parts.\"). Pre-Gemini-3 models leave\n * this undefined; preserving it round-trip is safe across families.\n */\n thoughtSignature?: string;\n };\n /** The result of a tool call, fed back into the loop on the next turn. */\n functionResponse?: {\n name: string;\n response: Record<string, unknown>;\n };\n}\n\nexport interface LLMContent {\n /** 'user' = user/tool-result; 'model' = assistant. */\n role: 'user' | 'model';\n parts: LLMPart[];\n}\n\nexport interface LLMFunctionDeclaration {\n name: string;\n description: string;\n /** JSON Schema. Use `type: 'object'` with `properties` for any tool. */\n parameters: {\n type: 'object';\n properties?: Record<string, unknown>;\n required?: string[];\n };\n}\n\nexport interface LLMTool {\n functionDeclarations: LLMFunctionDeclaration[];\n}\n\nexport interface LLMGenerationConfig {\n temperature?: number;\n topP?: number;\n topK?: number;\n maxOutputTokens?: number;\n}\n\nexport interface LLMSystemInstruction {\n parts: { text: string }[];\n}\n\nexport interface LLMToolUseRequest {\n /** Gemini model id (e.g. 'gemini-2.5-flash'). */\n model: string;\n /** Conversation so far, including any tool-result turns. */\n contents: LLMContent[];\n /** System prompt as Gemini-native systemInstruction. */\n systemInstruction?: LLMSystemInstruction;\n /** Tool declarations the model may call. */\n tools?: LLMTool[];\n /** Optional tool-call mode override. */\n toolConfig?: {\n functionCallingConfig?: {\n mode?: 'AUTO' | 'ANY' | 'NONE';\n allowedFunctionNames?: string[];\n };\n };\n generationConfig?: LLMGenerationConfig;\n}\n\nexport interface LLMUsageMetadata {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n}\n\nexport interface LLMCandidate {\n content: LLMContent;\n finishReason?: string;\n index?: number;\n}\n\nexport interface LLMToolUseResponse {\n candidates: LLMCandidate[];\n usageMetadata?: LLMUsageMetadata;\n}\n\n// ============================================================================\n// Preset Types\n// ============================================================================\n\nexport interface PluginPresetData {\n name: string;\n category: string;\n /** Base64-encoded plugin state — pass to setPluginState() */\n state: string;\n}\n\n/** Result of shufflePreset() — the new preset that was applied */\nexport interface ShufflePresetResult {\n presetName: string;\n presetCategory: string;\n}\n\n/**\n * One entry in a track's in-session \"sound history\" — the data behind the\n * TrackRow ↩ back-arrow and the drawer \"History\" tab (see `useSoundHistory`).\n *\n * `descriptor` is opaque to the SDK: each generator plugin defines its own shape\n * (a drum sample path string, an instrument `{ displayName, zones }`, a synth\n * `{ pluginIndex, stateBase64 }`) and is the value handed back to the plugin's\n * `applySound` callback to re-apply the sound.\n */\nexport interface SoundHistoryEntry {\n /** Human-readable label shown in the History list (filename, preset/instrument name). */\n label: string;\n /** Opaque, plugin-defined value used to re-apply this sound. */\n descriptor: unknown;\n /** User-starred. Favorited entries are never auto-evicted by the history cap. */\n favorite?: boolean;\n}\n\n// ============================================================================\n// Settings Types\n// ============================================================================\n\nexport interface PluginSettingsSchema {\n type: 'object';\n properties: Record<string, SettingDefinition>;\n}\n\nexport interface SettingDefinition {\n type: 'string' | 'number' | 'boolean' | 'select';\n label: string;\n description?: string;\n default?: unknown;\n /** For 'select' type */\n options?: Array<{ label: string; value: string }>;\n /** For 'number' type */\n min?: number;\n max?: number;\n}\n\nexport interface PluginSettingsStore {\n get<T>(key: string, defaultValue: T): T;\n set(key: string, value: unknown): void;\n getAll(): Record<string, unknown>;\n /** Subscribe to settings changes. Returns unsubscribe fn. */\n onChange(listener: (key: string, value: unknown) => void): UnsubscribeFn;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\nexport type PluginErrorCode =\n | 'NOT_OWNED' // Tried to modify a track not owned by this plugin\n | 'TRACK_NOT_FOUND' // Track ID doesn't exist in engine\n | 'TRACK_LIMIT_EXCEEDED' // Plugin has too many tracks\n | 'NO_ACTIVE_SCENE' // No scene selected\n | 'ENGINE_ERROR' // Tracktion engine call failed\n | 'INVALID_MIDI' // Malformed MIDI data\n | 'FILE_NOT_FOUND' // Audio file doesn't exist\n | 'INVALID_FORMAT' // Unsupported audio format\n | 'PLUGIN_NOT_FOUND' // VST/AU plugin not installed\n | 'LLM_BUDGET_EXCEEDED' // Over token limit\n | 'LLM_UNAVAILABLE' // Gateway unreachable\n | 'NOT_AUTHENTICATED' // User not logged in\n | 'TIMEOUT' // Operation timed out\n | 'CANCELLED' // User cancelled the operation\n | 'INCOMPATIBLE' // Plugin requires newer SDK version\n | 'CAPABILITY_DENIED' // Plugin lacks required capability\n | 'SECRET_NOT_FOUND' // Secret key doesn't exist\n | 'VALIDATION_ERROR' // Inputs failed schema/format validation\n | 'AUDIO_CAPTURE_DENIED'; // OS-level mic permission denied or input device unavailable\n\nexport class PluginError extends Error {\n public readonly code: PluginErrorCode;\n public readonly details?: Record<string, unknown>;\n\n constructor(\n code: PluginErrorCode,\n message: string,\n details?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'PluginError';\n this.code = code;\n this.details = details;\n }\n}\n\n// ============================================================================\n// Plugin Manifest (on-disk plugin.json)\n// ============================================================================\n\nexport interface PluginManifest {\n id: string;\n displayName: string;\n version: string;\n description: string;\n generatorType: GeneratorType;\n main: string; // e.g., 'dist/index.js'\n renderer?: string; // e.g., 'dist/ui.bundle.js' (UMD bundle for renderer)\n icon?: string; // e.g., 'assets/icon.svg'\n author?: string;\n license?: string;\n minHostVersion?: string;\n capabilities?: PluginCapabilities;\n settings?: Record<string, SettingDefinition>;\n builtIn?: boolean;\n repository?: string; // e.g., 'https://github.com/user/my-plugin'\n}\n\nexport interface PluginCapabilities {\n requiresLLM?: boolean;\n requiresSurgeXT?: boolean;\n requiresNetwork?: boolean;\n /** Allowed network hosts for httpRequest (e.g., ['api.splice.com']) */\n network?: { allowedHosts?: string[] };\n /** Plugin needs native file dialog access */\n fileDialog?: boolean;\n /**\n * Plugin needs microphone / line-in capture. Gates the recording host\n * methods (getAudioInputDevices, startTrackRecording, etc).\n * @since SDK 2.1.0\n */\n audioCapture?: boolean;\n}\n\n// ============================================================================\n// Audio Recording (since SDK 2.1.0)\n// ============================================================================\n\n/**\n * Audio input device exposed by the audio engine. The `deviceId` is the\n * stable identifier returned by JUCE's AudioDeviceManager and accepted as\n * the device argument to `startTrackRecording`.\n * @since SDK 2.1.0\n */\nexport interface AudioInputDevice {\n /** Stable device identifier — passed back to startTrackRecording. */\n deviceId: string;\n /** Human-readable device name (e.g., \"MacBook Pro Microphone\", \"USB Mic\"). */\n label: string;\n /** True if this is the system default input device. */\n isDefault: boolean;\n /** Number of input channels the device supports (1 = mono, 2 = stereo). */\n channelCount: number;\n}\n\n/**\n * Engine state snapshot that an audio-recording plugin needs before\n * starting a session.\n * @since SDK 2.1.0\n */\nexport interface RecordingTargetInfo {\n /** Engine device sample rate, e.g. 44100 or 48000. */\n engineSampleRate: number;\n /** Active scene id, or null when no scene is selected. */\n sceneId: string | null;\n /** True when a transition render lock is held — recorder must refuse. */\n isRenderLocked: boolean;\n /** Current project BPM. */\n bpm: number;\n /** Active scene length in bars (4/4 assumed), or null when no scene. */\n bars: number | null;\n /**\n * Sample-perfect-recording compatibility (Path 2 gate). When false,\n * the recorder must refuse to start a session and surface\n * `recordingCompatibilityReason` to the user — input + output\n * devices cannot be sample-aligned.\n * @since SDK 2.4.0\n */\n canRecordSamplePerfect?: boolean;\n recordingCompatibilityReason?: string;\n}\n\n/**\n * Event payload fired when the engine finalizes a recording chunk WAV\n * file (either at a boundary mark or at session stop).\n * @since SDK 2.1.0\n */\nexport interface RecordingChunkFinalizedEvent {\n /** Absolute path to the finalized WAV file on disk. */\n filePath: string;\n /** Zero-based chunk index within the active session. */\n chunkIndex: number;\n /** Duration of this chunk in milliseconds. */\n durationMs: number;\n /** WAV sample rate. */\n sampleRate: number;\n /** WAV channel count. */\n channels: number;\n /**\n * Sample-perfect-recording metadata (Path 2). When the chunk was\n * closed via a host-time-anchored `markRecordingChunkBoundary` call,\n * carries recorder-local sample positions plus the host-time at\n * which the boundary fired. Undefined / -1 means the boundary\n * lacked a host-time anchor (legacy or stop-driven finalize).\n * @since SDK 2.4.0\n */\n recorderSampleStart?: number;\n recorderSampleEnd?: number;\n boundaryHostTimeNs?: number;\n}\n\n// ============================================================================\n// Phase 2: File System Types\n// ============================================================================\n\nexport interface PluginFileDialogOptions {\n title?: string;\n defaultPath?: string;\n filters?: Array<{ name: string; extensions: string[] }>;\n /** For open dialog: allow selecting multiple files */\n multiSelections?: boolean;\n /** For open dialog: allow selecting directories */\n directories?: boolean;\n}\n\nexport interface PluginDownloadOptions {\n /** HTTP headers to include */\n headers?: Record<string, string>;\n /** Overwrite if file exists (default: false) */\n overwrite?: boolean;\n}\n\n// ============================================================================\n// Phase 2: Network Types\n// ============================================================================\n\nexport interface PluginHttpRequestOptions {\n url: string;\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n headers?: Record<string, string>;\n body?: string | Record<string, unknown>;\n /** Timeout in milliseconds (default: 30000) */\n timeoutMs?: number;\n}\n\nexport interface PluginHttpResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n}\n\n// ============================================================================\n// Phase 2: Sample Library Types\n// ============================================================================\n\nexport interface PluginSampleFilter {\n bpm?: number;\n key?: { tonic: string; mode?: string };\n category?: string;\n searchQuery?: string;\n}\n\nexport interface PluginSampleInfo {\n id: string;\n filename: string;\n filePath: string;\n category: string | null;\n bpm: number | null;\n keyTonic: string | null;\n keyMode: string | null;\n durationSeconds: number | null;\n fileSizeBytes: number | null;\n tags: string[] | null;\n}\n\nexport interface PluginSampleImportResult {\n imported: number;\n skipped: number;\n errors: string[];\n}\n\n/** Sample track with associated sample metadata (returned by getPluginSampleTracks) */\nexport interface PluginSampleTrackInfo {\n track: PluginTrackHandle;\n sample: PluginSampleInfo;\n volume: number;\n pan: number;\n}\n\n// ============================================================================\n// Phase 2: Audio Generation Types\n// ============================================================================\n\nexport interface PluginAudioTextureRequest {\n /** Text prompt describing the audio texture */\n prompt: string;\n /** Duration in seconds (default: scene length) */\n durationSeconds?: number;\n /** Target BPM (default: project BPM) */\n bpm?: number;\n}\n\nexport interface PluginAudioTextureResult {\n /** Path to the generated audio file */\n filePath: string;\n /** Duration of the generated audio in seconds */\n durationSeconds: number;\n /**\n * Beat positions inside the generated audio file plus the detected BPM.\n * Sample positions are relative to the file at `filePath`. Null when the\n * audio-processor did not surface detection data (older binary, fallback\n * path, or processing failed). Persist via `host.setCuePoints` after the\n * clip is written so the OffsetScrubber UI can read them later.\n */\n cuePoints: PluginCuePoints | null;\n /**\n * Path to the un-trimmed (raw) Lyria output. Used by the stems\n * trim editor to draw the full waveform. Persist via\n * `host.setRawAudioFilePath`. Null when no raw file is available.\n */\n rawFilePath?: string | null;\n /** Same beats as `cuePoints` in raw-file sample coordinates. */\n rawCuePoints?: PluginCuePoints | null;\n /**\n * Auto-detected start of the trim window inside the raw file (sample\n * offset). Null when detection was skipped.\n */\n inputStartSample?: number | null;\n}\n\n/**\n * Cue-points sidecar surfaced by the audio-processor `trim` command —\n * sample positions for each detected beat inside the generated WAV.\n * Mirrors the canonical `CuePoints` shape from the assistant; duplicated\n * here so external plugins don't reach into sas-app internals.\n */\nexport interface PluginCuePoints {\n /** Schema version (currently 1). */\n schema: 1;\n /** Sample rate the beat positions are expressed in. */\n sample_rate: number;\n /** Detected BPM (may differ from project BPM). Null when detection failed. */\n detected_bpm: number | null;\n /** Sample position of bar 1 / beat 1 inside the clip. */\n downbeat_sample: number;\n /** Monotone-increasing array of beat positions in samples. */\n beats: number[];\n /** ISO-8601 timestamp of when detection ran. */\n detected_at: string;\n}\n\n/**\n * A trim window inside a raw (un-trimmed) audio file. `start_sample` is\n * the offset from the start of the raw file; `duration_samples` is the\n * length of the trimmed slice. Both are in raw-file sample coordinates.\n */\nexport interface PluginTrimWindow {\n start_sample: number;\n duration_samples: number;\n}\n\n// ============================================================================\n// Scene Composition Types\n// ============================================================================\n\n/** Options for composing a full scene arrangement via LLM. */\nexport interface ComposeSceneOptions {\n /** The contract prompt / musical direction for the arrangement. */\n contractPrompt: string;\n /** Genre hint (e.g. 'techno', 'jazz'). Optional. */\n genre?: string | null;\n}\n\n/** Result from a scene composition. */\nexport interface ComposeSceneResult {\n /** Whether the composition completed successfully. */\n success: boolean;\n /** Number of tracks created. */\n tracksCreated: number;\n /** Error message if not successful. */\n error?: string;\n}\n\n/** Listener for composition progress events. */\nexport type ComposeProgressListener = (event: ComposeProgressEvent) => void;\n\n/** Progress event emitted during scene composition. */\nexport interface ComposeProgressEvent {\n /** Current phase: 'planning' (LLM deciding tracks), 'generating' (creating MIDI), 'complete', 'error'. */\n phase: 'planning' | 'generating' | 'complete' | 'error';\n /** Per-track placeholder state (available once planning is done). */\n placeholders?: BulkAddPlaceholderTrack[];\n /** Error message when phase is 'error'. */\n error?: string;\n /** Scene ID this compose event belongs to (for scene-keyed UI state). */\n sceneId?: string;\n}\n\n// ============================================================================\n// Phase 2: Plugin Preset Types\n// ============================================================================\n\nexport interface PluginPresetInfo {\n id: string;\n name: string;\n category: string | null;\n isBuiltIn: boolean;\n data: Record<string, unknown>;\n}\n\nexport interface SavePluginPresetOptions {\n name: string;\n category?: string;\n data: Record<string, unknown>;\n}\n\n// ============================================================================\n// App Tool Bridge (since SDK 1.2.0)\n// ============================================================================\n\n/** JSON Schema shape for a tool's input params. */\nexport interface PluginAppToolInputSchema {\n type: 'object';\n properties?: Record<string, unknown>;\n required?: string[];\n}\n\n/** Lightweight descriptor returned by `PluginHost.listAppTools`. */\nexport interface PluginAppTool {\n name: string;\n description: string;\n inputSchema: PluginAppToolInputSchema;\n /** `'scene'` = safe for scene-scoped callers. `'project'` = cross-scene. */\n scope?: 'scene' | 'project';\n /**\n * `true` = the operation cannot be undone via the host's checkpoint/undo\n * system (project delete, disk overwrite, external export, …). The host\n * gates such calls behind a user-approval flow when invoked with agent\n * provenance; agent UIs may also surface the flag (e.g. ⚠ in a tool list).\n * @since SDK 2.18.0\n */\n irreversible?: boolean;\n}\n\n/** Result shape returned by `PluginHost.executeAppTool`. */\nexport interface PluginAppToolResult {\n success: boolean;\n action: string;\n message?: string;\n error?: string;\n /**\n * Tool-specific payload. Concrete shape depends on the tool — callers\n * should treat this as opaque unless they know the tool.\n */\n data?: unknown;\n}\n\n// ============================================================================\n// Plugin Registry Types (used by host internals)\n// ============================================================================\n\nexport type PluginStatus = 'pending' | 'active' | 'failed' | 'disabled' | 'incompatible';\n\nexport interface PluginRegistration {\n /** The loaded plugin instance */\n plugin: GeneratorPlugin;\n /** Current status */\n status: PluginStatus;\n /** Resolved manifest from disk */\n manifest: PluginManifest;\n /** The scoped PluginHost instance for this plugin */\n host: PluginHost | null;\n /** Sort order for accordion display */\n sortOrder: number;\n /** Whether the plugin is enabled */\n enabled: boolean;\n /** Error message if status is 'failed' */\n error?: string;\n}\n","/**\n * FX Toggle Types\n *\n * Types and constants for per-track FX toggle buttons.\n * Each track can enable/disable 6 FX categories independently.\n * The engine is the source of truth — no database persistence needed.\n */\n\n/** Available FX categories in signal chain order */\nexport type FxCategory = 'eq' | 'compressor' | 'chorus' | 'phaser' | 'delay' | 'reverb';\n\n/** All FX categories in signal chain order */\nexport const FX_CATEGORIES: readonly FxCategory[] = [\n 'eq',\n 'compressor',\n 'chorus',\n 'phaser',\n 'delay',\n 'reverb',\n] as const;\n\n/** Position in the signal chain (lower = earlier) */\nexport const FX_CHAIN_ORDER: Record<FxCategory, number> = {\n eq: 0,\n compressor: 1,\n chorus: 2,\n phaser: 3,\n delay: 4,\n reverb: 5,\n};\n\n/** Map from FxCategory to Tracktion Engine built-in plugin xmlTypeName */\nexport const FX_ENGINE_PLUGIN_NAMES: Record<FxCategory, string> = {\n eq: '4bandEq',\n compressor: 'compressor',\n chorus: 'chorus',\n phaser: 'phaser',\n delay: 'delay',\n reverb: 'reverb',\n};\n\n/** Display labels for UI buttons */\nexport const FX_DISPLAY_LABELS: Record<FxCategory, string> = {\n eq: 'EQ',\n compressor: 'Comp',\n chorus: 'Chorus',\n phaser: 'Phaser',\n delay: 'Delay',\n reverb: 'Reverb',\n};\n\n/** Per-track FX state: which categories are active */\nexport interface TrackFxState {\n eq: boolean;\n compressor: boolean;\n chorus: boolean;\n phaser: boolean;\n delay: boolean;\n reverb: boolean;\n}\n\n/** Default state: all FX disabled */\nexport const EMPTY_FX_STATE: TrackFxState = {\n eq: false,\n compressor: false,\n chorus: false,\n phaser: false,\n delay: false,\n reverb: false,\n};\n\n// ============================================================================\n// Preset Types\n// ============================================================================\n\n/** A single FX preset definition */\nexport interface FxPreset {\n /** Display name (e.g. \"Room\", \"Hall\") */\n name: string;\n /** Short label for button (e.g. \"RM\", \"HL\") */\n shortLabel: string;\n /** Map from automatable parameter name -> value (set via setPluginParameter) */\n params: Record<string, number>;\n /** CachedValue params set via XML state (getPluginState/setPluginState) */\n xmlStateParams?: Record<string, number>;\n /** BPM-relative delay time multiplier (1.0 = quarter note). When set, Delay Time is computed at apply time. */\n noteMultiplier?: number;\n /** Fixed delay time in ms (non-BPM-synced). Mutually exclusive with noteMultiplier. */\n fixedLengthMs?: number;\n}\n\n/** How dry/wet is applied to the plugin */\nexport type MixInterpolation = 'direct' | 'gain-scale' | 'ratio-scale';\n\n/** Preset configuration for an FX category */\nexport interface FxPresetConfig {\n /** Exactly 5 presets */\n presets: [FxPreset, FxPreset, FxPreset, FxPreset, FxPreset];\n /** Name of the native mix/wet parameter, or null if no native dry/wet */\n mixParamName: string | null;\n /** XML attribute name for dry/wet control (for plugins with no automatable mix param, e.g. chorus/phaser) */\n mixXmlAttr?: string;\n /** How to apply dry/wet (defaults to 'direct') */\n mixInterpolation: MixInterpolation;\n}\n\n/** Per-category detail state for a single FX on a track */\nexport interface FxCategoryDetailState {\n enabled: boolean;\n presetIndex: number; // 0-4\n dryWet: number; // 0.0-1.0\n}\n\n/** Extended FX state per track with preset and dry/wet info */\nexport type TrackFxDetailState = Record<FxCategory, FxCategoryDetailState>;\n\n/** Default dry/wet mix level (33% — musically useful for most effects) */\nexport const DEFAULT_FX_DRY_WET = 0.33;\n\n/** Default detail state for a single category */\nexport const DEFAULT_FX_CATEGORY_DETAIL: FxCategoryDetailState = {\n enabled: false,\n presetIndex: 0,\n dryWet: DEFAULT_FX_DRY_WET,\n};\n\n/** Default detail state: all FX disabled, preset 0, full wet */\nexport const EMPTY_FX_DETAIL_STATE: TrackFxDetailState = {\n eq: { ...DEFAULT_FX_CATEGORY_DETAIL },\n compressor: { ...DEFAULT_FX_CATEGORY_DETAIL },\n chorus: { ...DEFAULT_FX_CATEGORY_DETAIL },\n phaser: { ...DEFAULT_FX_CATEGORY_DETAIL },\n delay: { ...DEFAULT_FX_CATEGORY_DETAIL },\n reverb: { ...DEFAULT_FX_CATEGORY_DETAIL },\n};\n\n/** Persisted FX data for a single category (stored as JSON in database) */\nexport interface FxPresetDataEntry {\n presetIndex: number;\n dryWet: number;\n enabled: boolean;\n}\n\n/** Persisted FX data format (stored as JSON in database) */\nexport type FxPresetData = Partial<Record<FxCategory, FxPresetDataEntry>>;\n","/**\n * SDK TrackRow — Reusable track row component for generator plugins.\n *\n * Renders a complete track UI with prompt input, generation controls,\n * shuffle/copy, volume/pan, mute/solo, FX drawer, and visual states\n * (amber pulse for \"needs generation\", progress overlay, error indicator).\n *\n * Layout matches TrackInput (main branch) for visual parity.\n *\n * Depends only on PluginHost types + existing shared renderer components.\n */\n\nimport React from 'react';\nimport { AlertCircle, ChevronDown, GripVertical } from 'lucide-react';\nimport { TrackDrawer, type DrawerTab } from './TrackDrawer';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { TrackMeterStrip } from './TrackMeterStrip';\nimport type { TrackLevelsHandle } from '../hooks/useTrackLevels';\nimport type { InstrumentDescriptor, SoundHistoryEntry, PluginMidiNote } from '../types/plugin-sdk.types';\nimport type { TrackRowDragProps } from '../hooks/useTrackReorder';\nimport { VolumeSlider } from './VolumeSlider';\nimport { PanSlider } from './PanSlider';\nimport { SorceryProgressBar } from './SorceryProgressBar';\nimport type { TrackFxDetailState, FxCategory } from '../types/fx-toggle.types';\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface SDKTrackRowProps {\n /** Track identity */\n track: { id: string; name: string; role?: string };\n /** Current prompt text (optional — omit when using contentSlot) */\n prompt?: string;\n /** Playback state */\n runtimeState: { muted: boolean; solo: boolean; volume: number; pan: number };\n /** True when ANOTHER track is soloed, so this (non-soloed) track is currently\n * silenced. Renders the row dimmed while leaving its Mute button UNLIT — the\n * engine's effective-mute model silences it without touching user-mute. Purely\n * visual; does not change mute/solo state. */\n soloedOut?: boolean;\n /** FX category states */\n fxDetailState: TrackFxDetailState;\n /** Whether the unified track drawer is open. */\n drawerOpen: boolean;\n /** Which tab the drawer is showing. */\n drawerTab: DrawerTab;\n /** Switch the active drawer tab (tab-strip clicks). Omit for single-tab panels (e.g. loops = FX only). */\n onTabChange?: (tab: DrawerTab) => void;\n /** Generation in progress */\n isGenerating?: boolean;\n /** Auth state */\n isAuthenticated?: boolean;\n /** Error from last generation */\n error?: string | null;\n /** Enables shuffle/copy buttons */\n hasMidi?: boolean;\n /** Progress % (for persistence across scene switches) */\n generationProgress?: number;\n /** For progress bar pacing */\n estimatedGenerationMs?: number;\n /** Prompt edit (optional — omit to hide prompt input) */\n onPromptChange?: (prompt: string) => void;\n /** \"Create\" button / Enter key (optional — omit to hide Create button) */\n onGenerate?: () => void;\n /** Shuffle preset (optional — omit to hide Shuffle button) */\n onShuffle?: () => void;\n /** Duplicate track (optional — omit to hide Copy button) */\n onCopy?: () => void;\n /** Delete track. Optional — omit to hide the delete button (e.g. a composite\n * like CrossfadeTrackRow owns a single delete for the whole pair). */\n onDelete?: () => void;\n /** Custom content replacing the prompt input (e.g., sample info display) */\n contentSlot?: React.ReactNode;\n /** Toggle mute */\n onMuteToggle: () => void;\n /** Toggle solo */\n onSoloToggle: () => void;\n /** Volume slider */\n onVolumeChange: (vol: number) => void;\n /** Pan slider */\n onPanChange: (pan: number) => void;\n /** FX category toggle (optional — omit to hide FX button) */\n onFxToggle?: (cat: FxCategory, enabled: boolean) => void;\n /** FX preset select */\n onFxPresetChange?: (cat: FxCategory, idx: number) => void;\n /** FX dry/wet */\n onFxDryWetChange?: (cat: FxCategory, val: number) => void;\n /** Open/close FX (optional — omit to hide FX button) */\n onToggleFxDrawer?: () => void;\n /** Progress persistence callback */\n onProgressChange?: (pct: number) => void;\n /** Left border accent color */\n accentColor?: string;\n // --- Instrument Plugin Selection ---\n /** Current instrument display name (null/undefined = Surge XT default) */\n instrumentName?: string | null;\n /** Whether the current instrument plugin is missing from the system */\n instrumentMissing?: boolean;\n /** Open/close the drawer to a non-FX tab (the ▾ button). Omit to hide it. */\n onToggleDrawer?: () => void;\n /** Available instrument plugins for the drawer */\n availableInstruments?: InstrumentDescriptor[];\n /** Currently loaded instrument plugin ID */\n currentInstrumentPluginId?: string | null;\n /** Called when user selects an instrument from the drawer */\n onInstrumentSelect?: (pluginId: string) => void;\n /** Whether instrument scan is loading */\n instrumentsLoading?: boolean;\n /** Re-scan for instruments */\n onRefreshInstruments?: () => void;\n // --- Instrument Editor (Stage 2) ---\n /** Pick-tab sub-view: native plugin editor instead of the instrument grid. */\n editorStage?: boolean;\n /** Called when user clicks \"Open Editor\" */\n onShowEditor?: () => void;\n /** Called when user wants to go back from editor view */\n onBackToInstruments?: () => void;\n // --- Sound History (drawer \"History\" tab) ---\n /** Ordered list of sounds this track has had this session. */\n soundHistory?: readonly SoundHistoryEntry[];\n /** Index into soundHistory of the currently-applied sound. */\n soundHistoryCursor?: number;\n /** Restore a sound from the History tab by index. */\n onRestoreSound?: (index: number) => void;\n /** Toggle the favorite (⭐) flag on a history entry. */\n onToggleFavorite?: (index: number) => void;\n /** Open the drawer's sound-import picker; omit to hide the button. */\n onImportSound?: () => void;\n /** Sound-import button label (\"Import Sample\" / \"Import Preset\"). */\n importSoundLabel?: string;\n // --- Edit tab (piano-roll MIDI editor) ---\n /** Current MIDI notes for the piano-roll editor (the 'edit' tab). */\n editNotes?: readonly PluginMidiNote[];\n /** Persist edited notes; PRESENCE of this callback enables the Edit tab. */\n onNotesChange?: (notes: PluginMidiNote[]) => void;\n /** Scene length in bars (piano-roll grid width). */\n editBars?: number;\n /** Scene BPM (piano-roll audition timing). */\n editBpm?: number;\n /** Snap step in quarter notes for the piano roll (default 0.25). */\n editSnap?: number;\n /** Optional single-note preview when the user adds a note. */\n onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;\n // --- Drag-to-reorder ---\n /** Drag props from {@link useTrackReorder}. When present, renders the grip\n * handle and makes the row a drop target. Omit for non-reorderable lists. */\n drag?: TrackRowDragProps;\n // --- Per-track peak meter (cosmetic) ---\n /** Shared meter handle from `useTrackLevels(host, isPlaying)`. When present,\n * a thin peak meter welds to the bottom of the row. Omit to hide it. */\n levels?: TrackLevelsHandle;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function TrackRow({\n track,\n prompt,\n runtimeState,\n soloedOut = false,\n fxDetailState,\n drawerOpen,\n drawerTab,\n onTabChange,\n isGenerating = false,\n isAuthenticated = false,\n error,\n hasMidi = false,\n generationProgress = 0,\n estimatedGenerationMs = 15000,\n onPromptChange,\n onGenerate,\n onShuffle,\n onCopy,\n onDelete,\n contentSlot,\n onMuteToggle,\n onSoloToggle,\n onVolumeChange,\n onPanChange,\n onFxToggle,\n onFxPresetChange,\n onFxDryWetChange,\n onToggleFxDrawer,\n onProgressChange,\n accentColor = '#A78BFA',\n instrumentName,\n instrumentMissing,\n onToggleDrawer,\n availableInstruments,\n currentInstrumentPluginId,\n onInstrumentSelect,\n instrumentsLoading,\n onRefreshInstruments,\n editorStage,\n onShowEditor,\n onBackToInstruments,\n soundHistory,\n soundHistoryCursor,\n onRestoreSound,\n onToggleFavorite,\n onImportSound,\n importSoundLabel,\n editNotes,\n onNotesChange,\n editBars,\n editBpm,\n editSnap,\n onAuditionNote,\n drag,\n levels,\n}: SDKTrackRowProps): React.ReactElement {\n const { muted: isMuted, solo: isSoloed, volume: currentVolume, pan: currentPan } = runtimeState;\n\n // Guard the (irreversible) delete behind a confirmation modal — the bare \"x\"\n // was one stray click away from losing a track's MIDI + sound.\n const [confirmDelete, setConfirmDelete] = React.useState(false);\n\n // \"Needs generation\" = has prompt, no MIDI yet, not currently generating\n const needsGeneration = !!(prompt?.trim() && !hasMidi && !isGenerating);\n\n const hasFxActive = Object.values(fxDetailState).some(\n (d: { enabled: boolean }) => d.enabled\n );\n\n // The two row buttons open the SAME unified drawer to different tabs:\n // FX → the 'fx' tab; ▾ → a non-FX tab (History/Pick/Import).\n const fxTabOpen = drawerOpen && drawerTab === 'fx';\n const soundTabOpen = drawerOpen && drawerTab !== 'fx';\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {\n if (e.key === 'Enter' && !e.shiftKey && onGenerate) {\n e.preventDefault();\n onGenerate();\n }\n };\n\n // Amber pulse class for \"needs generation\" state\n const borderColorStyle = needsGeneration\n ? undefined // handled by className animation\n : accentColor;\n\n const borderClass = needsGeneration\n ? 'border-amber-400 animate-pulse'\n : 'border-sas-border';\n\n return (\n <div data-testid=\"sdk-track-row-wrapper\" className=\"w-full\" {...(drag?.rowProps ?? {})}>\n <div\n data-testid=\"sdk-track-row\"\n className={`relative flex items-stretch gap-1 p-2 ${levels ? 'rounded-t-sm' : 'rounded-sm'} border w-full overflow-hidden ${borderClass} bg-sas-panel-alt ${drag?.isDragging ? 'opacity-40' : ''} ${drag?.isDragTarget ? 'ring-2 ring-sas-accent ring-inset' : ''}`}\n style={{\n borderLeftColor: needsGeneration ? '#f59e0b' : borderColorStyle,\n borderLeftWidth: '3px',\n }}\n >\n {/* Drag-to-reorder grip — only when reorder is enabled. z-30 keeps it\n above the generating overlay; only the grip is draggable, so the\n row's inputs and sliders stay interactive. */}\n {drag && (\n <div\n data-testid=\"sdk-drag-handle\"\n {...drag.handleProps}\n className=\"flex-shrink-0 self-stretch flex items-center -ml-0.5 pr-0.5 text-sas-muted/40 hover:text-sas-muted cursor-grab active:cursor-grabbing relative z-30\"\n title=\"Drag to reorder\"\n aria-label=\"Drag to reorder track\"\n >\n <GripVertical className=\"w-3.5 h-3.5\" strokeWidth={2} />\n </div>\n )}\n\n {/* Generating progress overlay - stops before buttons (right-44) */}\n {isGenerating && (\n <div className=\"absolute left-0 top-0 bottom-0 right-44 z-20\">\n <SorceryProgressBar\n isLoading={true}\n statusText=\"CONJURING MIDI...\"\n heightClass=\"h-full\"\n initialProgress={generationProgress}\n onProgressChange={onProgressChange}\n estimatedDurationMs={estimatedGenerationMs}\n />\n </div>\n )}\n\n {/* Left: Content area (prompt input or custom content slot) with track name, volume, and pan underneath.\n Dimmed when soloed-out (silenced by another track's solo); the Mute/Solo\n buttons below stay full-opacity and interactive so the user can un-solo. */}\n <div\n data-testid=\"sdk-track-content\"\n className={`flex flex-col flex-1 min-w-0 relative z-10 transition-opacity ${soloedOut ? 'opacity-40' : ''}`}\n title={soloedOut ? 'Silenced — another track is soloed' : undefined}\n >\n {contentSlot ? contentSlot : onPromptChange ? (\n <input\n type=\"text\"\n data-testid=\"sdk-prompt-input\"\n value={prompt ?? ''}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => onPromptChange(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Describe your part...\"\n disabled={isGenerating}\n className=\"sas-input w-full px-2 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n ) : null}\n {/* Track name, volume slider, and pan slider in horizontal row */}\n <div className=\"flex items-center gap-2 mt-1\">\n {track.name && (\n <span className=\"text-[10px] text-sas-muted/60 truncate pl-2 flex-shrink-0 max-w-[80px]\" title={track.name}>\n {track.name}\n </span>\n )}\n <span className=\"text-[9px] text-sas-muted/50 flex-shrink-0\">vol:</span>\n <VolumeSlider\n value={currentVolume}\n onChange={onVolumeChange}\n disabled={isGenerating}\n className=\"flex-1 min-w-[40px]\"\n />\n <span className=\"text-[9px] text-sas-muted/50 flex-shrink-0\">pan:</span>\n <PanSlider\n value={currentPan}\n onChange={onPanChange}\n disabled={isGenerating}\n className=\"w-10 flex-shrink-0\"\n />\n </div>\n </div>\n\n {/* Error indicator - shows when generation failed */}\n {error && (\n <div\n data-testid=\"sdk-error-indicator\"\n className=\"flex-shrink-0 relative z-10 self-stretch flex items-center px-1 group cursor-help\"\n title={error}\n >\n <div className=\"relative\">\n <AlertCircle\n className=\"w-5 h-5 text-red-500 animate-pulse\"\n strokeWidth={2.5}\n />\n {/* Tooltip - appears on hover */}\n <div className=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-red-900/95 text-red-100 text-xs rounded shadow-lg whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 max-w-[200px] truncate\">\n {error}\n </div>\n </div>\n </div>\n )}\n\n {/* Right: Button grid (2 rows) - z-30 to stay above generating overlay */}\n <div className=\"flex flex-col gap-0.5 flex-shrink-0 relative z-30 justify-center\">\n {/* Top row: [Create] [Copy] M x — Create/Copy only shown when handlers provided */}\n <div className=\"flex gap-1 items-center\">\n {onGenerate && (\n <button\n data-testid=\"sdk-generate-button\"\n onClick={onGenerate}\n disabled={!isAuthenticated || isGenerating || !prompt?.trim()}\n className={`w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${\n !isAuthenticated || isGenerating\n ? 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n : needsGeneration\n ? 'bg-amber-500/30 border-amber-500 text-amber-400 hover:bg-amber-500 hover:text-sas-bg animate-pulse'\n : prompt?.trim()\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n title={!isAuthenticated ? 'Please log in' : isGenerating ? 'Generating...' : 'Generate MIDI'}\n >\n Create\n </button>\n )}\n {onCopy && (\n <button\n data-testid=\"sdk-copy-button\"\n onClick={onCopy}\n disabled={!hasMidi || isGenerating}\n className={`w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${\n !hasMidi || isGenerating\n ? 'bg-sas-panel border-sas-border text-sas-muted/30 cursor-not-allowed'\n : 'bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={hasMidi ? 'Duplicate track with different preset' : 'Generate MIDI first'}\n >\n Copy\n </button>\n )}\n {/* Mute stays interactive during generation: users often regenerate\n because they dislike the current sound and want to silence the\n track while the new MIDI conjures. Solo + the rest stay disabled. */}\n <button\n data-testid=\"sdk-mute-button\"\n onClick={onMuteToggle}\n className={`px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${\n isMuted\n ? 'bg-red-600 text-white'\n : 'bg-sas-panel-alt text-sas-muted hover:bg-sas-border'\n }`}\n title={isMuted ? 'Unmute track' : 'Mute track'}\n >\n M\n </button>\n {onDelete && (\n <button\n data-testid=\"sdk-delete-button\"\n onClick={() => setConfirmDelete(true)}\n className=\"text-sas-danger/70 hover:text-sas-danger px-1 py-0.5 transition-colors text-sm\"\n title=\"Delete track\"\n >\n x\n </button>\n )}\n </div>\n {/* Bottom row: [Shuffle] [FX] Solo [▾] */}\n <div className=\"flex gap-1 items-center\">\n {onShuffle && (\n <button\n data-testid=\"sdk-shuffle-button\"\n onClick={onShuffle}\n disabled={!hasMidi || isGenerating || !!currentInstrumentPluginId}\n className={`w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${\n !hasMidi || isGenerating || !!currentInstrumentPluginId\n ? 'bg-sas-panel border-sas-border text-sas-muted/30 cursor-not-allowed'\n : 'bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={\n currentInstrumentPluginId\n ? 'Shuffle only works with default Surge XT'\n : hasMidi\n ? 'Re-roll sound (keep MIDI)'\n : 'Generate MIDI first'\n }\n >\n Shuffle\n </button>\n )}\n {onToggleFxDrawer && (\n <button\n data-testid=\"sdk-fx-button\"\n onClick={onToggleFxDrawer}\n disabled={isGenerating}\n className={`w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${\n isGenerating\n ? 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n : fxTabOpen\n ? 'bg-sas-accent border-sas-accent text-sas-bg'\n : hasFxActive\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={fxTabOpen ? 'Hide FX controls' : 'Show FX controls'}\n >\n FX\n </button>\n )}\n <button\n data-testid=\"sdk-solo-button\"\n onClick={onSoloToggle}\n disabled={isGenerating}\n className={`px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${\n isGenerating\n ? 'bg-sas-panel text-sas-muted/50 cursor-not-allowed'\n : isSoloed\n ? 'bg-yellow-500 text-black'\n : 'bg-sas-panel-alt text-sas-muted hover:bg-sas-border'\n }`}\n title={isSoloed ? 'Unsolo track' : 'Solo track'}\n >\n S\n </button>\n {onToggleDrawer && (\n <button\n data-testid=\"sdk-plugin-button\"\n onClick={onToggleDrawer}\n disabled={isGenerating}\n className={`px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${\n isGenerating\n ? 'bg-sas-panel text-sas-muted/50 cursor-not-allowed'\n : soundTabOpen\n ? 'bg-sas-accent border-sas-accent text-sas-bg'\n : instrumentMissing\n ? 'bg-amber-500/20 text-amber-400 hover:bg-amber-500/40'\n : 'bg-sas-panel-alt text-sas-muted hover:bg-sas-border'\n }`}\n title={`Sound — presets & history${instrumentMissing ? ' (instrument missing)' : ''}`}\n >\n <ChevronDown className=\"w-3 h-3\" strokeWidth={2.5} />\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* Thin per-track peak meter, welded to the bottom of the row (cosmetic).\n Isolated in TrackMeterStrip so its ~30Hz updates re-render only the\n strip, never this whole row. Squared bottom when a drawer welds below. */}\n {levels && (\n <TrackMeterStrip levels={levels} trackId={track.id} roundBottom={!drawerOpen} />\n )}\n\n {/* Unified track drawer — one drawer, contextual tabs (FX / Pick / History / Import).\n The FX button opens it to 'fx'; the ▾ button to a non-FX tab. Which tabs\n appear is computed inside TrackDrawer from the callbacks provided. */}\n {drawerOpen && (\n <div\n data-testid=\"sdk-track-drawer\"\n className=\"border border-t-0 border-sas-border bg-sas-bg rounded-b-sm px-3 py-2 max-h-[260px] overflow-y-auto\"\n >\n <TrackDrawer\n activeTab={drawerTab}\n onTabChange={onTabChange}\n trackId={track.id}\n fxState={fxDetailState}\n onFxToggle={onFxToggle}\n onFxPresetChange={onFxPresetChange}\n onFxDryWetChange={onFxDryWetChange}\n fxDisabled={isGenerating}\n instruments={availableInstruments}\n currentPluginId={currentInstrumentPluginId ?? null}\n isLoading={instrumentsLoading ?? false}\n onSelect={onInstrumentSelect}\n onRefresh={onRefreshInstruments}\n editorStage={editorStage}\n onShowEditor={onShowEditor}\n onBackToInstruments={onBackToInstruments}\n selectedInstrumentName={instrumentName}\n soundHistory={soundHistory}\n soundHistoryCursor={soundHistoryCursor}\n onRestoreSound={onRestoreSound}\n onToggleFavorite={onToggleFavorite}\n onImportSound={onImportSound}\n importSoundLabel={importSoundLabel}\n editNotes={editNotes}\n onNotesChange={onNotesChange}\n editBars={editBars}\n editBpm={editBpm}\n editSnap={editSnap}\n onAuditionNote={onAuditionNote}\n />\n </div>\n )}\n\n <ConfirmDialog\n open={confirmDelete}\n title=\"Delete track?\"\n message={\n <>\n <span className=\"text-sas-text\">{track.name?.trim() || 'This track'}</span> will be\n permanently removed from this scene. This cannot be undone.\n </>\n }\n confirmLabel=\"Delete\"\n onConfirm={() => {\n setConfirmDelete(false);\n onDelete?.();\n }}\n onCancel={() => setConfirmDelete(false)}\n testIdPrefix=\"track-delete-confirm\"\n />\n </div>\n );\n}\n\nexport default TrackRow;\n","/**\n * TrackDrawer — the unified per-track drawer body.\n *\n * ONE drawer with a flat contextual tab strip. Which tabs appear is computed\n * from which callbacks the host panel provides:\n * - FX (onFxToggle) — the 6-category FX toggle bar\n * - Pick (onSelect) — instrument-plugin picker (+ native editor stage)\n * - History (onRestoreSound) — sounds this track has had (restore / favorite)\n * - Import (onImportSound) — copy a sound from a matching track in another scene\n *\n * The active tab is CONTROLLED by the host (activeTab / onTabChange) so the\n * track row's FX button and ▾ button can open the SAME drawer to a chosen tab.\n * When only one tab is enabled (e.g. loops = FX only) the strip is hidden and\n * that single view renders directly.\n *\n * (Was `InstrumentDrawer` — renamed once it grew an FX tab + Import tab. A\n * `TrackDrawer as InstrumentDrawer` alias is exported from the barrel for\n * backwards compatibility.)\n */\n\nimport React, { useState, useMemo } from 'react';\nimport type { InstrumentDescriptor, SoundHistoryEntry, PluginMidiNote } from '../types/plugin-sdk.types';\nimport type { FxCategory, TrackFxDetailState } from '../types/fx-toggle.types';\nimport { FxToggleBar } from './FxToggleBar';\nimport { PianoRollEditor } from './PianoRollEditor';\n\n// ============================================================================\n// Tabs\n// ============================================================================\n\n/** The contextual tabs a track drawer can show, in display order. */\nexport type DrawerTab = 'fx' | 'pick' | 'history' | 'import' | 'edit';\n\nconst TAB_LABELS: Record<DrawerTab, string> = {\n fx: 'FX',\n pick: 'Pick',\n history: 'History',\n import: 'Import',\n edit: 'Edit',\n};\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface TrackDrawerProps {\n /** Which tab is active (controlled by the host TrackRow). */\n activeTab: DrawerTab;\n /** Switch tabs (strip clicks). */\n onTabChange?: (tab: DrawerTab) => void;\n\n // --- FX tab (enabled when onFxToggle is provided) ---\n trackId: string;\n fxState: TrackFxDetailState;\n onFxToggle?: (category: FxCategory, enabled: boolean) => void;\n onFxPresetChange?: (category: FxCategory, presetIndex: number) => void;\n onFxDryWetChange?: (category: FxCategory, value: number) => void;\n /** Disable FX controls (e.g. while the track is generating). */\n fxDisabled?: boolean;\n\n // --- Pick tab (enabled when onSelect is provided) ---\n /** Available instrument plugins from engine scan. */\n instruments?: InstrumentDescriptor[];\n /** Currently loaded instrument plugin ID (null = default Surge XT). */\n currentPluginId?: string | null;\n /** Whether the instrument scan is still in progress. */\n isLoading?: boolean;\n /** Called when user selects an instrument (presence enables the Pick tab). */\n onSelect?: (pluginId: string) => void;\n /** Re-scan plugins. */\n onRefresh?: () => void;\n /** Pick-tab sub-view: show the native plugin editor instead of the grid. */\n editorStage?: boolean;\n /** Called when user clicks \"Open Plugin Editor\". */\n onShowEditor?: () => void;\n /** Called when user goes back from the editor to the instrument grid. */\n onBackToInstruments?: () => void;\n /** Name of the selected instrument (shown in the editor header). */\n selectedInstrumentName?: string | null;\n\n // --- History tab (enabled when onRestoreSound is provided) ---\n soundHistory?: readonly SoundHistoryEntry[];\n soundHistoryCursor?: number;\n /** Restore a sound by index; presence enables the History tab. */\n onRestoreSound?: (index: number) => void;\n /** Toggle the favorite (⭐) flag on a history entry; omit to hide the star. */\n onToggleFavorite?: (index: number) => void;\n\n // --- Import tab (enabled when onImportSound is provided) ---\n /** Open the sound-import picker; presence enables the Import tab. */\n onImportSound?: () => void;\n /** Button label, e.g. \"Import Sample\" (drums/instruments) or \"Import Preset\" (synths). */\n importSoundLabel?: string;\n\n // --- Edit tab (enabled when onNotesChange is provided) ---\n /** Current MIDI notes for the piano-roll editor. */\n editNotes?: readonly PluginMidiNote[];\n /** Persist edited notes; PRESENCE of this callback enables the Edit tab. */\n onNotesChange?: (notes: PluginMidiNote[]) => void;\n /** Scene length in bars (piano-roll grid width). Default 4. */\n editBars?: number;\n /** Scene BPM (piano-roll audition timing). Default 120. */\n editBpm?: number;\n /** Snap step in quarter notes for the piano roll (default 0.25). */\n editSnap?: number;\n /** Optional single-note preview when the user adds a note. */\n onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function TrackDrawer({\n activeTab,\n onTabChange,\n trackId,\n fxState,\n onFxToggle,\n onFxPresetChange,\n onFxDryWetChange,\n fxDisabled = false,\n instruments = [],\n currentPluginId = null,\n isLoading = false,\n onSelect,\n onRefresh,\n editorStage = false,\n onShowEditor,\n onBackToInstruments,\n selectedInstrumentName,\n soundHistory,\n soundHistoryCursor = -1,\n onRestoreSound,\n onToggleFavorite,\n onImportSound,\n importSoundLabel,\n editNotes,\n onNotesChange,\n editBars,\n editBpm,\n editSnap,\n onAuditionNote,\n}: TrackDrawerProps): React.ReactElement {\n // --- Hooks (MUST stay above every early return) ---\n const [search, setSearch] = useState('');\n\n const fxEnabled = !!onFxToggle;\n const pickEnabled = !!onSelect;\n const historyEnabled = !!onRestoreSound;\n const importEnabled = !!onImportSound;\n const editEnabled = !!onNotesChange;\n\n const enabledTabs = useMemo((): DrawerTab[] => {\n const tabs: DrawerTab[] = [];\n if (fxEnabled) tabs.push('fx');\n if (pickEnabled) tabs.push('pick');\n if (historyEnabled) tabs.push('history');\n if (importEnabled) tabs.push('import');\n if (editEnabled) tabs.push('edit');\n return tabs;\n }, [fxEnabled, pickEnabled, historyEnabled, importEnabled, editEnabled]);\n\n /** Sentinel pluginId for the default Surge XT entry */\n const SURGE_XT_DEFAULT_ID = 'Surge XT';\n\n // Filter instruments by search query, with selected instrument always first.\n // Computed unconditionally so the hook order is stable across tab switches.\n const filtered = useMemo((): InstrumentDescriptor[] => {\n let all = instruments.filter((i: InstrumentDescriptor) => i.name !== 'Surge XT');\n if (search.trim()) {\n const q = search.toLowerCase();\n all = all.filter(\n (i: InstrumentDescriptor) =>\n i.name.toLowerCase().includes(q) || i.manufacturer.toLowerCase().includes(q),\n );\n }\n if (currentPluginId) {\n const selectedIdx = all.findIndex((i: InstrumentDescriptor) => i.pluginId === currentPluginId);\n if (selectedIdx > 0) {\n const [selected] = all.splice(selectedIdx, 1);\n all.unshift(selected);\n }\n }\n return all;\n }, [instruments, search, currentPluginId]);\n\n // --- Derived (non-hook) values ---\n const history = soundHistory ?? [];\n const effectiveTab: DrawerTab = enabledTabs.includes(activeTab)\n ? activeTab\n : enabledTabs[0] ?? 'fx';\n\n const tabClass = (active: boolean): string =>\n `px-2 py-0.5 text-xs rounded-sm transition-colors ${\n active ? 'bg-sas-accent/20 text-sas-accent font-medium' : 'text-sas-muted hover:text-sas-accent'\n }`;\n\n // The tab strip replaces the old \"Sound\" title. Hidden when only one tab is\n // enabled (e.g. loops = FX only) — that single view renders directly.\n const strip =\n enabledTabs.length > 1 ? (\n <div\n className=\"flex items-center gap-1 border-b border-sas-border pb-1\"\n data-testid=\"sdk-drawer-tabs\"\n >\n {enabledTabs.map((tab: DrawerTab) => (\n <button\n key={tab}\n type=\"button\"\n data-testid={`sdk-drawer-tab-${tab}`}\n onClick={() => onTabChange?.(tab)}\n className={tabClass(effectiveTab === tab)}\n >\n {tab === 'history' && history.length > 0\n ? `History (${history.length})`\n : TAB_LABELS[tab]}\n </button>\n ))}\n </div>\n ) : null;\n\n // Subtle current-sound hint (the \"Sound\" title was removed in favour of tabs).\n const currentSound =\n soundHistoryCursor >= 0 && soundHistoryCursor < history.length\n ? history[soundHistoryCursor].label\n : null;\n\n const header =\n strip || currentSound ? (\n <div className=\"flex flex-col gap-1\" data-testid=\"sdk-drawer-header\">\n {strip}\n {currentSound && (\n <span\n className=\"text-[10px] text-sas-muted/60 truncate px-0.5\"\n title={currentSound}\n >\n {currentSound}\n </span>\n )}\n </div>\n ) : null;\n\n // ---- Edit tab (piano-roll MIDI editor) ----\n if (effectiveTab === 'edit') {\n return (\n <div className=\"flex flex-col gap-2\" data-testid=\"sdk-drawer-edit\">\n {header}\n <PianoRollEditor\n notes={editNotes ?? []}\n onChange={onNotesChange ?? ((): void => {})}\n bars={editBars ?? 4}\n bpm={editBpm ?? 120}\n snap={editSnap}\n onAuditionNote={onAuditionNote}\n />\n </div>\n );\n }\n\n // ---- FX tab ----\n if (effectiveTab === 'fx') {\n return (\n <div className=\"flex flex-col gap-2\" data-testid=\"sdk-drawer-fx\">\n {header}\n <FxToggleBar\n trackId={trackId}\n fxState={fxState}\n onToggle={(_t: string, category: FxCategory, enabled: boolean) =>\n onFxToggle?.(category, enabled)\n }\n onPresetChange={(_t: string, category: FxCategory, presetIndex: number) =>\n onFxPresetChange?.(category, presetIndex)\n }\n onDryWetChange={(_t: string, category: FxCategory, value: number) =>\n onFxDryWetChange?.(category, value)\n }\n disabled={fxDisabled}\n />\n </div>\n );\n }\n\n // ---- Import tab ----\n if (effectiveTab === 'import') {\n const soundNoun = /preset/i.test(importSoundLabel ?? '')\n ? 'preset'\n : /sample/i.test(importSoundLabel ?? '')\n ? 'sample'\n : 'sound';\n return (\n <div className=\"flex flex-col gap-2\" data-testid=\"sdk-drawer-import\">\n {header}\n <p className=\"text-[11px] text-sas-muted/70 leading-snug\">\n Copy the sound from a matching track in another scene — your MIDI stays, only the{' '}\n {soundNoun} changes.\n </p>\n <button\n type=\"button\"\n data-testid=\"sdk-drawer-import-sound\"\n onClick={onImportSound}\n className=\"w-full px-2 py-1.5 text-[11px] rounded-sm border border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent transition-colors\"\n title=\"Copy a sound from a track in another scene (ignores contract)\"\n >\n ⇪ {importSoundLabel ?? 'Import Sound'}\n </button>\n </div>\n );\n }\n\n // ---- History tab ----\n if (effectiveTab === 'history') {\n const order = history.map((_, i) => i).reverse(); // newest first\n return (\n <div className=\"flex flex-col gap-2\">\n {header}\n {history.length === 0 ? (\n <div\n className=\"text-xs text-sas-muted/60 text-center py-3\"\n data-testid=\"sdk-history-empty\"\n >\n No sounds yet — shuffle to build history.\n </div>\n ) : (\n <ul\n className=\"flex flex-col gap-1 max-h-[160px] overflow-y-auto\"\n data-testid=\"sdk-history-list\"\n >\n {order.map((i) => {\n const entry = history[i];\n const isCurrent = i === soundHistoryCursor;\n return (\n <li key={i} className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n data-testid=\"sdk-history-entry\"\n disabled={isCurrent}\n onClick={() => onRestoreSound?.(i)}\n className={`flex-1 min-w-0 flex items-center justify-between px-2 py-1.5 rounded-sm border text-left text-xs transition-colors ${\n isCurrent\n ? 'border-sas-accent bg-sas-accent/20 text-sas-accent cursor-default'\n : 'border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={isCurrent ? 'Current sound' : `Restore: ${entry.label}`}\n >\n <span className=\"truncate\">{entry.label}</span>\n <span className=\"text-[10px] text-sas-muted/60 flex-shrink-0 ml-2\">\n {isCurrent ? '● current' : 'restore'}\n </span>\n </button>\n {onToggleFavorite && (\n <button\n type=\"button\"\n data-testid=\"sdk-history-favorite\"\n onClick={() => onToggleFavorite(i)}\n className={`flex-shrink-0 px-1 py-0.5 text-sm leading-none transition-colors ${\n entry.favorite\n ? 'text-yellow-400'\n : 'text-sas-muted/40 hover:text-yellow-400'\n }`}\n title={entry.favorite ? 'Unfavorite' : 'Favorite (keeps it from being evicted)'}\n >\n {entry.favorite ? '★' : '☆'}\n </button>\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n );\n }\n\n // ---- Pick tab: native editor stage ----\n if (effectiveTab === 'pick' && editorStage) {\n return (\n <div className=\"flex flex-col gap-2\">\n {header}\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => onBackToInstruments?.()}\n 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\"\n >\n &larr; Back\n </button>\n <span className=\"text-xs text-sas-muted font-medium truncate flex-1\">\n {selectedInstrumentName ?? 'Plugin'}\n </span>\n </div>\n <button\n onClick={() => onShowEditor?.()}\n className=\"w-full py-2 text-xs font-medium rounded-sm border border-sas-accent bg-sas-accent/20 text-sas-accent hover:bg-sas-accent/40 transition-colors\"\n >\n Open Plugin Editor\n </button>\n </div>\n );\n }\n\n // ---- Pick tab: instrument grid (default) ----\n const isDefaultSelected = currentPluginId === null;\n const isSelected = (pluginId: string): boolean => pluginId === currentPluginId;\n\n return (\n <div className=\"flex flex-col gap-2\">\n {header}\n {/* Search + Refresh row */}\n <div className=\"flex items-center gap-2\">\n <input\n type=\"text\"\n value={search}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.target.value)}\n placeholder=\"Search instruments...\"\n className=\"sas-input flex-1 px-2 py-1 text-xs\"\n />\n <button\n onClick={() => onRefresh?.()}\n disabled={isLoading}\n 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\"\n title=\"Re-scan plugins\"\n >\n {isLoading ? '...' : 'Refresh'}\n </button>\n </div>\n\n {/* Instrument grid */}\n {isLoading && instruments.length === 0 ? (\n <div className=\"text-xs text-sas-muted/60 text-center py-3\">Scanning plugins...</div>\n ) : (\n <div className=\"grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto\">\n {/* Permanent \"Surge XT (Default)\" entry — always available */}\n <button\n key=\"__surge-xt-default__\"\n onClick={() => onSelect?.(SURGE_XT_DEFAULT_ID)}\n className={`flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors ${\n isDefaultSelected\n ? 'border-sas-accent bg-sas-accent/20 text-sas-accent'\n : 'border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title=\"Surge XT — Default instrument\"\n >\n <span className=\"text-xs font-medium truncate w-full\">\n {isDefaultSelected && '✓ '}Surge XT\n </span>\n <span className=\"text-[9px] text-sas-muted/50 truncate w-full\">Default</span>\n </button>\n {/* Scanned instruments */}\n {filtered.map((inst: InstrumentDescriptor) => {\n const selected = isSelected(inst.pluginId);\n return (\n <button\n key={inst.pluginId}\n onClick={() => onSelect?.(inst.pluginId)}\n className={`flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors ${\n selected\n ? 'border-sas-accent bg-sas-accent/20 text-sas-accent'\n : inst.missing\n ? 'border-amber-500/50 bg-amber-500/10 text-amber-400 hover:border-amber-500'\n : 'border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n title={`${inst.name} by ${inst.manufacturer} (${inst.type.toUpperCase()})${inst.missing ? ' — MISSING' : ''}`}\n >\n <span className=\"text-xs font-medium truncate w-full\">\n {selected && '✓ '}\n {inst.name}\n </span>\n <span className=\"text-[9px] text-sas-muted/50 truncate w-full\">\n {inst.manufacturer || inst.type.toUpperCase()}\n </span>\n </button>\n );\n })}\n {filtered.length === 0 && (\n <div className=\"col-span-2 text-xs text-sas-muted/60 text-center py-2\">\n {search.trim() ? 'No matches' : 'No other plugins found'}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\n/** Backwards-compatible alias — the drawer was named `InstrumentDrawer` before it grew FX/Import tabs. */\nexport { TrackDrawer as InstrumentDrawer };\n\nexport default TrackDrawer;\n","/**\n * FX Preset Definitions\n *\n * 5 presets per FX category (30 total).\n *\n * Parameter names must match the Tracktion Engine's AutomatableParameter names\n * for each built-in plugin exactly (case-sensitive).\n *\n * Chorus & Phaser have ZERO automatable parameters — all values are set via\n * XML state (CachedValues on the plugin ValueTree).\n *\n * Lives in shared/ so both main process (services) and renderer (UI) can import.\n */\n\nimport type { FxCategory, FxPresetConfig } from '../types/fx-toggle.types';\n\n// ============================================================================\n// EQ (4-Band Equaliser)\n// ============================================================================\n\nconst EQ_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'The Smiley',\n shortLabel: 'SM',\n params: {\n 'Low-shelf freq': 80, 'Low-shelf gain': 4, 'Low-shelf Q': 0.5,\n 'Mid freq 1': 500, 'Mid gain 1': -3, 'Mid Q 1': 0.7,\n 'Mid freq 2': 2000, 'Mid gain 2': -2, 'Mid Q 2': 0.7,\n 'High-shelf freq': 12000, 'High-shelf gain': 4, 'High-shelf Q': 0.5,\n },\n },\n {\n name: 'Telephone',\n shortLabel: 'TP',\n params: {\n 'Low-shelf freq': 400, 'Low-shelf gain': -20, 'Low-shelf Q': 1.0,\n 'Mid freq 1': 1000, 'Mid gain 1': 5, 'Mid Q 1': 2.0,\n 'Mid freq 2': 3000, 'Mid gain 2': -5, 'Mid Q 2': 1.0,\n 'High-shelf freq': 5000, 'High-shelf gain': -20, 'High-shelf Q': 1.0,\n },\n },\n {\n name: 'Warmth',\n shortLabel: 'WM',\n params: {\n 'Low-shelf freq': 120, 'Low-shelf gain': 3, 'Low-shelf Q': 0.7,\n 'Mid freq 1': 400, 'Mid gain 1': 2, 'Mid Q 1': 1.0,\n 'Mid freq 2': 4000, 'Mid gain 2': 0, 'Mid Q 2': 0.5,\n 'High-shelf freq': 10000, 'High-shelf gain': -4, 'High-shelf Q': 0.5,\n },\n },\n {\n name: 'Vocal Air',\n shortLabel: 'VA',\n params: {\n 'Low-shelf freq': 100, 'Low-shelf gain': -6, 'Low-shelf Q': 0.7,\n 'Mid freq 1': 300, 'Mid gain 1': -2, 'Mid Q 1': 1.0,\n 'Mid freq 2': 1500, 'Mid gain 2': 0, 'Mid Q 2': 0.5,\n 'High-shelf freq': 14000, 'High-shelf gain': 6, 'High-shelf Q': 0.4,\n },\n },\n {\n name: 'De-Box',\n shortLabel: 'DB',\n params: {\n 'Low-shelf freq': 60, 'Low-shelf gain': 0, 'Low-shelf Q': 0.5,\n 'Mid freq 1': 350, 'Mid gain 1': -5, 'Mid Q 1': 2.0,\n 'Mid freq 2': 800, 'Mid gain 2': -3, 'Mid Q 2': 2.0,\n 'High-shelf freq': 10000, 'High-shelf gain': 0, 'High-shelf Q': 0.5,\n },\n },\n ],\n mixParamName: null,\n mixInterpolation: 'gain-scale',\n};\n\n// ============================================================================\n// Compressor\n// ============================================================================\n\nconst COMPRESSOR_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Vocal Leveler',\n shortLabel: 'VL',\n params: { 'Threshold': 0.251, 'Ratio': 0.5, 'Attack': 20.0, 'Release': 200.0, 'Output': 2.0 },\n },\n {\n name: 'Drum Smash',\n shortLabel: 'DS',\n params: { 'Threshold': 0.100, 'Ratio': 0.1, 'Attack': 0.5, 'Release': 100.0, 'Output': 8.0 },\n },\n {\n name: 'Bus Glue',\n shortLabel: 'BG',\n params: { 'Threshold': 0.316, 'Ratio': 0.666, 'Attack': 80.0, 'Release': 150.0, 'Output': 1.0 },\n },\n {\n name: 'Bass Anchor',\n shortLabel: 'BA',\n params: { 'Threshold': 0.177, 'Ratio': 0.25, 'Attack': 10.0, 'Release': 250.0, 'Output': 4.0 },\n },\n {\n name: 'Safety Net',\n shortLabel: 'SN',\n params: { 'Threshold': 0.891, 'Ratio': 0.0, 'Attack': 0.3, 'Release': 50.0, 'Output': 0.0 },\n },\n ],\n mixParamName: null,\n mixInterpolation: 'ratio-scale',\n};\n\n// ============================================================================\n// Chorus\n// ============================================================================\n\nconst CHORUS_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Dimension',\n shortLabel: 'DM',\n params: {},\n xmlStateParams: { depthMs: 1.5, speedHz: 0.5, width: 1.0, mixProportion: 0.5 },\n },\n {\n name: '80s Crystal',\n shortLabel: '80',\n params: {},\n xmlStateParams: { depthMs: 4.0, speedHz: 2.5, width: 0.8, mixProportion: 0.4 },\n },\n {\n name: 'Sea Sick',\n shortLabel: 'SS',\n params: {},\n xmlStateParams: { depthMs: 7.0, speedHz: 0.8, width: 0.3, mixProportion: 1.0 },\n },\n {\n name: 'Pseudo-Leslie',\n shortLabel: 'PL',\n params: {},\n xmlStateParams: { depthMs: 2.0, speedHz: 6.0, width: 0.9, mixProportion: 0.7 },\n },\n {\n name: 'Thickener',\n shortLabel: 'TK',\n params: {},\n xmlStateParams: { depthMs: 1.0, speedHz: 0.2, width: 1.0, mixProportion: 0.3 },\n },\n ],\n mixParamName: null,\n mixXmlAttr: 'mixProportion',\n mixInterpolation: 'direct',\n};\n\n// ============================================================================\n// Phaser\n// ============================================================================\n\nconst PHASER_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Slow Burn',\n shortLabel: 'SB',\n params: {},\n xmlStateParams: { depth: 6.0, rate: 0.1, feedback: 0.3 },\n },\n {\n name: 'Funky Quack',\n shortLabel: 'FQ',\n params: {},\n xmlStateParams: { depth: 3.0, rate: 2.0, feedback: 0.8 },\n },\n {\n name: 'Jet Plane',\n shortLabel: 'JP',\n params: {},\n xmlStateParams: { depth: 8.0, rate: 0.2, feedback: 0.9 },\n },\n {\n name: 'Underwater',\n shortLabel: 'UW',\n params: {},\n xmlStateParams: { depth: 1.5, rate: 4.0, feedback: 0.1 },\n },\n {\n name: 'Static Notch',\n shortLabel: 'ST',\n params: {},\n xmlStateParams: { depth: 2.0, rate: 0.05, feedback: 0.6 },\n },\n ],\n mixParamName: null,\n mixXmlAttr: 'depth',\n mixInterpolation: 'direct',\n};\n\n// ============================================================================\n// Delay\n// ============================================================================\n\nconst DELAY_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Vocal Slap',\n shortLabel: 'VS',\n fixedLengthMs: 110,\n params: { 'Feedback': -20.0, 'Mix proportion': 0.25 },\n },\n {\n name: 'Grand Canyon',\n shortLabel: 'GC',\n noteMultiplier: 1.0,\n params: { 'Feedback': -4.0, 'Mix proportion': 0.45 },\n },\n {\n name: 'Wide Doubler',\n shortLabel: 'WD',\n fixedLengthMs: 25,\n params: { 'Feedback': -30.0, 'Mix proportion': 0.5 },\n },\n {\n name: 'Dub Echo',\n shortLabel: 'DE',\n noteMultiplier: 0.6,\n params: { 'Feedback': -1.5, 'Mix proportion': 0.4 },\n },\n {\n name: 'Rhythmic Wash',\n shortLabel: 'RW',\n noteMultiplier: 0.75,\n params: { 'Feedback': -8.0, 'Mix proportion': 0.2 },\n },\n ],\n mixParamName: 'Mix proportion',\n mixInterpolation: 'direct',\n};\n\n// ============================================================================\n// Reverb\n// ============================================================================\n\nconst REVERB_PRESETS: FxPresetConfig = {\n presets: [\n {\n name: 'Drum Room',\n shortLabel: 'DR',\n params: { 'Room Size': 0.2, 'Damping': 0.2, 'Wet Level': 0.15, 'Dry Level': 0.5, 'Width': 0.8 },\n },\n {\n name: 'Vocal Hall',\n shortLabel: 'VH',\n params: { 'Room Size': 0.8, 'Damping': 0.6, 'Wet Level': 0.25, 'Dry Level': 0.5, 'Width': 1.0 },\n },\n {\n name: 'Cathedral',\n shortLabel: 'CT',\n params: { 'Room Size': 1.0, 'Damping': 0.1, 'Wet Level': 0.333, 'Dry Level': 0.2, 'Width': 1.0 },\n },\n {\n name: 'Tile Bathroom',\n shortLabel: 'TB',\n params: { 'Room Size': 0.15, 'Damping': 0.0, 'Wet Level': 0.2, 'Dry Level': 0.5, 'Width': 0.5 },\n },\n {\n name: 'Vintage Plate',\n shortLabel: 'VP',\n params: { 'Room Size': 0.4, 'Damping': 1.0, 'Wet Level': 0.2, 'Dry Level': 0.5, 'Width': 1.0 },\n },\n ],\n mixParamName: 'Wet Level',\n mixInterpolation: 'direct',\n};\n\n// ============================================================================\n// Export\n// ============================================================================\n\n/** All preset configs keyed by FX category */\nexport const FX_PRESET_CONFIGS: Record<FxCategory, FxPresetConfig> = {\n eq: EQ_PRESETS,\n compressor: COMPRESSOR_PRESETS,\n chorus: CHORUS_PRESETS,\n phaser: PHASER_PRESETS,\n delay: DELAY_PRESETS,\n reverb: REVERB_PRESETS,\n};\n","/**\n * FxToggleBar Component\n *\n * Per-track FX control panel with 6 rows (one per FX category).\n * Each row: [Category toggle] [Preset 1-5 buttons] [Dry/Wet slider]\n *\n * Signal chain order: EQ -> Compressor -> Chorus -> Phaser -> Delay -> Reverb\n */\n\nimport React from 'react';\nimport type { FxCategory, TrackFxDetailState, FxCategoryDetailState } from '../types/fx-toggle.types';\nimport { FX_CATEGORIES, FX_DISPLAY_LABELS } from '../types/fx-toggle.types';\nimport { FX_PRESET_CONFIGS } from '../constants/fx-presets';\n\n/** Per-category active colors */\nconst FX_COLORS: Record<FxCategory, string> = {\n eq: 'bg-blue-500',\n compressor: 'bg-orange-500',\n chorus: 'bg-teal-500',\n phaser: 'bg-purple-500',\n delay: 'bg-green-500',\n reverb: 'bg-cyan-500',\n};\n\nexport interface FxToggleBarProps {\n trackId: string;\n fxState: TrackFxDetailState;\n onToggle: (trackId: string, category: FxCategory, enabled: boolean) => void;\n onPresetChange: (trackId: string, category: FxCategory, presetIndex: number) => void;\n onDryWetChange: (trackId: string, category: FxCategory, value: number) => void;\n disabled?: boolean;\n}\n\nexport const FxToggleBar: React.FC<FxToggleBarProps> = ({\n trackId,\n fxState,\n onToggle,\n onPresetChange,\n onDryWetChange,\n disabled = false,\n}) => {\n return (\n <div className=\"flex flex-col gap-1\" data-testid=\"fx-toggle-bar\">\n {FX_CATEGORIES.map((category: FxCategory) => {\n const detail: FxCategoryDetailState = fxState[category];\n const isActive = detail.enabled;\n const label = FX_DISPLAY_LABELS[category];\n const activeColor = FX_COLORS[category];\n const config = FX_PRESET_CONFIGS[category];\n\n return (\n <div key={category} className=\"flex items-center gap-0.5\">\n {/* Category toggle button */}\n <button\n data-testid={`fx-toggle-${category}`}\n disabled={disabled}\n onClick={() => onToggle(trackId, category, !isActive)}\n className={`w-14 py-0.5 text-[10px] font-semibold rounded-sm transition-colors leading-none flex-shrink-0 text-center ${\n disabled\n ? 'bg-sas-panel text-sas-muted/30 cursor-not-allowed'\n : isActive\n ? `${activeColor} text-white`\n : 'bg-sas-panel-alt text-sas-muted/60 hover:bg-sas-border hover:text-sas-muted'\n }`}\n title={`${isActive ? 'Disable' : 'Enable'} ${category.toUpperCase()}`}\n >\n {label}\n </button>\n\n {/* Preset buttons 1-5 */}\n {config.presets.map((preset, idx: number) => (\n <button\n key={idx}\n data-testid={`fx-preset-${category}-${idx}`}\n disabled={disabled || !isActive}\n onClick={() => onPresetChange(trackId, category, idx)}\n className={`w-5 h-5 text-[9px] font-medium rounded-sm transition-colors leading-none flex-shrink-0 ${\n disabled || !isActive\n ? 'bg-sas-panel text-sas-muted/20 cursor-not-allowed'\n : detail.presetIndex === idx\n ? `${activeColor} text-white`\n : 'bg-sas-panel-alt text-sas-muted/50 hover:bg-sas-border hover:text-sas-muted'\n }`}\n title={preset.name}\n >\n {idx + 1}\n </button>\n ))}\n\n {/* Dry/Wet slider */}\n <input\n type=\"range\"\n data-testid={`fx-drywet-${category}`}\n min=\"0\"\n max=\"100\"\n value={Math.round(detail.dryWet * 100)}\n disabled={disabled || !isActive}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n onDryWetChange(trackId, category, Number(e.target.value) / 100)\n }\n className=\"flex-1 min-w-[30px] h-3 accent-sas-accent disabled:opacity-30 cursor-pointer disabled:cursor-not-allowed\"\n title={`Dry/Wet: ${Math.round(detail.dryWet * 100)}%`}\n />\n <span className=\"text-[8px] text-sas-muted/50 w-6 text-right flex-shrink-0\">\n {Math.round(detail.dryWet * 100)}%\n </span>\n </div>\n );\n })}\n </div>\n );\n};\n","/**\n * PianoRollEditor — a compact, DOM-based MIDI note editor for the track drawer.\n *\n * Controlled: `notes` in, `onChange(next)` out. Notes render as absolutely-\n * positioned divs over a beat/pitch grid (DOM, not canvas — so it themes with\n * sas-* tokens and is fully driveable by React Testing Library). Supports:\n * - add : click an empty grid cell\n * - delete : click an existing note (no drag)\n * - move : drag a note's body (snap-quantised)\n * - resize : drag a note's right-edge handle (snap-quantised, ≥ one step)\n * - octave : shift the whole clip ±12 (toolbar) — no velocity lane\n * / marquee yet.\n * On load the viewport auto-scrolls to vertically center the note cluster, so a\n * low melody isn't stranded off-screen at the bottom of the pitch range.\n *\n * Coordinate spaces:\n * pitch (0-127) ── row = hi - pitch ── top px = row * ROW_HEIGHT\n * beat (¼ notes) ─────────────────────── left px = beat * PX_PER_BEAT\n *\n * The pure helpers (`cellToPx` / `pxToCell` / `transposeNotes`) and layout\n * constants are exported so coordinate math can be unit-tested without a DOM.\n */\nimport React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { PluginMidiNote } from '../types/plugin-sdk.types';\n\n// ============================================================================\n// Layout constants (exported for tests)\n// ============================================================================\n\n/** Horizontal pixels per quarter-note beat. */\nexport const PX_PER_BEAT = 24;\n/** Vertical pixels per semitone row. */\nexport const ROW_HEIGHT = 12;\n/** Left keyboard-gutter width (px). */\nexport const GUTTER_W = 28;\n/** Pointer travel (px) before a press on a note becomes a drag instead of a click. */\nexport const DRAG_DEAD_ZONE = 4;\n/** Width (px) of the right-edge grab handle that resizes a note's length. */\nexport const RESIZE_HANDLE_PX = 6;\n/** Max height (px) of the vertical scroll viewport — drives load-time centering. */\nexport const SCROLL_MAX_H = 150;\n\nconst NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as const;\nconst BLACK_KEYS = new Set([1, 3, 6, 8, 10]);\n\nconst SNAP_LABELS: Record<string, string> = {\n '2': '1/2',\n '1': '1/4',\n '0.5': '1/8',\n '0.25': '1/16',\n '0.125': '1/32',\n};\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v));\n}\n\nfunction snapLabel(s: number): string {\n return SNAP_LABELS[String(s)] ?? `${s}`;\n}\n\n// ============================================================================\n// Pure helpers (DOM-free, exported for unit tests)\n// ============================================================================\n\n/** MIDI pitch → scientific note name (60 = C4). */\nexport function pitchToName(pitch: number): string {\n const name = NOTE_NAMES[((pitch % 12) + 12) % 12];\n const octave = Math.floor(pitch / 12) - 1;\n return `${name}${octave}`;\n}\n\n/**\n * Cell (pitch, startBeat) → top-left pixel offset within the grid.\n * `hi` is the highest (top) visible pitch.\n */\nexport function cellToPx(\n pitch: number,\n startBeat: number,\n hi: number,\n): { left: number; top: number } {\n return { left: startBeat * PX_PER_BEAT, top: (hi - pitch) * ROW_HEIGHT };\n}\n\n/**\n * Grid-local pixel → snapped cell. `hi` is the highest visible pitch; the beat\n * snaps to the nearest `snap` step and clamps to `[0, totalBeats - snap]`;\n * pitch clamps to `[0, 127]`.\n */\nexport function pxToCell(\n localX: number,\n localY: number,\n hi: number,\n snap: number,\n bars: number,\n beatsPerBar: number,\n): { pitch: number; startBeat: number } {\n const totalBeats = bars * beatsPerBar;\n const pitch = clamp(hi - Math.floor(localY / ROW_HEIGHT), 0, 127);\n const rawBeat = localX / PX_PER_BEAT;\n const snapped = Math.round(rawBeat / snap) * snap;\n const startBeat = clamp(snapped, 0, Math.max(0, totalBeats - snap));\n return { pitch, startBeat };\n}\n\n/**\n * New `durationBeats` for a note whose right edge is dragged to grid-local pixel\n * `localX`. The end snaps to the nearest `snap` step, is clamped to at least one\n * step past `startBeat`, and never extends beyond the grid's right edge\n * (`bars * beatsPerBar`). `startBeat` and `pitch` are untouched.\n */\nexport function resizeNoteDuration(\n startBeat: number,\n localX: number,\n snap: number,\n bars: number,\n beatsPerBar: number,\n): number {\n const totalBeats = bars * beatsPerBar;\n const snappedEnd = Math.round(localX / PX_PER_BEAT / snap) * snap;\n const end = clamp(snappedEnd, startBeat + snap, totalBeats);\n return end - startBeat;\n}\n\n/**\n * `scrollTop` that vertically centers the bulk of the notes in a `viewportH`-px\n * window. Targets the MEDIAN pitch (robust to a stray high/low outlier — keeps\n * \"where the majority of notes are\" framed) and clamps to the valid scroll\n * range. `hi` is the top visible pitch; `rowCount` the total rows in the grid.\n * Returns 0 when there are no notes.\n */\nexport function centerScrollTop(\n pitches: readonly number[],\n hi: number,\n rowCount: number,\n viewportH: number,\n): number {\n if (pitches.length === 0) return 0;\n const sorted = [...pitches].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n const median = sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;\n // Pixel center of the median row, then offset so it lands mid-viewport.\n const medianRowCenterPx = (hi - median) * ROW_HEIGHT + ROW_HEIGHT / 2;\n const maxScroll = Math.max(0, rowCount * ROW_HEIGHT - viewportH);\n return clamp(medianRowCenterPx - viewportH / 2, 0, maxScroll);\n}\n\n/** Transpose every note by `semitones`, clamping pitch to [0,127] (never drops a note). */\nexport function transposeNotes(\n notes: readonly PluginMidiNote[],\n semitones: number,\n): PluginMidiNote[] {\n return notes.map((n) => ({ ...n, pitch: clamp(n.pitch + semitones, 0, 127) }));\n}\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface PianoRollEditorProps {\n /** Controlled note list (quarter-note beats). The editor never mutates this. */\n notes: readonly PluginMidiNote[];\n /** Emitted on every edit (add / delete / move / transpose) with the full next array. */\n onChange: (next: PluginMidiNote[]) => void;\n /** Scene length in bars → grid width = bars * beatsPerBar * PX_PER_BEAT. */\n bars: number;\n /** BPM — used only for audition timing in v1. */\n bpm: number;\n /** Beats per bar (time-signature numerator). Default 4. */\n beatsPerBar?: number;\n /** Snap step in quarter notes (1 = ¼ note, 0.25 = 1/16). Default 0.25. */\n snap?: number;\n /** Snap steps the toolbar selector offers. Default [1, 0.5, 0.25]. */\n snapOptions?: number[];\n /** Notified when the user changes snap (the editor still tracks it internally). */\n onSnapChange?: (snap: number) => void;\n /** Lowest pitch always visible. Default C2 (36). */\n minPitch?: number;\n /** Highest pitch always visible. Default C6 (84). */\n maxPitch?: number;\n /** Expand the visible window to include notes outside [minPitch,maxPitch]. Default true. */\n autoFit?: boolean;\n /** Optional single-note preview, fired when a note is added. */\n onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;\n /** Velocity for newly-added notes. Default 100. */\n defaultVelocity?: number;\n /** Disable all interaction (e.g. while the track is generating). Default false. */\n disabled?: boolean;\n /** Extra className for the outer container. */\n className?: string;\n /** Test id for the outer container. Default \"sdk-piano-roll\". */\n testId?: string;\n}\n\ninterface DragState {\n /**\n * `pending-*` is an undecided press (becomes the matching committed mode once\n * the pointer travels past {@link DRAG_DEAD_ZONE}, else resolves on pointer-up):\n * pending-note → drag (move) | no travel → delete\n * pending-resize → resize | no travel → delete\n * pending-add → (on up) add a note\n */\n mode: 'pending-note' | 'pending-resize' | 'pending-add' | 'drag' | 'resize';\n /** Index into `notes` for a note press; -1 for an empty-grid press. */\n index: number;\n startX: number;\n startY: number;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function PianoRollEditor({\n notes,\n onChange,\n bars,\n bpm,\n beatsPerBar = 4,\n snap = 0.25,\n snapOptions = [1, 0.5, 0.25],\n onSnapChange,\n minPitch = 36,\n maxPitch = 84,\n autoFit = true,\n onAuditionNote,\n defaultVelocity = 100,\n disabled = false,\n className,\n testId = 'sdk-piano-roll',\n}: PianoRollEditorProps): React.ReactElement {\n const [snapState, setSnapState] = useState(snap);\n const gridRef = useRef<HTMLDivElement | null>(null);\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const dragRef = useRef<DragState | null>(null);\n // True once we've auto-centered the current note set; re-armed when the notes\n // clear or the user octave-shifts, so the view re-frames only on a fresh load.\n const didCenterRef = useRef(false);\n\n // Visible pitch window: the default [minPitch, maxPitch], expanded to include\n // any notes that fall outside (± 2 semitones of headroom). Stable + testable.\n const { lo, hi } = useMemo((): { lo: number; hi: number } => {\n if (autoFit && notes.length > 0) {\n const ps = notes.map((n) => n.pitch);\n return {\n lo: Math.max(0, Math.min(minPitch, Math.min(...ps) - 2)),\n hi: Math.min(127, Math.max(maxPitch, Math.max(...ps) + 2)),\n };\n }\n return { lo: minPitch, hi: maxPitch };\n }, [autoFit, notes, minPitch, maxPitch]);\n\n const rowCount = hi - lo + 1;\n const totalBeats = bars * beatsPerBar;\n const gridWidth = totalBeats * PX_PER_BEAT;\n const gridHeight = rowCount * ROW_HEIGHT;\n\n // Latest values for the stable pointer handlers — avoids stale closures and\n // handler re-binding (the documented render-loop hazard). Assigned during\n // render so the handlers always read current props/state.\n const stateRef = useRef({\n notes, onChange, snapState, hi, bars, beatsPerBar, defaultVelocity, bpm, onAuditionNote, disabled,\n });\n stateRef.current = {\n notes, onChange, snapState, hi, bars, beatsPerBar, defaultVelocity, bpm, onAuditionNote, disabled,\n };\n\n const localCoords = useCallback((clientX: number, clientY: number): { x: number; y: number } => {\n const rect = gridRef.current?.getBoundingClientRect();\n return { x: clientX - (rect?.left ?? 0), y: clientY - (rect?.top ?? 0) };\n }, []);\n\n const handlePointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>): void => {\n if (stateRef.current.disabled) return;\n const target = e.target as HTMLElement;\n const noteEl = target.closest('[data-testid=\"sdk-pr-note\"]') as HTMLElement | null;\n const idxAttr = noteEl?.getAttribute('data-index');\n // A press that lands on the note's right-edge handle resizes; anywhere else\n // on the note moves/deletes; empty grid adds.\n const onResizeHandle = idxAttr != null && target.closest('[data-resize-handle]') != null;\n dragRef.current = {\n mode: idxAttr == null ? 'pending-add' : onResizeHandle ? 'pending-resize' : 'pending-note',\n index: idxAttr != null ? Number(idxAttr) : -1,\n startX: e.clientX,\n startY: e.clientY,\n };\n try {\n (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);\n } catch {\n /* jsdom / unsupported — drag still works via grid-level handlers */\n }\n }, []);\n\n const handlePointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>): void => {\n const drag = dragRef.current;\n if (!drag) return;\n const dist = Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY);\n if (dist > DRAG_DEAD_ZONE) {\n if (drag.mode === 'pending-note') drag.mode = 'drag';\n else if (drag.mode === 'pending-resize') drag.mode = 'resize';\n }\n const s = stateRef.current;\n const { x, y } = localCoords(e.clientX, e.clientY);\n\n if (drag.mode === 'resize') {\n const note = s.notes[drag.index];\n if (!note) return;\n const durationBeats = resizeNoteDuration(note.startBeat, x, s.snapState, s.bars, s.beatsPerBar);\n if (durationBeats === note.durationBeats) return;\n const next = s.notes.map((n, i) => (i === drag.index ? { ...n, durationBeats } : n));\n s.onChange(next);\n return;\n }\n\n if (drag.mode !== 'drag') return;\n const { pitch, startBeat } = pxToCell(x, y, s.hi, s.snapState, s.bars, s.beatsPerBar);\n const next = s.notes.map((n, i) => (i === drag.index ? { ...n, pitch, startBeat } : n));\n s.onChange(next);\n }, [localCoords]);\n\n const handlePointerUp = useCallback((e: React.PointerEvent<HTMLDivElement>): void => {\n const drag = dragRef.current;\n dragRef.current = null;\n if (!drag) return;\n const s = stateRef.current;\n if (s.disabled) return;\n\n if (drag.mode === 'pending-note' || drag.mode === 'pending-resize') {\n // Pressed a note (body or resize handle) without dragging past the dead\n // zone → treat as a plain click → delete it.\n s.onChange(s.notes.filter((_, i) => i !== drag.index));\n return;\n }\n if (drag.mode === 'pending-add') {\n const { x, y } = localCoords(e.clientX, e.clientY);\n const { pitch, startBeat } = pxToCell(x, y, s.hi, s.snapState, s.bars, s.beatsPerBar);\n const note: PluginMidiNote = {\n pitch,\n startBeat,\n durationBeats: s.snapState,\n velocity: s.defaultVelocity,\n channel: 0,\n };\n s.onChange([...s.notes, note]);\n s.onAuditionNote?.(pitch, s.defaultVelocity, Math.max(1, s.snapState * (60 / s.bpm) * 1000));\n }\n // mode 'drag' / 'resize' already emitted their final state during pointermove.\n }, [localCoords]);\n\n const handlePointerCancel = useCallback((): void => {\n dragRef.current = null;\n }, []);\n\n const handleOctave = useCallback((delta: number): void => {\n const s = stateRef.current;\n if (s.disabled) return;\n // The whole clip jumps an octave — re-frame the view onto its new position.\n didCenterRef.current = false;\n s.onChange(transposeNotes(s.notes, delta));\n }, []);\n\n const handleSnapChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>): void => {\n const v = Number(e.target.value);\n setSnapState(v);\n onSnapChange?.(v);\n }, [onSnapChange]);\n\n // Auto-frame the notes on load: the autoFit window already contains every note\n // vertically, but the scroll viewport starts pinned to the top — so a melody\n // sitting low needs a manual scroll to find. Center the note cluster once per\n // load (re-armed on clear / octave-shift), never mid-edit or mid-drag.\n useLayoutEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n if (notes.length === 0) {\n didCenterRef.current = false;\n return;\n }\n if (didCenterRef.current || dragRef.current) return;\n didCenterRef.current = true;\n const viewportH = el.clientHeight || SCROLL_MAX_H;\n el.scrollTop = centerScrollTop(\n notes.map((n) => n.pitch),\n hi,\n rowCount,\n viewportH,\n );\n }, [notes, hi, rowCount]);\n\n // Pitch rows for the keyboard gutter, top (hi) first.\n const rows = useMemo((): number[] => {\n const out: number[] = [];\n for (let p = hi; p >= lo; p--) out.push(p);\n return out;\n }, [hi, lo]);\n\n // Beat columns + bar columns + row lines, drawn purely in CSS so the only\n // hit-testable DOM in the grid is the notes themselves.\n const gridBg = useMemo((): string => {\n const beatPx = PX_PER_BEAT;\n const barPx = PX_PER_BEAT * beatsPerBar;\n return [\n `repeating-linear-gradient(to right, transparent 0 ${beatPx - 1}px, rgba(255,255,255,0.06) ${beatPx - 1}px ${beatPx}px)`,\n `repeating-linear-gradient(to right, transparent 0 ${barPx - 1}px, rgba(255,255,255,0.16) ${barPx - 1}px ${barPx}px)`,\n `repeating-linear-gradient(to bottom, transparent 0 ${ROW_HEIGHT - 1}px, rgba(255,255,255,0.04) ${ROW_HEIGHT - 1}px ${ROW_HEIGHT}px)`,\n ].join(', ');\n }, [beatsPerBar]);\n\n const octaveDisabled = disabled || notes.length === 0;\n\n return (\n <div className={`flex flex-col gap-1 ${className ?? ''}`} data-testid={testId}>\n {/* Toolbar */}\n <div className=\"flex items-center gap-1\" data-testid=\"sdk-pr-toolbar\">\n <button\n type=\"button\"\n data-testid=\"sdk-pr-octave-down\"\n disabled={octaveDisabled}\n onClick={() => handleOctave(-12)}\n className=\"px-1.5 py-0.5 text-[10px] rounded-sm border border-sas-border text-sas-muted hover:text-sas-accent hover:border-sas-accent transition-colors disabled:opacity-40\"\n title=\"Octave down (−12 semitones)\"\n >\n Oct −\n </button>\n <button\n type=\"button\"\n data-testid=\"sdk-pr-octave-up\"\n disabled={octaveDisabled}\n onClick={() => handleOctave(12)}\n className=\"px-1.5 py-0.5 text-[10px] rounded-sm border border-sas-border text-sas-muted hover:text-sas-accent hover:border-sas-accent transition-colors disabled:opacity-40\"\n title=\"Octave up (+12 semitones)\"\n >\n Oct +\n </button>\n <label className=\"flex items-center gap-1 text-[10px] text-sas-muted/70 ml-1\">\n Snap\n <select\n data-testid=\"sdk-pr-snap\"\n value={snapState}\n disabled={disabled}\n onChange={handleSnapChange}\n className=\"sas-input px-1 py-0.5 text-[10px]\"\n >\n {snapOptions.map((s) => (\n <option key={s} value={s}>\n {snapLabel(s)}\n </option>\n ))}\n </select>\n </label>\n <span className=\"text-[10px] text-sas-muted/60 ml-auto\" data-testid=\"sdk-pr-note-count\">\n {notes.length} {notes.length === 1 ? 'note' : 'notes'}\n </span>\n </div>\n\n {/* Scroll region: keyboard gutter + note grid */}\n <div\n ref={scrollRef}\n className=\"overflow-auto border border-sas-border rounded-sm bg-sas-bg\"\n style={{ maxHeight: SCROLL_MAX_H }}\n data-testid=\"sdk-pr-scroll\"\n >\n <div className=\"flex\" style={{ width: GUTTER_W + gridWidth }}>\n {/* Keyboard gutter — pinned left during horizontal scroll */}\n <div\n data-testid=\"sdk-pr-gutter\"\n className=\"sticky left-0 z-10 flex-shrink-0 bg-sas-panel-alt\"\n style={{ width: GUTTER_W }}\n >\n {rows.map((p) => (\n <div\n key={p}\n data-testid=\"sdk-pr-key\"\n data-pitch={p}\n className={`flex items-center justify-end pr-1 text-[8px] leading-none border-b border-sas-border/30 ${\n BLACK_KEYS.has(((p % 12) + 12) % 12)\n ? 'bg-sas-bg text-sas-muted/40'\n : 'text-sas-muted/70'\n }`}\n style={{ height: ROW_HEIGHT }}\n >\n {p % 12 === 0 ? pitchToName(p) : ''}\n </div>\n ))}\n </div>\n\n {/* Note grid */}\n <div\n ref={gridRef}\n data-testid=\"sdk-pr-grid\"\n className=\"relative flex-shrink-0\"\n style={{\n width: gridWidth,\n height: gridHeight,\n backgroundImage: gridBg,\n cursor: disabled ? 'not-allowed' : 'crosshair',\n touchAction: 'none',\n }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerCancel}\n >\n {notes.map((n, i) => {\n const { left, top } = cellToPx(n.pitch, n.startBeat, hi);\n const width = Math.max(3, n.durationBeats * PX_PER_BEAT);\n // Handle never exceeds half the note, so even a 1-step note keeps a\n // left \"body\" zone for moving.\n const handleW = Math.min(RESIZE_HANDLE_PX, width / 2);\n return (\n <div\n key={i}\n data-testid=\"sdk-pr-note\"\n data-index={i}\n data-pitch={n.pitch}\n data-start-beat={n.startBeat}\n data-duration-beats={n.durationBeats}\n className=\"absolute rounded-[2px] bg-sas-accent/80 border border-sas-accent hover:bg-sas-accent\"\n style={{ left, top, width, height: ROW_HEIGHT }}\n title={`${pitchToName(n.pitch)} · beat ${n.startBeat} · ${n.durationBeats}♪ · vel ${n.velocity}`}\n >\n {!disabled && (\n <div\n data-resize-handle=\"\"\n data-testid=\"sdk-pr-note-resize\"\n className=\"absolute top-0 right-0 h-full rounded-r-[2px] hover:bg-sas-bg/40\"\n style={{ width: handleW, cursor: 'ew-resize' }}\n />\n )}\n </div>\n );\n })}\n {notes.length === 0 && (\n <div\n data-testid=\"sdk-pr-empty\"\n className=\"absolute inset-0 flex items-center justify-center text-[10px] text-sas-muted/50 pointer-events-none\"\n >\n No notes — click to add\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n );\n}\n\nexport default PianoRollEditor;\n","/**\n * ConfirmDialog — styled in-app confirmation modal (SDK component).\n *\n * A small, reusable \"are you sure?\" dialog matching the app's dark theme\n * (mirrors ImportTrackModal chrome: sas-panel / sas-border / shadow-xl). It\n * guards destructive actions; the first consumer is track deletion, which was\n * one stray click away from losing a track's MIDI + sound.\n *\n * Controlled component — the caller owns `open` and the confirm/cancel\n * handlers. Escape and a backdrop click both cancel, and the Cancel button is\n * auto-focused on open so a reflexive Enter dismisses rather than deletes.\n *\n * @since SDK 2.17.0\n */\n\nimport React, { useRef } from 'react';\nimport { Modal } from './Modal';\n\nexport interface ConfirmDialogProps {\n /** Controls visibility (the caller owns open/closed). */\n open: boolean;\n /** Bold heading line. */\n title: string;\n /** Body copy — a string or richer node. */\n message: React.ReactNode;\n /** Confirm button label (default \"Delete\"). */\n confirmLabel?: string;\n /** Cancel button label (default \"Cancel\"). */\n cancelLabel?: string;\n /** When true (default), the confirm button reads as a destructive (red) action. */\n destructive?: boolean;\n /** Fired when the user confirms. */\n onConfirm: () => void;\n /** Fired on Cancel, Escape, or backdrop click. */\n onCancel: () => void;\n /** data-testid prefix so each dialog is addressable in tests. */\n testIdPrefix?: string;\n}\n\nexport function ConfirmDialog({\n open,\n title,\n message,\n confirmLabel = 'Delete',\n cancelLabel = 'Cancel',\n destructive = true,\n onConfirm,\n onCancel,\n testIdPrefix = 'confirm-dialog',\n}: ConfirmDialogProps): React.ReactElement | null {\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n // Escape, backdrop click, and focus-on-open are owned by the shared <Modal>.\n return (\n <Modal open={open} onClose={onCancel} testIdPrefix={testIdPrefix} initialFocusRef={cancelRef}>\n <div\n className=\"w-[360px] max-w-[90vw] flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl\"\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n data-testid={`${testIdPrefix}-modal`}\n >\n {/* Header */}\n <div className=\"px-4 py-3 border-b border-sas-border\">\n <span className=\"text-sm font-medium text-sas-text\" data-testid={`${testIdPrefix}-title`}>\n {title}\n </span>\n </div>\n\n {/* Body */}\n <div\n className=\"px-4 py-3 text-xs text-sas-muted leading-relaxed break-words\"\n data-testid={`${testIdPrefix}-message`}\n >\n {message}\n </div>\n\n {/* Footer */}\n <div className=\"flex justify-end gap-2 px-4 py-3 border-t border-sas-border\">\n <button\n ref={cancelRef}\n type=\"button\"\n className=\"px-3 py-1 rounded-sm text-xs font-medium border border-sas-border bg-sas-panel-alt text-sas-text hover:border-sas-accent hover:text-sas-accent transition-colors\"\n onClick={onCancel}\n data-testid={`${testIdPrefix}-cancel`}\n >\n {cancelLabel}\n </button>\n <button\n type=\"button\"\n className={`px-3 py-1 rounded-sm text-xs font-medium border transition-colors ${\n destructive\n ? 'border-sas-danger bg-sas-danger/20 text-sas-danger hover:bg-sas-danger hover:text-sas-bg'\n : 'border-sas-accent bg-sas-accent/20 text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n }`}\n onClick={onConfirm}\n data-testid={`${testIdPrefix}-confirm`}\n >\n {confirmLabel}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport default ConfirmDialog;\n","/**\n * Modal — the SDK's one modal-stacking primitive (portal + z-tier + backdrop).\n *\n * Every SDK modal renders INSIDE a plugin's accordion section, whose animated\n * `overflow-hidden` + `transition-all` wrapper establishes a stacking context.\n * An inline `position: fixed` overlay is therefore scoped to that section and\n * can be painted UNDER a neighbouring panel (the \"import modal invisible on a\n * later open\" bug). This component solves that once: it portals the overlay to\n * <body> — out of every panel's stacking context — at a z-tier above all the\n * app's `z-50` dropdowns/banners but below the toast tier (`z-[9999]`), so\n * toasts still float over modals.\n *\n * Controlled: the caller owns `open` and `onClose`. The caller renders its own\n * dialog box as `children` (keep the box's `onClick={e => e.stopPropagation()}`\n * so inside-clicks don't dismiss). Escape and a backdrop click both close.\n *\n * @since SDK 2.21.0\n */\n\nimport React, { useEffect } from 'react';\nimport { createPortal } from 'react-dom';\n\nexport interface ModalProps {\n /** Controls visibility (the caller owns open/closed). */\n open: boolean;\n /** Close handler — fired on Escape and backdrop click. */\n onClose: () => void;\n /** The dialog box. Give it `onClick={e => e.stopPropagation()}`. */\n children: React.ReactNode;\n /** data-testid prefix; the backdrop is `${testIdPrefix}-overlay`. */\n testIdPrefix?: string;\n /** Close when the backdrop is clicked (default true). */\n closeOnBackdrop?: boolean;\n /** Close on Escape (default true). */\n closeOnEscape?: boolean;\n /** Focused when the modal opens (e.g. a Cancel button) so a reflexive Enter is safe. */\n initialFocusRef?: React.RefObject<HTMLElement>;\n}\n\nexport function Modal({\n open,\n onClose,\n children,\n testIdPrefix = 'modal',\n closeOnBackdrop = true,\n closeOnEscape = true,\n initialFocusRef,\n}: ModalProps): React.ReactElement | null {\n // Escape closes; focus the requested element on open.\n useEffect(() => {\n if (!open) return undefined;\n const onKey = (e: KeyboardEvent): void => {\n if (closeOnEscape && e.key === 'Escape') {\n e.preventDefault();\n onClose();\n }\n };\n window.addEventListener('keydown', onKey);\n initialFocusRef?.current?.focus();\n return () => window.removeEventListener('keydown', onKey);\n }, [open, onClose, closeOnEscape, initialFocusRef]);\n\n if (!open) return null;\n\n return createPortal(\n <div\n className=\"fixed inset-0 z-[1000] flex items-center justify-center bg-black/60\"\n data-testid={`${testIdPrefix}-overlay`}\n onClick={closeOnBackdrop ? onClose : undefined}\n >\n {children}\n </div>,\n document.body,\n );\n}\n\nexport default Modal;\n","/**\n * Shared level-meter component.\n *\n * Renders a horizontal LED-style bar over -60dBFS → 0dBFS:\n * - A fixed left-to-right gradient (green → orange → red), so the color is\n * tied to POSITION: a quiet signal lights only the green left, a hot signal\n * reaches the red right. An \"unlit\" mask hides the gradient beyond the\n * current level.\n * - A deterministic segment grid (the \"LED monitor\" look) drawn as a pure-CSS\n * repeating overlay — constant DOM, no per-frame cost.\n * - An optional peak-hold marker (`peakHoldDb`) — a bright line at the recent\n * maximum that the caller holds/decays (see `useTrackMeter`).\n * - An optional CLIP badge the caller wires up.\n *\n * Pure presentational: takes the current dB + `active` flag (+ optional held\n * peak) and draws. The only production consumer is the per-track strip\n * (`TrackMeterStrip`, via `compact`). `compact` shrinks the bar and drops the\n * numeric dB readout.\n */\n\nimport React from 'react';\n\n// Traffic-light gradient (introduced for the LED meter; the Magic Terminal\n// palette has no green/orange/red tokens). Tweakable.\nconst COLOR_GREEN = '#2BD576';\nconst COLOR_ORANGE = '#F5A623';\nconst COLOR_RED = '#FF4D5E';\nconst COLOR_TRACK_BG = '#121822'; // panel-alt — the unlit bar / mask\nconst COLOR_TRACK_BORDER = '#1F2A3A'; // border\nconst COLOR_SEGMENT_GAP = '#0A0E14'; // dark gutter between LED cells\nconst COLOR_PEAK = '#F7FFFB'; // held-peak marker (bright)\n\n// The positional gradient. Mostly green, orange in the upper-mid, red near the\n// top — the classic meter feel, while still visibly tri-color across the bar.\nconst METER_GRADIENT = `linear-gradient(90deg, ${COLOR_GREEN} 0%, ${COLOR_GREEN} 45%, ${COLOR_ORANGE} 72%, ${COLOR_RED} 90%, ${COLOR_RED} 100%)`;\n\n// Deterministic LED sections + the gutter width between them.\nconst SEGMENTS = 22;\nconst SEGMENT_GAP_PX = 2;\n\n/** dBFS → bar % : -60dB → 0%, 0dB → 100%, clamped. */\nfunction dbToPct(db: number): number {\n return Math.max(0, Math.min(100, ((db + 60) / 60) * 100));\n}\n\nexport interface LevelMeterProps {\n /** Current peak level in dBFS. -120 means \"no signal\". */\n peakDb: number;\n /** True when the underlying audio callback is firing. False = floor. */\n active: boolean;\n /**\n * Held peak in dBFS for the peak-hold marker. Omit to draw no marker. The\n * marker is hidden when this is at/below the visible floor (-60).\n */\n peakHoldDb?: number;\n /** Latched clip flag. When true, render the CLIP badge. */\n clipped?: boolean;\n /** User-clickable handler to clear the latched clip indicator. */\n onClearClip?: () => void;\n /**\n * Thin strip mode for per-track meters: hides the numeric dB readout and\n * shrinks the bar. Keeps the (rare) CLIP badge.\n */\n compact?: boolean;\n /** Optional className overlaid on the wrapper for layout tweaks. */\n className?: string;\n /** Inline test id — make multiple instances distinguishable. */\n 'data-testid'?: string;\n}\n\nexport const LevelMeter: React.FC<LevelMeterProps> = ({\n peakDb,\n active,\n peakHoldDb,\n clipped,\n onClearClip,\n compact = false,\n className,\n 'data-testid': testId,\n}) => {\n const id = testId ?? 'sas-level-meter';\n const widthPct = active ? dbToPct(peakDb) : 0;\n const showPeak = peakHoldDb != null && active && peakHoldDb > -60;\n const peakHoldPct = showPeak ? dbToPct(peakHoldDb!) : 0;\n\n return (\n <div\n className={`sas-level-meter ${className ?? ''}`}\n data-testid={id}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: compact ? 0 : 6,\n }}\n >\n <div\n style={{\n position: 'relative',\n flex: 1,\n height: compact ? 5 : 7,\n background: COLOR_TRACK_BG,\n border: `1px solid ${COLOR_TRACK_BORDER}`,\n borderRadius: 2,\n overflow: 'hidden',\n minWidth: compact ? 0 : 60,\n }}\n >\n {/* Positional green→orange→red gradient, full bar width. */}\n <div style={{ position: 'absolute', inset: 0, background: METER_GRADIENT }} />\n\n {/* Unlit mask: hides the gradient from the current level rightward. */}\n <div\n style={{\n position: 'absolute',\n top: 0,\n bottom: 0,\n left: `${widthPct}%`,\n right: 0,\n background: COLOR_TRACK_BG,\n transition: 'left 30ms linear',\n }}\n />\n\n {/* Deterministic LED segment gutters — pure CSS, constant DOM. */}\n <div\n data-testid={`${id}-segments`}\n style={{\n position: 'absolute',\n inset: 0,\n pointerEvents: 'none',\n backgroundImage: `linear-gradient(90deg, transparent 0, transparent calc(100% - ${SEGMENT_GAP_PX}px), ${COLOR_SEGMENT_GAP} calc(100% - ${SEGMENT_GAP_PX}px), ${COLOR_SEGMENT_GAP} 100%)`,\n backgroundSize: `calc(100% / ${SEGMENTS}) 100%`,\n }}\n />\n\n {/* Peak-hold marker: a bright line at the recent maximum. */}\n {showPeak && (\n <div\n data-testid={`${id}-peak`}\n style={{\n position: 'absolute',\n top: -1,\n bottom: -1,\n left: `${peakHoldPct}%`,\n width: 2,\n marginLeft: -1,\n background: COLOR_PEAK,\n boxShadow: '0 0 4px rgba(247, 255, 251, 0.7)',\n transition: 'left 80ms linear',\n }}\n title=\"Peak\"\n />\n )}\n </div>\n\n {!compact && (\n <span\n style={{\n fontSize: 10,\n color: 'var(--sas-muted, #888)',\n fontVariantNumeric: 'tabular-nums',\n minWidth: 48,\n textAlign: 'right',\n }}\n >\n {active && peakDb > -120 ? `${peakDb.toFixed(0)} dB` : '—'}\n </span>\n )}\n {clipped && (\n <span\n data-testid={`${id}-clip`}\n onClick={onClearClip}\n style={{\n padding: '1px 5px',\n fontSize: 9,\n fontWeight: 'bold',\n background: COLOR_RED,\n color: '#0A0E14',\n borderRadius: 2,\n cursor: onClearClip ? 'pointer' : 'default',\n marginLeft: compact ? 3 : 0,\n }}\n title={onClearClip ? 'Clipped — click to clear' : 'Clipped'}\n >\n CLIP\n </span>\n )}\n </div>\n );\n};\n\nexport default LevelMeter;\n","/**\n * useTrackLevels — drives the cosmetic per-track strip meters.\n *\n * The hard constraint for this feature is \"playback ALWAYS wins over the GUI;\n * NO blocking threads.\" This hook is built around that:\n *\n * - It polls `host.getTrackLevels()` at ~30Hz with a recursive setTimeout that\n * only schedules the NEXT tick AFTER the previous await resolves. That is\n * automatic backpressure: a slow/stalled engine simply slows the meter, it\n * can never queue a backlog of requests. (The host + bridge also coalesce,\n * so a busy engine yields a STALE snapshot, never a pile-up.)\n * - It writes into a ref-held Map and notifies row subscribers, so the OWNING\n * panel never re-renders at 30Hz. Each row reads its own value via\n * `useTrackLevel` and re-renders only itself.\n * - It polls while the panel is mounted and the window is visible, and pauses\n * when the window is hidden. It deliberately does NOT gate on transport\n * \"is playing\": this app drives playback through decks / the clip launcher,\n * and the linear-transport play flag does not track that reliably. When\n * audio is stopped the engine simply returns floor levels, so the bars are\n * empty anyway — no need (and no reliable signal) to stop polling.\n *\n * Usage (panel):\n * const levels = useTrackLevels(host);\n * ...<TrackRow levels={levels} ... /> // row calls useTrackLevel(levels, id)\n */\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { PluginHost, PluginTrackLevel } from '../types/plugin-sdk.types';\n\n/** [MeterDiagR] per-trackId throttle for the dead-meter renderer-side diagnostic. */\nconst meterDiagRLast = new Map<string, number>();\n\n/** Polling cadence — matches the recording input meter (~30Hz). */\nconst POLL_INTERVAL_MS = 33;\n/** Slow idle re-check while the window is hidden (polling is paused). */\nconst HIDDEN_RECHECK_MS = 250;\n\n/** dBFS floor / \"no signal\" sentinel (matches PluginTrackLevel). */\nconst METER_FLOOR_DB = -120;\n/** Hold the peak marker this long after a fresh peak before it starts to fall. */\nconst PEAK_HOLD_MS = 1500;\n/** Fall rate once the hold window expires (dB per second). */\nconst PEAK_DECAY_DB_PER_SEC = 24;\n\n/**\n * Stable handle returned by {@link useTrackLevels}. Rows read their own level\n * and subscribe to per-tick notifications through it; its identity is stable\n * across renders so a row's subscription is set up once.\n */\nexport interface TrackLevelsHandle {\n /** Current level for a track, or null when idle/absent (renders an empty bar). */\n getLevel(trackId: string): PluginTrackLevel | null;\n /** Subscribe to per-tick updates. Returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n}\n\nfunction isHidden(): boolean {\n return typeof document !== 'undefined' && document.hidden === true;\n}\n\n/**\n * Poll every owned track's level while mounted + visible. Returns a stable\n * handle; the owning component does NOT re-render per tick. Pass `enabled =\n * false` to turn it off entirely (e.g. a panel that wants no meters). Safe to\n * call even when the host predates `getTrackLevels` (older SDK) — it stays idle.\n */\nexport function useTrackLevels(\n host: PluginHost | null | undefined,\n enabled: boolean = true\n): TrackLevelsHandle {\n const mapRef = useRef<Map<string, PluginTrackLevel>>(new Map());\n const listenersRef = useRef<Set<() => void>>(new Set());\n\n // Built exactly once so the handle identity is stable across renders.\n const handleRef = useRef<TrackLevelsHandle | null>(null);\n if (handleRef.current === null) {\n handleRef.current = {\n getLevel: (trackId: string) => mapRef.current.get(trackId) ?? null,\n subscribe: (listener: () => void) => {\n listenersRef.current.add(listener);\n return () => {\n listenersRef.current.delete(listener);\n };\n },\n };\n }\n\n useEffect(() => {\n const notify = (): void => {\n listenersRef.current.forEach((l) => l());\n };\n\n const clearToIdle = (): void => {\n if (mapRef.current.size > 0) {\n mapRef.current.clear();\n notify();\n }\n };\n\n const canPoll =\n enabled && !!host && typeof host.getTrackLevels === 'function';\n\n if (!canPoll) {\n clearToIdle();\n return;\n }\n\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const schedule = (delay: number): void => {\n if (stopped) return;\n timer = setTimeout(tick, delay);\n };\n\n const tick = async (): Promise<void> => {\n if (stopped) return;\n\n // Paused while the window is hidden: do no engine work, just idle-poll\n // until it comes back. (visibilitychange below resumes immediately.)\n if (isHidden()) {\n schedule(HIDDEN_RECHECK_MS);\n return;\n }\n\n try {\n const levels = await host!.getTrackLevels!();\n if (stopped) return;\n\n // Rebuild the map: upsert present tracks, drop ones that vanished.\n const seen = new Set<string>();\n for (const lvl of levels) {\n mapRef.current.set(lvl.trackId, lvl);\n seen.add(lvl.trackId);\n }\n for (const key of Array.from(mapRef.current.keys())) {\n if (!seen.has(key)) mapRef.current.delete(key);\n }\n notify();\n } catch {\n // Cosmetic meter: swallow transient read failures and keep polling.\n }\n\n // Schedule the NEXT tick only now — backpressure: never overlap reads.\n schedule(POLL_INTERVAL_MS);\n };\n\n const onVisibility = (): void => {\n if (stopped) return;\n if (!isHidden()) {\n // Becoming visible: cancel the slow idle-poll and resume immediately.\n if (timer) clearTimeout(timer);\n void tick();\n }\n };\n\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', onVisibility);\n }\n\n void tick();\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', onVisibility);\n }\n // Leave the map intact on teardown; the next active effect rebuilds it.\n };\n }, [host, enabled]);\n\n return handleRef.current;\n}\n\n/** Cheap equality so unchanged rows skip re-rendering between ticks. */\nfunction sameLevel(\n a: PluginTrackLevel | null,\n b: PluginTrackLevel | null\n): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n return a.peakDb === b.peakDb && a.clipped === b.clipped;\n}\n\n/**\n * Per-row selector. Subscribes to the shared scheduler and re-renders ONLY the\n * calling component when this track's level changes. Returns null when idle\n * (transport stopped, window hidden, or the track has no meter yet).\n */\nexport function useTrackLevel(\n handle: TrackLevelsHandle | null | undefined,\n trackId: string\n): PluginTrackLevel | null {\n const [level, setLevel] = useState<PluginTrackLevel | null>(null);\n\n useEffect(() => {\n if (!handle) {\n setLevel(null);\n return;\n }\n const update = (): void => {\n const next = handle.getLevel(trackId);\n setLevel((prev) => (sameLevel(prev, next) ? prev : next));\n };\n update(); // seed immediately\n return handle.subscribe(update);\n }, [handle, trackId]);\n\n return level;\n}\n\n/**\n * Per-row meter view-model: the current level plus a held peak for the meter UI.\n */\nexport interface TrackMeterView {\n /** Current mono peak in dBFS (floored at -120). */\n peakDb: number;\n /** Held peak in dBFS — stays at the recent maximum for ~PEAK_HOLD_MS, then falls. */\n peakHoldDb: number;\n /** Latched clip flag for the last poll window. */\n clipped: boolean;\n /** True when the track currently has a live meter row. */\n active: boolean;\n}\n\nconst IDLE_METER_VIEW: TrackMeterView = {\n peakDb: METER_FLOOR_DB,\n peakHoldDb: METER_FLOOR_DB,\n clipped: false,\n active: false,\n};\n\n/** Equality gate for the meter view. Quantizes the held peak to ½ dB so a\n * steady hold and sub-pixel decay don't thrash renders, while a real change\n * (level jitter, decay step, clip, active) still re-renders the strip. */\nfunction sameMeter(a: TrackMeterView, b: TrackMeterView): boolean {\n return (\n a.active === b.active &&\n a.clipped === b.clipped &&\n a.peakDb === b.peakDb &&\n Math.round(a.peakHoldDb * 2) === Math.round(b.peakHoldDb * 2)\n );\n}\n\n/**\n * Per-row meter selector WITH PEAK-HOLD. Like {@link useTrackLevel} it subscribes\n * to the shared ~30Hz scheduler and re-renders only the calling component, but it\n * also tracks a held peak that stays at the recent maximum for ~PEAK_HOLD_MS then\n * decays — so the eye can register where the signal peaked while the bar itself\n * moves fast. No extra timers or rAF: the held value is recomputed on each\n * scheduler notify, using performance.now() for hold/decay timing.\n */\nexport function useTrackMeter(\n handle: TrackLevelsHandle | null | undefined,\n trackId: string\n): TrackMeterView {\n const [view, setView] = useState<TrackMeterView>(IDLE_METER_VIEW);\n\n // Peak-hold state lives in refs so it survives between notifies without\n // forcing a render; only the derived `view` is state.\n const heldDbRef = useRef(METER_FLOOR_DB);\n const heldAtRef = useRef(0);\n const lastTickRef = useRef(0);\n\n useEffect(() => {\n if (!handle) {\n heldDbRef.current = METER_FLOOR_DB;\n lastTickRef.current = 0;\n setView(IDLE_METER_VIEW);\n return;\n }\n\n const update = (): void => {\n const level = handle.getLevel(trackId);\n // [MeterDiagR] throttled per-trackId: does THIS row's lookup HIT or MISS?\n // Pairs with the host [MeterDiag] to prove whether the id the renderer looks\n // up matches the id the levels are keyed by.\n const dNow = Date.now();\n if ((meterDiagRLast.get(trackId) ?? 0) < dNow - 3000) {\n meterDiagRLast.set(trackId, dNow);\n // eslint-disable-next-line no-console\n console.log(`[MeterDiagR] lookup trackId=${trackId} → ${level === null ? 'MISS (no level for this id)' : 'hit'}`);\n }\n const now = performance.now();\n const dtSec = lastTickRef.current ? Math.max(0, (now - lastTickRef.current) / 1000) : 0;\n lastTickRef.current = now;\n\n if (level === null) {\n // No live row for this track — go idle and reset the hold.\n heldDbRef.current = METER_FLOOR_DB;\n setView((prev) => (sameMeter(prev, IDLE_METER_VIEW) ? prev : IDLE_METER_VIEW));\n return;\n }\n\n const p = level.peakDb;\n if (p >= heldDbRef.current) {\n // Fresh peak: snap the held value up and restart the hold window.\n heldDbRef.current = p;\n heldAtRef.current = now;\n } else if (now - heldAtRef.current > PEAK_HOLD_MS) {\n // Hold expired: fall toward the current level.\n heldDbRef.current = Math.max(p, heldDbRef.current - PEAK_DECAY_DB_PER_SEC * dtSec);\n }\n // else: still within the hold window — keep the held value steady.\n\n const next: TrackMeterView = {\n peakDb: p,\n peakHoldDb: heldDbRef.current,\n clipped: level.clipped,\n active: true,\n };\n setView((prev) => (sameMeter(prev, next) ? prev : next));\n };\n\n update(); // seed immediately\n return handle.subscribe(update);\n }, [handle, trackId]);\n\n return view;\n}\n\n/**\n * Track the transport's play/stop state for a plugin. Seeds from\n * `getTransportState()` and follows `onTransportEvent`. Use its result as the\n * `active` arg to {@link useTrackLevels} so meters animate only during playback.\n */\nexport function useTransportPlaying(host: PluginHost | null | undefined): boolean {\n const [playing, setPlaying] = useState(false);\n\n useEffect(() => {\n if (!host) {\n setPlaying(false);\n return;\n }\n let cancelled = false;\n\n host\n .getTransportState()\n .then((state) => {\n if (!cancelled) setPlaying(!!state.isPlaying);\n })\n .catch(() => {\n /* seed best-effort; events will correct it */\n });\n\n const unsub = host.onTransportEvent?.((evt) => {\n if (typeof evt.isPlaying === 'boolean') {\n setPlaying(evt.isPlaying);\n } else if (evt.type === 'play') {\n setPlaying(true);\n } else if (evt.type === 'stop' || evt.type === 'pause') {\n setPlaying(false);\n }\n });\n\n return () => {\n cancelled = true;\n unsub?.();\n };\n }, [host]);\n\n return playing;\n}\n","/**\n * TrackMeterStrip — the thin per-track peak meter welded to the bottom of a\n * track row. Cosmetic: gives a general sense of each track's level and adds\n * motion during playback.\n *\n * This is deliberately its OWN component so the per-row meter selector\n * (`useTrackMeter`) re-renders ONLY this strip at ~30Hz, never the heavy\n * TrackRow around it. Render it as a full-width sibling directly under a row\n * body; it welds on with a squared top edge (like the track drawer does).\n */\n\nimport React from 'react';\nimport { LevelMeter } from './LevelMeter';\nimport { useTrackMeter, type TrackLevelsHandle } from '../hooks/useTrackLevels';\n\nexport interface TrackMeterStripProps {\n /** Shared meter handle from `useTrackLevels(host, isPlaying)`. */\n levels: TrackLevelsHandle;\n /** Tracktion engine track id (matches `PluginTrackHandle.id`). */\n trackId: string;\n /** Round the bottom corners (false when a drawer welds on below). Default true. */\n roundBottom?: boolean;\n /** Optional className for layout tweaks on the wrapper. */\n className?: string;\n}\n\nexport const TrackMeterStrip: React.FC<TrackMeterStripProps> = ({\n levels,\n trackId,\n roundBottom = true,\n className,\n}) => {\n const meter = useTrackMeter(levels, trackId);\n\n return (\n <div\n data-testid=\"sdk-track-meter\"\n className={`w-full px-2 py-1 bg-sas-panel-alt border border-t-0 border-sas-border ${roundBottom ? 'rounded-b-sm' : ''} ${className ?? ''}`}\n >\n <LevelMeter\n compact\n active={meter.active}\n peakDb={meter.peakDb}\n peakHoldDb={meter.peakHoldDb}\n clipped={meter.clipped}\n data-testid={`sdk-track-meter-bar-${trackId}`}\n />\n </div>\n );\n};\n\nexport default TrackMeterStrip;\n","/**\n * VolumeSlider Component\n *\n * Compact horizontal volume slider for track volume control.\n * Uses native HTML range input with custom styling.\n */\n\nimport React, { useCallback, useState, useRef, useEffect } from 'react';\nimport { sliderToDb } from '../utils/volume-conversion';\n\ninterface VolumeSliderProps {\n /** Volume value from 0 to 1 */\n value: number;\n /** Called when volume changes (debounced) */\n onChange: (value: number) => void;\n /** Disable the slider */\n disabled?: boolean;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Format slider value as dB for tooltip display\n */\nfunction formatDb(value: number): string {\n const db = sliderToDb(value);\n if (db <= -60) return '-∞ dB';\n const sign = db >= 0 ? '+' : '';\n return `${sign}${db.toFixed(1)} dB`;\n}\n\n/**\n * Debounce helper for volume changes\n */\nfunction useDebouncedCallback<T extends (...args: never[]) => void>(\n callback: T,\n delay: number\n): T {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n\n // Update callback ref when callback changes\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n const debouncedCallback = useCallback(\n (...args: Parameters<T>) => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n }, delay);\n },\n [delay]\n ) as T;\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return debouncedCallback;\n}\n\nexport const VolumeSlider: React.FC<VolumeSliderProps> = ({\n value,\n onChange,\n disabled = false,\n className = '',\n}) => {\n // Local state for immediate visual feedback\n const [localValue, setLocalValue] = useState(value);\n const [isDragging, setIsDragging] = useState(false);\n\n // Sync local value with prop when not dragging\n useEffect(() => {\n if (!isDragging) {\n setLocalValue(value);\n }\n }, [value, isDragging]);\n\n // Debounced onChange to prevent IPC spam\n const debouncedOnChange = useDebouncedCallback(onChange, 50);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = parseFloat(e.target.value);\n setLocalValue(newValue);\n debouncedOnChange(newValue);\n },\n [debouncedOnChange]\n );\n\n const handleMouseDown = useCallback(() => {\n setIsDragging(true);\n }, []);\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n // Send final value immediately on release\n onChange(localValue);\n }, [localValue, onChange]);\n\n return (\n <div\n className={`flex items-center ${className}`}\n title={`Volume: ${formatDb(localValue)}`}\n >\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={localValue}\n onChange={handleChange}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onTouchStart={handleMouseDown}\n onTouchEnd={handleMouseUp}\n disabled={disabled}\n className={`\n w-full h-1.5 rounded-full appearance-none cursor-pointer\n bg-gray-700\n disabled:opacity-50 disabled:cursor-not-allowed\n [&::-webkit-slider-thumb]:appearance-none\n [&::-webkit-slider-thumb]:w-3\n [&::-webkit-slider-thumb]:h-3\n [&::-webkit-slider-thumb]:rounded-full\n [&::-webkit-slider-thumb]:bg-sas-accent\n [&::-webkit-slider-thumb]:cursor-pointer\n [&::-webkit-slider-thumb]:transition-transform\n [&::-webkit-slider-thumb]:hover:scale-110\n [&::-moz-range-thumb]:w-3\n [&::-moz-range-thumb]:h-3\n [&::-moz-range-thumb]:rounded-full\n [&::-moz-range-thumb]:bg-sas-accent\n [&::-moz-range-thumb]:border-0\n [&::-moz-range-thumb]:cursor-pointer\n `}\n />\n </div>\n );\n};\n\nexport default VolumeSlider;\n","/**\n * Volume Conversion Utilities\n *\n * Converts between UI slider position (0-1) and engine dB values using a power\n * curve with +6 dB headroom. The curve places unity gain (0 dB) at slider 0.75,\n * giving the top 25% of the slider a meaningful 6 dB boost range instead of the\n * previous perceptual dead zone.\n *\n * Mapping:\n * slider 0.00 → -60 dB (silence)\n * slider 0.75 → 0 dB (unity gain)\n * slider 1.00 → +6 dB (max boost)\n */\n\n/** Slider position that maps to 0 dB (unity gain) */\nexport const SLIDER_UNITY = 0.75;\n\n/** Maximum dB value at slider = 1.0 */\nexport const DB_MAX = 6;\n\n/** Minimum dB value (silence floor) */\nexport const DB_MIN = -60;\n\n/**\n * Exponent derived so that slider=1.0 yields exactly DB_MAX dB.\n *\n * gain_at_1 = (1 / SLIDER_UNITY) ^ EXPONENT = 10^(DB_MAX/20)\n * EXPONENT = log(10^(DB_MAX/20)) / log(1/SLIDER_UNITY)\n */\nconst EXPONENT: number =\n Math.log(Math.pow(10, DB_MAX / 20)) / Math.log(1 / SLIDER_UNITY);\n\n/**\n * Convert a UI slider position (0-1) to engine dB.\n *\n * @param slider - Slider value in [0, 1]\n * @returns dB value in [DB_MIN, DB_MAX]\n */\nexport function sliderToDb(slider: number): number {\n if (slider <= 0) return DB_MIN;\n const gain = Math.pow(slider / SLIDER_UNITY, EXPONENT);\n const db = 20 * Math.log10(gain);\n return Math.max(DB_MIN, Math.min(DB_MAX, db));\n}\n\n/**\n * Convert an engine dB value back to a UI slider position (0-1).\n * Inverse of sliderToDb().\n *\n * @param db - Volume in dB\n * @returns Slider value in [0, 1]\n */\nexport function dbToSlider(db: number): number {\n if (db <= DB_MIN) return 0;\n if (db >= DB_MAX) return 1;\n const gain = Math.pow(10, db / 20);\n const slider = SLIDER_UNITY * Math.pow(gain, 1 / EXPONENT);\n return Math.min(1, Math.max(0, slider));\n}\n","/**\n * PanSlider Component\n *\n * Compact horizontal pan slider for track stereo positioning.\n * Range: -1 (left) to +1 (right), 0 = center.\n * No text label - tooltip only.\n */\n\nimport React, { useCallback, useState, useRef, useEffect } from 'react';\n\ninterface PanSliderProps {\n /** Pan value from -1 (left) to 1 (right), 0 = center */\n value: number;\n /** Called when pan changes (debounced) */\n onChange: (value: number) => void;\n /** Disable the slider */\n disabled?: boolean;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Convert pan value (-1 to 1) to display string\n */\nfunction toPanDisplay(value: number): string {\n if (Math.abs(value) < 0.02) {\n return 'Center';\n }\n const percent = Math.abs(Math.round(value * 100));\n return value < 0 ? `L${percent}` : `R${percent}`;\n}\n\n/**\n * Debounce helper for pan changes\n */\nfunction useDebouncedCallback<T extends (...args: never[]) => void>(\n callback: T,\n delay: number\n): T {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n const debouncedCallback = useCallback(\n (...args: Parameters<T>) => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n }, delay);\n },\n [delay]\n ) as T;\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return debouncedCallback;\n}\n\nexport const PanSlider: React.FC<PanSliderProps> = ({\n value,\n onChange,\n disabled = false,\n className = '',\n}) => {\n // Local state for immediate visual feedback\n const [localValue, setLocalValue] = useState(value);\n const [isDragging, setIsDragging] = useState(false);\n\n // Sync local value with prop when not dragging\n useEffect(() => {\n if (!isDragging) {\n setLocalValue(value);\n }\n }, [value, isDragging]);\n\n // Debounced onChange to prevent IPC spam\n const debouncedOnChange = useDebouncedCallback(onChange, 50);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = parseFloat(e.target.value);\n setLocalValue(newValue);\n debouncedOnChange(newValue);\n },\n [debouncedOnChange]\n );\n\n const handleMouseDown = useCallback(() => {\n setIsDragging(true);\n }, []);\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n // Send final value immediately on release\n onChange(localValue);\n }, [localValue, onChange]);\n\n // Double-click to reset to center\n const handleDoubleClick = useCallback(() => {\n setLocalValue(0);\n onChange(0);\n }, [onChange]);\n\n return (\n <div\n className={`flex items-center ${className}`}\n title={`Pan: ${toPanDisplay(localValue)}`}\n >\n <input\n type=\"range\"\n min=\"-1\"\n max=\"1\"\n step=\"0.01\"\n value={localValue}\n onChange={handleChange}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onTouchStart={handleMouseDown}\n onTouchEnd={handleMouseUp}\n onDoubleClick={handleDoubleClick}\n disabled={disabled}\n className={`\n w-full h-1.5 rounded-full appearance-none cursor-pointer\n bg-gray-700\n disabled:opacity-50 disabled:cursor-not-allowed\n [&::-webkit-slider-thumb]:appearance-none\n [&::-webkit-slider-thumb]:w-3\n [&::-webkit-slider-thumb]:h-3\n [&::-webkit-slider-thumb]:rounded-full\n [&::-webkit-slider-thumb]:bg-sas-accent\n [&::-webkit-slider-thumb]:cursor-pointer\n [&::-webkit-slider-thumb]:transition-transform\n [&::-webkit-slider-thumb]:hover:scale-110\n [&::-moz-range-thumb]:w-3\n [&::-moz-range-thumb]:h-3\n [&::-moz-range-thumb]:rounded-full\n [&::-moz-range-thumb]:bg-sas-accent\n [&::-moz-range-thumb]:border-0\n [&::-moz-range-thumb]:cursor-pointer\n `}\n />\n </div>\n );\n};\n\nexport default PanSlider;\n","/**\n * SorceryProgressBar Component\n *\n * A progress bar for long, uncertain wait times (10-30s). Supports two modes:\n *\n * 1. **Time-based mode** (when `estimatedDurationMs` is provided):\n * Uses elapsed time and an ease-out curve to pace progress realistically.\n * Reaches ~90% at the estimated completion time, then asymptotically\n * approaches 95% if the operation runs long.\n *\n * 2. **Phase-based mode** (legacy fallback, no `estimatedDurationMs`):\n * \"Zeno's Paradox\" style - progress moves quickly at first, then\n * asymptotically slows toward 95%.\n *\n * Visual style: Segmented \"retro CLI\" look with glowing teal accent,\n * diagonal stripes, and subtle pulse animation.\n */\n\nimport React, { useState, useEffect, useRef } from 'react';\n\n/**\n * Props for SorceryProgressBar component\n */\ninterface SorceryProgressBarProps {\n /** Whether loading is in progress */\n isLoading: boolean;\n /** Text shown during loading (default: \"CONJURING...\") */\n statusText?: string;\n /** Text shown on completion (default: \"COMPLETE\") */\n completeText?: string;\n /** Callback when loading completes */\n onComplete?: () => void;\n /** Height class override (default: \"h-10\") */\n heightClass?: string;\n /** Initial progress value (0-100) to resume from - persists across scene switches */\n initialProgress?: number;\n /** Callback when progress changes - use to persist progress in parent state */\n onProgressChange?: (progress: number) => void;\n /** Estimated total duration in ms - enables time-aware pacing */\n estimatedDurationMs?: number;\n}\n\n/**\n * Calculates target progress based on elapsed time and estimated duration.\n * Uses an ease-out power curve for natural-feeling progress:\n * - At 10% of estimated time: ~21% (feels responsive early)\n * - At 30% of estimated time: ~53% (good midpoint feel)\n * - At 50% of estimated time: ~74% (past halfway visually)\n * - At 80% of estimated time: ~88% (approaching completion)\n * - At 100% of estimated time: 90% (leaves room for overshoot)\n * - Beyond estimate: asymptotically approaches 95%\n */\nexport function calculateTimeBasedTarget(elapsedMs: number, estimatedDurationMs: number): number {\n const t = elapsedMs / estimatedDurationMs;\n if (t <= 0) return 0;\n\n if (t <= 1.0) {\n // Ease-out power curve reaching 90% at t=1.0\n return 90 * (1 - Math.pow(1 - t, 2.5));\n }\n\n // Beyond estimate: asymptotically approach 95%\n const overshootRatio = (elapsedMs - estimatedDurationMs) / estimatedDurationMs;\n return 90 + 5 * (1 - Math.exp(-overshootRatio * 3));\n}\n\n/**\n * Calculates the next progress value using \"Zeno's Paradox\" algorithm (legacy fallback).\n * - Phase 1 (0-20%): Rapid progress (5-15% per tick)\n * - Phase 2 (20-60%): Steady progress (2-7% per tick)\n * - Phase 3 (60-95%): Asymptotic slowdown\n * - Caps at 95% until actual completion\n */\nfunction calculateNextProgress(currentProgress: number): number {\n if (currentProgress < 20) {\n return currentProgress + Math.random() * 10 + 5;\n }\n if (currentProgress < 60) {\n return currentProgress + Math.random() * 5 + 2;\n }\n if (currentProgress < 95) {\n const remaining = 95 - currentProgress;\n const increment = remaining * (Math.random() * 0.2 + 0.1);\n return currentProgress + Math.max(increment, 0.1);\n }\n return 95;\n}\n\n/**\n * Calculates the next tick interval for phase-based mode (legacy fallback).\n */\nfunction calculateNextTickInterval(progress: number): number {\n if (progress < 30) {\n return Math.random() * 200 + 150; // 150-350ms\n }\n if (progress < 70) {\n return Math.random() * 300 + 200; // 200-500ms\n }\n return Math.random() * 600 + 400; // 400-1000ms\n}\n\n/** Tick interval for time-based mode (ms) */\nconst TIME_BASED_TICK_MIN = 200;\nconst TIME_BASED_TICK_RANGE = 100;\n\n/**\n * SorceryProgressBar - A mystical progress bar for uncertain wait times\n */\nexport function SorceryProgressBar({\n isLoading,\n statusText = 'CONJURING...',\n completeText = 'COMPLETE',\n onComplete,\n heightClass = 'h-10',\n initialProgress = 0,\n onProgressChange,\n estimatedDurationMs,\n}: SorceryProgressBarProps): React.ReactElement | null {\n const [progress, setProgress] = useState<number>(initialProgress);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Initialize to false so first render with isLoading=true triggers animation start\n const isLoadingRef = useRef<boolean>(false);\n const hasStartedRef = useRef<boolean>(false);\n const startTimeRef = useRef<number>(0);\n\n // Store callbacks in refs to avoid dependency issues\n const onProgressChangeRef = useRef(onProgressChange);\n const onCompleteRef = useRef(onComplete);\n onProgressChangeRef.current = onProgressChange;\n onCompleteRef.current = onComplete;\n\n // Store props in refs - only used when loading starts, not as dependencies\n const initialProgressRef = useRef(initialProgress);\n initialProgressRef.current = initialProgress;\n const estimatedDurationMsRef = useRef(estimatedDurationMs);\n estimatedDurationMsRef.current = estimatedDurationMs;\n\n // Effect to handle loading state changes - ONLY depends on isLoading\n useEffect(() => {\n const wasLoading = isLoadingRef.current;\n isLoadingRef.current = isLoading;\n\n if (isLoading && !wasLoading) {\n // Loading just started\n hasStartedRef.current = true;\n startTimeRef.current = Date.now();\n\n // Start fresh or resume from initial progress (read from ref)\n const startProgress = initialProgressRef.current > 0 ? initialProgressRef.current : 0;\n setProgress(startProgress);\n\n const duration = estimatedDurationMsRef.current;\n\n if (duration && duration > 0) {\n // Time-based mode: pace progress using elapsed time\n const tick = (): void => {\n setProgress((prev) => {\n const elapsed = Date.now() - startTimeRef.current;\n const target = calculateTimeBasedTarget(elapsed, duration);\n\n // Add subtle jitter for organic feel (±0.5%)\n const jitter = (Math.random() - 0.5) * 1.0;\n // Move toward target, ensure monotonically increasing, cap at 95%\n const next = Math.min(Math.max(target + jitter, prev + 0.05), 95);\n\n onProgressChangeRef.current?.(next);\n timerRef.current = setTimeout(tick, TIME_BASED_TICK_MIN + Math.random() * TIME_BASED_TICK_RANGE);\n return next;\n });\n };\n\n timerRef.current = setTimeout(tick, TIME_BASED_TICK_MIN);\n } else {\n // Phase-based mode (legacy fallback)\n const tick = (): void => {\n setProgress((prev) => {\n if (prev >= 95) {\n timerRef.current = setTimeout(tick, 1000);\n return 95;\n }\n\n const next = Math.min(calculateNextProgress(prev), 95);\n onProgressChangeRef.current?.(next);\n\n const interval = calculateNextTickInterval(next);\n timerRef.current = setTimeout(tick, interval);\n\n return next;\n });\n };\n\n const firstInterval = calculateNextTickInterval(startProgress);\n timerRef.current = setTimeout(tick, firstInterval);\n }\n } else if (!isLoading && wasLoading && hasStartedRef.current) {\n // Loading just finished - jump to 100%\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n setProgress(100);\n onProgressChangeRef.current?.(100);\n onCompleteRef.current?.();\n hasStartedRef.current = false;\n }\n\n // Cleanup on unmount only\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n };\n // ONLY depend on isLoading - other props are read from refs\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isLoading]);\n\n // Don't render if not loading and progress is 0\n if (!isLoading && progress === 0) {\n return null;\n }\n\n const displayProgress = Math.floor(progress);\n const isComplete = !isLoading && progress === 100;\n\n // Calculate transition duration based on progress phase\n const transitionDuration = progress < 50 ? '300ms' : progress < 80 ? '500ms' : '700ms';\n\n return (\n <div\n className={`relative w-full ${heightClass} bg-sas-panel-alt border border-sas-border rounded-sm overflow-hidden shadow-inner`}\n >\n {/* Progress fill with stripes and glow */}\n <div\n className={`\n h-full\n bg-gradient-to-r from-sas-accent/70 to-sas-accent\n shadow-glow-soft\n sorcery-progress-fill\n animate-progress-stripes\n ${progress > 70 ? 'animate-progress-pulse' : ''}\n transition-all ease-out\n `}\n style={{\n width: `${progress}%`,\n transitionDuration,\n }}\n />\n\n {/* Text overlay */}\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {isLoading && progress < 100 ? (\n <span className=\"font-mono text-xs text-sas-accent font-bold drop-shadow-md tracking-wider\">\n {statusText} {displayProgress}%\n </span>\n ) : isComplete ? (\n <span className=\"font-mono text-xs text-sas-text font-bold drop-shadow-md tracking-wider\">\n {completeText}\n </span>\n ) : null}\n </div>\n\n {/* Scanline overlay for retro CRT effect */}\n <div\n className=\"absolute inset-0 pointer-events-none opacity-10\"\n style={{\n backgroundImage: `repeating-linear-gradient(\n to bottom,\n transparent,\n transparent 2px,\n rgba(0, 0, 0, 0.3) 2px,\n rgba(0, 0, 0, 0.3) 4px\n )`,\n }}\n />\n </div>\n );\n}\n\nexport default SorceryProgressBar;\n","/**\n * CrossfadeTrackRow — a transition \"crossfade track\": two stacked TrackRows\n * (origin on top, target on bottom) joined by a horizontal crossfade slider.\n *\n * Both layers play the SAME generated MIDI; the top wears the ORIGIN scene\n * track's preset and the bottom wears the TARGET scene track's preset. The user\n * cannot regenerate, shuffle, or change the preset/sample on either layer —\n * those controls are simply not wired into the inner TrackRows (the SDK\n * TrackRow is \"controlled by omission\"). What remains: per-layer volume/pan,\n * GROUP mute/solo (both layers toggle together), and a single delete that\n * removes the whole pair.\n *\n * The slider represents WHERE the crossfade happens. In this phase it is\n * centered and non-functional (omit `onSliderChange` → it renders disabled); a\n * later phase wires it to fade origin→target across the bars.\n *\n * @since SDK 2.22.0\n */\nimport React from 'react';\nimport { TrackRow } from './TrackRow';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { EMPTY_FX_DETAIL_STATE } from '../types/fx-toggle.types';\nimport type { TrackLevelsHandle } from '../hooks/useTrackLevels';\nimport type { CrossfadeSlot } from '../crossfade-meta';\n\n/** One layer (engine track) of a crossfade pair. */\nexport interface CrossfadeLayer {\n /** Engine track id of this layer's track (also the meter key). */\n trackId: string;\n /** Display name of this layer's (newly created) track. */\n name: string;\n /** Musical role (same for both layers — crossfades are same-role). */\n role?: string;\n /** Name of the SOURCE track this layer was cloned from (origin/target scene). */\n sourceName?: string;\n /** Human label of the copied preset/sound, shown in the caption. */\n soundLabel?: string;\n /** Playback state for this layer. */\n runtimeState: { muted: boolean; solo: boolean; volume: number; pan: number };\n}\n\nexport interface CrossfadeTrackRowProps {\n /** Top layer — wears the origin (from) scene track's preset. */\n origin: CrossfadeLayer;\n /** Bottom layer — wears the target (to) scene track's preset. */\n target: CrossfadeLayer;\n /** Crossfade position 0..1 (0 = all origin, 1 = all target). Defaults centered. */\n sliderPos?: number;\n /** Toggle mute on BOTH layers together (group mute). */\n onMuteToggle: () => void;\n /** Toggle solo on BOTH layers together (group solo). */\n onSoloToggle: () => void;\n /** Change one layer's volume (per-layer). */\n onVolumeChange: (slot: CrossfadeSlot, volume: number) => void;\n /** Change one layer's pan (per-layer). */\n onPanChange: (slot: CrossfadeSlot, pan: number) => void;\n /** Delete the whole pair. */\n onDelete: () => void;\n /** Move the crossfade point. Omit to render the slider read-only (phase 1). */\n onSliderChange?: (pos: number) => void;\n /** Shared meter handle (welds a peak meter to each layer). */\n levels?: TrackLevelsHandle;\n /** Left-border accent. Defaults to transition purple. */\n accentColor?: string;\n}\n\nfunction LayerCaption({ tag, layer }: { tag: string; layer: CrossfadeLayer }): React.ReactElement {\n return (\n <div className=\"flex items-center gap-1.5 min-w-0 px-2 py-0.5\">\n <span className=\"text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0\">{tag}</span>\n <span className=\"text-[11px] text-sas-text truncate\" title={layer.sourceName ?? layer.name}>\n {layer.sourceName ?? layer.name}\n </span>\n {layer.soundLabel && (\n <span className=\"text-[9px] text-sas-muted/60 truncate flex-shrink-0\" title={layer.soundLabel}>\n · {layer.soundLabel}\n </span>\n )}\n </div>\n );\n}\n\nexport function CrossfadeTrackRow({\n origin,\n target,\n sliderPos = 0.5,\n onMuteToggle,\n onSoloToggle,\n onVolumeChange,\n onPanChange,\n onDelete,\n onSliderChange,\n levels,\n accentColor = '#9333EA',\n}: CrossfadeTrackRowProps): React.ReactElement {\n const [confirmDelete, setConfirmDelete] = React.useState(false);\n\n // A locked crossfade layer. The inner track's `name` is suppressed (the\n // meaningful name lives in the caption); every sound/generation handler is\n // omitted so shuffle / Create / preset-pick / FX / drawer / delete never\n // render. Mute/solo are GROUP-wired (same handler on both layers); volume/pan\n // are per-layer.\n const renderLayer = (layer: CrossfadeLayer, slot: CrossfadeSlot, tag: string): React.ReactElement => (\n <TrackRow\n track={{ id: layer.trackId, name: '', role: layer.role }}\n runtimeState={layer.runtimeState}\n fxDetailState={EMPTY_FX_DETAIL_STATE}\n drawerOpen={false}\n drawerTab=\"fx\"\n levels={levels}\n accentColor={accentColor}\n contentSlot={<LayerCaption tag={tag} layer={layer} />}\n onMuteToggle={onMuteToggle}\n onSoloToggle={onSoloToggle}\n onVolumeChange={(v: number) => onVolumeChange(slot, v)}\n onPanChange={(p: number) => onPanChange(slot, p)}\n />\n );\n\n return (\n <div\n data-testid=\"crossfade-track-row\"\n className=\"w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden\"\n style={{ borderLeftColor: accentColor, borderLeftWidth: '3px' }}\n >\n {/* Header — crossfade label + single delete for the whole pair. */}\n <div className=\"flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60\">\n <span className=\"text-[10px] font-bold uppercase tracking-wide\" style={{ color: accentColor }}>\n ⇄ Crossfade\n </span>\n <button\n data-testid=\"crossfade-delete-button\"\n onClick={() => setConfirmDelete(true)}\n className=\"text-sas-danger/70 hover:text-sas-danger px-1 transition-colors text-sm\"\n title=\"Delete crossfade pair\"\n aria-label=\"Delete crossfade pair\"\n >\n x\n </button>\n </div>\n\n {renderLayer(origin, 'origin', 'Origin')}\n\n {/* Crossfade slider — represents WHERE origin fades into target. Read-only\n (disabled) until the functional fader ships. */}\n <div className=\"flex items-center gap-2 px-3 py-1.5\" data-testid=\"crossfade-slider-row\">\n <span\n className=\"text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0\"\n title={origin.sourceName ?? origin.name}\n >\n {origin.sourceName ?? origin.name}\n </span>\n <input\n type=\"range\"\n data-testid=\"crossfade-slider\"\n min={0}\n max={1}\n step={0.01}\n value={sliderPos}\n disabled={!onSliderChange}\n onChange={\n onSliderChange\n ? (e: React.ChangeEvent<HTMLInputElement>) => onSliderChange(Number(e.target.value))\n : undefined\n }\n style={{ accentColor }}\n className=\"flex-1 disabled:opacity-60 disabled:cursor-not-allowed\"\n aria-label=\"Crossfade position\"\n />\n <span\n className=\"text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0\"\n title={target.sourceName ?? target.name}\n >\n {target.sourceName ?? target.name}\n </span>\n </div>\n\n {renderLayer(target, 'target', 'Target')}\n\n <ConfirmDialog\n open={confirmDelete}\n title=\"Delete crossfade?\"\n message={\n <>\n This crossfade pair (both layers) will be permanently removed from this scene. This cannot\n be undone.\n </>\n }\n confirmLabel=\"Delete\"\n onConfirm={() => {\n setConfirmDelete(false);\n onDelete();\n }}\n onCancel={() => setConfirmDelete(false)}\n testIdPrefix=\"crossfade-delete-confirm\"\n />\n </div>\n );\n}\n\nexport default CrossfadeTrackRow;\n","/**\n * Crossfade-pair metadata — family-agnostic types + parsing shared by every\n * generator panel that supports transition crossfades (synth / drum / instrument).\n *\n * A crossfade pair is two normal tracks linked by a shared `groupId`, persisted\n * in scene plugin_data under `track:<dbId>:crossfade`. Both members play the\n * same MIDI; one wears the origin preset, the other the target preset. The panel\n * owns the family-specific create flow (how a preset/sample is copied) and the\n * render; this module owns only the shape + the scene-data → pairs parse so the\n * logic can't drift across the three panels.\n *\n * @since SDK 2.23.0\n */\n\nimport type { TrackSoundSnapshot } from './types/plugin-sdk.types';\n\n/**\n * Stable, state-aware identity for a track's sound — used to auto-detect when a\n * transition's SOURCE preset/sample has drifted from the copy on its layer. A\n * preset hashes its full STATE blob (so a same-name param tweak still differs); a\n * sample uses its path; an instrument uses its id + zones. Empty string = no sound.\n * @since SDK 2.32.0\n */\nexport function hashString(s: string): string {\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = (((h << 5) + h) ^ s.charCodeAt(i)) | 0;\n return (h >>> 0).toString(36);\n}\nexport function soundIdentity(snap: TrackSoundSnapshot | null | undefined): string {\n if (!snap) return '';\n if (snap.kind === 'preset') return `p:${hashString(snap.state)}`;\n if (snap.kind === 'sample') return `s:${snap.samplePath}`;\n if (snap.kind === 'instrument') return `i:${snap.instrumentId ?? ''}:${hashString(JSON.stringify(snap.zones))}`;\n return '';\n}\n\n/** Which half of the pair a per-layer control / member targets. */\nexport type CrossfadeSlot = 'origin' | 'target';\n\n/**\n * Equal-power center gain (~-3 dB, 1/√2) applied to BOTH crossfade layers so a\n * centered, non-functional slider already sounds like a midpoint blend. The\n * per-layer volume sliders start here; a later phase's fader drives them.\n */\nexport const EQUAL_POWER_GAIN = 0.707;\n\n/**\n * Per-member crossfade metadata (one scene-data value per member track). The two\n * members (origin/target) of a pair share a `groupId`.\n */\nexport interface CrossfadeMeta {\n groupId: string;\n slot: CrossfadeSlot;\n /** DB id of the partner member track. */\n partnerDbId: string;\n /** DB id of the SOURCE track this layer's preset/sample was copied from. */\n sourceTrackDbId: string;\n /** DB id of the scene the source track lives in (the from/to scene). */\n sourceSceneId: string;\n /** Source track display name (shown in the caption). */\n sourceName: string;\n /** Copied preset/sample label (shown in the caption). */\n soundLabel: string;\n /** Crossfade position 0..1 (kept identical on both members). */\n sliderPos: number;\n}\n\n/** A complete crossfade pair (both members present), keyed by groupId. */\nexport interface CrossfadePairMeta {\n groupId: string;\n sliderPos: number;\n originDbId: string;\n targetDbId: string;\n /** DB id of the ORIGIN source track (in the from scene) — drives the \"used once\" exclusion. */\n originSourceDbId: string;\n /** DB id of the TARGET source track (in the to scene). */\n targetSourceDbId: string;\n originSourceName: string;\n originSoundLabel: string;\n targetSourceName: string;\n targetSoundLabel: string;\n}\n\n/** Narrow an unknown scene-data value to CrossfadeMeta (defensive — survives partial blobs). */\nexport function asCrossfadeMeta(val: unknown): CrossfadeMeta | null {\n if (!val || typeof val !== 'object') return null;\n const m = val as Partial<CrossfadeMeta>;\n if (typeof m.groupId !== 'string' || (m.slot !== 'origin' && m.slot !== 'target')) return null;\n if (typeof m.partnerDbId !== 'string') return null;\n return {\n groupId: m.groupId,\n slot: m.slot,\n partnerDbId: m.partnerDbId,\n sourceTrackDbId: typeof m.sourceTrackDbId === 'string' ? m.sourceTrackDbId : '',\n sourceSceneId: typeof m.sourceSceneId === 'string' ? m.sourceSceneId : '',\n sourceName: typeof m.sourceName === 'string' ? m.sourceName : '',\n soundLabel: typeof m.soundLabel === 'string' ? m.soundLabel : '',\n sliderPos: typeof m.sliderPos === 'number' ? m.sliderPos : 0.5,\n };\n}\n\n/**\n * Scan all `track:<dbId>:crossfade` keys in a scene's plugin_data and assemble\n * COMPLETE pairs (both origin + target present). A half-broken group (partner\n * deleted underneath) is omitted, so its surviving member falls back to a normal\n * row instead of vanishing.\n */\nexport function parseCrossfadePairs(sceneData: Record<string, unknown>): CrossfadePairMeta[] {\n const groups = new Map<\n string,\n { origin?: { dbId: string; meta: CrossfadeMeta }; target?: { dbId: string; meta: CrossfadeMeta } }\n >();\n for (const [key, val] of Object.entries(sceneData)) {\n const match = /^track:(.+):crossfade$/.exec(key);\n if (!match) continue;\n const meta = asCrossfadeMeta(val);\n if (!meta) continue;\n const dbId = match[1];\n const g = groups.get(meta.groupId) ?? {};\n if (meta.slot === 'origin') g.origin = { dbId, meta };\n else g.target = { dbId, meta };\n groups.set(meta.groupId, g);\n }\n const pairs: CrossfadePairMeta[] = [];\n for (const [groupId, g] of groups) {\n if (!g.origin || !g.target) continue;\n pairs.push({\n groupId,\n sliderPos: g.origin.meta.sliderPos,\n originDbId: g.origin.dbId,\n targetDbId: g.target.dbId,\n originSourceDbId: g.origin.meta.sourceTrackDbId,\n targetSourceDbId: g.target.meta.sourceTrackDbId,\n originSourceName: g.origin.meta.sourceName,\n originSoundLabel: g.origin.meta.soundLabel,\n targetSourceName: g.target.meta.sourceName,\n targetSoundLabel: g.target.meta.soundLabel,\n });\n }\n return pairs;\n}\n\n// ============================================================================\n// Crossfade volume automation (Phase 3 — the functional fader)\n// ============================================================================\n\n/** One volume-automation point: a dB value at a time offset (seconds from clip start). */\nexport interface VolumeAutomationPoint {\n time: number; // seconds\n db: number; // gain in dB (-80 ≈ silent, 0 = unity)\n}\n\n/** Origin + target volume curves for one crossfade pair. */\nexport interface CrossfadeVolumeCurves {\n origin: VolumeAutomationPoint[];\n target: VolumeAutomationPoint[];\n}\n\n// Exported so fade-meta.ts can reuse the same floor + gain→dB mapping (a fade is\n// a crossfade with one empty endpoint; its volume curve must match exactly).\nexport const FADE_FLOOR_DB = -80;\n\nexport function gainToDb(gain: number): number {\n return gain <= 1e-4 ? FADE_FLOOR_DB : Math.max(FADE_FLOOR_DB, 20 * Math.log10(gain));\n}\n\n/**\n * Equal-power crossfade volume curves over a transition of `bars` at `bpm`.\n * The ORIGIN layer fades OUT and the TARGET fades IN; `sliderPos` (0..1) sets\n * WHERE in time the equal-power (-3 dB) crossover sits — 0 = hand off near the\n * start, 1 = hold the origin until near the end. Points span the clip window\n * [0, durationSeconds] so the engine re-reads them each loop (re-fade per loop).\n * `steps`+1 points with linear interpolation approximate the cos/sin curve.\n *\n * Returns dB point arrays for `host.setTrackVolumeAutomation` — origin on the top\n * layer, target on the bottom. @since SDK 2.25.0\n */\nexport function buildCrossfadeVolumeCurves(\n bars: number,\n bpm: number,\n sliderPos: number,\n steps = 32,\n): CrossfadeVolumeCurves {\n const durationSeconds = (bars * 4 * 60) / Math.max(1, bpm);\n // Keep the crossover off the exact ends so there's always an actual fade.\n const s = Math.min(0.98, Math.max(0.02, sliderPos));\n const round = (n: number): number => Math.round(n * 1000) / 1000;\n const origin: VolumeAutomationPoint[] = [];\n const target: VolumeAutomationPoint[] = [];\n for (let i = 0; i <= steps; i++) {\n const x = i / steps; // normalized time 0..1\n const time = round(x * durationSeconds);\n // Piecewise-linear angle so the equal-power crossover (π/4) lands at x = s.\n const theta = x <= s ? (x / s) * (Math.PI / 4) : Math.PI / 4 + ((x - s) / (1 - s)) * (Math.PI / 4);\n origin.push({ time, db: Math.round(gainToDb(Math.cos(theta)) * 100) / 100 });\n target.push({ time, db: Math.round(gainToDb(Math.sin(theta)) * 100) / 100 });\n }\n return { origin, target };\n}\n","/**\n * Crossfade MIDI inpainting — builds the LLM user-prompt for a bridge that\n * MORPHS the ORIGIN part into the TARGET part.\n *\n * A normal scene generation composes a part standalone from the scene's chords.\n * A crossfade bridge is different: it is INPAINTING between two fixed endpoints.\n * The generated part must begin feeling continuous with the origin pattern and\n * end feeling continuous with the target pattern, transforming between them\n * across the transition's bars.\n *\n * The harmonic frame — Key / mode / BPM / bars / the transition chord\n * progression (with beat timing) / scene contract — is injected AUTOMATICALLY by\n * `host.generateWithLLM` (it prepends the active scene's \"Musical Context\" block\n * unless `skipContextPrefix` is set). So this prompt does NOT restate key/bpm/\n * chords — it adds only the two endpoint patterns + the morph instructions, and\n * references the harmonic frame as \"given above\".\n *\n * REPRESENTATION (researched for Gemini): ABC notation is the LLM-native format\n * for melodic generation, but it's weak for percussion, would need a separate\n * output parser (our output is JSON note-events, already proven with Gemini),\n * and an inpainting task wants input/output FORMAT SYMMETRY. So each endpoint is\n * given as the exact JSON note-events PLUS a pitch-named, bar-structured \"gloss\"\n * — the transferable wins from the research (pitch NAMES over raw MIDI numbers,\n * explicit bar/beat structure) layered on the precise, symmetric JSON. Drums\n * (uniform pitch) get a rhythmic gloss instead of pitch names.\n *\n * This changes only the LLM INPUT framing: the OUTPUT schema is unchanged, so the\n * calling panel keeps its system prompt + parser (and, for drums, its flatten step).\n *\n * @since SDK 2.24.0\n */\nimport type { PluginMidiNote } from './types/plugin-sdk.types';\n\nexport interface CrossfadeInpaintInput {\n /** Musical role of the bridge part (e.g. 'bass'). '' falls back to \"melodic\". */\n role: string;\n /** Transition length in bars (the morph timeline). */\n bars: number;\n /** Display name of the ORIGIN source track (the part the bridge begins from). */\n originName: string;\n /** Display name of the TARGET source track (the part the bridge arrives at). */\n targetName: string;\n /** ORIGIN source scene's key label (e.g. \"G minor\"). Null/omitted = unknown. */\n originKey?: string | null;\n /** TARGET source scene's key label. Null/omitted = unknown. */\n targetKey?: string | null;\n /** ORIGIN pattern notes (beat-based; from the FROM scene). May be empty. */\n originNotes: readonly PluginMidiNote[];\n /** TARGET pattern notes (beat-based; from the TO scene). May be empty. */\n targetNotes: readonly PluginMidiNote[];\n /** Drums: pitch is uniform (flattened), so gloss RHYTHM instead of pitch names. */\n percussive?: boolean;\n}\n\nconst PITCH_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];\n\n/** Round to 3 dp, dropping trailing-zero noise. */\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\n/** MIDI number → scientific pitch name (60 → C4), the app's octave convention. */\nfunction pitchName(p: number): string {\n return `${PITCH_NAMES[((p % 12) + 12) % 12]}${Math.floor(p / 12) - 1}`;\n}\n\n/** Compact a note to the 4 fields the LLM needs (drops channel), beats rounded. */\nfunction compactNote(n: PluginMidiNote): { pitch: number; startBeat: number; durationBeats: number; velocity: number } {\n return { pitch: n.pitch, startBeat: round3(n.startBeat), durationBeats: round3(n.durationBeats), velocity: n.velocity };\n}\n\n/** One-line shape summary so the LLM grasps register/density before the detail. */\nfunction summarize(notes: readonly PluginMidiNote[], percussive: boolean): string {\n if (notes.length === 0) return 'empty (no notes)';\n const span = round3(Math.max(...notes.map((n) => n.startBeat + n.durationBeats)));\n if (percussive) return `${notes.length} hits, spans ~${span} beats`;\n const pitches = notes.map((n) => n.pitch);\n return `${notes.length} notes, ${pitchName(Math.min(...pitches))}–${pitchName(Math.max(...pitches))}, spans ~${span} beats`;\n}\n\n/** Pitch-named (melodic) or rhythmic (drums) gloss, grouped by inferred bar. */\nfunction gloss(notes: readonly PluginMidiNote[], percussive: boolean): string {\n const sorted = [...notes].sort((a, b) => a.startBeat - b.startBeat);\n const maxEnd = Math.max(...sorted.map((n) => n.startBeat + n.durationBeats));\n const bars = Math.max(1, Math.ceil(maxEnd / 4));\n const lines: string[] = [];\n for (let b = 0; b < bars; b++) {\n const inBar = sorted.filter((n) => n.startBeat >= b * 4 && n.startBeat < (b + 1) * 4);\n if (inBar.length === 0) continue;\n const body = percussive\n ? inBar.map((n) => `${round3(n.startBeat)}(v${n.velocity})`).join(' ')\n : inBar.map((n) => `${pitchName(n.pitch)}@${round3(n.startBeat)}`).join(' ');\n lines.push(` Bar ${b + 1}: ${body}`);\n }\n return lines.join('\\n');\n}\n\nfunction patternBlock(\n label: string,\n name: string,\n key: string | null | undefined,\n notes: readonly PluginMidiNote[],\n percussive: boolean,\n): string {\n const keyLabel = key ? ` in ${key}` : '';\n const header = `${label} — \"${name}\"${keyLabel} (${summarize(notes, percussive)}):`;\n if (notes.length === 0) return `${header}\\n (no notes — treat this end as open)`;\n return `${header}\\n${gloss(notes, percussive)}\\n exact JSON: ${JSON.stringify(notes.map(compactNote))}`;\n}\n\n/**\n * Build the inpainting user-prompt. The result is the prompt BODY only — pass it\n * as `request.user` to `host.generateWithLLM` with the panel's normal system\n * prompt and `responseFormat: 'json'`; the harmonic context auto-prefixes.\n */\nexport function buildCrossfadeInpaintPrompt(input: CrossfadeInpaintInput): string {\n const { role, bars, originName, targetName, originKey, targetKey, originNotes, targetNotes } = input;\n const percussive = input.percussive ?? false;\n const part = role || (percussive ? 'drum' : 'melodic');\n const modulation =\n originKey && targetKey\n ? originKey === targetKey\n ? `stays in ${targetKey}`\n : `modulates from ${originKey} toward ${targetKey}`\n : 'resolves toward the destination key';\n\n const lines: string[] = [\n `TASK — TRANSITION BRIDGE (musical inpainting).`,\n `Compose a ${part} part that MORPHS from the ORIGIN pattern into the TARGET pattern across the ${bars} bars`,\n `of this transition. The Key / BPM / chord progression are given above — it ${modulation}; honour that`,\n `frame, don't restate it. Each pattern below is shown as a pitch/rhythm gloss for musicality plus its exact`,\n `JSON; output your bridge in the same JSON note schema (per the system prompt).`,\n ``,\n patternBlock('ORIGIN pattern (where the bridge BEGINS)', originName, originKey, originNotes, percussive),\n ``,\n patternBlock('TARGET pattern (where the bridge must ARRIVE)', targetName, targetKey, targetNotes, percussive),\n ``,\n `Requirements:`,\n `- The FIRST bar feels continuous with the ORIGIN — borrow its register, rhythm, and contour so the seam`,\n ` from the previous scene is seamless.`,\n `- Across the middle bars, gradually transform toward the TARGET (shift register / rhythm / motifs step by step).`,\n `- The LAST bar lands on the TARGET's material and resolves onto the destination chord, so the seam into the`,\n ` next scene is seamless.`,\n `- Stay within the transition chord progression above; favour chord tones at the bar boundaries.`,\n `- This is inpainting between two FIXED endpoints — a listener should not be able to point to where the`,\n ` origin ends or the target begins.`,\n ];\n\n if (originNotes.length === 0 || targetNotes.length === 0) {\n lines.push(\n ``,\n originNotes.length === 0 && targetNotes.length === 0\n ? `(Both endpoints are empty — compose a short ${part} bridge from the chords alone.)`\n : originNotes.length === 0\n ? `(The ORIGIN is empty — begin sparse and grow INTO the TARGET.)`\n : `(The TARGET is empty — begin from the ORIGIN and dissolve toward the destination chord.)`,\n );\n }\n\n return lines.join('\\n');\n}\n","/**\n * Fade metadata — family-agnostic types + parsing for transition orphan fades\n * (synth / drum / instrument panels).\n *\n * A fade is a CROSSFADE WITH ONE EMPTY ENDPOINT: a single generated track that\n * either fades IN (a target-only track entering — `morph(∅ → target)`) or fades\n * OUT (an origin-only track leaving — `morph(origin → ∅)`) across the transition\n * loop. It reuses the same generation pipeline (`buildCrossfadeInpaintPrompt`\n * with one empty endpoint) and the same volume-automation fader as crossfade.\n *\n * Stored in scene plugin_data under `track:<dbId>:fade` (ONE entry per track —\n * unlike crossfade there is no partner / groupId). Kept as a SEPARATE type from\n * crossfade so the load-bearing \"drop a half-broken pair\" guard in\n * `parseCrossfadePairs` stays intact.\n *\n * @since SDK 2.28.0\n */\n\nimport { type VolumeAutomationPoint, FADE_FLOOR_DB, gainToDb } from './crossfade-meta';\n\n/** Which way the lone track fades over the transition. */\nexport type FadeDirection = 'in' | 'out';\n\n/**\n * How the fade is shaped:\n * - `volume` — a one-sided level ramp does the work (DJ-style). Best for\n * textural/sustained material (pads, atmospheres).\n * - `build` — the MIDI carries the fade (the inpaint grows sparse→full on the way\n * in, or dissolves on the way out); the level stays flat. Best for articulated\n * material (lead, bass, drums, winds, vocals).\n */\nexport type FadeGesture = 'volume' | 'build';\n\n/** Per-track fade metadata (one scene-data value per fade track). */\nexport interface FadeMeta {\n direction: FadeDirection;\n gesture: FadeGesture;\n /**\n * Audio transition variant for one-sided LOOP transitions. `'fade'` (default,\n * and the only value MIDI panels write) is a plain level ramp; `stutter` /\n * `chopped` re-render the loop's audio, `delay` adds a delay-throw FX. Shown as\n * a badge on the row. @since SDK 2.32.0\n */\n effect?: 'fade' | 'stutter' | 'chopped' | 'delay';\n /** DB id of the SOURCE track this fade's preset/sample + pattern was seeded from. */\n sourceTrackDbId: string;\n /** DB id of the scene the source track lives in (the from/to scene). */\n sourceSceneId: string;\n /** Source track display name (shown in the caption). */\n sourceName: string;\n /** Copied preset/sample label (shown in the caption). */\n soundLabel: string;\n /** Fade position 0..1 — WHERE in time the fade midpoint sits. */\n sliderPos: number;\n}\n\n/** A fade entry resolved from scene data: the fade track's dbId + its metadata. */\nexport interface FadeEntry {\n dbId: string;\n meta: FadeMeta;\n}\n\n/** Narrow an unknown scene-data value to FadeMeta (defensive — survives partial blobs). */\nexport function asFadeMeta(val: unknown): FadeMeta | null {\n if (!val || typeof val !== 'object') return null;\n const m = val as Partial<FadeMeta>;\n if (m.direction !== 'in' && m.direction !== 'out') return null;\n if (m.gesture !== 'volume' && m.gesture !== 'build') return null;\n const effect =\n m.effect === 'stutter' || m.effect === 'chopped' || m.effect === 'delay' || m.effect === 'fade'\n ? m.effect\n : undefined;\n return {\n direction: m.direction,\n gesture: m.gesture,\n effect,\n sourceTrackDbId: typeof m.sourceTrackDbId === 'string' ? m.sourceTrackDbId : '',\n sourceSceneId: typeof m.sourceSceneId === 'string' ? m.sourceSceneId : '',\n sourceName: typeof m.sourceName === 'string' ? m.sourceName : '',\n soundLabel: typeof m.soundLabel === 'string' ? m.soundLabel : '',\n sliderPos: typeof m.sliderPos === 'number' ? m.sliderPos : 0.5,\n };\n}\n\n/**\n * Scan all `track:<dbId>:fade` keys in a scene's plugin_data and return one entry\n * per valid fade. Unlike crossfade there is no grouping or both-present gate — a\n * fade is intrinsically a single track.\n */\nexport function parseFades(sceneData: Record<string, unknown>): FadeEntry[] {\n const out: FadeEntry[] = [];\n for (const [key, val] of Object.entries(sceneData)) {\n const match = /^track:(.+):fade$/.exec(key);\n if (!match) continue;\n const meta = asFadeMeta(val);\n if (!meta) continue;\n out.push({ dbId: match[1], meta });\n }\n return out;\n}\n\n/**\n * Build a ONE-sided volume-automation curve for a fade over `bars` at `bpm`.\n *\n * - `gesture === 'build'` → flat at unity (0 dB). The compositional build in the\n * MIDI carries the fade; layering a volume ramp on top would double-fade.\n * - `gesture === 'volume'` → an equal-power ramp identical to ONE half of\n * `buildCrossfadeVolumeCurves`: fade-out ≡ its `origin` curve (unity→floor),\n * fade-in ≡ its `target` curve (floor→unity). `sliderPos` sets WHERE the −3 dB\n * midpoint sits in time.\n *\n * Points span [0, durationSeconds] so the engine re-reads them each loop. Returns\n * dB points for `host.setTrackVolumeAutomation`.\n *\n * @since SDK 2.28.0\n */\nexport function buildFadeVolumeCurve(\n bars: number,\n bpm: number,\n direction: FadeDirection,\n sliderPos: number,\n gesture: FadeGesture,\n steps = 32,\n): VolumeAutomationPoint[] {\n const durationSeconds = (bars * 4 * 60) / Math.max(1, bpm);\n\n // build: the notes do the fade — hold the level flat at unity.\n if (gesture === 'build') {\n return [\n { time: 0, db: 0 },\n { time: Math.round(durationSeconds * 1000) / 1000, db: 0 },\n ];\n }\n\n // volume: one half of the equal-power crossfade curve.\n const s = Math.min(0.98, Math.max(0.02, sliderPos));\n const round = (n: number): number => Math.round(n * 1000) / 1000;\n const points: VolumeAutomationPoint[] = [];\n for (let i = 0; i <= steps; i++) {\n const x = i / steps; // normalized time 0..1\n const time = round(x * durationSeconds);\n // Piecewise-linear angle so the equal-power midpoint (π/4) lands at x = s.\n const theta = x <= s ? (x / s) * (Math.PI / 4) : Math.PI / 4 + ((x - s) / (1 - s)) * (Math.PI / 4);\n // fade-out follows cos (unity→floor); fade-in follows sin (floor→unity).\n const gain = direction === 'out' ? Math.cos(theta) : Math.sin(theta);\n points.push({ time, db: Math.round(gainToDb(gain) * 100) / 100 });\n }\n return points;\n}\n\n/**\n * Roles whose fades default to a `volume` (level) ramp — sustained/textural\n * material that enters/leaves by level in real productions. Everything else\n * defaults to `build` (the notes carry the fade).\n *\n * This is a UI default heuristic over role tokens, NOT the canonical role list\n * (that lives in the app's instrument-classification + `host.getValidRoles()`).\n * There is no textural↔articulated axis in the taxonomy, so this small curated\n * subset lives next to its consumer (the fade modal/panels).\n *\n * @since SDK 2.28.0\n */\nexport const TEXTURAL_ROLES: ReadonlySet<string> = new Set<string>([\n 'pads',\n 'pad',\n 'strings',\n 'atmospheres',\n 'atmosphere',\n 'atmos',\n 'drones',\n 'drone',\n 'soundscapes',\n 'soundscape',\n]);\n\n/** Pick the default fade gesture for a track's role (textural → volume, else build). */\nexport function defaultFadeGesture(role: string | null | undefined): FadeGesture {\n if (!role) return 'build';\n const norm = role.toLowerCase().replace(/[\\s_-]+/g, ' ').trim();\n if (TEXTURAL_ROLES.has(norm)) return 'volume';\n for (const token of norm.split(' ')) {\n if (TEXTURAL_ROLES.has(token)) return 'volume';\n }\n return 'build';\n}\n","/**\n * FadeTrackRow — a transition \"fade track\": a single locked TrackRow with a\n * direction badge (Fade in / Fade out) and a one-sided fade slider.\n *\n * A fade is a crossfade with one empty endpoint — a lone generated track that\n * either enters (fade in, for a target-only track) or leaves (fade out, for an\n * origin-only track) across the transition loop. Like a crossfade layer, the\n * sound/generation controls are omitted (the SDK TrackRow is \"controlled by\n * omission\"): no shuffle / Create / preset-pick / FX / drawer / inner-delete.\n * What remains: per-track volume/pan/mute/solo and a single delete.\n *\n * The slider represents WHERE in the loop the fade sits (earlier ↔ later). Omit\n * `onSliderChange` to render it read-only.\n *\n * @since SDK 2.28.0\n */\nimport React from 'react';\nimport { TrackRow } from './TrackRow';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { EMPTY_FX_DETAIL_STATE } from '../types/fx-toggle.types';\nimport type { TrackLevelsHandle } from '../hooks/useTrackLevels';\nimport type { FadeDirection, FadeGesture } from '../fade-meta';\n\n/** The single (engine track) layer of a fade. */\nexport interface FadeLayer {\n /** Engine track id of this fade's track (also the meter key). */\n trackId: string;\n /** Display name of this fade's (newly created) track. */\n name: string;\n /** Musical role (drives the auto gesture). */\n role?: string;\n /** Name of the SOURCE track this fade was seeded from (origin/target scene). */\n sourceName?: string;\n /** Human label of the copied preset/sound, shown in the caption. */\n soundLabel?: string;\n /** Playback state for this track. */\n runtimeState: { muted: boolean; solo: boolean; volume: number; pan: number };\n}\n\nexport interface FadeTrackRowProps {\n /** The lone fade track. */\n layer: FadeLayer;\n /** 'in' (enters across the loop) or 'out' (leaves across the loop). */\n direction: FadeDirection;\n /** How the fade is shaped — shown read-only (volume = level ramp, build = notes). */\n gesture: FadeGesture;\n /** Audio transition variant — relabels the badge (Stutter/Chopped/Delay). @since SDK 2.32.0 */\n effect?: 'fade' | 'stutter' | 'chopped' | 'delay';\n /** Fade position 0..1 — WHERE in time the fade sits. Defaults centered. */\n sliderPos?: number;\n /** Toggle mute. */\n onMuteToggle: () => void;\n /** Toggle solo. */\n onSoloToggle: () => void;\n /** Change the track's volume. */\n onVolumeChange: (volume: number) => void;\n /** Change the track's pan. */\n onPanChange: (pan: number) => void;\n /** Delete the fade. */\n onDelete: () => void;\n /** Move the fade point. Omit to render the slider read-only. */\n onSliderChange?: (pos: number) => void;\n /** Shared meter handle (welds a peak meter to the track). */\n levels?: TrackLevelsHandle;\n /** Left-border accent. Defaults to transition purple. */\n accentColor?: string;\n}\n\nfunction FadeCaption({\n layer,\n direction,\n gesture,\n}: {\n layer: FadeLayer;\n direction: FadeDirection;\n gesture: FadeGesture;\n}): React.ReactElement {\n const tag = direction === 'in' ? 'Fade in' : 'Fade out';\n return (\n <div className=\"flex items-center gap-1.5 min-w-0 px-2 py-0.5\">\n <span className=\"text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0\">{tag}</span>\n <span className=\"text-[11px] text-sas-text truncate\" title={layer.sourceName ?? layer.name}>\n {layer.sourceName ?? layer.name}\n </span>\n {layer.soundLabel && (\n <span className=\"text-[9px] text-sas-muted/60 truncate flex-shrink-0\" title={layer.soundLabel}>\n · {layer.soundLabel}\n </span>\n )}\n <span className=\"text-[9px] text-sas-muted/50 flex-shrink-0\" title={`Fade gesture: ${gesture}`}>\n · {gesture}\n </span>\n </div>\n );\n}\n\nexport function FadeTrackRow({\n layer,\n direction,\n gesture,\n effect,\n sliderPos = 0.5,\n onMuteToggle,\n onSoloToggle,\n onVolumeChange,\n onPanChange,\n onDelete,\n onSliderChange,\n levels,\n accentColor = '#9333EA',\n}: FadeTrackRowProps): React.ReactElement {\n const [confirmDelete, setConfirmDelete] = React.useState(false);\n\n // Slider end labels: a fade-in goes (silent → track), a fade-out goes (track → silent).\n const leftLabel = direction === 'in' ? '(silent)' : (layer.sourceName ?? layer.name);\n const rightLabel = direction === 'in' ? (layer.sourceName ?? layer.name) : '(silent)';\n const verb = effect && effect !== 'fade' ? effect.charAt(0).toUpperCase() + effect.slice(1) : 'Fade';\n const badge = direction === 'in' ? `↗ ${verb} in` : `↘ ${verb} out`;\n\n return (\n <div\n data-testid=\"fade-track-row\"\n className=\"w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden\"\n style={{ borderLeftColor: accentColor, borderLeftWidth: '3px' }}\n >\n {/* Header — direction badge + single delete. */}\n <div className=\"flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60\">\n <span\n data-testid=\"fade-direction-badge\"\n className=\"text-[10px] font-bold uppercase tracking-wide\"\n style={{ color: accentColor }}\n >\n {badge}\n </span>\n <button\n data-testid=\"fade-delete-button\"\n onClick={() => setConfirmDelete(true)}\n className=\"text-sas-danger/70 hover:text-sas-danger px-1 transition-colors text-sm\"\n title=\"Delete fade\"\n aria-label=\"Delete fade\"\n >\n x\n </button>\n </div>\n\n {/* The lone, locked track. Sound/generation controls are omitted; the\n meaningful name + direction live in the caption. */}\n <TrackRow\n track={{ id: layer.trackId, name: '', role: layer.role }}\n runtimeState={layer.runtimeState}\n fxDetailState={EMPTY_FX_DETAIL_STATE}\n drawerOpen={false}\n drawerTab=\"fx\"\n levels={levels}\n accentColor={accentColor}\n contentSlot={<FadeCaption layer={layer} direction={direction} gesture={gesture} />}\n onMuteToggle={onMuteToggle}\n onSoloToggle={onSoloToggle}\n onVolumeChange={onVolumeChange}\n onPanChange={onPanChange}\n />\n\n {/* Fade slider — WHERE in the loop the fade sits. Read-only until wired. */}\n <div className=\"flex items-center gap-2 px-3 py-1.5\" data-testid=\"fade-slider-row\">\n <span\n className=\"text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0\"\n title={leftLabel}\n >\n {leftLabel}\n </span>\n <input\n type=\"range\"\n data-testid=\"fade-slider\"\n min={0}\n max={1}\n step={0.01}\n value={sliderPos}\n disabled={!onSliderChange}\n onChange={\n onSliderChange\n ? (e: React.ChangeEvent<HTMLInputElement>) => onSliderChange(Number(e.target.value))\n : undefined\n }\n style={{ accentColor }}\n className=\"flex-1 disabled:opacity-60 disabled:cursor-not-allowed\"\n aria-label=\"Fade position\"\n />\n <span\n className=\"text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0\"\n title={rightLabel}\n >\n {rightLabel}\n </span>\n </div>\n\n <ConfirmDialog\n open={confirmDelete}\n title=\"Delete fade?\"\n message={<>This fade track will be permanently removed from this scene. This cannot be undone.</>}\n confirmLabel=\"Delete\"\n onConfirm={() => {\n setConfirmDelete(false);\n onDelete();\n }}\n onCancel={() => setConfirmDelete(false)}\n testIdPrefix=\"fade-delete-confirm\"\n />\n </div>\n );\n}\n\nexport default FadeTrackRow;\n","/**\n * FadeModal — \"add a fade\" picker for a transition scene.\n *\n * Shown only inside a `scene_type='transition'` scene. It self-fetches the FROM\n * (origin) and TO (target) scenes' family tracks and diffs them by role to find\n * ORPHANS — tracks with no counterpart on the other side:\n * - origin-only surplus → \"Fade out\" candidates (the track leaves)\n * - target-only surplus → \"Fade in\" candidates (the track enters)\n * Tracks whose role is matched on both sides are crossfade territory and are NOT\n * shown here. A source already used in a crossfade or a fade is hidden (via\n * excludeSourceDbIds).\n *\n * The fade GESTURE (volume vs build) is auto-selected from the track's role and\n * shown read-only — the user does not choose it. On confirm the modal hands the\n * selection + direction + gesture to `onCreate`, which the panel implements\n * (create one track, generate a chord-conforming part, copy the sound, apply a\n * one-sided volume curve). `onCreate` should reject on failure so the modal can\n * show it and stay open.\n *\n * @since SDK 2.28.0\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Modal } from './Modal';\nimport type { PluginHost, SceneFamilyTrack } from '../types/plugin-sdk.types';\nimport { type FadeDirection, type FadeGesture, defaultFadeGesture } from '../fade-meta';\n\n/** A picked orphan track handed to `onCreate`. */\nexport interface FadeSelection {\n /** Source track DB id (selector for getTrackSound + seeding the part). */\n dbId: string;\n /** Display name (for the row caption). */\n name: string;\n /** Musical role of the source track (drives the auto gesture). */\n role?: string;\n}\n\nexport interface FadeModalProps {\n /** Scoped host — the modal calls listSceneFamilyTracks itself. */\n host: PluginHost;\n /** Controls visibility (the panel owns open/closed from its header button). */\n open: boolean;\n /** DB id of the transition's FROM (origin) scene. */\n fromSceneId: string;\n /** DB id of the transition's TO (target) scene. */\n toSceneId: string;\n /** Display name for the origin scene heading (optional). */\n fromSceneName?: string;\n /** Display name for the target scene heading (optional). */\n toSceneName?: string;\n /** Source-track DB ids already used in a crossfade OR a fade — hidden here. */\n excludeSourceDbIds?: readonly string[];\n /** Close handler (Escape, backdrop, Cancel, or after a successful create). */\n onClose: () => void;\n /** Build the fade. Should reject on failure so the modal shows it. */\n onCreate: (selection: FadeSelection, direction: FadeDirection, gesture: FadeGesture) => Promise<void>;\n /** data-testid prefix. */\n testIdPrefix?: string;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; from: SceneFamilyTrack[]; to: SceneFamilyTrack[] };\n\n/** Short, recognisable id prefix — the full id lives in the row's title. */\nfunction shortId(dbId: string): string {\n return dbId.length > 8 ? dbId.slice(0, 8) : dbId;\n}\n\nconst normRole = (r: string | undefined): string => (r ?? '').toLowerCase().trim();\n\n/**\n * Multiset role-diff: per role token, pair min(from,to) as SHARED (crossfade\n * territory — hidden), and return the surplus on each side as orphans.\n */\nfunction computeOrphans(\n from: SceneFamilyTrack[],\n to: SceneFamilyTrack[],\n excludeSet: ReadonlySet<string>,\n): { fadeOut: SceneFamilyTrack[]; fadeIn: SceneFamilyTrack[] } {\n const bucket = (list: SceneFamilyTrack[]): Map<string, SceneFamilyTrack[]> => {\n const m = new Map<string, SceneFamilyTrack[]>();\n for (const t of list) {\n const k = normRole(t.role);\n const arr = m.get(k);\n if (arr) arr.push(t);\n else m.set(k, [t]);\n }\n return m;\n };\n const fromByRole = bucket(from);\n const toByRole = bucket(to);\n const roles = new Set<string>([...fromByRole.keys(), ...toByRole.keys()]);\n const fadeOut: SceneFamilyTrack[] = [];\n const fadeIn: SceneFamilyTrack[] = [];\n for (const role of roles) {\n const f = fromByRole.get(role) ?? [];\n const t = toByRole.get(role) ?? [];\n const shared = Math.min(f.length, t.length);\n fadeOut.push(...f.slice(shared));\n fadeIn.push(...t.slice(shared));\n }\n return {\n fadeOut: fadeOut.filter((x) => !excludeSet.has(x.dbId)),\n fadeIn: fadeIn.filter((x) => !excludeSet.has(x.dbId)),\n };\n}\n\n/**\n * One selectable orphan row. Prompt-first (users recognise tracks by prompt);\n * role + id + the auto gesture sit underneath in a smaller, muted font.\n */\nfunction OrphanRow({\n track,\n gesture,\n selected,\n disabled,\n onSelect,\n testId,\n}: {\n track: SceneFamilyTrack;\n gesture: FadeGesture;\n selected: boolean;\n disabled: boolean;\n onSelect: () => void;\n testId: string;\n}): React.ReactElement {\n const primary = track.prompt?.trim() || track.name;\n const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(' · ');\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n data-testid={testId}\n data-value={track.dbId}\n onClick={onSelect}\n disabled={disabled}\n className={`w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${\n selected\n ? 'bg-sas-accent/15 border-sas-accent'\n : 'bg-sas-panel border-sas-border hover:border-sas-accent/50'\n }`}\n >\n <div className=\"text-xs text-sas-text truncate\" title={primary}>\n {primary}\n </div>\n {meta && (\n <div className=\"text-[10px] text-sas-muted truncate mt-0.5\" title={track.dbId}>\n {meta}\n </div>\n )}\n </button>\n );\n}\n\nexport function FadeModal({\n host,\n open,\n fromSceneId,\n toSceneId,\n fromSceneName,\n toSceneName,\n excludeSourceDbIds,\n onClose,\n onCreate,\n testIdPrefix = 'fade-modal',\n}: FadeModalProps): React.ReactElement | null {\n const [load, setLoad] = useState<LoadState>({ status: 'loading' });\n const [selectedDbId, setSelectedDbId] = useState<string>('');\n const [isCreating, setIsCreating] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [fromName, setFromName] = useState<string | null>(null);\n const [toName, setToName] = useState<string | null>(null);\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!host.listSceneFamilyTracks) {\n setLoad({ status: 'error', message: 'This host does not support fades.' });\n return;\n }\n setLoad({ status: 'loading' });\n try {\n const [from, to, fName, tName] = await Promise.all([\n host.listSceneFamilyTracks(fromSceneId),\n host.listSceneFamilyTracks(toSceneId),\n host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),\n host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),\n ]);\n setFromName(fName);\n setToName(tName);\n setLoad({ status: 'ready', from, to });\n } catch (err: unknown) {\n setLoad({ status: 'error', message: err instanceof Error ? err.message : 'Failed to load tracks.' });\n }\n }, [host, fromSceneId, toSceneId]);\n\n // Fetch on open; reset state.\n useEffect(() => {\n if (open) {\n setError(null);\n setIsCreating(false);\n setSelectedDbId('');\n void refresh();\n }\n }, [open, refresh]);\n\n const excludeSet = useMemo(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);\n\n const { fadeOut, fadeIn } = useMemo(\n () =>\n load.status === 'ready'\n ? computeOrphans(load.from, load.to, excludeSet)\n : { fadeOut: [] as SceneFamilyTrack[], fadeIn: [] as SceneFamilyTrack[] },\n [load, excludeSet],\n );\n\n // One flat selection space across both sections (dbIds are unique).\n const allOrphans = useMemo(\n () => [\n ...fadeOut.map((t) => ({ track: t, direction: 'out' as FadeDirection })),\n ...fadeIn.map((t) => ({ track: t, direction: 'in' as FadeDirection })),\n ],\n [fadeOut, fadeIn],\n );\n\n // Keep the selection valid / defaulted to the first orphan.\n useEffect(() => {\n if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {\n setSelectedDbId(allOrphans[0]?.track.dbId ?? '');\n }\n }, [allOrphans, selectedDbId]);\n\n const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;\n const canCreate = !isCreating && !!selected;\n\n const handleClose = useCallback((): void => {\n if (!isCreating) onClose();\n }, [isCreating, onClose]);\n\n const handleCreate = useCallback(async (): Promise<void> => {\n if (!selected) return;\n setIsCreating(true);\n setError(null);\n try {\n await onCreate(\n { dbId: selected.track.dbId, name: selected.track.name, role: selected.track.role },\n selected.direction,\n defaultFadeGesture(selected.track.role),\n );\n onClose();\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Failed to create fade.');\n setIsCreating(false);\n }\n }, [selected, onCreate, onClose]);\n\n const fromLabel = fromName ?? fromSceneName ?? null;\n const toLabel = toName ?? toSceneName ?? null;\n\n if (!open) return null;\n\n const renderSection = (\n heading: string,\n list: SceneFamilyTrack[],\n section: 'out' | 'in',\n ): React.ReactElement | null => {\n if (list.length === 0) return null;\n return (\n <div className=\"block\">\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted\">{heading}</span>\n <div\n role=\"radiogroup\"\n aria-label={heading}\n data-testid={`${testIdPrefix}-${section === 'out' ? 'fade-out' : 'fade-in'}-list`}\n className=\"mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5\"\n >\n {list.map((t) => (\n <OrphanRow\n key={t.dbId}\n track={t}\n gesture={defaultFadeGesture(t.role)}\n selected={t.dbId === selectedDbId}\n disabled={isCreating}\n onSelect={() => setSelectedDbId(t.dbId)}\n testId={`${testIdPrefix}-option-${t.dbId}`}\n />\n ))}\n </div>\n </div>\n );\n };\n\n return (\n <Modal open={open} onClose={handleClose} testIdPrefix={testIdPrefix} initialFocusRef={cancelRef}>\n <div\n className=\"bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3\"\n onClick={(e: React.MouseEvent) => e.stopPropagation()}\n data-testid={`${testIdPrefix}-box`}\n >\n <h3 className=\"text-sm font-bold text-sas-text\">Add fade</h3>\n <p className=\"text-[11px] text-sas-muted leading-relaxed\">\n Tracks with no counterpart between{' '}\n <span className=\"text-sas-text\">{fromLabel ?? 'the origin scene'}</span> and{' '}\n <span className=\"text-sas-text\">{toLabel ?? 'the target scene'}</span> can gracefully fade\n out (leaving) or fade in (entering) across this transition.\n </p>\n\n {load.status === 'loading' && (\n <div className=\"text-xs text-sas-muted py-4 text-center\">Loading tracks…</div>\n )}\n {load.status === 'error' && (\n <div className=\"text-xs text-sas-danger py-4 text-center\">{load.message}</div>\n )}\n {load.status === 'ready' &&\n (allOrphans.length === 0 ? (\n <div className=\"text-xs text-sas-muted py-4 text-center\" data-testid={`${testIdPrefix}-empty`}>\n Every track has a counterpart in the other scene — nothing to fade. Use “+ Crossfade” to\n bridge matching tracks.\n </div>\n ) : (\n <>\n {renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ''}`, fadeOut, 'out')}\n {renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ''}`, fadeIn, 'in')}\n </>\n ))}\n\n {error && (\n <div className=\"text-xs text-sas-danger\" data-testid={`${testIdPrefix}-error`}>\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-2 pt-1\">\n <button\n ref={cancelRef}\n data-testid={`${testIdPrefix}-cancel`}\n onClick={onClose}\n disabled={isCreating}\n className=\"px-3 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-text disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n data-testid={`${testIdPrefix}-confirm`}\n onClick={handleCreate}\n disabled={!canCreate}\n className={`px-3 py-1 text-xs rounded-sm border transition-colors ${\n canCreate\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n >\n {isCreating ? 'Generating fade…' : 'Create fade'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport default FadeModal;\n","/**\n * ImportTrackModal — \"import a track from another scene\" picker (SDK component).\n *\n * Shared by all five generator panels (drums / instruments / synths / loops /\n * stems). Self-fetching: given the scoped `host`, it calls\n * `host.listImportableTracks()` to enumerate candidates (already filtered to\n * the calling panel's type and gate-annotated by the host) and\n * `host.importTrack()` to perform the copy. The UI only renders `importable` +\n * `disabledReason` — it never computes the harmonic/length/tempo gate itself.\n *\n * Two-step picker: choose a source scene, then a track in it. Incompatible\n * tracks render disabled with a reason tooltip (never hidden), per product\n * decision.\n *\n * @since SDK 2.13.0\n */\n\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { Modal } from './Modal';\nimport type {\n PluginHost,\n ImportCandidateScene,\n ImportCandidateTrack,\n PluginTrackHandle,\n} from '../types/plugin-sdk.types';\n\nexport interface ImportTrackModalProps {\n /** Scoped host — the modal calls listImportableTracks / importTrack itself. */\n host: PluginHost;\n /** Controls visibility (the panel owns open/closed from its header button). */\n open: boolean;\n /** Close handler (Escape, backdrop, Cancel, or after a successful import). */\n onClose: () => void;\n /** Fired after a successful import with the new track handle. */\n onImported: (handle: PluginTrackHandle) => void;\n /** Optional modal title (default names the whole-track import). */\n title?: string;\n /** data-testid prefix so each panel's modal is addressable in tests. */\n testIdPrefix?: string;\n /**\n * 'track' (default) imports a whole track via `importTrack`. 'sound' copies\n * ONLY the sound onto an existing track: every candidate is selectable (the\n * contract gate is ignored) and the chosen track is handed back via `onPick`\n * instead of being imported — the panel applies it via `host.getTrackSound`.\n */\n mode?: 'track' | 'sound';\n /** Sound-mode pick handler — required when `mode='sound'`. */\n onPick?: (sel: { sourceTrackDbId: string; trackName: string; sceneName: string }) => void | Promise<void>;\n /**\n * Cross-panel port handler (track mode). When provided, the modal also lists\n * the ACTIVE scene's tracks owned by OTHER panels as a `sameScene` group —\n * shown first and selected by default — and routes a pick there to this\n * callback instead of `importTrack`. The panel re-sounds the part on its own\n * instrument (create track → copy MIDI → load native sound). @since SDK 2.20.0\n */\n onPortTrack?: (sel: { sourceTrackDbId: string; trackName: string; role?: string }) => void | Promise<void>;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; scenes: ImportCandidateScene[] };\n\nexport function ImportTrackModal({\n host,\n open,\n onClose,\n onImported,\n title = 'Import track from scene (must match contract)',\n testIdPrefix = 'import-track',\n mode = 'track',\n onPick,\n onPortTrack,\n}: ImportTrackModalProps): React.ReactElement | null {\n const [load, setLoad] = useState<LoadState>({ status: 'loading' });\n const [selectedSceneId, setSelectedSceneId] = useState<string | null>(null);\n const [importingTrackId, setImportingTrackId] = useState<string | null>(null);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!host.listImportableTracks) {\n setLoad({ status: 'error', message: 'This host does not support importing tracks.' });\n return;\n }\n setLoad({ status: 'loading' });\n try {\n // Track mode with a port handler also wants the \"this scene — other\n // panels\" group (cross-panel re-sound source); plain/sound flows don't.\n const wantsPort = mode === 'track' && !!onPortTrack;\n const scenes = await host.listImportableTracks(wantsPort ? { includeSameScene: true } : undefined);\n setLoad({ status: 'ready', scenes });\n // Default to the same-scene group when present so the user lands on\n // cross-panel tracks (they can ← back to pick another scene).\n const sameScene = scenes.find((s) => s.sameScene);\n if (sameScene) setSelectedSceneId(sameScene.sceneId);\n } catch (err: unknown) {\n setLoad({ status: 'error', message: err instanceof Error ? err.message : 'Failed to load scenes.' });\n }\n }, [host, mode, onPortTrack]);\n\n // Fetch candidates each time the modal opens; reset selection on close.\n useEffect(() => {\n if (open) {\n setSelectedSceneId(null);\n setImportingTrackId(null);\n void refresh();\n }\n }, [open, refresh]);\n\n const handleImport = useCallback(\n async (\n track: ImportCandidateTrack,\n sourceSceneId: string,\n sceneName: string,\n isSameScene: boolean,\n ): Promise<void> => {\n // Same-scene, other-panel pick: re-sound the part on THIS panel's\n // instrument. The panel creates a track, copies the MIDI, and loads its\n // own sound (see onPortTrack) — never a faithful copy / importTrack.\n if (isSameScene && onPortTrack) {\n if (!track.importable) return;\n setImportingTrackId(track.trackId);\n try {\n await onPortTrack({ sourceTrackDbId: track.dbId, trackName: track.name, role: track.role });\n onClose();\n } catch (err: unknown) {\n host.showToast?.('error', err instanceof Error ? err.message : 'Import failed');\n setImportingTrackId(null);\n }\n return;\n }\n // Sound mode: ignore the gate and hand the pick back to the panel, which\n // reads the source sound via host.getTrackSound and applies it itself.\n if (mode === 'sound') {\n setImportingTrackId(track.trackId);\n try {\n await onPick?.({ sourceTrackDbId: track.dbId, trackName: track.name, sceneName });\n onClose();\n } catch (err: unknown) {\n host.showToast?.('error', err instanceof Error ? err.message : 'Import failed');\n setImportingTrackId(null);\n }\n return;\n }\n if (!track.importable || !host.importTrack) return;\n setImportingTrackId(track.trackId);\n try {\n const handle = await host.importTrack({ sourceSceneId, sourceTrackId: track.trackId });\n onImported(handle);\n onClose();\n } catch (err: unknown) {\n host.showToast?.('error', err instanceof Error ? err.message : 'Import failed');\n setImportingTrackId(null);\n }\n },\n [host, onImported, onClose, mode, onPick, onPortTrack],\n );\n\n if (!open) return null;\n\n const scenes = load.status === 'ready' ? load.scenes : [];\n const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;\n\n return (\n <Modal open={open} onClose={onClose} testIdPrefix={testIdPrefix}>\n <div\n className=\"w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl\"\n onClick={(e) => e.stopPropagation()}\n data-testid={`${testIdPrefix}-modal`}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-sas-border\">\n <div className=\"flex items-center gap-2\">\n {selectedScene && (\n <button\n className=\"text-sas-muted hover:text-sas-accent text-xs\"\n onClick={() => setSelectedSceneId(null)}\n data-testid={`${testIdPrefix}-back`}\n >\n ←\n </button>\n )}\n <span className=\"text-sm font-medium text-sas-text\">\n {selectedScene ? selectedScene.sceneName : title}\n </span>\n </div>\n <button\n className=\"text-sas-muted hover:text-sas-accent text-sm\"\n onClick={onClose}\n data-testid={`${testIdPrefix}-close`}\n >\n ✕\n </button>\n </div>\n\n {/* Body */}\n <div className=\"overflow-y-auto p-2 flex-1\">\n {load.status === 'loading' && (\n <div className=\"py-8 text-center text-xs text-sas-muted\" data-testid={`${testIdPrefix}-loading`}>\n Loading scenes…\n </div>\n )}\n\n {load.status === 'error' && (\n <div className=\"py-8 text-center text-xs text-red-400\" data-testid={`${testIdPrefix}-error`}>\n {load.message}\n </div>\n )}\n\n {load.status === 'ready' && scenes.length === 0 && (\n <div className=\"py-8 text-center text-xs text-sas-muted\" data-testid={`${testIdPrefix}-empty`}>\n {mode === 'sound'\n ? 'No other scenes have a sound to import.'\n : 'No other scenes have a compatible track to import.'}\n </div>\n )}\n\n {/* Scene list */}\n {load.status === 'ready' && scenes.length > 0 && !selectedScene && (\n <ul className=\"flex flex-col gap-1\" data-testid={`${testIdPrefix}-scene-list`}>\n {scenes.map((scene) => (\n <li key={scene.sceneId}>\n <button\n className=\"w-full flex items-center justify-between px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt text-left text-xs text-sas-text hover:border-sas-accent hover:text-sas-accent transition-colors\"\n onClick={() => setSelectedSceneId(scene.sceneId)}\n data-testid={`${testIdPrefix}-scene`}\n >\n <span className=\"truncate\">{scene.sceneName}</span>\n <span className=\"text-sas-muted\">{scene.tracks.length} →</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n\n {/* Track list */}\n {selectedScene && (\n <ul className=\"flex flex-col gap-1\" data-testid={`${testIdPrefix}-track-list`}>\n {selectedScene.tracks.map((track) => {\n const busy = importingTrackId === track.trackId;\n // Sound mode ignores the contract gate — every candidate is a\n // valid sound source. Track mode honors `importable`.\n const gated = mode === 'track' && !track.importable;\n const disabled = gated || busy;\n return (\n <li key={track.dbId}>\n <button\n className={`w-full flex items-center justify-between px-2 py-1.5 rounded-sm border text-left text-xs transition-colors ${\n disabled\n ? 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n : 'bg-sas-panel-alt border-sas-border text-sas-text hover:border-sas-accent hover:text-sas-accent'\n }`}\n disabled={disabled}\n title={gated ? track.disabledReason : undefined}\n onClick={() => void handleImport(track, selectedScene.sceneId, selectedScene.sceneName, !!selectedScene.sameScene)}\n data-testid={`${testIdPrefix}-track`}\n data-importable={mode === 'sound' || track.importable ? 'true' : 'false'}\n >\n <span className=\"truncate\">\n {track.name}\n {track.role ? <span className=\"text-sas-muted\"> · {track.role}</span> : null}\n </span>\n {busy ? (\n <span className=\"text-sas-muted\">…</span>\n ) : gated ? (\n <span className=\"text-sas-muted\">⊘</span>\n ) : null}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n </Modal>\n );\n}\n","/**\n * CrossfadeModal — \"add a crossfade track\" picker for a transition scene.\n *\n * Shown only inside a `scene_type='transition'` scene. The user picks an ORIGIN\n * track (from the transition's FROM scene) and a TARGET track (from its TO\n * scene), in ANY order — the only constraint is same plugin/family (the picker is\n * per-panel). A source track already used in a crossfade is hidden (via\n * excludeSourceDbIds), so each source is used at most once.\n *\n * Self-fetching: given the scoped `host`, it calls `host.listSceneFamilyTracks`\n * for both scenes (ungated — a transition deliberately bridges different keys).\n * It does NOT build the pair itself; it hands the two selections to `onCreate`,\n * which the panel implements (create two tracks, generate one shared MIDI clip,\n * copy each preset). `onCreate` should reject on failure so the modal can show\n * it and stay open.\n *\n * @since SDK 2.22.0\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Modal } from './Modal';\nimport type { PluginHost, SceneFamilyTrack } from '../types/plugin-sdk.types';\n\n/** A picked source track handed to `onCreate`. */\nexport interface CrossfadeSelection {\n /** Source track DB id (selector for getTrackSound + crossfade metadata). */\n dbId: string;\n /** Display name (for the row caption). */\n name: string;\n /** Musical role of the source track (the panel uses the TARGET's for generation). */\n role?: string;\n}\n\nexport interface CrossfadeModalProps {\n /** Scoped host — the modal calls listSceneFamilyTracks itself. */\n host: PluginHost;\n /** Controls visibility (the panel owns open/closed from its header button). */\n open: boolean;\n /** DB id of the transition's FROM (origin) scene. */\n fromSceneId: string;\n /** DB id of the transition's TO (target) scene. */\n toSceneId: string;\n /** Display name for the origin scene heading (optional). */\n fromSceneName?: string;\n /** Display name for the target scene heading (optional). */\n toSceneName?: string;\n /**\n * Source-track DB ids already used in a crossfade (origin + target of every\n * existing pair in this panel). Hidden from BOTH dropdowns so each source is\n * used at most once. @since SDK 2.26.0\n */\n excludeSourceDbIds?: readonly string[];\n /** Close handler (Escape, backdrop, Cancel, or after a successful create). */\n onClose: () => void;\n /** Build the crossfade pair. Should reject on failure so the modal shows it. */\n onCreate: (origin: CrossfadeSelection, target: CrossfadeSelection) => Promise<void>;\n /** data-testid prefix. */\n testIdPrefix?: string;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; origin: SceneFamilyTrack[]; target: SceneFamilyTrack[] };\n\n/** Short, recognisable id prefix — the full id lives in the row's title. */\nfunction shortId(dbId: string): string {\n return dbId.length > 8 ? dbId.slice(0, 8) : dbId;\n}\n\n/**\n * One selectable track row. Users recognise tracks by their generation prompt,\n * so the prompt is the prominent line; role + id sit underneath in a smaller,\n * muted font (prompt → role → id order). Falls back to the display name when a\n * track has no prompt (e.g. sample/audio).\n */\nfunction CandidateRow({\n track,\n selected,\n disabled,\n onSelect,\n testId,\n}: {\n track: SceneFamilyTrack;\n selected: boolean;\n disabled: boolean;\n onSelect: () => void;\n testId: string;\n}): React.ReactElement {\n const primary = track.prompt?.trim() || track.name;\n const meta = [track.role, shortId(track.dbId)].filter(Boolean).join(' · ');\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n data-testid={testId}\n data-value={track.dbId}\n onClick={onSelect}\n disabled={disabled}\n className={`w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${\n selected\n ? 'bg-sas-accent/15 border-sas-accent'\n : 'bg-sas-panel border-sas-border hover:border-sas-accent/50'\n }`}\n >\n <div className=\"text-xs text-sas-text truncate\" title={primary}>\n {primary}\n </div>\n {meta && (\n <div className=\"text-[10px] text-sas-muted truncate mt-0.5\" title={track.dbId}>\n {meta}\n </div>\n )}\n </button>\n );\n}\n\nexport function CrossfadeModal({\n host,\n open,\n fromSceneId,\n toSceneId,\n fromSceneName,\n toSceneName,\n excludeSourceDbIds,\n onClose,\n onCreate,\n testIdPrefix = 'crossfade-modal',\n}: CrossfadeModalProps): React.ReactElement | null {\n const [load, setLoad] = useState<LoadState>({ status: 'loading' });\n const [originDbId, setOriginDbId] = useState<string>('');\n const [targetDbId, setTargetDbId] = useState<string>('');\n const [isCreating, setIsCreating] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [fromName, setFromName] = useState<string | null>(null);\n const [toName, setToName] = useState<string | null>(null);\n const cancelRef = useRef<HTMLButtonElement>(null);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!host.listSceneFamilyTracks) {\n setLoad({ status: 'error', message: 'This host does not support crossfade tracks.' });\n return;\n }\n setLoad({ status: 'loading' });\n try {\n const [origin, target, fName, tName] = await Promise.all([\n host.listSceneFamilyTracks(fromSceneId),\n host.listSceneFamilyTracks(toSceneId),\n host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),\n host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),\n ]);\n setFromName(fName);\n setToName(tName);\n setLoad({ status: 'ready', origin, target });\n } catch (err: unknown) {\n setLoad({ status: 'error', message: err instanceof Error ? err.message : 'Failed to load tracks.' });\n }\n }, [host, fromSceneId, toSceneId]);\n\n // Fetch on open; reset state.\n useEffect(() => {\n if (open) {\n setError(null);\n setIsCreating(false);\n setOriginDbId('');\n setTargetDbId('');\n void refresh();\n }\n }, [open, refresh]);\n\n // Hide any source track already used in a crossfade (each source used once).\n const excludeSet = useMemo(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);\n\n // The only constraint is same plugin/family (already enforced per-panel), so the\n // two lists are independent — pick in any order, any role.\n const originCandidates = useMemo(\n () => (load.status === 'ready' ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : []),\n [load, excludeSet],\n );\n const targetCandidates = useMemo(\n () => (load.status === 'ready' ? load.target.filter((t) => !excludeSet.has(t.dbId)) : []),\n [load, excludeSet],\n );\n\n // Keep each selection valid / defaulted to its first candidate, independently.\n useEffect(() => {\n if (!originCandidates.some((t) => t.dbId === originDbId)) {\n setOriginDbId(originCandidates[0]?.dbId ?? '');\n }\n }, [originCandidates, originDbId]);\n useEffect(() => {\n if (!targetCandidates.some((t) => t.dbId === targetDbId)) {\n setTargetDbId(targetCandidates[0]?.dbId ?? '');\n }\n }, [targetCandidates, targetDbId]);\n\n const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;\n const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;\n const canCreate = !isCreating && !!originTrack && !!targetTrack;\n\n const handleClose = useCallback((): void => {\n if (!isCreating) onClose();\n }, [isCreating, onClose]);\n\n const handleCreate = useCallback(async (): Promise<void> => {\n if (!originTrack || !targetTrack) return;\n setIsCreating(true);\n setError(null);\n try {\n await onCreate(\n { dbId: originTrack.dbId, name: originTrack.name, role: originTrack.role },\n { dbId: targetTrack.dbId, name: targetTrack.name, role: targetTrack.role },\n );\n onClose();\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Failed to create crossfade.');\n setIsCreating(false);\n }\n }, [originTrack, targetTrack, onCreate, onClose]);\n\n // Prefer the live-fetched scene names; fall back to the optional props.\n const fromLabel = fromName ?? fromSceneName ?? null;\n const toLabel = toName ?? toSceneName ?? null;\n\n if (!open) return null;\n\n return (\n <Modal open={open} onClose={handleClose} testIdPrefix={testIdPrefix} initialFocusRef={cancelRef}>\n <div\n className=\"bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3\"\n onClick={(e: React.MouseEvent) => e.stopPropagation()}\n data-testid={`${testIdPrefix}-box`}\n >\n <h3 className=\"text-sm font-bold text-sas-text\">Add crossfade</h3>\n <p className=\"text-[11px] text-sas-muted leading-relaxed\">\n Bridge a track from{' '}\n <span className=\"text-sas-text\">{fromLabel ?? 'the origin scene'}</span> into one from{' '}\n <span className=\"text-sas-text\">{toLabel ?? 'the target scene'}</span>. Both layers share one\n generated part; each keeps its own preset.\n </p>\n\n {load.status === 'loading' && (\n <div className=\"text-xs text-sas-muted py-4 text-center\">Loading tracks…</div>\n )}\n {load.status === 'error' && (\n <div className=\"text-xs text-sas-danger py-4 text-center\">{load.message}</div>\n )}\n {load.status === 'ready' &&\n (originCandidates.length === 0 ? (\n <div\n className=\"text-xs text-sas-muted py-4 text-center\"\n data-testid={`${testIdPrefix}-empty-origin`}\n >\n No available tracks in {fromLabel ?? 'the origin scene'}. Add one (or free one from another\n crossfade) first.\n </div>\n ) : (\n <>\n <div className=\"block\">\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted\">\n Origin {fromLabel ? `(${fromLabel})` : '(top)'}\n </span>\n <div\n role=\"radiogroup\"\n aria-label=\"Origin track\"\n data-testid={`${testIdPrefix}-origin-list`}\n className=\"mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5\"\n >\n {originCandidates.map((t) => (\n <CandidateRow\n key={t.dbId}\n track={t}\n selected={t.dbId === originDbId}\n disabled={isCreating}\n onSelect={() => setOriginDbId(t.dbId)}\n testId={`${testIdPrefix}-origin-option-${t.dbId}`}\n />\n ))}\n </div>\n </div>\n\n <div className=\"block\">\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted\">\n Target {toLabel ? `(${toLabel})` : '(bottom)'}\n </span>\n {targetCandidates.length === 0 ? (\n <div className=\"text-xs text-sas-danger mt-0.5\" data-testid={`${testIdPrefix}-empty-target`}>\n No available tracks in {toLabel ?? 'the target scene'} to crossfade into.\n </div>\n ) : (\n <div\n role=\"radiogroup\"\n aria-label=\"Target track\"\n data-testid={`${testIdPrefix}-target-list`}\n className=\"mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5\"\n >\n {targetCandidates.map((t) => (\n <CandidateRow\n key={t.dbId}\n track={t}\n selected={t.dbId === targetDbId}\n disabled={isCreating}\n onSelect={() => setTargetDbId(t.dbId)}\n testId={`${testIdPrefix}-target-option-${t.dbId}`}\n />\n ))}\n </div>\n )}\n </div>\n </>\n ))}\n\n {error && (\n <div className=\"text-xs text-sas-danger\" data-testid={`${testIdPrefix}-error`}>\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-2 pt-1\">\n <button\n ref={cancelRef}\n data-testid={`${testIdPrefix}-cancel`}\n onClick={onClose}\n disabled={isCreating}\n className=\"px-3 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-text disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n data-testid={`${testIdPrefix}-confirm`}\n onClick={handleCreate}\n disabled={!canCreate}\n className={`px-3 py-1 text-xs rounded-sm border transition-colors ${\n canCreate\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n >\n {isCreating ? 'Generating bridge…' : 'Create crossfade'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport default CrossfadeModal;\n","/**\n * TransitionDesigner — the per-panel transition staging board, rendered INLINE as\n * a toggled view inside a generator panel (NOT a modal).\n *\n * The multi-row, persistent successor to {@link CrossfadeModal} + {@link FadeModal}:\n * instead of opening a single-pair dialog ~20 times, the panel header gains a\n * Tracks ⇄ Transition toggle; flipping to Transition replaces the panel's track\n * list with this board. Playback is unaffected (the engine keeps playing the\n * scene's tracks — they're just not shown until you toggle back). Shown only\n * inside a `scene_type='transition'` scene and scoped to one panel family (a synth\n * board shows only synth tracks — \"drums can't crossfade to synth\" is enforced\n * structurally because each board asks its own family-scoped host).\n *\n * Two index-aligned, drag-reorderable columns: origin (scene A, left) and target\n * (scene B, right). Row i pairs origin[i] with target[i]; the pairing derives the\n * transition type (both → crossfade, origin-only → fade out, target-only → fade\n * in). Insert a \"gap\" above a cell to push a track so it fades instead of\n * crossfading with whatever sits opposite. The pool per column is the scene's\n * family tracks MINUS sources already consumed by a committed crossfade/fade\n * (`excludeSourceDbIds`).\n *\n * Per-row **Create** reuses the panel's EXISTING orchestration via `onCreateCrossfade`\n * / `onCreateFade`. Creates run CONCURRENTLY — fire several at once, or **Create all**\n * (a bounded pool). Each in-flight row shows its own progress bar and locks just its\n * own cells; the rest of the board stays editable. On success the source leaves the\n * pool (the panel updates `excludeSourceDbIds`) and the row collapses; deleting the\n * committed crossfade/fade on the deck returns the source here. The staged\n * arrangement persists to the transition scene's plugin_data so it survives toggles.\n *\n * @since SDK 2.29.0 (modal); inline toggle view + concurrent creation since 2.30.0.\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { DragEvent } from 'react';\nimport { SorceryProgressBar } from './SorceryProgressBar';\nimport { moveItem } from '../hooks/useTrackReorder';\nimport type { PluginHost, SceneFamilyTrack } from '../types/plugin-sdk.types';\nimport type { CrossfadeSelection } from './CrossfadeModal';\nimport type { FadeSelection } from './FadeModal';\nimport { type FadeDirection, type FadeGesture, defaultFadeGesture } from '../fade-meta';\nimport {\n TRANSITION_DESIGNER_DRAFT_KEY,\n type TransitionRowType,\n type DesignerRowSlots,\n asTransitionDesignerDraft,\n reconcileSlots,\n buildRowSlots,\n normalizeSlots,\n padPair,\n slotsEqual,\n rowKey,\n dbIdsFromKeys,\n type AudioEffect,\n AUDIO_EFFECTS,\n AUDIO_EFFECT_LABEL,\n} from '../transition-designer-meta';\n\ntype Column = 'origin' | 'target';\n\nexport interface TransitionDesignerProps {\n /** Scoped host — the board calls listSceneFamilyTracks / getSceneName itself. */\n host: PluginHost;\n /** DB id of the transition's FROM (origin) scene. */\n fromSceneId: string;\n /** DB id of the transition's TO (target) scene. */\n toSceneId: string;\n /** DB id of the transition scene itself — the staged draft is persisted here. */\n transitionSceneId: string;\n /**\n * Source-track DB ids already consumed by a committed crossfade OR fade in this\n * panel. Hidden from both columns so each source is used at most once; when the\n * deck row is deleted the panel drops the id and the source reappears here.\n */\n excludeSourceDbIds?: readonly string[];\n /**\n * Build a crossfade pair — the panel's existing handler (create two tracks, one\n * morphed clip, copy each preset). Should reject on failure. Safe to call\n * concurrently.\n */\n onCreateCrossfade: (origin: CrossfadeSelection, target: CrossfadeSelection) => Promise<void>;\n /** Build a one-sided fade — the panel's existing handler. Should reject on failure. */\n onCreateFade: (\n selection: FadeSelection,\n direction: FadeDirection,\n gesture: FadeGesture,\n ) => Promise<void>;\n /**\n * Build an AUDIO-only one-sided transition (stutter / chopped / delay). When\n * provided, one-sided rows render an effect selector; absent (MIDI panels) →\n * one-sided rows stay plain fades. @since SDK 2.32.0\n */\n onCreateAudioTransition?: (\n selection: FadeSelection,\n direction: FadeDirection,\n effect: 'stutter' | 'chopped' | 'delay',\n ) => Promise<void>;\n /** Short family label for the heading, e.g. \"Synths\". */\n familyLabel?: string;\n /** data-testid prefix. */\n testIdPrefix?: string;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; origin: SceneFamilyTrack[]; target: SceneFamilyTrack[] };\n\n/** ~time the LLM morph/fade generation takes, for the time-based progress bar. */\nconst CROSSFADE_ESTIMATE_MS = 15000;\nconst FADE_ESTIMATE_MS = 11000;\n/** Bounded fan-out for \"Create all\" (the user wants ~3-5 at once). */\nconst CREATE_ALL_CONCURRENCY = 5;\n\nconst TYPE_LABEL: Record<TransitionRowType, string> = {\n crossfade: 'Crossfade',\n 'fade-out': 'Fade out',\n 'fade-in': 'Fade in',\n};\n\n/** Short, recognisable id prefix — the full id lives in the cell's title. */\nfunction shortId(dbId: string): string {\n return dbId.length > 8 ? dbId.slice(0, 8) : dbId;\n}\n\nexport function TransitionDesigner({\n host,\n fromSceneId,\n toSceneId,\n transitionSceneId,\n excludeSourceDbIds,\n onCreateCrossfade,\n onCreateFade,\n onCreateAudioTransition,\n familyLabel,\n testIdPrefix = 'transition-designer',\n}: TransitionDesignerProps): React.ReactElement {\n const [load, setLoad] = useState<LoadState>({ status: 'loading' });\n const [fromName, setFromName] = useState<string | null>(null);\n const [toName, setToName] = useState<string | null>(null);\n // Columns are kept padded to equal length (aligned rendering + drag); trimmed\n // only at persist time (normalizeSlots).\n const [originSlots, setOriginSlots] = useState<(string | null)[]>([]);\n const [targetSlots, setTargetSlots] = useState<(string | null)[]>([]);\n // In-flight creates, keyed by a STABLE row key (source dbIds, not index) so\n // several can run at once and a reorder mid-create still tracks the right row.\n const [creatingKeys, setCreatingKeys] = useState<Set<string>>(() => new Set());\n const [rowErrors, setRowErrors] = useState<Record<string, string>>({});\n // Per one-sided-row audio effect (keyed by source dbId). Only meaningful when\n // onCreateAudioTransition is provided (audio panels).\n const [rowEffects, setRowEffects] = useState<Record<string, AudioEffect>>({});\n const rowEffectsRef = useRef(rowEffects);\n rowEffectsRef.current = rowEffects;\n const audioEffectsEnabled = !!onCreateAudioTransition;\n\n // Latest props/state read inside effects + handlers without widening deps.\n const excludeRef = useRef(excludeSourceDbIds);\n excludeRef.current = excludeSourceDbIds;\n const originSlotsRef = useRef(originSlots);\n originSlotsRef.current = originSlots;\n const targetSlotsRef = useRef(targetSlots);\n targetSlotsRef.current = targetSlots;\n const creatingKeysRef = useRef(creatingKeys);\n creatingKeysRef.current = creatingKeys;\n\n // Drag state: a ref drives the drop computation (no stale closure); the matching\n // React state drives the dim/highlight visuals.\n const dragRef = useRef<{ col: Column; index: number } | null>(null);\n const [dragging, setDragging] = useState<{ col: Column; index: number } | null>(null);\n const [dragOver, setDragOver] = useState<{ col: Column; index: number } | null>(null);\n\n const excludeSet = useMemo(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);\n const originPool = useMemo(\n () => (load.status === 'ready' ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : []),\n [load, excludeSet],\n );\n const targetPool = useMemo(\n () => (load.status === 'ready' ? load.target.filter((t) => !excludeSet.has(t.dbId)) : []),\n [load, excludeSet],\n );\n const originById = useMemo(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);\n const targetById = useMemo(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);\n const originByIdRef = useRef(originById);\n originByIdRef.current = originById;\n const targetByIdRef = useRef(targetById);\n targetByIdRef.current = targetById;\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!host.listSceneFamilyTracks) {\n setLoad({ status: 'error', message: 'This host does not support transition tracks.' });\n return;\n }\n setLoad({ status: 'loading' });\n try {\n const [origin, target, fName, tName, draftRaw] = await Promise.all([\n host.listSceneFamilyTracks(fromSceneId),\n host.listSceneFamilyTracks(toSceneId),\n host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),\n host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),\n host.getSceneData\n ? host.getSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY)\n : Promise.resolve(null),\n ]);\n const draft = asTransitionDesignerDraft(draftRaw);\n const exSet = new Set(excludeRef.current ?? []);\n const originIds = origin.filter((t) => !exSet.has(t.dbId)).map((t) => t.dbId);\n const targetIds = target.filter((t) => !exSet.has(t.dbId)).map((t) => t.dbId);\n const [po, pt] = padPair(\n reconcileSlots(draft?.originOrder, originIds),\n reconcileSlots(draft?.targetOrder, targetIds),\n );\n setOriginSlots(po);\n setTargetSlots(pt);\n setRowEffects(draft?.rowEffects ?? {});\n setFromName(fName);\n setToName(tName);\n setLoad({ status: 'ready', origin, target });\n } catch (err: unknown) {\n setLoad({\n status: 'error',\n message: err instanceof Error ? err.message : 'Failed to load tracks.',\n });\n }\n }, [host, fromSceneId, toSceneId, transitionSceneId]);\n\n // Fetch on mount (the panel mounts this only when the Transition view is active)\n // and whenever the bridged scenes change.\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n // Keep the columns in sync with the pool: drop sources consumed by a just-created\n // crossfade/fade (excludeSourceDbIds grew) and append any newly-added tracks.\n useEffect(() => {\n if (load.status !== 'ready') return;\n const [po, pt] = padPair(\n reconcileSlots(originSlotsRef.current, originPool.map((t) => t.dbId)),\n reconcileSlots(targetSlotsRef.current, targetPool.map((t) => t.dbId)),\n );\n if (!slotsEqual(po, originSlotsRef.current)) setOriginSlots(po);\n if (!slotsEqual(pt, targetSlotsRef.current)) setTargetSlots(pt);\n }, [originPool, targetPool, load.status]);\n\n // Persist the trimmed draft; re-pad state so rendering stays aligned.\n const mutate = useCallback(\n (nextOrigin: (string | null)[], nextTarget: (string | null)[]): void => {\n const norm = normalizeSlots(nextOrigin, nextTarget);\n const [po, pt] = padPair(norm.originOrder, norm.targetOrder);\n setOriginSlots(po);\n setTargetSlots(pt);\n if (host.setSceneData) {\n host.setSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY, { ...norm, rowEffects: rowEffectsRef.current }).catch(() => {});\n }\n },\n [host, transitionSceneId],\n );\n\n // Change a one-sided row's audio effect; persist alongside the slot draft.\n const setRowEffect = useCallback(\n (sourceDbId: string, effect: AudioEffect): void => {\n setRowEffects((prev) => {\n const next = { ...prev, [sourceDbId]: effect };\n if (host.setSceneData) {\n const norm = normalizeSlots(originSlotsRef.current, targetSlotsRef.current);\n host.setSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY, { ...norm, rowEffects: next }).catch(() => {});\n }\n return next;\n });\n },\n [host, transitionSceneId],\n );\n\n const insertGapAbove = useCallback(\n (col: Column, index: number): void => {\n const slots = col === 'origin' ? originSlots : targetSlots;\n const next = [...slots.slice(0, index), null, ...slots.slice(index)];\n if (col === 'origin') mutate(next, targetSlots);\n else mutate(originSlots, next);\n },\n [originSlots, targetSlots, mutate],\n );\n\n const removeGap = useCallback(\n (col: Column, index: number): void => {\n const slots = col === 'origin' ? originSlots : targetSlots;\n const next = slots.filter((_, i) => i !== index);\n if (col === 'origin') mutate(next, targetSlots);\n else mutate(originSlots, next);\n },\n [originSlots, targetSlots, mutate],\n );\n\n const handleDrop = useCallback(\n (col: Column, to: number): void => {\n const from = dragRef.current;\n dragRef.current = null;\n setDragging(null);\n setDragOver(null);\n if (!from || from.col !== col || from.index === to) return;\n if (col === 'origin') mutate(moveItem(originSlots, from.index, to), targetSlots);\n else mutate(originSlots, moveItem(targetSlots, from.index, to));\n },\n [originSlots, targetSlots, mutate],\n );\n\n const rows = useMemo(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);\n // Source dbIds with a create in flight — their cells lock (no drag / gap edits).\n const creatingDbIds = useMemo(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);\n const eligibleCount = useMemo(\n () => rows.filter((r) => { const k = rowKey(r); return k !== null && !creatingKeys.has(k); }).length,\n [rows, creatingKeys],\n );\n\n // Create ONE row. Concurrency-safe: keyed by source dbIds; reuses the panel's\n // existing crossfade/fade orchestration. Reads latest maps/keys via refs.\n const createRow = useCallback(\n async (row: DesignerRowSlots): Promise<void> => {\n const key = rowKey(row);\n if (!key || !row.type || creatingKeysRef.current.has(key)) return;\n setCreatingKeys((prev) => new Set(prev).add(key));\n setRowErrors((prev) => {\n if (!(key in prev)) return prev;\n const next = { ...prev };\n delete next[key];\n return next;\n });\n try {\n if (row.type === 'crossfade') {\n const o = row.originId ? originByIdRef.current.get(row.originId) : undefined;\n const t = row.targetId ? targetByIdRef.current.get(row.targetId) : undefined;\n if (!o || !t) throw new Error('Track is no longer available — refresh and retry.');\n await onCreateCrossfade(\n { dbId: o.dbId, name: o.name, role: o.role },\n { dbId: t.dbId, name: t.name, role: t.role },\n );\n } else if (row.type === 'fade-out') {\n const o = row.originId ? originByIdRef.current.get(row.originId) : undefined;\n if (!o) throw new Error('Track is no longer available — refresh and retry.');\n const eff = rowEffectsRef.current[o.dbId] ?? 'fade';\n if (eff !== 'fade' && onCreateAudioTransition) {\n await onCreateAudioTransition({ dbId: o.dbId, name: o.name, role: o.role }, 'out', eff);\n } else {\n await onCreateFade({ dbId: o.dbId, name: o.name, role: o.role }, 'out', defaultFadeGesture(o.role));\n }\n } else {\n const t = row.targetId ? targetByIdRef.current.get(row.targetId) : undefined;\n if (!t) throw new Error('Track is no longer available — refresh and retry.');\n const eff = rowEffectsRef.current[t.dbId] ?? 'fade';\n if (eff !== 'fade' && onCreateAudioTransition) {\n await onCreateAudioTransition({ dbId: t.dbId, name: t.name, role: t.role }, 'in', eff);\n } else {\n await onCreateFade({ dbId: t.dbId, name: t.name, role: t.role }, 'in', defaultFadeGesture(t.role));\n }\n }\n } catch (err: unknown) {\n setRowErrors((prev) => ({\n ...prev,\n [key]: err instanceof Error ? err.message : 'Failed to create transition.',\n }));\n } finally {\n setCreatingKeys((prev) => {\n const next = new Set(prev);\n next.delete(key);\n return next;\n });\n }\n },\n [onCreateCrossfade, onCreateFade, onCreateAudioTransition],\n );\n\n // Fire every eligible row through a bounded concurrency pool.\n const createAll = useCallback(async (): Promise<void> => {\n const eligible = buildRowSlots(originSlotsRef.current, targetSlotsRef.current).filter((r) => {\n const k = rowKey(r);\n return k !== null && !creatingKeysRef.current.has(k);\n });\n if (eligible.length === 0) return;\n let cursor = 0;\n const worker = async (): Promise<void> => {\n while (cursor < eligible.length) {\n const row = eligible[cursor];\n cursor += 1;\n await createRow(row);\n }\n };\n await Promise.all(\n Array.from({ length: Math.min(CREATE_ALL_CONCURRENCY, eligible.length) }, () => worker()),\n );\n }, [createRow]);\n\n const fromLabel = fromName ?? 'origin';\n const toLabel = toName ?? 'target';\n\n const cellDragProps = (\n col: Column,\n index: number,\n locked: boolean,\n ): {\n draggable: boolean;\n onDragStart: (e: DragEvent<HTMLElement>) => void;\n onDragEnd: () => void;\n onDragEnter: (e: DragEvent<HTMLElement>) => void;\n onDragOver: (e: DragEvent<HTMLElement>) => void;\n onDragLeave: () => void;\n onDrop: (e: DragEvent<HTMLElement>) => void;\n } => ({\n draggable: !locked,\n onDragStart: (e) => {\n if (locked) return;\n dragRef.current = { col, index };\n setDragging({ col, index });\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n try {\n e.dataTransfer.setData('text/plain', String(index));\n } catch {\n /* some environments disallow setData — drag still works */\n }\n }\n },\n onDragEnd: () => {\n dragRef.current = null;\n setDragging(null);\n setDragOver(null);\n },\n onDragEnter: (e) => {\n const d = dragRef.current;\n if (!d || d.col !== col) return;\n e.preventDefault();\n setDragOver({ col, index });\n },\n onDragOver: (e) => {\n const d = dragRef.current;\n if (!d || d.col !== col) return;\n e.preventDefault();\n if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';\n },\n onDragLeave: () => {\n setDragOver((cur) => (cur && cur.col === col && cur.index === index ? null : cur));\n },\n onDrop: (e) => {\n e.preventDefault();\n handleDrop(col, index);\n },\n });\n\n const renderCell = (col: Column, index: number, slotId: string | null): React.ReactElement => {\n const byId = col === 'origin' ? originById : targetById;\n const track = slotId ? byId.get(slotId) : undefined;\n const locked = slotId !== null && creatingDbIds.has(slotId);\n const isDragging = dragging?.col === col && dragging.index === index;\n const isDragTarget = dragOver?.col === col && dragOver.index === index && !isDragging;\n const base =\n 'group relative rounded-sm border px-2 py-1.5 text-left transition-colors select-none';\n const tone = isDragTarget\n ? 'border-sas-accent bg-sas-accent/10'\n : 'border-sas-border bg-sas-panel';\n\n if (slotId === null) {\n return (\n <div\n {...cellDragProps(col, index, false)}\n data-testid={`${testIdPrefix}-${col}-gap-${index}`}\n className={`${base} ${tone} border-dashed flex items-center justify-between ${\n isDragging ? 'opacity-40' : 'opacity-70'\n }`}\n >\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted\">— gap —</span>\n <button\n type=\"button\"\n data-testid={`${testIdPrefix}-${col}-remove-gap-${index}`}\n onClick={() => removeGap(col, index)}\n title=\"Remove gap\"\n className=\"text-[10px] text-sas-muted hover:text-sas-danger\"\n >\n ✕\n </button>\n </div>\n );\n }\n\n const primary = track ? track.prompt?.trim() || track.name : slotId;\n const meta = track ? [track.role, shortId(track.dbId)].filter(Boolean).join(' · ') : 'missing';\n return (\n <div\n {...cellDragProps(col, index, locked)}\n data-testid={`${testIdPrefix}-${col}-cell-${slotId}`}\n data-value={slotId}\n className={`${base} ${tone} ${isDragging ? 'opacity-40' : ''} ${\n locked ? 'opacity-60' : 'cursor-grab active:cursor-grabbing'\n }`}\n title={track ? track.dbId : 'Track no longer available'}\n >\n <div className=\"flex items-start gap-1\">\n <span className=\"text-sas-muted/60 text-xs leading-tight pt-0.5\" aria-hidden>\n ⠿\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-xs text-sas-text truncate\">{primary}</div>\n {meta && <div className=\"text-[10px] text-sas-muted truncate mt-0.5\">{meta}</div>}\n </div>\n <button\n type=\"button\"\n data-testid={`${testIdPrefix}-${col}-insert-gap-${index}`}\n onClick={() => insertGapAbove(col, index)}\n disabled={locked}\n title=\"Insert a gap above (make this a fade)\"\n className=\"text-[10px] text-sas-muted opacity-0 group-hover:opacity-100 hover:text-sas-accent disabled:opacity-30\"\n >\n +gap\n </button>\n </div>\n </div>\n );\n };\n\n return (\n <div className=\"space-y-2\" data-testid={`${testIdPrefix}-box`}>\n {/* Header: hint + Create all */}\n <div className=\"flex items-center justify-between gap-3 pb-1 border-b border-sas-border\">\n <p className=\"text-[11px] text-sas-muted leading-snug min-w-0\">\n <span className=\"text-sas-text\">{fromLabel}</span> →{' '}\n <span className=\"text-sas-text\">{toLabel}</span>\n {familyLabel ? ` · ${familyLabel}` : ''} · line up a track on each side to crossfade;\n leave one blank (or insert a gap) to fade.\n </p>\n <div className=\"flex items-center gap-2 shrink-0\">\n {creatingKeys.size > 0 && (\n <span className=\"text-[10px] text-sas-accent whitespace-nowrap\" data-testid={`${testIdPrefix}-creating-count`}>\n {creatingKeys.size} creating…\n </span>\n )}\n <button\n type=\"button\"\n data-testid={`${testIdPrefix}-create-all`}\n onClick={createAll}\n disabled={eligibleCount === 0}\n title=\"Create every staged transition at once (runs several concurrently)\"\n className={`px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors whitespace-nowrap ${\n eligibleCount > 0\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n >\n Create all{eligibleCount > 0 ? ` (${eligibleCount})` : ''}\n </button>\n </div>\n </div>\n\n {/* Column headings */}\n <div className=\"grid grid-cols-[1fr_auto_1fr] gap-2\">\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted truncate\">\n Origin ({fromLabel})\n </span>\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted text-center px-2\">\n Transition\n </span>\n <span className=\"text-[10px] uppercase tracking-wide text-sas-muted truncate text-right\">\n Target ({toLabel})\n </span>\n </div>\n\n {/* Body */}\n {load.status === 'loading' && (\n <div className=\"text-xs text-sas-muted py-6 text-center\">Loading tracks…</div>\n )}\n {load.status === 'error' && (\n <div className=\"text-xs text-sas-danger py-6 text-center\" data-testid={`${testIdPrefix}-error`}>\n {load.message}\n </div>\n )}\n {load.status === 'ready' &&\n (rows.length === 0 ? (\n <div className=\"text-xs text-sas-muted py-6 text-center\" data-testid={`${testIdPrefix}-empty`}>\n No tracks to arrange in this panel for either scene. Add tracks to {fromLabel} or {toLabel}{' '}\n first (or free one by deleting an existing crossfade/fade).\n </div>\n ) : (\n <div className=\"space-y-2\">\n {rows.map((row, i) => {\n const key = rowKey(row);\n const isCreatingThis = key !== null && creatingKeys.has(key);\n const errMsg = key !== null ? rowErrors[key] : undefined;\n return (\n <div\n key={i}\n data-testid={`${testIdPrefix}-row-${i}`}\n className=\"grid grid-cols-[1fr_auto_1fr] gap-2 items-center\"\n >\n {renderCell('origin', i, row.originId)}\n\n {/* Center: type + create / progress */}\n <div className=\"w-[160px] flex flex-col items-center gap-1\">\n {!row.type ? (\n <span className=\"text-[10px] text-sas-muted/50\">—</span>\n ) : row.type === 'crossfade' ? (\n <span\n data-testid={`${testIdPrefix}-type-${i}`}\n className=\"text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-accent/50 text-sas-accent\"\n >\n {TYPE_LABEL[row.type]}\n </span>\n ) : audioEffectsEnabled ? (\n <div className=\"flex items-center gap-1\" data-testid={`${testIdPrefix}-type-${i}`}>\n <select\n data-testid={`${testIdPrefix}-effect-${i}`}\n value={rowEffects[(row.originId ?? row.targetId) as string] ?? 'fade'}\n onChange={(e) => {\n const id = row.originId ?? row.targetId;\n if (id) setRowEffect(id, e.target.value as AudioEffect);\n }}\n className=\"text-[10px] bg-sas-panel border border-sas-border rounded-sm px-1 py-0.5 text-sas-text\"\n >\n {AUDIO_EFFECTS.map((eff) => (\n <option key={eff} value={eff}>\n {AUDIO_EFFECT_LABEL[eff]}\n </option>\n ))}\n </select>\n <span className=\"text-[9px] text-sas-muted\">{row.type === 'fade-out' ? 'out' : 'in'}</span>\n </div>\n ) : (\n <span\n data-testid={`${testIdPrefix}-type-${i}`}\n className=\"text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-border text-sas-muted\"\n >\n {TYPE_LABEL[row.type]}\n </span>\n )}\n {isCreatingThis ? (\n <div className=\"w-full\">\n <SorceryProgressBar\n isLoading\n heightClass=\"h-5\"\n statusText=\"CREATING\"\n estimatedDurationMs={\n row.type === 'crossfade' ? CROSSFADE_ESTIMATE_MS : FADE_ESTIMATE_MS\n }\n />\n </div>\n ) : (\n <button\n type=\"button\"\n data-testid={`${testIdPrefix}-create-${i}`}\n onClick={() => createRow(row)}\n disabled={!row.type}\n className={`w-full px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors ${\n row.type\n ? 'bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg'\n : 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n }`}\n >\n Create\n </button>\n )}\n {errMsg && (\n <span\n data-testid={`${testIdPrefix}-row-error-${i}`}\n className=\"text-[10px] text-sas-danger text-center leading-tight\"\n >\n {errMsg}\n </span>\n )}\n </div>\n\n {renderCell('target', i, row.targetId)}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n\nexport default TransitionDesigner;\n","/**\n * useTrackReorder — shared drag-and-drop row reordering for generator panels.\n *\n * One hook drives the whole flow so every panel (drums / instruments / synths)\n * behaves identically: HTML5 drag mechanics (zero dependencies), an optimistic\n * local reorder, persistence via {@link PluginHost.reorderTracks}, and an\n * automatic revert if persistence fails. Panels supply their track array + its\n * setter and spread the returned props onto each {@link TrackRow}; the grip\n * handle and drop-target visuals live in TrackRow.\n *\n * Persisted ids should be STABLE (use `getId: t => t.handle.dbId`) — engine\n * track ids are not stable across project reopen.\n */\nimport { useCallback, useRef, useState } from 'react';\nimport type { DragEvent, Dispatch, SetStateAction } from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\n\n/**\n * Props the reorder machinery hands to a single row. Spread `handleProps` on the\n * drag grip and `rowProps` on the row's outer element; `isDragging` /\n * `isDragTarget` drive the visual state.\n */\nexport interface TrackRowDragProps {\n handleProps: {\n draggable: true;\n onDragStart: (e: DragEvent<HTMLElement>) => void;\n onDragEnd: (e: DragEvent<HTMLElement>) => void;\n };\n rowProps: {\n onDragEnter: (e: DragEvent<HTMLElement>) => void;\n onDragOver: (e: DragEvent<HTMLElement>) => void;\n onDragLeave: (e: DragEvent<HTMLElement>) => void;\n onDrop: (e: DragEvent<HTMLElement>) => void;\n };\n /** This row is the one currently being dragged (dim it). */\n isDragging: boolean;\n /** This row is the current drop target (show an insertion accent). */\n isDragTarget: boolean;\n}\n\n/**\n * Pure helper: return a NEW array with the item at `from` moved to `to`.\n * Out-of-range or no-op moves return a shallow copy unchanged. Exported for\n * unit testing the index math without a DOM.\n */\nexport function moveItem<T>(arr: readonly T[], from: number, to: number): T[] {\n const next = arr.slice();\n if (\n from === to ||\n from < 0 ||\n to < 0 ||\n from >= next.length ||\n to >= next.length\n ) {\n return next;\n }\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved);\n return next;\n}\n\nexport interface UseTrackReorderOptions<T> {\n /** Host (only {@link PluginHost.reorderTracks} is used). */\n host: Pick<PluginHost, 'reorderTracks'>;\n /** The panel's current track array (also the render order). */\n items: T[];\n /** The panel's state setter for `items` (used for optimistic update + revert). */\n setItems: Dispatch<SetStateAction<T[]>>;\n /** Stable id for persistence — use the track's dbId, not its engine id. */\n getId: (item: T) => string;\n /** Called if persistence fails, after the optimistic update is reverted. */\n onError?: (err: unknown) => void;\n}\n\nexport interface UseTrackReorderResult {\n /** Build the drag props for the row at `index`; spread onto its TrackRow. */\n dragPropsFor: (index: number) => TrackRowDragProps;\n /** Index of the row being dragged, or null. */\n draggingIndex: number | null;\n /** Index of the current drop-target row, or null. */\n dragOverIndex: number | null;\n}\n\n/**\n * Drag-and-drop reordering for a panel's track list. Dropping a row onto another\n * row moves it into that row's position (everything between shifts); the top and\n * bottom are reachable by dropping on the first/last row.\n */\nexport function useTrackReorder<T>({\n host,\n items,\n setItems,\n getId,\n onError,\n}: UseTrackReorderOptions<T>): UseTrackReorderResult {\n const [draggingIndex, setDraggingIndex] = useState<number | null>(null);\n const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);\n // Source index for the in-flight drag; a ref avoids stale-closure reads in the\n // drop handler. itemsRef keeps the freshest array without re-creating handlers.\n const fromRef = useRef<number | null>(null);\n const itemsRef = useRef(items);\n itemsRef.current = items;\n\n const dragPropsFor = useCallback(\n (index: number): TrackRowDragProps => ({\n handleProps: {\n draggable: true,\n onDragStart: (e) => {\n fromRef.current = index;\n setDraggingIndex(index);\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n // Required by Firefox to start a drag; the value itself is unused.\n try {\n e.dataTransfer.setData('text/plain', String(index));\n } catch {\n /* some environments disallow setData — drag still works */\n }\n }\n },\n onDragEnd: () => {\n fromRef.current = null;\n setDraggingIndex(null);\n setDragOverIndex(null);\n },\n },\n rowProps: {\n onDragEnter: (e) => {\n if (fromRef.current === null) return;\n e.preventDefault();\n setDragOverIndex(index);\n },\n onDragOver: (e) => {\n if (fromRef.current === null) return;\n e.preventDefault(); // allow drop\n if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';\n setDragOverIndex((cur) => (cur === index ? cur : index));\n },\n onDragLeave: () => {\n setDragOverIndex((cur) => (cur === index ? null : cur));\n },\n onDrop: (e) => {\n e.preventDefault();\n const from = fromRef.current;\n fromRef.current = null;\n setDraggingIndex(null);\n setDragOverIndex(null);\n if (from === null || from === index) return;\n\n const prev = itemsRef.current;\n const next = moveItem(prev, from, index);\n setItems(next);\n const ids = next.map(getId);\n Promise.resolve(host.reorderTracks(ids)).catch((err) => {\n // Persistence failed — roll back to the pre-drag order.\n setItems(prev);\n onError?.(err);\n });\n },\n },\n isDragging: draggingIndex === index,\n isDragTarget: dragOverIndex === index && draggingIndex !== index,\n }),\n [host, setItems, getId, onError, draggingIndex, dragOverIndex]\n );\n\n return { dragPropsFor, draggingIndex, dragOverIndex };\n}\n","/**\n * Transition Designer — pure helpers for the per-panel transition staging board.\n *\n * The designer is the multi-row, persistent successor to CrossfadeModal/FadeModal:\n * it lays out ONE panel-family's origin (scene A) and target (scene B) source\n * tracks as two index-aligned, drag-reorderable columns. Row i pairs the origin\n * slot at index i with the target slot at index i, and the pairing DERIVES the\n * transition type:\n * - both filled → crossfade (morph A→B)\n * - origin filled, target blank → fade out (the track leaves)\n * - target filled, origin blank → fade in (the track enters)\n * A slot may be a source-track dbId or `null` (a blank spacer). Blanks let the\n * user open a gap so a mid-list track becomes a fade instead of crossfading with\n * whatever happens to sit opposite it (the CSV-style layout).\n *\n * The \"available pool\" per column is the scene's family tracks MINUS the sources\n * already consumed by a committed crossfade/fade (excludeSourceDbIds). Creating a\n * row reuses the panel's existing crossfade/fade orchestration; deleting the\n * committed crossfade/fade on the deck returns its source to the pool.\n *\n * This module owns only the shape + the pure slot/row math so it can be unit\n * tested without a DOM and can't drift across the three panels. The component\n * (TransitionDesigner.tsx) owns the overlay, drag wiring, and persistence.\n *\n * @since SDK 2.29.0\n */\n\n/**\n * Persisted per-transition-scene draft: the two columns' slot orders. A slot is\n * a source-track dbId, or `null` for a blank spacer (an intentional gap). Stored\n * in the transition scene's plugin_data under {@link TRANSITION_DESIGNER_DRAFT_KEY};\n * because plugin_data is scoped by (plugin_id, sceneId), each panel family keeps\n * its own draft automatically.\n */\nexport interface TransitionDesignerDraft {\n /** Origin (scene A) column order — dbIds or `null` blanks. */\n originOrder: (string | null)[];\n /** Target (scene B) column order — dbIds or `null` blanks. */\n targetOrder: (string | null)[];\n /** Per one-sided-row audio effect, keyed by the source dbId. @since SDK 2.32.0 */\n rowEffects?: Record<string, AudioEffect>;\n}\n\n/** scene-data key (under the transition scene) holding the staged draft. */\nexport const TRANSITION_DESIGNER_DRAFT_KEY = 'transitionDesigner:draft';\n\n/** The transition a single aligned row represents (derived from its two slots). */\nexport type TransitionRowType = 'crossfade' | 'fade-out' | 'fade-in';\n\n/**\n * Audio-only transition gesture for a ONE-SIDED (orphan) loop. `'fade'` is the\n * default level ramp (works for any family); `stutter`/`chopped`/`delay` are\n * audio panels only, surfaced via the row's effect selector when the panel\n * passes `onCreateAudioTransition`. @since SDK 2.32.0\n */\nexport type AudioEffect = 'fade' | 'stutter' | 'chopped' | 'delay';\nexport const AUDIO_EFFECTS: readonly AudioEffect[] = ['fade', 'stutter', 'chopped', 'delay'];\nexport const AUDIO_EFFECT_LABEL: Record<AudioEffect, string> = {\n fade: 'Fade',\n stutter: 'Stutter',\n chopped: 'Chopped',\n delay: 'Delay',\n};\nexport function asAudioEffect(v: unknown): AudioEffect | null {\n return v === 'fade' || v === 'stutter' || v === 'chopped' || v === 'delay' ? v : null;\n}\n\n/** Derive a row's transition type from which slots are filled. `null` = empty row. */\nexport function rowType(hasOrigin: boolean, hasTarget: boolean): TransitionRowType | null {\n if (hasOrigin && hasTarget) return 'crossfade';\n if (hasOrigin) return 'fade-out';\n if (hasTarget) return 'fade-in';\n return null;\n}\n\n/** Narrow an unknown scene-data value to a TransitionDesignerDraft (defensive). */\nexport function asTransitionDesignerDraft(val: unknown): TransitionDesignerDraft | null {\n if (!val || typeof val !== 'object') return null;\n const d = val as Partial<TransitionDesignerDraft>;\n const clean = (a: unknown): (string | null)[] =>\n Array.isArray(a)\n ? (a.filter((x) => x === null || typeof x === 'string') as (string | null)[])\n : [];\n const cleanEffects = (e: unknown): Record<string, AudioEffect> => {\n const out: Record<string, AudioEffect> = {};\n if (e && typeof e === 'object') {\n for (const [k, v] of Object.entries(e as Record<string, unknown>)) {\n const eff = asAudioEffect(v);\n if (eff) out[k] = eff;\n }\n }\n return out;\n };\n return {\n originOrder: clean(d.originOrder),\n targetOrder: clean(d.targetOrder),\n rowEffects: cleanEffects(d.rowEffects),\n };\n}\n\n/**\n * Reconcile a saved slot order against the current pool of available source ids:\n * - keep saved ids still in the pool (in their saved position),\n * - keep `null` blanks,\n * - drop ids no longer in the pool (consumed by a created crossfade/fade, or the\n * source track was deleted) and any duplicates,\n * - append pool ids missing from the saved order (newly added tracks) at the end.\n *\n * Pure; exported for unit testing.\n */\nexport function reconcileSlots(\n saved: readonly (string | null)[] | undefined,\n poolIds: readonly string[],\n): (string | null)[] {\n const pool = new Set(poolIds);\n const seen = new Set<string>();\n const out: (string | null)[] = [];\n for (const slot of saved ?? []) {\n if (slot === null) {\n out.push(null);\n continue;\n }\n if (pool.has(slot) && !seen.has(slot)) {\n out.push(slot);\n seen.add(slot);\n }\n }\n for (const id of poolIds) {\n if (!seen.has(id)) {\n out.push(id);\n seen.add(id);\n }\n }\n return out;\n}\n\n/** One assembled designer row: the two source dbIds (or `null`) + derived type. */\nexport interface DesignerRowSlots {\n originId: string | null;\n targetId: string | null;\n type: TransitionRowType | null;\n}\n\n/** Zip two slot columns into index-aligned rows with their derived type. */\nexport function buildRowSlots(\n originSlots: readonly (string | null)[],\n targetSlots: readonly (string | null)[],\n): DesignerRowSlots[] {\n const n = Math.max(originSlots.length, targetSlots.length);\n const rows: DesignerRowSlots[] = [];\n for (let i = 0; i < n; i++) {\n const originId = originSlots[i] ?? null;\n const targetId = targetSlots[i] ?? null;\n rows.push({ originId, targetId, type: rowType(originId !== null, targetId !== null) });\n }\n return rows;\n}\n\n/**\n * Tidy the columns for persistence: drop rows where BOTH slots are blank (a\n * meaningless gap) and trim trailing blanks per column. Returns clean columns\n * suitable for {@link TransitionDesignerDraft}.\n */\nexport function normalizeSlots(\n originSlots: readonly (string | null)[],\n targetSlots: readonly (string | null)[],\n): TransitionDesignerDraft {\n const rows = buildRowSlots(originSlots, targetSlots).filter(\n (r) => r.originId !== null || r.targetId !== null,\n );\n const trimTrailing = (a: (string | null)[]): (string | null)[] => {\n let end = a.length;\n while (end > 0 && a[end - 1] === null) end--;\n return a.slice(0, end);\n };\n return {\n originOrder: trimTrailing(rows.map((r) => r.originId)),\n targetOrder: trimTrailing(rows.map((r) => r.targetId)),\n };\n}\n\n/** Pad a column with trailing `null`s up to `n` (so both columns render aligned). */\nexport function padSlots(slots: readonly (string | null)[], n: number): (string | null)[] {\n if (slots.length >= n) return slots.slice();\n return [...slots, ...new Array<null>(n - slots.length).fill(null)];\n}\n\n/** Pad both columns to equal length (= the longer column). */\nexport function padPair(\n originSlots: readonly (string | null)[],\n targetSlots: readonly (string | null)[],\n): [(string | null)[], (string | null)[]] {\n const n = Math.max(originSlots.length, targetSlots.length);\n return [padSlots(originSlots, n), padSlots(targetSlots, n)];\n}\n\n/** Shallow element-wise equality for two slot columns. */\nexport function slotsEqual(a: readonly (string | null)[], b: readonly (string | null)[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Stable key identifying an in-flight create, derived from the row's SOURCE dbIds\n * (not its row index) — so reordering or inserting a gap mid-create still maps the\n * progress indicator to the right row, and concurrent creates never collide. dbIds\n * are UUIDs, so `|` is a safe origin/target separator. `null` for an empty row.\n *\n * @since SDK 2.30.0\n */\nexport function rowKey(row: DesignerRowSlots): string | null {\n if (row.type === 'crossfade') return `xf:${row.originId}|${row.targetId}`;\n if (row.type === 'fade-out') return `fo:${row.originId}`;\n if (row.type === 'fade-in') return `fi:${row.targetId}`;\n return null;\n}\n\n/**\n * The set of source dbIds referenced by a collection of in-flight {@link rowKey}s —\n * used to lock those cells (no drag / gap edits) while their create runs.\n *\n * @since SDK 2.30.0\n */\nexport function dbIdsFromKeys(keys: Iterable<string>): Set<string> {\n const out = new Set<string>();\n for (const k of keys) {\n const body = k.slice(3); // strip the 3-char \"xf:\" / \"fo:\" / \"fi:\" tag\n if (k.startsWith('xf:')) {\n const sep = body.indexOf('|');\n out.add(body.slice(0, sep));\n out.add(body.slice(sep + 1));\n } else {\n out.add(body);\n }\n }\n return out;\n}\n","/**\n * DownloadPackButton — versioned-pack download trigger (SDK component).\n *\n * Parameterized by `packId`; drives the download through the host\n * (`host.startSamplePackDownload` / `host.onSamplePackProgress`) so plugins\n * never reach into the app's IPC (`window.electronAPI`). Two display variants:\n * - 'compact' (default) — small uppercase button for panel headers\n * - 'large' — bigger CTA used inside SamplePackCTACard\n *\n * @since SDK 2.8.0 (moved from the app and refactored onto PluginHost).\n */\n\nimport React, { useCallback, useEffect, useState } from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\n\nexport type DownloadPackButtonVariant = 'compact' | 'large';\n\ntype PackDownloadStatus =\n | 'idle'\n | 'downloading'\n | 'verifying'\n | 'extracting'\n | 'installing'\n | 'complete'\n | 'error';\n\nexport interface DownloadPackButtonProps {\n /** Host the plugin received; drives the download + progress. */\n host: PluginHost;\n packId: string;\n /** Pack display name, e.g. 'Drum Sample Library'. Used in tooltips/labels. */\n displayName: string;\n /** Bundle size in bytes (shown in the large-variant label). */\n sizeBytes?: number;\n variant?: DownloadPackButtonVariant;\n /** Called once after the install completes (status === 'complete'). */\n onDownloadComplete?: () => void;\n}\n\n// Base-1024 (GiB/MiB) to match the host's own SamplePackDownloader formatter and\n// the `_pack-version.json` / sample-packs.ts size comments (e.g. a 28.5e9-byte\n// instrument bundle reads as \"26.6 GB\", not the decimal \"28.5 GB\").\nfunction formatSize(bytes?: number): string {\n if (!bytes || bytes <= 0) return '';\n const gb = bytes / 1024 ** 3;\n if (gb >= 1) return `${gb.toFixed(1)} GB`;\n const mb = bytes / 1024 ** 2;\n return `${Math.round(mb)} MB`;\n}\n\nexport const DownloadPackButton: React.FC<DownloadPackButtonProps> = ({\n host,\n packId,\n displayName,\n sizeBytes,\n variant = 'compact',\n onDownloadComplete,\n}) => {\n const [status, setStatus] = useState<PackDownloadStatus>('idle');\n const [progress, setProgress] = useState(0);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n useEffect(() => {\n const unsub = host.onSamplePackProgress(packId, (p) => {\n setStatus(p.status as PackDownloadStatus);\n setProgress(p.progress);\n if (p.status === 'error') {\n setErrorMessage(p.message || 'Download failed');\n } else if (p.status === 'complete') {\n setErrorMessage(null);\n setTimeout(() => onDownloadComplete?.(), 250);\n } else {\n setErrorMessage(null);\n }\n });\n return unsub;\n }, [host, packId, onDownloadComplete]);\n\n const handleClick = useCallback(async (): Promise<void> => {\n if (status !== 'idle' && status !== 'error') return;\n try {\n setStatus('downloading');\n setProgress(0);\n setErrorMessage(null);\n const result = await host.startSamplePackDownload(packId);\n if (!result.success) {\n setStatus('error');\n setErrorMessage(result.error || 'Download failed');\n }\n } catch (err) {\n console.error('[DownloadPackButton] start failed:', err);\n setStatus('error');\n setErrorMessage(err instanceof Error ? err.message : String(err));\n }\n }, [host, packId, status]);\n\n const isWorking =\n status === 'downloading' ||\n status === 'verifying' ||\n status === 'extracting' ||\n status === 'installing';\n const isDisabled = isWorking || status === 'complete';\n\n const buttonLabel = (() => {\n switch (status) {\n case 'downloading':\n return `${progress}%`;\n case 'verifying':\n return 'Verifying...';\n case 'extracting':\n return 'Extracting...';\n case 'installing':\n return 'Installing...';\n case 'complete':\n return 'Done!';\n case 'error':\n return 'Retry';\n default:\n return variant === 'large'\n ? `Download ${displayName}${sizeBytes ? ` (${formatSize(sizeBytes)})` : ''}`\n : 'Download';\n }\n })();\n\n const tooltip = (() => {\n if (status === 'error') return errorMessage || 'Download failed. Click to retry.';\n if (isWorking) return `${buttonLabel} — ${displayName}`;\n if (status === 'complete') return 'Installation complete';\n return `Download ${displayName}${sizeBytes ? ` (${formatSize(sizeBytes)})` : ''}`;\n })();\n\n const baseClasses =\n variant === 'large'\n ? 'px-4 py-2 text-sm font-medium rounded border transition-colors'\n : 'px-2 py-0.5 text-[10px] uppercase tracking-wide rounded-sm border transition-colors';\n\n let className: string;\n if (status === 'error') {\n className = `${baseClasses} text-red-400 border-red-400/50 hover:text-red-300 hover:border-red-300`;\n } else if (status === 'complete') {\n className = `${baseClasses} text-green-400 border-green-400/50`;\n } else if (isDisabled) {\n className = `${baseClasses} text-sas-accent border-sas-accent/50 cursor-wait`;\n } else {\n className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;\n }\n\n return (\n <div>\n <button\n data-testid={`download-pack-button-${packId}`}\n onClick={handleClick}\n disabled={isDisabled}\n className={className}\n title={tooltip}\n >\n {buttonLabel}\n </button>\n {variant === 'large' && status === 'error' && errorMessage && (\n <div className=\"text-xs text-sas-danger mt-2\" data-testid={`download-pack-error-${packId}`}>\n {errorMessage}\n </div>\n )}\n </div>\n );\n};\n\nexport default DownloadPackButton;\n","/**\n * SamplePackCTACard — empty-state card a generator panel renders when its\n * sample pack is missing OR a newer version is available. Wraps\n * DownloadPackButton in a centered card. The completion callback should\n * re-fetch pack status on the parent so the card unmounts and the normal panel\n * UI takes over.\n *\n * @since SDK 2.8.0 (moved from the app; download driven through PluginHost).\n */\n\nimport React from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\nimport { DownloadPackButton } from './DownloadPackButton';\n\nexport type SamplePackCTACardStatus = 'missing' | 'stale' | 'checking';\n\n/** Minimal pack info the card needs. A PackConfig is structurally compatible. */\nexport interface SamplePackCardInfo {\n packId: string;\n displayName: string;\n description: string;\n sizeBytes?: number;\n}\n\nexport interface SamplePackCTACardProps {\n /** Host the plugin received; drives the download. */\n host: PluginHost;\n pack: SamplePackCardInfo;\n status: SamplePackCTACardStatus;\n onDownloadComplete?: () => void;\n}\n\nexport const SamplePackCTACard: React.FC<SamplePackCTACardProps> = ({\n host,\n pack,\n status,\n onDownloadComplete,\n}) => {\n if (status === 'checking') {\n return (\n <div\n data-testid={`sample-pack-cta-checking-${pack.packId}`}\n className=\"flex items-center justify-center py-16 text-sas-muted text-sm\"\n >\n Checking sample library...\n </div>\n );\n }\n\n const headline =\n status === 'stale'\n ? `${pack.displayName} update available`\n : `${pack.displayName} not installed`;\n\n const sublabel =\n status === 'stale'\n ? `A newer version is available for download.`\n : pack.description;\n\n return (\n <div\n data-testid={`sample-pack-cta-${pack.packId}`}\n className=\"flex flex-col items-center justify-center py-12 px-6 text-center\"\n >\n <div className=\"text-sm uppercase tracking-wide text-sas-muted mb-2\">\n {status === 'stale' ? 'Update available' : 'Sample library not installed'}\n </div>\n <div className=\"text-base text-sas-text mb-1\">{headline}</div>\n <div className=\"text-xs text-sas-muted mb-6 max-w-md\">{sublabel}</div>\n <DownloadPackButton\n host={host}\n packId={pack.packId}\n displayName={pack.displayName}\n sizeBytes={pack.sizeBytes}\n variant=\"large\"\n onDownloadComplete={onDownloadComplete}\n />\n </div>\n );\n};\n\nexport default SamplePackCTACard;\n","/**\n * WaveformView — small canvas waveform for an audio file on disk.\n *\n * Reads bytes via `host.getAudioFileBytes`, decodes via\n * `AudioContext.decodeAudioData`, computes peaks, and renders to a\n * canvas. Suitable for take rows, sample previews, or any place a\n * decorative ~40px waveform makes sense.\n *\n * The component is self-contained: it owns the AudioContext and the\n * peak buffer, decodes once per `filePath` change, and tears down on\n * unmount. Failures (file missing, decode error) render as a silent\n * blank canvas — the caller can decide how to surface errors.\n */\n\nimport React, { useEffect, useRef, useState } from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\nimport { computePeaks, drawWaveform, type WaveformPeaks } from './waveform';\n\nexport interface WaveformViewProps {\n host: PluginHost;\n filePath: string;\n /** Number of bins to compute. Default 256 — plenty for ~40px tall rows. */\n bins?: number;\n /** Tailwind / inline className for sizing. Default: w-full h-10. */\n className?: string;\n /** Override the bar fill style (e.g., to match a track color). */\n fillStyle?: string;\n /**\n * If set, the bin range spans `targetSamples` instead of the file's\n * actual length. Bins beyond the audio render as flat silence — used\n * to align a partial recording inside a full-loop-width canvas so\n * every take row has the same time scale.\n */\n targetSamples?: number;\n}\n\nexport const WaveformView: React.FC<WaveformViewProps> = ({\n host,\n filePath,\n bins = 256,\n className,\n fillStyle,\n targetSamples,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [peaks, setPeaks] = useState<WaveformPeaks | null>(null);\n\n // Decode + compute peaks whenever the file changes.\n useEffect(() => {\n let cancelled = false;\n let audioContext: AudioContext | null = null;\n\n (async () => {\n try {\n const bytes = await host.getAudioFileBytes(filePath);\n if (cancelled) return;\n\n // OfflineAudioContext would be cheaper but its constructor needs\n // sampleRate/length up front — we don't know them until decode.\n const ContextCtor: typeof AudioContext =\n (window as unknown as { AudioContext?: typeof AudioContext }).AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext!;\n audioContext = new ContextCtor();\n\n // decodeAudioData mutates / detaches the buffer in some impls,\n // so pass a copy.\n const audioBuffer = await audioContext.decodeAudioData(bytes.slice(0));\n if (cancelled) return;\n\n const computed = computePeaks(audioBuffer, bins, targetSamples);\n setPeaks(computed);\n } catch (err) {\n // Silent: the canvas stays blank. Caller can layer their own\n // error UI on top if needed.\n console.warn('[WaveformView] failed to decode', filePath, err);\n } finally {\n if (audioContext) {\n audioContext.close().catch(() => { /* ignore */ });\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [host, filePath, bins, targetSamples]);\n\n // Repaint whenever peaks update — including layout-driven resizes via\n // ResizeObserver so the canvas stays crisp at the current CSS width.\n useEffect(() => {\n if (!peaks) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n drawWaveform(canvas, peaks, fillStyle ? { fillStyle } : undefined);\n\n const observer = new ResizeObserver(() => {\n drawWaveform(canvas, peaks, fillStyle ? { fillStyle } : undefined);\n });\n observer.observe(canvas);\n return () => observer.disconnect();\n }, [peaks, fillStyle]);\n\n return (\n <canvas\n ref={canvasRef}\n data-testid=\"waveform-view\"\n className={className ?? 'w-full h-10'}\n />\n );\n};\n\nexport default WaveformView;\n","/**\n * Shared waveform peaks + canvas drawer.\n *\n * Originally inlined in `stems/TrimEditorDrawer.tsx`; lifted to\n * this module so the recorder plugin's per-take rows can render the\n * same compact min/max display without duplicating the math.\n *\n * Design:\n * - `computePeaks` reduces an AudioBuffer to `bins` min/max pairs (mono\n * average across channels). Output layout is interleaved\n * `[min0, max0, min1, max1, ...]` so the renderer reads pairs\n * sequentially without index arithmetic.\n * - `drawWaveform` paints one 1px vertical bar per canvas column,\n * dpr-aware so it stays crisp on retina displays.\n *\n * No host or React dependencies — pure functions are safe to use from\n * tests, web workers, or non-React renderers.\n */\n\nexport interface WaveformPeaks {\n /** Sample rate of the source file (used to convert sample → seconds). */\n sampleRate: number;\n /** Total length of the raw file in samples. */\n totalSamples: number;\n /** Min/max pairs per bin (length = bins × 2). */\n peaks: Float32Array;\n}\n\n/**\n * Reduce an AudioBuffer to `bins` min/max pairs. Mono averages across\n * channels. The output buffer is fixed-size (`bins * 2`) for fast canvas\n * traversal.\n *\n * `targetSamples` (optional) extends the bin range to a fixed sample\n * count larger than the buffer's actual length — bins falling beyond\n * the buffer get (0, 0) pairs, which renders as a flat tail. Used by\n * the recorder so a partial last chunk's waveform sits at the start of\n * a full-loop-width canvas instead of being stretched to fill.\n */\nexport function computePeaks(\n audioBuffer: AudioBuffer,\n bins: number,\n targetSamples?: number\n): WaveformPeaks {\n const { length, numberOfChannels, sampleRate } = audioBuffer;\n const channels: Float32Array[] = [];\n for (let c = 0; c < numberOfChannels; c++) {\n channels.push(audioBuffer.getChannelData(c));\n }\n const totalForBinning =\n typeof targetSamples === 'number' && targetSamples > length ? targetSamples : length;\n const samplesPerBin = Math.max(1, Math.floor(totalForBinning / bins));\n const out = new Float32Array(bins * 2);\n for (let i = 0; i < bins; i++) {\n const startIdx = i * samplesPerBin;\n const endIdx = Math.min(length, startIdx + samplesPerBin);\n if (startIdx >= length) {\n // Bin falls entirely past the audio's end — render as silence.\n out[i * 2] = 0;\n out[i * 2 + 1] = 0;\n continue;\n }\n let mn = Infinity;\n let mx = -Infinity;\n for (let j = startIdx; j < endIdx; j++) {\n let v = 0;\n for (let c = 0; c < numberOfChannels; c++) {\n v += channels[c][j];\n }\n v /= numberOfChannels;\n if (v < mn) mn = v;\n if (v > mx) mx = v;\n }\n if (!Number.isFinite(mn)) mn = 0;\n if (!Number.isFinite(mx)) mx = 0;\n out[i * 2] = mn;\n out[i * 2 + 1] = mx;\n }\n return { sampleRate, totalSamples: totalForBinning, peaks: out };\n}\n\n/**\n * Draw min/max peaks to the given canvas. Resizes the canvas backing\n * store to CSS pixels × devicePixelRatio so the result is crisp on\n * retina. Caller controls CSS sizing via the `<canvas>` element's\n * className.\n */\nexport function drawWaveform(\n canvas: HTMLCanvasElement,\n peaks: WaveformPeaks,\n options: { fillStyle?: string } = {}\n): void {\n const dpr = window.devicePixelRatio || 1;\n const cssWidth = canvas.clientWidth;\n const cssHeight = canvas.clientHeight;\n if (cssWidth === 0 || cssHeight === 0) return;\n canvas.width = Math.floor(cssWidth * dpr);\n canvas.height = Math.floor(cssHeight * dpr);\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n ctx.scale(dpr, dpr);\n ctx.clearRect(0, 0, cssWidth, cssHeight);\n ctx.fillStyle = options.fillStyle ?? 'rgba(255, 255, 255, 0.4)';\n\n const bins = peaks.peaks.length / 2;\n const mid = cssHeight / 2;\n for (let x = 0; x < cssWidth; x++) {\n const binIdx = Math.floor((x / cssWidth) * bins);\n const mn = peaks.peaks[binIdx * 2];\n const mx = peaks.peaks[binIdx * 2 + 1];\n const yTop = mid - mx * mid;\n const yBot = mid - mn * mid;\n ctx.fillRect(x, yTop, 1, Math.max(1, yBot - yTop));\n }\n}\n","/**\n * ScrollingWaveform — live waveform during recording (Phase 8.10).\n *\n * Reads the platform's `peakDb` history and renders it as a horizontal\n * bar-graph that scrolls left as new samples arrive. Two halves: top\n * band shows positive amplitude, bottom band mirrors it (matches the\n * static waveform's min/max layout in `WaveformView`).\n *\n * The data source is a function the caller supplies — typically a ref\n * to the `inputLevelDb` value from `AudioRoutingContext` polled at\n * ~30Hz. The component samples that ref via requestAnimationFrame and\n * shifts a fixed-size float ring buffer one column per frame.\n *\n * Pure presentational + animation logic; no IPC. Stops animating\n * when `active` is false (engine isn't running the audio callback).\n */\n\nimport React, { useEffect, useRef } from 'react';\n\nexport interface ScrollingWaveformProps {\n /** Function returning the latest peak in dBFS. Called per RAF. */\n getPeakDb: () => number;\n /** True while the audio callback is running; false freezes the wave. */\n active: boolean;\n /** Number of horizontal columns in the ring buffer. */\n columns?: number;\n /** Optional className for sizing. */\n className?: string;\n /** Highlight color for the wave. */\n fillStyle?: string;\n}\n\nexport const ScrollingWaveform: React.FC<ScrollingWaveformProps> = ({\n getPeakDb,\n active,\n columns = 256,\n className,\n fillStyle,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const ringRef = useRef<Float32Array>(new Float32Array(columns));\n const writeIdxRef = useRef(0);\n const rafRef = useRef<number | null>(null);\n\n // Recreate the ring buffer if `columns` changes — preserve any data\n // that fits.\n useEffect(() => {\n if (ringRef.current.length !== columns) {\n const next = new Float32Array(columns);\n const prev = ringRef.current;\n const copyLen = Math.min(prev.length, columns);\n // Copy the tail of the previous buffer into the head of the new one.\n for (let i = 0; i < copyLen; i++) {\n next[i] = prev[i];\n }\n ringRef.current = next;\n writeIdxRef.current = writeIdxRef.current % columns;\n }\n }, [columns]);\n\n useEffect(() => {\n if (!active) {\n // Freeze the wave but leave the existing buffer on screen.\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n return;\n }\n\n const tick = (): void => {\n const peakDb = getPeakDb();\n // Map dBFS → normalised amplitude [0, 1]. -60dB → 0, 0dB → 1.\n const amp =\n peakDb <= -120\n ? 0\n : Math.max(0, Math.min(1, (peakDb + 60) / 60));\n const ring = ringRef.current;\n ring[writeIdxRef.current] = amp;\n writeIdxRef.current = (writeIdxRef.current + 1) % ring.length;\n\n // Draw.\n const canvas = canvasRef.current;\n if (canvas) {\n const dpr = window.devicePixelRatio || 1;\n const cssW = canvas.clientWidth;\n const cssH = canvas.clientHeight;\n if (cssW > 0 && cssH > 0) {\n if (canvas.width !== Math.floor(cssW * dpr) || canvas.height !== Math.floor(cssH * dpr)) {\n canvas.width = Math.floor(cssW * dpr);\n canvas.height = Math.floor(cssH * dpr);\n }\n const ctx = canvas.getContext('2d');\n if (ctx) {\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, cssW, cssH);\n ctx.fillStyle = fillStyle ?? '#6af2c5';\n const mid = cssH / 2;\n const cols = ring.length;\n const colW = cssW / cols;\n // Read the ring oldest → newest so the wave scrolls left.\n const start = writeIdxRef.current; // oldest sample\n for (let x = 0; x < cols; x++) {\n const ringIdx = (start + x) % cols;\n const a = ring[ringIdx];\n const half = a * mid;\n ctx.fillRect(x * colW, mid - half, Math.max(1, colW), Math.max(1, half * 2));\n }\n }\n }\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n };\n }, [active, getPeakDb, fillStyle]);\n\n return (\n <canvas\n ref={canvasRef}\n data-testid=\"scrolling-waveform\"\n className={className ?? 'w-full h-12'}\n />\n );\n};\n\nexport default ScrollingWaveform;\n","/**\n * OffsetScrubber — manual sample-offset slider for Lyria-generated audio.\n *\n * Renders a thin horizontal track with one tick per detected beat (tall\n * tick on the downbeat) and a draggable thumb. Drag distance maps to a\n * sample offset that is applied to the audio clip via\n * `host.setAudioOffsetSamples(trackId, n)`.\n *\n * Snap behavior:\n * - Default: snap to the nearest beat in `cuePoints.beats`.\n * - Hold Shift: bypass snap (free 1-sample resolution).\n * - Click on a tick mark: jump to that beat exactly.\n *\n * The visible range is one bar (= meter beats) on each side of bar 1.\n * For a 4-bar / 4/4 clip at 44100 Hz, one bar at 120 BPM is 88_200\n * samples — so the slider covers ±88_200 samples, ~2 s either way. That\n * matches the alignment errors we observe from Lyria detection misses\n * (typically <1 beat off).\n *\n * BPM mismatch chip: shown when `cuePoints.detected_bpm` is more than\n * 1 BPM away from the project BPM, since the beat ticks won't line up\n * with the project grid in that case.\n */\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { PluginCuePoints } from '../types/plugin-sdk.types';\n\nconst SLIDER_HEIGHT_PX = 28;\nconst TICK_HEIGHT_PX = 14;\nconst DOWNBEAT_TICK_HEIGHT_PX = 22;\nconst THUMB_WIDTH_PX = 4;\n\nexport interface OffsetScrubberProps {\n /** Detected beat positions + sample rate. Slider is disabled when null. */\n cuePoints: PluginCuePoints | null;\n /** Current offset, in samples (signed). */\n offsetSamples: number;\n /** Project BPM — used to compute the visible range and the mismatch chip. */\n projectBpm: number;\n /** Beats per bar, defaults to 4. */\n meter?: number;\n /** Called on drag-end with the resolved offset (already snapped). */\n onChange: (offsetSamples: number) => void;\n /** Disable interaction (e.g., during generation / split). */\n disabled?: boolean;\n}\n\nexport function OffsetScrubber({\n cuePoints,\n offsetSamples,\n projectBpm,\n meter = 4,\n onChange,\n disabled = false,\n}: OffsetScrubberProps): React.ReactElement {\n const trackRef = useRef<HTMLDivElement | null>(null);\n // Local optimistic offset during drag — committed on mouseup\n const [draftOffset, setDraftOffset] = useState<number>(offsetSamples);\n const [isDragging, setIsDragging] = useState(false);\n\n // Keep the draft synced with the parent prop when not dragging.\n useEffect(() => {\n if (!isDragging) setDraftOffset(offsetSamples);\n }, [offsetSamples, isDragging]);\n\n // Range is ±1 bar of samples around the downbeat.\n // beats are 60 / bpm seconds; bar = meter beats.\n const sampleRate = cuePoints?.sample_rate ?? 44100;\n const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;\n const beatsForRange = useMemo(() => {\n // Use the project BPM for the visible range so the slider scale\n // matches what the user is editing against in the timeline.\n return Math.round((60 / projectBpm) * sampleRate);\n }, [projectBpm, sampleRate]);\n const rangeSamples = beatsForRange * meter; // ±1 bar\n\n // Map a sample offset to a 0..1 position on the slider track.\n const sampleToFraction = useCallback(\n (sample: number): number => {\n const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));\n return (clamped + rangeSamples) / (2 * rangeSamples);\n },\n [rangeSamples],\n );\n\n const fractionToSample = useCallback(\n (fraction: number): number => {\n const clamped = Math.max(0, Math.min(1, fraction));\n return Math.round(clamped * 2 * rangeSamples - rangeSamples);\n },\n [rangeSamples],\n );\n\n // Snap a candidate sample to the nearest detected beat. Beats are\n // CuePoints.beats positions (relative to clip start). Offset slider\n // semantics: positive = shift clip later; we map offset onto the\n // beats array so the user lines up the desired beat with bar 1.\n //\n // Implementation: each beat[i] corresponds to a candidate offset\n // value of `beats[i] - beats[0]` (the relative distance the user has\n // shifted the clip). Snap to the nearest such candidate.\n const snapTargets = useMemo(() => {\n if (!cuePoints || cuePoints.beats.length === 0) return [];\n const downbeat = cuePoints.beats[0];\n // Snap candidates: differences between every beat and the downbeat\n // (positive shifts) plus their negation (negative shifts). De-dup +\n // sort so binary search is cheap if the array gets large.\n const positives = cuePoints.beats.map((b) => b - downbeat);\n const negatives = positives.slice(1).map((p) => -p); // skip 0 to avoid dupe\n return [...negatives, ...positives].sort((a, b) => a - b);\n }, [cuePoints]);\n\n const snapToBeat = useCallback(\n (sample: number): number => {\n if (snapTargets.length === 0) return sample;\n // Linear scan — beats[] is small (≤ 16 for v1). Switch to binary\n // search if we ever generate longer clips.\n let best = snapTargets[0];\n let bestDist = Math.abs(sample - best);\n for (const t of snapTargets) {\n const d = Math.abs(sample - t);\n if (d < bestDist) {\n best = t;\n bestDist = d;\n }\n }\n return best;\n },\n [snapTargets],\n );\n\n // Drag handler — pointer events let us track outside the element.\n const handlePointerDown = useCallback(\n (e: React.PointerEvent<HTMLDivElement>): void => {\n if (disabled || !cuePoints) return;\n e.preventDefault();\n const track = trackRef.current;\n if (!track) return;\n track.setPointerCapture(e.pointerId);\n setIsDragging(true);\n\n const updateFromEvent = (clientX: number, shiftHeld: boolean): number => {\n const rect = track.getBoundingClientRect();\n const fraction = (clientX - rect.left) / rect.width;\n const raw = fractionToSample(fraction);\n return shiftHeld ? raw : snapToBeat(raw);\n };\n\n // Apply the initial click position immediately.\n setDraftOffset(updateFromEvent(e.clientX, e.shiftKey));\n\n const onMove = (ev: PointerEvent): void => {\n setDraftOffset(updateFromEvent(ev.clientX, ev.shiftKey));\n };\n const onUp = (ev: PointerEvent): void => {\n const final = updateFromEvent(ev.clientX, ev.shiftKey);\n track.releasePointerCapture(e.pointerId);\n track.removeEventListener('pointermove', onMove);\n track.removeEventListener('pointerup', onUp);\n track.removeEventListener('pointercancel', onUp);\n setIsDragging(false);\n setDraftOffset(final);\n onChange(final);\n };\n\n track.addEventListener('pointermove', onMove);\n track.addEventListener('pointerup', onUp);\n track.addEventListener('pointercancel', onUp);\n },\n [disabled, cuePoints, fractionToSample, onChange, snapToBeat],\n );\n\n // Reset to 0 (downbeat-aligned) — handy \"snap to bar 1\" button.\n const handleResetToZero = useCallback((): void => {\n if (disabled) return;\n setDraftOffset(0);\n onChange(0);\n }, [disabled, onChange]);\n\n const thumbFraction = sampleToFraction(draftOffset);\n const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;\n\n // BPM mismatch — show a chip when detected BPM diverges from project.\n const bpmMismatch = cuePoints?.detected_bpm != null\n && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;\n\n // Render tick marks for each beat in the snap-target list. Convert\n // sample → fraction → percent for CSS positioning.\n const ticks = useMemo(() => {\n if (!cuePoints) return [];\n const downbeat = cuePoints.beats[0] ?? 0;\n return cuePoints.beats.map((b, i) => {\n const offsetCandidate = b - downbeat;\n const fraction = sampleToFraction(offsetCandidate);\n const isDownbeat = i === 0;\n return { i, fraction, isDownbeat };\n });\n }, [cuePoints, sampleToFraction]);\n\n const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;\n\n return (\n <div data-testid=\"offset-scrubber\" className=\"flex items-center gap-2 w-full\">\n <span className=\"text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0\">\n Align\n </span>\n <div\n ref={trackRef}\n data-testid=\"offset-scrubber-track\"\n onPointerDown={handlePointerDown}\n className={`relative flex-1 min-w-0 rounded-sm select-none ${\n isDisabled\n ? 'bg-sas-panel cursor-not-allowed opacity-40'\n : 'bg-sas-bg cursor-pointer'\n }`}\n style={{ height: SLIDER_HEIGHT_PX }}\n title={\n isDisabled\n ? 'Generate audio first to enable offset alignment'\n : 'Drag to align beat 1. Hold Shift for free, no-snap movement.'\n }\n role=\"slider\"\n aria-label=\"Audio offset alignment\"\n aria-valuemin={-rangeSamples}\n aria-valuemax={rangeSamples}\n aria-valuenow={draftOffset}\n aria-disabled={isDisabled}\n >\n {/* Center marker — bar 1 / beat 1 reference line */}\n <div\n aria-hidden=\"true\"\n className=\"absolute top-0 bottom-0 w-px bg-sas-accent/40\"\n style={{ left: '50%' }}\n />\n {/* Beat ticks */}\n {ticks.map((t) => (\n <div\n key={t.i}\n data-testid={t.isDownbeat ? 'offset-tick-downbeat' : 'offset-tick'}\n aria-hidden=\"true\"\n className={t.isDownbeat ? 'absolute bg-sas-accent' : 'absolute bg-sas-muted/50'}\n style={{\n left: `${(t.fraction * 100).toFixed(2)}%`,\n top: (SLIDER_HEIGHT_PX - (t.isDownbeat ? DOWNBEAT_TICK_HEIGHT_PX : TICK_HEIGHT_PX)) / 2,\n width: 1,\n height: t.isDownbeat ? DOWNBEAT_TICK_HEIGHT_PX : TICK_HEIGHT_PX,\n }}\n />\n ))}\n {/* Thumb */}\n <div\n data-testid=\"offset-scrubber-thumb\"\n aria-hidden=\"true\"\n className={`absolute top-0 bottom-0 rounded-sm ${\n isDragging ? 'bg-sas-accent' : 'bg-sas-accent/80'\n }`}\n style={{\n left: thumbLeftPct,\n width: THUMB_WIDTH_PX,\n transform: 'translateX(-50%)',\n pointerEvents: 'none',\n }}\n />\n </div>\n {/* Numeric readout — samples + millisecond equivalent */}\n <span\n data-testid=\"offset-scrubber-readout\"\n className=\"text-[10px] text-sas-muted/70 tabular-nums flex-shrink-0 min-w-[64px] text-right\"\n >\n {formatOffset(draftOffset, sampleRate)}\n </span>\n {/* Reset button (snap back to 0) */}\n <button\n type=\"button\"\n data-testid=\"offset-scrubber-reset\"\n onClick={handleResetToZero}\n disabled={isDisabled || draftOffset === 0}\n className={`text-[10px] px-1 py-0.5 rounded-sm border transition-colors flex-shrink-0 ${\n isDisabled || draftOffset === 0\n ? 'border-sas-border text-sas-muted/30 cursor-not-allowed'\n : 'border-sas-border text-sas-muted/70 hover:border-sas-accent hover:text-sas-accent'\n }`}\n title=\"Reset offset to 0 (bar 1)\"\n >\n ⌖\n </button>\n {bpmMismatch && (\n <span\n data-testid=\"offset-bpm-mismatch\"\n className=\"text-[9px] px-1 py-0.5 rounded-sm bg-amber-500/15 text-amber-400 border border-amber-500/30 flex-shrink-0\"\n title={`Detected ${detectedBpm.toFixed(1)} BPM — beats may not align with project ${projectBpm} BPM grid`}\n >\n BPM ≠\n </span>\n )}\n </div>\n );\n}\n\n/** Format an offset in samples as `+12345 spl (+279 ms)` for the readout. */\nfunction formatOffset(samples: number, sampleRate: number): string {\n const sign = samples > 0 ? '+' : samples < 0 ? '-' : '';\n const abs = Math.abs(samples);\n const ms = Math.round((abs / sampleRate) * 1000);\n return `${sign}${abs} spl (${sign}${ms} ms)`;\n}\n\nexport default OffsetScrubber;\n","/**\n * WAV peak analyzer (Phase 8.10).\n *\n * Reads a WAV file via the plugin host, decodes it via Web Audio,\n * scans every channel for the absolute maximum sample, and returns\n * peak dBFS + a clipped flag (true when the peak >= -1dBFS, matching\n * the engine's hard-limiter ceiling).\n *\n * Used by the recorder's take rows to surface \"this take peaked at\n * -8dB\" or \"this take CLIPPED\" without the user having to click play.\n */\n\nimport type { PluginHost } from '../types/plugin-sdk.types';\n\nexport interface PeakAnalysis {\n peakLinear: number;\n peakDb: number;\n clipped: boolean;\n}\n\n/** Threshold matching the engine's -1dBFS hard limiter ceiling. */\nconst CLIP_THRESHOLD_LINEAR = 0.891;\n\nexport async function analyzeWavPeak(\n host: PluginHost,\n filePath: string\n): Promise<PeakAnalysis> {\n const bytes = await host.getAudioFileBytes(filePath);\n const ContextCtor: typeof AudioContext =\n (window as unknown as { AudioContext?: typeof AudioContext }).AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext!;\n const audioContext = new ContextCtor();\n try {\n const audioBuffer = await audioContext.decodeAudioData(bytes.slice(0));\n let peak = 0;\n for (let c = 0; c < audioBuffer.numberOfChannels; c++) {\n const data = audioBuffer.getChannelData(c);\n for (let i = 0; i < data.length; i++) {\n const a = Math.abs(data[i]);\n if (a > peak) peak = a;\n }\n }\n const peakDb = peak > 1e-6 ? 20 * Math.log10(peak) : -120;\n return {\n peakLinear: peak,\n peakDb,\n clipped: peak >= CLIP_THRESHOLD_LINEAR - 0.005,\n };\n } finally {\n await audioContext.close().catch(() => { /* ignore */ });\n }\n}\n","/**\n * Synthesize a PluginCuePoints object from raw BPM/sample-rate inputs.\n *\n * The OffsetScrubber consumes PluginCuePoints — a beat grid plus\n * per-beat sample positions, normally produced by Lyria's onset\n * detector. The recorder doesn't have detected cue points (live\n * recordings have no detection pass), but it always knows the project\n * BPM, the engine sample rate, and the loop length in bars. That's\n * enough to construct a synthetic grid where every beat sits on a\n * regular interval — which is exactly what the scrubber needs to\n * provide tick marks + snap behavior for nudging the take's offset.\n */\n\nimport type { PluginCuePoints } from '../types/plugin-sdk.types';\n\nexport interface SynthesizeCuePointsOptions {\n bpm: number;\n sampleRate: number;\n /** Total bars in the clip (e.g. 4 for a 4-bar loop). */\n bars: number;\n /** Beats per bar. Defaults to 4 (4/4). */\n meter?: number;\n}\n\nexport function synthesizeCuePoints({\n bpm,\n sampleRate,\n bars,\n meter = 4,\n}: SynthesizeCuePointsOptions): PluginCuePoints {\n const safeBpm = bpm > 0 ? bpm : 120;\n const safeSampleRate = sampleRate > 0 ? sampleRate : 48000;\n const samplesPerBeat = Math.round((60 / safeBpm) * safeSampleRate);\n const totalBeats = Math.max(1, Math.round(bars * meter));\n const beats: number[] = [];\n for (let i = 0; i < totalBeats; i++) {\n beats.push(i * samplesPerBeat);\n }\n return {\n schema: 1,\n sample_rate: safeSampleRate,\n detected_bpm: safeBpm,\n downbeat_sample: 0,\n beats,\n detected_at: new Date().toISOString(),\n };\n}\n","/**\n * useGeneratorPanelCore — the shared state/effects/handlers engine behind\n * generator panels (synth today; bass next; drum/instrument candidates).\n *\n * Verbatim extraction of the synth panel monolith's family-agnostic ~85%\n * (SynthGeneratorPanel.tsx), parameterized by a GeneratorPanelAdapter. Every\n * timing (500ms prompt debounce, 500ms agent-mutation coalesce, 300ms notes\n * save, 350ms add-focus), every scene-data key (`track:<dbId>:…`), every\n * toast string, and every host-call sequence is frozen by the Phase-0\n * behavior pin (sas-app/src/__tests__/synth-panel-behavior.test.tsx).\n *\n * The returned `core` object is consumed by GeneratorPanelShell (render) and\n * closed over by family adapters (generation strategies, group renderers).\n *\n * @since SDK 2.35.0\n */\n\nimport React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';\nimport type {\n PluginUIProps,\n PluginTrackHandle,\n PluginTrackRuntimeState,\n PluginMidiNote,\n BulkAddPlaceholderTrack,\n InstrumentDescriptor,\n} from '../types/plugin-sdk.types';\nimport type { FxCategory } from '../types/fx-toggle.types';\nimport { useSceneState } from '../hooks/useSceneState';\nimport { useAnySolo } from '../hooks/useAnySolo';\nimport { useSoundHistory, type TrackSoundHistory } from '../hooks/useSoundHistory';\nimport { useTrackReorder, type UseTrackReorderResult } from '../hooks/useTrackReorder';\nimport { useTrackLevels, type TrackLevelsHandle } from '../hooks/useTrackLevels';\nimport { parseCrossfadePairs, type CrossfadePairMeta } from '../crossfade-meta';\nimport { parseFades, type FadeEntry } from '../fade-meta';\nimport type { DrawerTab } from '../components/TrackDrawer';\nimport { type GeneratorTrackState, newTrackState } from './track-state';\nimport { pluginFxToToggleFx, trackDataKey } from './panel-helpers';\nimport {\n parseTrackGroups,\n resolveTrackGroups,\n type TrackGroupMeta,\n type ResolvedGroupsResult,\n type ResolvedTrackGroup,\n} from './group-meta';\nimport type {\n GeneratorPanelAdapter,\n GenerationServices,\n CoreTrackHandlers,\n} from './adapter.types';\nimport {\n useTransitionOps,\n type TransitionOps,\n type ResolvedCrossfadePair,\n type ResolvedFade,\n} from './useTransitionOps';\n\nconst EMPTY_PLACEHOLDERS: BulkAddPlaceholderTrack[] = [];\n\nexport interface UseGeneratorPanelCoreOptions {\n /** The panel's PluginUIProps, passed through whole. */\n ui: PluginUIProps;\n /** Family adapter — MUST be referentially stable (useMemo on [host]). */\n adapter: GeneratorPanelAdapter;\n}\n\n/** Everything GeneratorPanelShell + family extensions consume. */\nexport interface GeneratorPanelCore {\n ui: PluginUIProps;\n adapter: GeneratorPanelAdapter;\n\n // Track state\n tracks: GeneratorTrackState[];\n setTracks: React.Dispatch<React.SetStateAction<GeneratorTrackState[]>>;\n isLoadingTracks: boolean;\n loadTracks(incremental?: boolean): Promise<void>;\n engineToDbId(trackId: string): string;\n\n // Meters / solo / reorder / history\n supportsMeters: boolean;\n trackLevels: TrackLevelsHandle;\n anySolo: boolean;\n reorder: UseTrackReorderResult;\n soundHistory: ReturnType<typeof useSoundHistory>;\n\n // Bulk compose\n isComposing: boolean;\n placeholders: BulkAddPlaceholderTrack[];\n\n // Header-derived state\n isAddingTrack: boolean;\n isExportingMidi: boolean;\n designerView: boolean;\n canCrossfade: boolean;\n needsContract: boolean;\n xfFromId: string | null;\n xfToId: string | null;\n\n // Import modals\n importOpen: boolean;\n setImportOpen(open: boolean): void;\n soundImportTarget: GeneratorTrackState | null;\n setSoundImportTarget(t: GeneratorTrackState | null): void;\n handleSoundImportPick(sel: {\n sourceTrackDbId: string;\n trackName: string;\n sceneName: string;\n }): Promise<void>;\n handlePortTrack(sel: {\n sourceTrackDbId: string;\n trackName: string;\n role?: string;\n }): Promise<void>;\n\n // Transition machinery\n transition: TransitionOps;\n crossfadePairsMeta: CrossfadePairMeta[];\n fadesMeta: FadeEntry[];\n resolvedCrossfadePairs: ResolvedCrossfadePair[];\n crossfadeMemberDbIds: Set<string>;\n resolvedFades: ResolvedFade[];\n fadeMemberDbIds: Set<string>;\n\n // Generic group extensions\n resolvedGenericGroups: Record<string, ResolvedGroupsResult<unknown, GeneratorTrackState>>;\n genericGroupMemberDbIds: Set<string>;\n\n // Instrument picker\n availableInstruments: InstrumentDescriptor[];\n instrumentsLoading: boolean;\n\n // Per-track handlers (bundled for group render contexts + shell rows)\n handlers: CoreTrackHandlers;\n handleGenerate(trackId: string): Promise<void>;\n handleShuffle(trackId: string): Promise<void>;\n handleAddTrack(): Promise<void>;\n handleDeleteTrack(trackId: string): Promise<void>;\n handleExportMidi(): Promise<void>;\n handlePromptChange(trackId: string, prompt: string): void;\n handleMuteToggle(trackId: string): void;\n handleSoloToggle(trackId: string): void;\n handleVolumeChange(trackId: string, volume: number): void;\n handlePanChange(trackId: string, pan: number): void;\n handleTabChange(trackId: string, tab: DrawerTab): void;\n handleToggleDrawer(trackId: string): void;\n toggleFxDrawer(trackId: string): void;\n handleNotesChange(trackId: string, notes: PluginMidiNote[]): void;\n handleProgressChange(trackId: string, pct: number): void;\n handleCopy(trackId: string): Promise<void>;\n handleFxToggle(trackId: string, category: FxCategory, enabled: boolean): void;\n handleFxPresetChange(trackId: string, category: FxCategory, presetIndex: number): void;\n handleFxDryWetChange(trackId: string, category: FxCategory, value: number): void;\n handleInstrumentSelect(trackId: string, pluginId: string): Promise<void>;\n handleShowEditor(trackId: string): Promise<void>;\n handleBackToInstruments(trackId: string): void;\n handleRefreshInstruments(): void;\n onAuditionNote(trackId: string, pitch: number, velocity: number, ms: number): void;\n\n // Services factory for strategies / group ops\n makeServices(): GenerationServices;\n setGroupMute(trackIds: string[], muted: boolean): void;\n setGroupSolo(trackIds: string[], solo: boolean): void;\n deleteGroup(\n members: Array<{ engineId: string; dbId: string }>,\n cleanupKeySuffixes: string[],\n ): Promise<void>;\n}\n\nexport function useGeneratorPanelCore({\n ui,\n adapter,\n}: UseGeneratorPanelCoreOptions): GeneratorPanelCore {\n const {\n host,\n activeSceneId,\n isAuthenticated,\n isConnected,\n onHeaderContent,\n onLoading,\n sceneContext,\n onOpenContract,\n onExpandSelf,\n isExpanded,\n } = ui;\n const { identity, features } = adapter;\n const logTag = identity.logTag;\n\n // Dev guard for the historical render-loop failure mode: an unstable\n // adapter identity re-creates loadTracks every render.\n const adapterRef = useRef(adapter);\n useEffect(() => {\n if (adapterRef.current !== adapter) {\n adapterRef.current = adapter;\n // eslint-disable-next-line no-console\n console.warn(\n `[${logTag}] GeneratorPanelAdapter identity changed between renders — ` +\n 'wrap it in useMemo(() => createAdapter(host), [host]) to avoid load loops.',\n );\n }\n }, [adapter, logTag]);\n\n // Cosmetic per-track peak meters. Poll ONLY while this panel is expanded.\n // Older hosts (no getTrackLevels) degrade to no meter via `supportsMeters`.\n const supportsMeters = typeof host.getTrackLevels === 'function';\n const trackLevels = useTrackLevels(host, isExpanded);\n\n const [tracks, setTracks] = useState<GeneratorTrackState[]>([]);\n const [isLoadingTracks, setIsLoadingTracks] = useState(false);\n const [importOpen, setImportOpen] = useState(false);\n const [soundImportTarget, setSoundImportTarget] = useState<GeneratorTrackState | null>(null);\n const [designerView, setDesignerView] = useState(false);\n const [transitionSourceTotal, setTransitionSourceTotal] = useState(0);\n const [crossfadePairsMeta, setCrossfadePairsMeta] = useState<CrossfadePairMeta[]>([]);\n const [fadesMeta, setFadesMeta] = useState<FadeEntry[]>([]);\n const [genericGroupMetas, setGenericGroupMetas] = useState<\n Record<string, TrackGroupMeta<unknown>[]>\n >({});\n // Scene-keyed compose state: preserved when switching scenes via SDK hook.\n const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);\n const [placeholders, , setPlaceholdersForScene] = useSceneState<BulkAddPlaceholderTrack[]>(\n activeSceneId,\n EMPTY_PLACEHOLDERS,\n );\n const saveTimeoutRefs = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\n // Tracks whose piano-roll notes have already been loaded (or seeded from a\n // fresh generation). Guards the Edit tab against re-fetching on every open\n // and against clobbering unsaved edits.\n const editLoadStartedRef = useRef<Set<string>>(new Set());\n const [availableInstruments, setAvailableInstruments] = useState<InstrumentDescriptor[]>([]);\n const [instrumentsLoading, setInstrumentsLoading] = useState(false);\n /** Maps engine track ID → stable DB UUID for plugin_data key construction */\n const engineToDbIdRef = useRef<Map<string, string>>(new Map());\n\n // Stale-scene guard: clear on real scene transitions so the gap between\n // scene switch and load completion shows empty, not the prior scene's tracks.\n const tracksLoadedForSceneRef = useRef<string | null>(null);\n\n // --- Sound history ------------------------------------------------------\n // Persist per-track history to project scene-data so it survives reopen.\n const persistSoundHistory = useCallback(\n (trackId: string, state: TrackSoundHistory): void => {\n if (!activeSceneId) return;\n const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;\n host.setSceneData(activeSceneId, trackDataKey(dbId, 'soundHistory'), state).catch(() => {});\n },\n [host, activeSceneId],\n );\n const soundHistory = useSoundHistory(adapter.sound.applySound, {\n max: adapter.sound.historyMax,\n onChange: persistSoundHistory,\n });\n // Cross-panel: dim non-soloed rows when ANY track (any panel) is soloed.\n const anySolo = useAnySolo(host);\n\n // Drag-to-reorder rows: optimistic local reorder + persist via\n // host.reorderTracks by the stable dbId so order survives project reopen.\n const reorder = useTrackReorder<GeneratorTrackState>({\n host,\n items: tracks,\n setItems: setTracks,\n getId: (t) => t.handle.dbId,\n });\n\n // --- Load tracks when scene changes -------------------------------------\n const loadTracks = useCallback(\n async (incremental = false): Promise<void> => {\n // Snapshot the scene this load is for. Each await is a chance for\n // activeSceneId to change or a newer load to take over; when that\n // happens this load must NOT write state.\n const sceneAtStart = activeSceneId;\n if (!sceneAtStart) {\n setTracks([]);\n setCrossfadePairsMeta([]);\n setFadesMeta([]);\n setGenericGroupMetas({});\n tracksLoadedForSceneRef.current = null;\n // No scene → nothing to load → not loading (prevents a stuck spinner\n // when a load is superseded by a brief null effectiveSceneId).\n setIsLoadingTracks(false);\n return;\n }\n\n // Scene changed since the last load → clear immediately so the user\n // sees the new (empty) state, not the prior scene's tracks.\n if (!incremental && tracksLoadedForSceneRef.current !== sceneAtStart) {\n setTracks([]);\n }\n tracksLoadedForSceneRef.current = sceneAtStart;\n // Reset sound-history on a full (re)load so history resets per scene/reopen.\n if (!incremental) soundHistory.reset();\n\n const isStale = (): boolean => tracksLoadedForSceneRef.current !== sceneAtStart;\n\n // Only show \"Loading tracks...\" when there are no tracks yet.\n if (!incremental) setIsLoadingTracks(true);\n try {\n await host.adoptSceneTracks();\n if (isStale()) return;\n const handles = await host.getPluginTracks();\n if (isStale()) return;\n const sceneData = (await host.getAllSceneData(sceneAtStart)) as Record<string, unknown>;\n if (isStale()) return;\n\n // Build engine→dbId lookup for callbacks that receive engine IDs\n const idMap = new Map<string, string>();\n for (const h of handles) {\n idMap.set(h.id, h.dbId);\n }\n engineToDbIdRef.current = idMap;\n\n const trackStates: GeneratorTrackState[] = [];\n for (const handle of handles) {\n // Get runtime state\n let runtimeState: PluginTrackRuntimeState = {\n id: handle.id,\n muted: false,\n solo: false,\n volume: 0.75,\n pan: 0,\n };\n let hasMidi = false;\n try {\n const info = await host.getTrackInfo(handle.id);\n runtimeState = {\n id: handle.id,\n muted: info.muted,\n solo: info.soloed,\n volume: info.volume,\n pan: info.pan,\n };\n hasMidi = info.hasMidi;\n } catch {\n // Use defaults\n }\n\n // Get FX state\n let fxDetailState = newTrackState(handle).fxDetailState;\n try {\n const fxState = await host.getTrackFxState(handle.id);\n fxDetailState = pluginFxToToggleFx(fxState);\n } catch {\n // Use defaults\n }\n\n // Use stable DB UUID for plugin_data keys (engine IDs change on reload)\n const promptKey = trackDataKey(handle.dbId, 'prompt');\n let prompt = typeof sceneData[promptKey] === 'string' ? (sceneData[promptKey] as string) : '';\n\n // Fallback: read prompt from tracks table (bulk-add saves there)\n if (!prompt && handle.prompt) {\n prompt = handle.prompt;\n // Backfill into plugin_data so future loads find it directly.\n host.setSceneData(sceneAtStart, promptKey, prompt).catch(() => {});\n }\n\n // Detect hasMidi from role presence as a fallback\n if (!hasMidi && handle.role) {\n hasMidi = true;\n }\n\n // Detect missing instrument plugins (only for custom instruments)\n let instrumentMissing = false;\n if (handle.instrumentPluginId) {\n try {\n const instrDescriptor = await host.getTrackInstrument(handle.id);\n if (instrDescriptor?.missing) {\n instrumentMissing = true;\n }\n } catch {\n // Non-fatal — assume available\n }\n }\n\n trackStates.push(\n newTrackState(handle, {\n prompt,\n role: handle.role ?? '',\n runtimeState,\n fxDetailState,\n hasMidi,\n instrumentMissing,\n }),\n );\n }\n if (isStale()) return;\n // Carry forward the in-memory piano-roll edit buffer for tracks that\n // still exist, matched by stable DB UUID — a reload fired after a\n // generation must not wipe seeded notes while editLoadStartedRef still\n // marks the track loaded.\n setTracks((prev) => {\n const prevByDbId = new Map(prev.map((p) => [p.handle.dbId, p]));\n return trackStates.map((ts) => {\n const carry = prevByDbId.get(ts.handle.dbId);\n return carry\n ? { ...ts, editNotes: carry.editNotes, editBars: carry.editBars, editBpm: carry.editBpm }\n : ts;\n });\n });\n // Restore persisted history so it survives reopen.\n for (const ts of trackStates) {\n const persisted = sceneData[trackDataKey(ts.handle.dbId, 'soundHistory')];\n if (persisted && typeof persisted === 'object') {\n soundHistory.restore(ts.handle.id, persisted as TrackSoundHistory);\n }\n }\n // Group crossfade members / fades (normal tracks linked via scene-data).\n if (!isStale()) {\n setCrossfadePairsMeta(parseCrossfadePairs(sceneData));\n setFadesMeta(parseFades(sceneData));\n // Generic group extensions (additive; families without extensions skip).\n if (adapter.groupExtensions && adapter.groupExtensions.length > 0) {\n const map: Record<string, TrackGroupMeta<unknown>[]> = {};\n for (const ext of adapter.groupExtensions) {\n map[ext.metaKey] = parseTrackGroups(sceneData, ext);\n }\n setGenericGroupMetas(map);\n }\n }\n } catch (error: unknown) {\n console.error(`[${logTag}] Failed to load tracks:`, error);\n } finally {\n // Only clear the loading indicator if no newer loadTracks took over.\n if (tracksLoadedForSceneRef.current === sceneAtStart) {\n setIsLoadingTracks(false);\n }\n }\n },\n [host, activeSceneId, soundHistory, adapter, logTag],\n );\n\n useEffect(() => {\n loadTracks();\n }, [loadTracks]);\n\n // Keep engine→dbId ref in sync with current tracks (for newly created\n // tracks that weren't present when loadTracks last ran)\n useEffect(() => {\n const map = new Map<string, string>();\n for (const t of tracks) {\n map.set(t.handle.id, t.handle.dbId);\n }\n engineToDbIdRef.current = map;\n }, [tracks]);\n\n // --- Reload tracks incrementally as individual bulk tracks complete ----\n const loadedCompletedIdsRef = useRef<Set<string>>(new Set());\n useEffect(() => {\n if (placeholders.length === 0) {\n loadedCompletedIdsRef.current.clear();\n return;\n }\n const newCompleted = placeholders.filter(\n (ph: BulkAddPlaceholderTrack) =>\n ph.status === 'completed' && !loadedCompletedIdsRef.current.has(ph.id),\n );\n if (newCompleted.length > 0) {\n for (const ph of newCompleted) {\n loadedCompletedIdsRef.current.add(ph.id);\n }\n console.log(\n `[${logTag}] ${newCompleted.length} track(s) completed, reloading. IDs:`,\n newCompleted.map((ph: BulkAddPlaceholderTrack) => ph.id),\n );\n loadTracks(true);\n }\n }, [placeholders, loadTracks, logTag]);\n\n // --- Re-adopt tracks after engine finishes full loading ---------------\n const adoptAndLoad = useCallback((): void => {\n loadTracks(true);\n }, [loadTracks]);\n\n useEffect(() => {\n const unsub = host.onEngineReady(() => {\n adoptAndLoad();\n });\n return unsub;\n }, [host, adoptAndLoad]);\n\n // --- Re-adopt tracks after agent/CLI tool mutations --------------------\n // Debounced 500ms so a burst of tool calls coalesces into one reload.\n useEffect(() => {\n if (typeof host.onAfterAgentMutation !== 'function') return;\n let timer: ReturnType<typeof setTimeout> | null = null;\n const unsub = host.onAfterAgentMutation(() => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n loadTracks(true);\n }, 500);\n });\n return () => {\n unsub?.();\n if (timer) clearTimeout(timer);\n };\n }, [host, loadTracks]);\n\n // --- Subscribe to real-time track state changes -----------------------\n useEffect(() => {\n const unsub = host.onTrackStateChange((trackId: string, state: PluginTrackRuntimeState) => {\n setTracks((prev) => prev.map((t) => (t.handle.id === trackId ? { ...t, runtimeState: state } : t)));\n });\n return unsub;\n }, [host]);\n\n // --- Subscribe to compose progress events -----------------------------\n useEffect(() => {\n if (!features.bulkComposePlaceholders) return;\n console.log(`[${logTag}] Subscribing to composeProgress`);\n const unsub = host.onComposeProgress((event) => {\n const targetScene = event.sceneId;\n if (!targetScene) return;\n console.log(\n `[${logTag}] composeProgress event:`,\n event.phase,\n 'sceneId:',\n targetScene,\n 'placeholders:',\n event.placeholders?.length ?? 'none',\n );\n switch (event.phase) {\n case 'planning':\n setIsComposingForScene(targetScene, true);\n setPlaceholdersForScene(targetScene, []);\n break;\n case 'generating':\n setIsComposingForScene(targetScene, false);\n if (event.placeholders) {\n setPlaceholdersForScene(targetScene, event.placeholders);\n }\n break;\n case 'complete':\n case 'error':\n setIsComposingForScene(targetScene, false);\n setPlaceholdersForScene(targetScene, EMPTY_PLACEHOLDERS);\n break;\n }\n });\n return unsub;\n }, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);\n\n // --- Cleanup save timeouts on unmount ---------------------------------\n useEffect(() => {\n const refs = saveTimeoutRefs;\n return () => {\n for (const timeout of Object.values(refs.current)) {\n clearTimeout(timeout);\n }\n };\n }, []);\n\n // --- Add track --------------------------------------------------------\n // Re-entry guard: the ref is synchronous so rapid double-clicks can't both\n // pass the gate; the state mirrors it for the button's visual disable.\n const isAddingTrackRef = useRef(false);\n const [isAddingTrack, setIsAddingTrack] = useState(false);\n const handleAddTrack = useCallback(async (): Promise<void> => {\n if (isAddingTrackRef.current) return;\n if (!activeSceneId) {\n host.showToast('warning', 'Select SCENE');\n return;\n }\n if (!isConnected) {\n host.showToast('warning', 'Systems not connected');\n return;\n }\n if (!isAuthenticated) {\n host.showToast('warning', 'Sign In Required', 'Please sign in to add tracks');\n return;\n }\n if (tracks.length >= identity.maxTracks) return;\n\n isAddingTrackRef.current = true;\n setIsAddingTrack(true);\n try {\n const handle = await host.createTrack({\n name: `${identity.trackNamePrefix}-${Date.now()}`,\n ...adapter.createTrackOptions(),\n });\n setTracks((prev) => [...prev, newTrackState(handle)]);\n onExpandSelf?.();\n // Auto-focus the prompt input of the newly created track after the\n // accordion animation.\n setTimeout(() => {\n const inputs = document.querySelectorAll<HTMLInputElement>(\n `[data-testid=\"${identity.familyKey}-section\"] [data-testid=\"sdk-prompt-input\"]`,\n );\n if (inputs.length > 0) {\n inputs[inputs.length - 1].focus();\n }\n }, 350);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : 'Unknown error';\n host.showToast('error', 'Failed to create track', msg);\n } finally {\n isAddingTrackRef.current = false;\n setIsAddingTrack(false);\n }\n }, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);\n\n // --- Port track (cross-panel import) -----------------------------------\n // Pull a MIDI part out of a track owned by ANOTHER panel in THIS scene and\n // play it on a fresh family instrument. The sound never carries across\n // families; we copy only MIDI + role, then the adapter applies a native sound.\n const handlePortTrack = useCallback(\n async (sel: { sourceTrackDbId: string; trackName: string; role?: string }): Promise<void> => {\n if (!activeSceneId) {\n host.showToast('warning', 'Select SCENE');\n return;\n }\n if (!isConnected) {\n host.showToast('warning', 'Systems not connected');\n return;\n }\n if (tracks.length >= identity.maxTracks) {\n host.showToast('warning', 'Track limit reached');\n return;\n }\n if (!host.readImportableTrackMidi) return;\n let handle: PluginTrackHandle | null = null;\n try {\n handle = await host.createTrack({\n name: `${identity.trackNamePrefix}-${Date.now()}`,\n ...adapter.createTrackOptions(),\n });\n if (sel.role) {\n try {\n await host.setTrackRole(handle.id, sel.role);\n } catch {\n /* non-fatal: MIDI still ports */\n }\n }\n const midi = await host.readImportableTrackMidi(sel.sourceTrackDbId);\n const notes = midi.clips[0]?.notes ?? [];\n if (notes.length > 0) {\n const mc = await host.getMusicalContext();\n await host.writeMidiClip(handle.id, {\n startTime: 0,\n endTime: (mc.bars * 4 * 60) / mc.bpm,\n tempo: mc.bpm,\n notes,\n });\n }\n // Native, role-appropriate family sound (adapter owns non-fatality).\n await adapter.applyPortedTrackSound(handle, sel.role);\n host.showToast(\n 'success',\n `Imported to ${identity.familyKey}`,\n notes.length ? `${sel.trackName} → ${identity.familyKey}` : `${sel.trackName} (no MIDI yet)`,\n );\n await loadTracks(true);\n } catch (err: unknown) {\n // Roll back the half-made track we created (we own it).\n if (handle) {\n try {\n await host.deleteTrack(handle.id);\n } catch {\n /* best effort */\n }\n }\n host.showToast('error', 'Import failed', err instanceof Error ? err.message : String(err));\n }\n },\n [host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks],\n );\n\n // --- Sound import (drawer \"Import <noun>\") ------------------------------\n const handleSoundImportPick = useCallback(\n async (sel: { sourceTrackDbId: string; trackName: string; sceneName: string }): Promise<void> => {\n const target = soundImportTarget;\n if (!target || !host.getTrackSound) {\n setSoundImportTarget(null);\n return;\n }\n const noun = adapter.sound.importNoun;\n const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);\n try {\n const snap = await host.getTrackSound(sel.sourceTrackDbId);\n if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {\n host.showToast(\n 'error',\n `No ${noun} to import`,\n `${sel.trackName} has no ${identity.familyKey} ${noun}.`,\n );\n return;\n }\n const descriptor = adapter.sound.descriptorFromSnapshot(snap);\n await adapter.sound.applySound(target.handle.id, descriptor);\n soundHistory.record(target.handle.id, descriptor, snap.label);\n host.showToast('success', `${nounTitle} imported`, `${snap.label} → ${target.handle.name}`);\n } catch (err: unknown) {\n host.showToast('error', 'Import failed', err instanceof Error ? err.message : String(err));\n } finally {\n setSoundImportTarget(null);\n }\n },\n [soundImportTarget, host, adapter, identity.familyKey, soundHistory],\n );\n\n // --- Export tracks as MIDI bundle -------------------------------------\n const [isExportingMidi, setIsExportingMidi] = useState(false);\n const handleExportMidi = useCallback(async (): Promise<void> => {\n if (isExportingMidi) return;\n setIsExportingMidi(true);\n try {\n const result = await host.exportTracksAsMidiBundle({\n defaultName: identity.exportDefaultName ?? 'midi-tracks',\n });\n if (result.success) {\n const filename = result.filePath.split('/').pop() || result.filePath;\n const skippedNote =\n result.skippedCount > 0\n ? ` (${result.skippedCount} empty track${result.skippedCount === 1 ? '' : 's'} skipped)`\n : '';\n host.showToast(\n 'success',\n 'MIDI exported',\n `${result.trackCount} track${result.trackCount === 1 ? '' : 's'} → ${filename}${skippedNote}`,\n );\n } else if (!('canceled' in result && result.canceled)) {\n const errMsg = 'error' in result ? result.error : 'Unknown error';\n host.showToast('error', 'Export failed', errMsg);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n host.showToast('error', 'Export failed', msg);\n } finally {\n setIsExportingMidi(false);\n }\n }, [host, identity.exportDefaultName, isExportingMidi]);\n\n // --- Header content (Add / Import / designer toggle) --------------------\n const isBulkActive = !!(isComposing || placeholders.length > 0);\n const needsContract = !sceneContext?.hasContract;\n const xfFromId = sceneContext?.transitionFromSceneId ?? null;\n const xfToId = sceneContext?.transitionToSceneId ?? null;\n const canCrossfade =\n features.transitionDesigner &&\n sceneContext?.sceneType === 'transition' &&\n !!xfFromId &&\n !!xfToId &&\n !!host.listSceneFamilyTracks;\n // Leaving a transition scene drops back to the Tracks view.\n useEffect(() => {\n if (!canCrossfade) setDesignerView(false);\n }, [canCrossfade]);\n // Fetch the source-track total once per transition scene (stable denominator).\n useEffect(() => {\n if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {\n setTransitionSourceTotal(0);\n return;\n }\n let cancelled = false;\n void Promise.all([host.listSceneFamilyTracks(xfFromId), host.listSceneFamilyTracks(xfToId)])\n .then(([a, b]) => {\n if (!cancelled) setTransitionSourceTotal(a.length + b.length);\n })\n .catch(() => {\n if (!cancelled) setTransitionSourceTotal(0);\n });\n return () => {\n cancelled = true;\n };\n }, [canCrossfade, xfFromId, xfToId, host]);\n // Tracks already turned into transitions: 2 sources per pair, 1 per fade.\n const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;\n useEffect(() => {\n if (!onHeaderContent) return;\n const addDisabled =\n needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;\n\n onHeaderContent(\n <div className=\"flex gap-1 items-center\">\n {features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && (\n <button\n data-testid={`import-from-scene-${identity.familyKey}-button`}\n onClick={(e: React.MouseEvent) => {\n e.stopPropagation();\n onExpandSelf?.();\n setImportOpen(true);\n }}\n disabled={!activeSceneId || needsContract}\n className={`px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors ${\n !activeSceneId || needsContract\n ? 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n : 'bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent'\n }`}\n >\n {identity.importTrackLabel ?? 'Import Track'}\n </button>\n )}\n {(!canCrossfade || !designerView) && (\n <button\n data-testid={`add-${identity.familyKey}-track-button`}\n onClick={(e: React.MouseEvent) => {\n e.stopPropagation();\n if (needsContract) {\n onOpenContract?.();\n return;\n }\n handleAddTrack();\n }}\n className={`px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors ${\n addDisabled\n ? 'bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed'\n : 'bg-sas-accent/10 border-sas-accent/30 text-sas-accent hover:bg-sas-accent/20'\n }`}\n >\n {identity.addTrackLabel ?? 'Add Track'}\n </button>\n )}\n {canCrossfade && (\n <button\n data-testid={`${identity.familyKey}-view-toggle`}\n onClick={(e: React.MouseEvent) => {\n e.stopPropagation();\n if (!designerView) {\n if (needsContract) {\n onOpenContract?.();\n return;\n }\n onExpandSelf?.();\n }\n setDesignerView((v) => !v);\n }}\n disabled={!designerView && needsContract}\n title={designerView ? 'Back to the track list' : 'Open the transition designer'}\n 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\"\n >\n {transitionSourceTotal > 0 && (\n <span\n className=\"absolute inset-y-0 left-0 bg-sas-accent/25\"\n style={{ width: `${Math.min(100, (transitionDone / transitionSourceTotal) * 100)}%` }}\n aria-hidden\n />\n )}\n <span className=\"relative\">\n ⇄ {designerView ? 'Transition' : 'Tracks'}\n {transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ''}\n </span>\n </button>\n )}\n </div>,\n );\n return () => {\n onHeaderContent(null);\n };\n }, [\n onHeaderContent,\n needsContract,\n isConnected,\n activeSceneId,\n tracks.length,\n isAddingTrack,\n handleAddTrack,\n onOpenContract,\n host,\n canCrossfade,\n designerView,\n transitionDone,\n transitionSourceTotal,\n onExpandSelf,\n identity,\n features.importTracks,\n ]);\n\n // --- Push loading state to accordion header ---------------------------\n useEffect(() => {\n if (!onLoading) return;\n const anyGenerating = tracks.some((t: GeneratorTrackState) => t.isGenerating);\n onLoading(isLoadingTracks || anyGenerating || isBulkActive);\n return () => {\n onLoading(false);\n };\n }, [onLoading, isLoadingTracks, tracks, isBulkActive]);\n\n // --- Delete track -----------------------------------------------------\n const handleDeleteTrack = useCallback(\n async (trackId: string): Promise<void> => {\n try {\n await host.deleteTrack(trackId);\n // Clean up prompt from scene data (stable DB UUID key)\n const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;\n if (activeSceneId) {\n await host.deleteSceneData(activeSceneId, trackDataKey(dbId, 'prompt'));\n }\n setTracks((prev) => prev.filter((t) => t.handle.id !== trackId));\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : 'Unknown error';\n host.showToast('error', 'Failed to delete track', msg);\n }\n },\n [host, activeSceneId],\n );\n\n // --- Update prompt (debounced save) -----------------------------------\n const handlePromptChange = useCallback(\n (trackId: string, prompt: string): void => {\n setTracks((prev) => prev.map((t) => (t.handle.id === trackId ? { ...t, prompt } : t)));\n\n // Debounced save to scene data (stable DB UUID key)\n const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;\n if (saveTimeoutRefs.current[trackId]) {\n clearTimeout(saveTimeoutRefs.current[trackId]);\n }\n saveTimeoutRefs.current[trackId] = setTimeout(() => {\n if (activeSceneId) {\n host.setSceneData(activeSceneId, trackDataKey(dbId, 'prompt'), prompt).catch(() => {});\n }\n }, 500);\n },\n [host, activeSceneId],\n );\n\n // --- Generic group resolution ------------------------------------------\n const resolvedGenericGroups = useMemo(() => {\n const out: Record<string, ResolvedGroupsResult<unknown, GeneratorTrackState>> = {};\n for (const ext of adapter.groupExtensions ?? []) {\n out[ext.metaKey] = resolveTrackGroups(\n genericGroupMetas[ext.metaKey] ?? [],\n tracks,\n (t) => t.handle.dbId,\n {\n isComplete: ext.isComplete as\n | ((g: ResolvedTrackGroup<unknown, GeneratorTrackState>, p: TrackGroupMeta<unknown>) => boolean)\n | undefined,\n },\n );\n }\n return out;\n }, [adapter, genericGroupMetas, tracks]);\n const genericGroupMemberDbIds = useMemo(() => {\n const s = new Set<string>();\n for (const r of Object.values(resolvedGenericGroups)) {\n for (const dbId of r.memberDbIds) s.add(dbId);\n }\n return s;\n }, [resolvedGenericGroups]);\n\n // --- Services factory ---------------------------------------------------\n const engineToDbId = useCallback(\n (trackId: string): string => engineToDbIdRef.current.get(trackId) ?? trackId,\n [],\n );\n const updateTrack = useCallback(\n (\n trackId: string,\n patch: Partial<GeneratorTrackState> | ((t: GeneratorTrackState) => GeneratorTrackState),\n ): void => {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId ? (typeof patch === 'function' ? patch(t) : { ...t, ...patch }) : t,\n ),\n );\n },\n [],\n );\n const markEditLoaded = useCallback((trackId: string): void => {\n editLoadStartedRef.current.add(trackId);\n }, []);\n const tracksRef = useRef(tracks);\n useEffect(() => {\n tracksRef.current = tracks;\n }, [tracks]);\n const resolvedGenericGroupsRef = useRef(resolvedGenericGroups);\n useEffect(() => {\n resolvedGenericGroupsRef.current = resolvedGenericGroups;\n }, [resolvedGenericGroups]);\n const makeServices = useCallback((): GenerationServices => {\n return {\n host,\n activeSceneId,\n tracks: tracksRef.current,\n updateTrack,\n setTracks,\n reloadTracks: loadTracks,\n soundHistory,\n engineToDbId,\n trackDataKey,\n markEditLoaded,\n createFamilyTrack: (nameSuffix = '') =>\n host.createTrack({\n name: `${identity.trackNamePrefix}-${Date.now()}${nameSuffix}`,\n ...adapter.createTrackOptions(),\n }),\n resolvedGroups: <M,>(metaKey: string) =>\n (resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []) as ResolvedTrackGroup<\n M,\n GeneratorTrackState\n >[],\n };\n }, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);\n\n // --- Generate (core wrapper; adapter strategy owns the body) ------------\n const handleGenerate = useCallback(\n async (trackId: string): Promise<void> => {\n const track = tracks.find((t) => t.handle.id === trackId);\n if (!track || !track.prompt.trim()) return;\n if (!isAuthenticated) {\n host.showToast('warning', 'Sign In Required', 'Please sign in to generate MIDI');\n return;\n }\n\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId ? { ...t, isGenerating: true, error: null, generationProgress: 0 } : t,\n ),\n );\n\n try {\n await adapter.generation.generate(track, makeServices());\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : 'Generation failed';\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId ? { ...t, isGenerating: false, error: msg, generationProgress: 0 } : t,\n ),\n );\n host.showToast('error', 'Generation failed', msg);\n }\n },\n [host, adapter, tracks, isAuthenticated, makeServices],\n );\n\n // --- Mute/Solo/Volume/Pan -----------------------------------------------\n const handleMuteToggle = useCallback(\n (trackId: string): void => {\n const track = tracks.find((t) => t.handle.id === trackId);\n if (!track) return;\n const newMuted = !track.runtimeState.muted;\n // Optimistic update\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, muted: newMuted } } : t,\n ),\n );\n host.setTrackMute(trackId, newMuted).catch(() => {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, muted: !newMuted } } : t,\n ),\n );\n });\n },\n [host, tracks],\n );\n\n const handleSoloToggle = useCallback(\n (trackId: string): void => {\n const track = tracks.find((t) => t.handle.id === trackId);\n if (!track) return;\n const newSolo = !track.runtimeState.solo;\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, solo: newSolo } } : t,\n ),\n );\n host.setTrackSolo(trackId, newSolo).catch(() => {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, solo: !newSolo } } : t,\n ),\n );\n });\n },\n [host, tracks],\n );\n\n const handleVolumeChange = useCallback(\n (trackId: string, volume: number): void => {\n setTracks((prev) =>\n prev.map((t) => (t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)),\n );\n host.setTrackVolume(trackId, volume).catch(() => {});\n },\n [host],\n );\n\n const handlePanChange = useCallback(\n (trackId: string, pan: number): void => {\n setTracks((prev) =>\n prev.map((t) => (t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)),\n );\n host.setTrackPan(trackId, pan).catch(() => {});\n },\n [host],\n );\n\n // --- Shuffle sound (keep MIDI, new sound) -------------------------------\n // Cycle pattern: exclude everything already tried; on exhaustion (adapter\n // classifies the error) wipe the deck and retry once with no exclusions.\n const handleShuffle = useCallback(\n async (trackId: string): Promise<void> => {\n const track = tracks.find((t) => t.handle.id === trackId);\n if (!track) return;\n // Lazy-seed: capture the pre-shuffle sound on the first shuffle so undo\n // can return to it.\n if (soundHistory.list(trackId).entries.length === 0) {\n try {\n const cap = await adapter.sound.captureSoundDescriptor(trackId);\n if (cap) soundHistory.record(trackId, cap.descriptor, adapter.sound.previousSoundLabel);\n } catch {\n // Non-fatal — history just won't include this sound.\n }\n }\n try {\n let result: { appliedName: string };\n let nextHistory: Set<string>;\n try {\n result = await adapter.shuffle.shuffle(track, Array.from(track.shuffleHistory));\n nextHistory = new Set(track.shuffleHistory);\n } catch (firstErr: unknown) {\n // Distinguish \"exhausted\" (expected, retry with fresh deck) from\n // other failures (surface to user).\n if (adapter.shuffle.isExhaustedError(firstErr)) {\n nextHistory = new Set<string>();\n result = await adapter.shuffle.shuffle(track, []);\n } else {\n throw firstErr;\n }\n }\n nextHistory.add(result.appliedName);\n setTracks((prev) =>\n prev.map((t) => (t.handle.id === trackId ? { ...t, shuffleHistory: nextHistory } : t)),\n );\n // Record the new sound so the History tab can return to it.\n try {\n const cap = await adapter.sound.captureSoundDescriptor(trackId);\n if (cap) soundHistory.record(trackId, cap.descriptor, result.appliedName);\n } catch {\n // Non-fatal.\n }\n console.log(`[${logTag}] Sound shuffled: ${result.appliedName} (history ${nextHistory.size})`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : 'Shuffle failed';\n host.showToast('error', 'Shuffle failed', msg);\n }\n },\n [host, adapter, tracks, soundHistory, logTag],\n );\n\n // --- Duplicate track (copy MIDI, new sound) -----------------------------\n const handleCopy = useCallback(\n async (trackId: string): Promise<void> => {\n try {\n const newHandle = await host.duplicateTrack(trackId);\n // Reload tracks to pick up the new one with full state\n await loadTracks();\n host.showToast('success', 'Track duplicated', newHandle.name);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : 'Copy failed';\n host.showToast('error', 'Copy failed', msg);\n }\n },\n [host, loadTracks],\n );\n\n // --- FX Operations (optimistic UI) --------------------------------------\n const handleFxToggle = useCallback(\n (trackId: string, category: FxCategory, enabled: boolean): void => {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId\n ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], enabled } } }\n : t,\n ),\n );\n host.toggleTrackFx(trackId, category, enabled).catch(() => {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId\n ? {\n ...t,\n fxDetailState: {\n ...t.fxDetailState,\n [category]: { ...t.fxDetailState[category], enabled: !enabled },\n },\n }\n : t,\n ),\n );\n });\n },\n [host],\n );\n\n const handleFxPresetChange = useCallback(\n (trackId: string, category: FxCategory, presetIndex: number): void => {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId\n ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], presetIndex } } }\n : t,\n ),\n );\n host\n .setTrackFxPreset(trackId, category, presetIndex)\n .then((result) => {\n if (result.dryWet !== undefined) {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId\n ? {\n ...t,\n fxDetailState: {\n ...t.fxDetailState,\n [category]: { ...t.fxDetailState[category], dryWet: result.dryWet as number },\n },\n }\n : t,\n ),\n );\n }\n })\n .catch(() => {});\n },\n [host],\n );\n\n const handleFxDryWetChange = useCallback(\n (trackId: string, category: FxCategory, value: number): void => {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === trackId\n ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], dryWet: value } } }\n : t,\n ),\n );\n host.setTrackFxDryWet(trackId, category, value).catch(() => {});\n },\n [host],\n );\n\n const toggleFxDrawer = useCallback(\n (trackId: string): void => {\n setTracks((prev) =>\n prev.map((t) => {\n if (t.handle.id !== trackId) return t;\n const onFx = t.drawerOpen && t.drawerTab === 'fx';\n return { ...t, drawerOpen: !onFx, drawerTab: 'fx', editorStage: false };\n }),\n );\n // Refresh FX state from the engine whenever we OPEN the FX tab.\n const track = tracks.find((t) => t.handle.id === trackId);\n const wasOnFx = !!track && track.drawerOpen && track.drawerTab === 'fx';\n if (track && !wasOnFx) {\n host\n .getTrackFxState(trackId)\n .then((fxState) => {\n setTracks((prev) =>\n prev.map((t) => (t.handle.id === trackId ? { ...t, fxDetailState: pluginFxToToggleFx(fxState) } : t)),\n );\n })\n .catch(() => {});\n }\n },\n [host, tracks],\n );\n\n // --- Piano-roll edit: load + save ---------------------------------------\n const loadEditNotes = useCallback(\n async (trackId: string): Promise<void> => {\n try {\n const mc = await host.getMusicalContext();\n let notes: PluginMidiNote[] = [];\n if (typeof host.readMidiNotes === 'function') {\n const result = await host.readMidiNotes(trackId);\n notes = result.clips[0]?.notes ?? [];\n }\n setTracks((prev) =>\n prev.map((t) => (t.handle.id === trackId ? { ...t, editNotes: notes, editBars: mc.bars, editBpm: mc.bpm } : t)),\n );\n } catch (err: unknown) {\n console.warn(`[${logTag}] Failed to load MIDI for editing:`, err);\n }\n },\n [host, logTag],\n );\n\n // SAVE: optimistic local update, then debounced (300 ms) persist. Empty\n // note arrays go through clearMidi. Stable ([host]) to avoid re-subscribing\n // the editor / triggering load loops.\n const handleNotesChange = useCallback(\n (trackId: string, notes: PluginMidiNote[]): void => {\n setTracks((prev) => prev.map((t) => (t.handle.id === trackId ? { ...t, editNotes: notes } : t)));\n const key = `edit:${trackId}`;\n if (saveTimeoutRefs.current[key]) {\n clearTimeout(saveTimeoutRefs.current[key]);\n }\n saveTimeoutRefs.current[key] = setTimeout(() => {\n void (async (): Promise<void> => {\n try {\n if (notes.length === 0) {\n await host.clearMidi(trackId);\n } else {\n const mc = await host.getMusicalContext();\n await host.writeMidiClip(trackId, {\n startTime: 0,\n endTime: (mc.bars * 4 * 60) / mc.bpm,\n tempo: mc.bpm,\n notes,\n });\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n host.showToast('error', 'Failed to save edit', msg);\n }\n })();\n }, 300);\n },\n [host],\n );\n\n // Tab-strip clicks: switch the active tab, keeping the drawer open.\n const handleTabChange = useCallback(\n (trackId: string, tab: DrawerTab): void => {\n setTracks((prev) => prev.map((t) => (t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t)));\n if (tab === 'fx') {\n host\n .getTrackFxState(trackId)\n .then((fxState) => {\n setTracks((prev) =>\n prev.map((t) => (t.handle.id === trackId ? { ...t, fxDetailState: pluginFxToToggleFx(fxState) } : t)),\n );\n })\n .catch(() => {});\n } else if (tab === 'pick' && availableInstruments.length === 0 && !instrumentsLoading) {\n // Lazy-load available instruments the first time the Pick tab opens.\n setInstrumentsLoading(true);\n host\n .getAvailableInstruments()\n .then((instruments: InstrumentDescriptor[]) => {\n setAvailableInstruments(instruments);\n })\n .catch(() => {})\n .finally(() => {\n setInstrumentsLoading(false);\n });\n } else if (tab === 'edit' && !editLoadStartedRef.current.has(trackId)) {\n // Lazy-load the track's MIDI the first time the Edit tab opens.\n editLoadStartedRef.current.add(trackId);\n void loadEditNotes(trackId);\n }\n },\n [host, availableInstruments.length, instrumentsLoading, loadEditNotes],\n );\n\n // --- Progress persistence callback --------------------------------------\n const handleProgressChange = useCallback((trackId: string, pct: number): void => {\n setTracks((prev) => prev.map((t) => (t.handle.id === trackId ? { ...t, generationProgress: pct } : t)));\n }, []);\n\n // --- Instrument selection callbacks --------------------------------------\n const handleToggleDrawer = useCallback((trackId: string): void => {\n setTracks((prev) =>\n prev.map((t: GeneratorTrackState) => {\n if (t.handle.id !== trackId) return t;\n const onSound = t.drawerOpen && t.drawerTab !== 'fx';\n return { ...t, drawerOpen: !onSound, drawerTab: 'history', editorStage: false };\n }),\n );\n }, []);\n\n const handleInstrumentSelect = useCallback(\n async (trackId: string, pluginId: string): Promise<void> => {\n const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? 'Surge XT');\n\n if (isDefaultInstrument) {\n // Revert to default — close drawer\n setTracks((prev) =>\n prev.map((t: GeneratorTrackState) => (t.handle.id === trackId ? { ...t, drawerOpen: false, editorStage: false } : t)),\n );\n try {\n await host.setTrackInstrument(trackId, pluginId);\n const descriptor = await host.getTrackInstrument(trackId);\n setTracks((prev) =>\n prev.map((t: GeneratorTrackState) =>\n t.handle.id === trackId\n ? {\n ...t,\n instrumentPluginId: descriptor?.pluginId ?? null,\n instrumentName: descriptor?.name ?? null,\n instrumentMissing: descriptor?.missing ?? false,\n }\n : t,\n ),\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : 'Failed to load instrument';\n host.showToast('error', 'Instrument load failed', msg);\n }\n return;\n }\n\n // Custom instrument — load it, then transition to the editor stage\n setTracks((prev) =>\n prev.map((t: GeneratorTrackState) => (t.handle.id === trackId ? { ...t, drawerTab: 'pick', editorStage: true } : t)),\n );\n\n try {\n await host.setTrackInstrument(trackId, pluginId);\n const descriptor = await host.getTrackInstrument(trackId);\n setTracks((prev) =>\n prev.map((t: GeneratorTrackState) =>\n t.handle.id === trackId\n ? {\n ...t,\n instrumentPluginId: descriptor?.pluginId ?? null,\n instrumentName: descriptor?.name ?? null,\n instrumentMissing: descriptor?.missing ?? false,\n }\n : t,\n ),\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : 'Failed to load instrument';\n console.error(`[${logTag}] Failed to set instrument:`, err);\n host.showToast('error', 'Instrument load failed', msg);\n // Revert to the instrument grid on failure\n setTracks((prev) =>\n prev.map((t: GeneratorTrackState) => (t.handle.id === trackId ? { ...t, editorStage: false } : t)),\n );\n }\n },\n [host, identity.defaultInstrumentPluginId, logTag],\n );\n\n const handleShowEditor = useCallback(\n async (trackId: string): Promise<void> => {\n try {\n await host.showInstrumentEditor(trackId);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : 'Failed to open editor';\n host.showToast('error', 'Editor failed', msg);\n }\n },\n [host],\n );\n\n const handleBackToInstruments = useCallback((trackId: string): void => {\n setTracks((prev) =>\n prev.map((t: GeneratorTrackState) => (t.handle.id === trackId ? { ...t, editorStage: false } : t)),\n );\n }, []);\n\n const handleRefreshInstruments = useCallback((): void => {\n setInstrumentsLoading(true);\n host\n .getAvailableInstruments()\n .then((instruments: InstrumentDescriptor[]) => {\n setAvailableInstruments(instruments);\n })\n .catch(() => {})\n .finally(() => {\n setInstrumentsLoading(false);\n });\n }, [host]);\n\n const onAuditionNote = useCallback(\n (trackId: string, pitch: number, velocity: number, ms: number): void => {\n void host.auditionNote(trackId, pitch, velocity, ms);\n },\n [host],\n );\n\n // --- Resolve crossfade pairs / fades against live track state -----------\n const { resolvedCrossfadePairs, crossfadeMemberDbIds } = useMemo(() => {\n const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));\n const pairs: ResolvedCrossfadePair[] = [];\n const members = new Set<string>();\n for (const p of crossfadePairsMeta) {\n const origin = byDbId.get(p.originDbId);\n const target = byDbId.get(p.targetDbId);\n if (origin && target) {\n pairs.push({ ...p, origin, target });\n members.add(p.originDbId);\n members.add(p.targetDbId);\n }\n }\n return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };\n }, [tracks, crossfadePairsMeta]);\n\n const { resolvedFades, fadeMemberDbIds } = useMemo(() => {\n const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));\n const list: ResolvedFade[] = [];\n const members = new Set<string>();\n for (const f of fadesMeta) {\n const track = byDbId.get(f.dbId);\n if (track) {\n list.push({ ...f, track });\n members.add(f.dbId);\n }\n }\n return { resolvedFades: list, fadeMemberDbIds: members };\n }, [tracks, fadesMeta]);\n\n // --- Transition ops (create/controls/effects) ---------------------------\n const transition = useTransitionOps({\n host,\n adapter,\n activeSceneId,\n isConnected,\n isAuthenticated,\n sceneContext,\n tracks,\n setTracks,\n loadTracks,\n setCrossfadePairsMeta,\n setFadesMeta,\n resolvedCrossfadePairs,\n resolvedFades,\n });\n\n // --- Group ops (generic extensions) --------------------------------------\n const setGroupMute = useCallback(\n (trackIds: string[], muted: boolean): void => {\n for (const id of trackIds) {\n setTracks((prev) =>\n prev.map((t) => (t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, muted } } : t)),\n );\n host.setTrackMute(id, muted).catch(() => {});\n }\n },\n [host],\n );\n const setGroupSolo = useCallback(\n (trackIds: string[], solo: boolean): void => {\n for (const id of trackIds) {\n setTracks((prev) =>\n prev.map((t) => (t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, solo } } : t)),\n );\n host.setTrackSolo(id, solo).catch(() => {});\n }\n },\n [host],\n );\n const deleteGroup = useCallback(\n async (\n members: Array<{ engineId: string; dbId: string }>,\n cleanupKeySuffixes: string[],\n ): Promise<void> => {\n for (const member of members) {\n try {\n await host.deleteTrack(member.engineId);\n } catch {\n /* best effort */\n }\n if (activeSceneId) {\n for (const suffix of cleanupKeySuffixes) {\n await host.deleteSceneData(activeSceneId, trackDataKey(member.dbId, suffix)).catch(() => {});\n }\n }\n }\n const gone = new Set(members.map((m) => m.engineId));\n setTracks((prev) => prev.filter((t) => !gone.has(t.handle.id)));\n await loadTracks(true);\n },\n [host, activeSceneId, loadTracks],\n );\n\n // --- Bundled per-track handlers (group render contexts + shell rows) -----\n const handlers = useMemo<CoreTrackHandlers>(\n () => ({\n promptChange: handlePromptChange,\n generate: (trackId: string) => {\n void handleGenerate(trackId);\n },\n shuffle: (trackId: string) => {\n void handleShuffle(trackId);\n },\n copy: (trackId: string) => {\n void handleCopy(trackId);\n },\n delete: (trackId: string) => {\n void handleDeleteTrack(trackId);\n },\n muteToggle: handleMuteToggle,\n soloToggle: handleSoloToggle,\n volumeChange: handleVolumeChange,\n panChange: handlePanChange,\n tabChange: handleTabChange,\n toggleDrawer: handleToggleDrawer,\n toggleFxDrawer,\n notesChange: handleNotesChange,\n progressChange: handleProgressChange,\n }),\n [\n handlePromptChange,\n handleGenerate,\n handleShuffle,\n handleCopy,\n handleDeleteTrack,\n handleMuteToggle,\n handleSoloToggle,\n handleVolumeChange,\n handlePanChange,\n handleTabChange,\n handleToggleDrawer,\n toggleFxDrawer,\n handleNotesChange,\n handleProgressChange,\n ],\n );\n\n return {\n ui,\n adapter,\n tracks,\n setTracks,\n isLoadingTracks,\n loadTracks,\n engineToDbId,\n supportsMeters,\n trackLevels,\n anySolo,\n reorder,\n soundHistory,\n isComposing,\n placeholders,\n isAddingTrack,\n isExportingMidi,\n designerView,\n canCrossfade,\n needsContract,\n xfFromId,\n xfToId,\n importOpen,\n setImportOpen,\n soundImportTarget,\n setSoundImportTarget,\n handleSoundImportPick,\n handlePortTrack,\n transition,\n crossfadePairsMeta,\n fadesMeta,\n resolvedCrossfadePairs,\n crossfadeMemberDbIds,\n resolvedFades,\n fadeMemberDbIds,\n resolvedGenericGroups,\n genericGroupMemberDbIds,\n availableInstruments,\n instrumentsLoading,\n handlers,\n handleGenerate,\n handleShuffle,\n handleAddTrack,\n handleDeleteTrack,\n handleExportMidi,\n handlePromptChange,\n handleMuteToggle,\n handleSoloToggle,\n handleVolumeChange,\n handlePanChange,\n handleTabChange,\n handleToggleDrawer,\n toggleFxDrawer,\n handleNotesChange,\n handleProgressChange,\n handleCopy,\n handleFxToggle,\n handleFxPresetChange,\n handleFxDryWetChange,\n handleInstrumentSelect,\n handleShowEditor,\n handleBackToInstruments,\n handleRefreshInstruments,\n onAuditionNote,\n makeServices,\n setGroupMute,\n setGroupSolo,\n deleteGroup,\n };\n}\n","/**\n * useSceneState — Scene-keyed state hook for plugin developers.\n *\n * Works like `useState`, but maintains separate state per scene.\n * When the user switches scenes, the previous scene's state is preserved\n * and restored when they switch back.\n *\n * Returns `[value, setForCurrentScene, setForScene]`:\n * - `value` — state for the currently active scene\n * - `setForCurrentScene(v)` — updates state for whatever scene is active at call time\n * - `setForScene(sceneId, v)` — updates state for a specific scene (for async callbacks)\n *\n * Both setters support the functional updater pattern: `prev => next`.\n *\n * **Important:** For object/array `initialValue`, hoist to a module-level constant\n * to keep the setter callbacks referentially stable:\n * ```ts\n * const EMPTY: string[] = [];\n * const [items, setItems, setItemsForScene] = useSceneState(activeSceneId, EMPTY);\n * ```\n */\n\nimport { useState, useCallback, useRef } from 'react';\n\ntype SetSceneState<T> = (value: T | ((prev: T) => T)) => void;\ntype SetSceneStateForScene<T> = (sceneId: string, value: T | ((prev: T) => T)) => void;\n\nexport function useSceneState<T>(\n activeSceneId: string | null,\n initialValue: T\n): [T, SetSceneState<T>, SetSceneStateForScene<T>] {\n const [stateMap, setStateMap] = useState<Map<string, T>>(() => new Map());\n const activeSceneIdRef = useRef(activeSceneId);\n activeSceneIdRef.current = activeSceneId;\n\n const currentValue = activeSceneId !== null && stateMap.has(activeSceneId)\n ? stateMap.get(activeSceneId)!\n : initialValue;\n\n const setForCurrentScene = useCallback((value: T | ((prev: T) => T)): void => {\n const sid = activeSceneIdRef.current;\n if (sid === null) return;\n setStateMap(prev => {\n const current = prev.has(sid) ? prev.get(sid)! : initialValue;\n const next = typeof value === 'function' ? (value as (prev: T) => T)(current) : value;\n const newMap = new Map(prev);\n newMap.set(sid, next);\n return newMap;\n });\n }, [initialValue]);\n\n const setForScene = useCallback((sceneId: string, value: T | ((prev: T) => T)): void => {\n setStateMap(prev => {\n const current = prev.has(sceneId) ? prev.get(sceneId)! : initialValue;\n const next = typeof value === 'function' ? (value as (prev: T) => T)(current) : value;\n const newMap = new Map(prev);\n newMap.set(sceneId, next);\n return newMap;\n });\n }, [initialValue]);\n\n return [currentValue, setForCurrentScene, setForScene];\n}\n","/**\n * useAnySolo — reactively reports whether ANY track in the project is soloed.\n *\n * Solo is cross-panel: when the user solos a track in ANY panel, the engine's\n * effective-mute model silences every non-soloed track. A panel uses this flag\n * to DIM its own non-soloed rows without lighting their Mute buttons:\n *\n * ```tsx\n * const anySolo = useAnySolo(host);\n * // ...\n * <TrackRow soloedOut={anySolo && !track.runtimeState.solo} ... />\n * ```\n *\n * Refreshes on mount and on every track-state change. `onTrackStateChange`\n * fires for tracks in ALL panels (not just this plugin's), so a solo toggled in\n * another panel updates this flag too.\n */\n\nimport { useEffect, useState } from 'react';\nimport type { PluginHost } from '../types/plugin-sdk.types';\n\nexport function useAnySolo(\n host: Pick<PluginHost, 'isAnySoloActive' | 'onTrackStateChange'>\n): boolean {\n const [anySolo, setAnySolo] = useState(false);\n\n useEffect(() => {\n let active = true;\n const refresh = (): void => {\n host\n .isAnySoloActive()\n .then((v) => {\n if (active) setAnySolo(v);\n })\n .catch(() => {\n /* engine unreachable — leave the flag as-is rather than flicker */\n });\n };\n refresh();\n const unsub = host.onTrackStateChange(() => refresh());\n return () => {\n active = false;\n unsub();\n };\n }, [host]);\n\n return anySolo;\n}\n","/**\n * useSoundHistory — generic, per-track \"what sounds has this track had?\" stack.\n *\n * Powers the drawer \"History\" tab: restore any earlier sound, star favorites,\n * and (via the host plugin) persist across project reopen. The SDK is ignorant\n * of WHAT a sound is — each plugin records an opaque `descriptor` (a drum sample\n * path / an instrument `{ displayName, zones }` / a synth Surge state blob) plus\n * a human `label`, and supplies `applySound` to re-apply a chosen descriptor.\n *\n * Persistence is the plugin's job: pass `opts.onChange` (called after every\n * mutation with the new state) to save, and call `restore()` on load to seed.\n * Favorited entries are never auto-evicted by the cap.\n *\n * Robustness: `applySound` + `onChange` are read through refs, so the returned\n * object is referentially STABLE regardless of whether the caller memoizes them.\n * Plugins list this object in `loadTracks` deps — an unstable return previously\n * caused a render loop, so keep it stable.\n *\n * @since SDK 2.13.0\n */\n\nimport { useCallback, useMemo, useRef, useState } from 'react';\nimport type { SoundHistoryEntry } from '../types/plugin-sdk.types';\n\nexport type { SoundHistoryEntry };\n\n/** A track's ordered sound history plus the index of the currently-applied sound. */\nexport interface TrackSoundHistory {\n entries: readonly SoundHistoryEntry[];\n /** Index into `entries` of the currently-applied sound; -1 when empty. */\n cursor: number;\n}\n\nexport interface UseSoundHistoryOptions {\n /** Max non-favorited entries kept per track (favorites are never evicted). Default 24. */\n max?: number;\n /**\n * Called after every mutation (record/undo/restoreTo/toggleFavorite/clear) with the\n * track's new state — use it to persist. NOT called by `restore()` (that's a load).\n */\n onChange?: (trackId: string, state: TrackSoundHistory) => void;\n}\n\nexport interface UseSoundHistoryResult {\n /** Remember a sound that was just applied (generation, scene-load, or shuffle). */\n record(trackId: string, descriptor: unknown, label: string): void;\n /** Re-apply the sound one step before the current one. Resolves true if it moved. */\n undo(trackId: string): Promise<boolean>;\n /** Re-apply a specific entry by index. Resolves true if it applied. */\n restoreTo(trackId: string, index: number): Promise<boolean>;\n /** The ordered history + cursor for a track (safe empty default). */\n list(trackId: string): TrackSoundHistory;\n /** Whether there is an earlier sound to step back to. */\n canUndo(trackId: string): boolean;\n /** Forget a track's history (e.g. on regenerate). Persists the cleared state. */\n clear(trackId: string): void;\n /** Forget ALL tracks' history in memory (e.g. before re-seeding on scene load). */\n reset(): void;\n /** Seed a track's full history (e.g. from persistence on load). Does NOT fire onChange. */\n restore(\n trackId: string,\n state: { entries?: readonly SoundHistoryEntry[]; cursor?: number } | null | undefined,\n ): void;\n /** Toggle the favorite flag on an entry (favorites survive cap eviction). */\n toggleFavorite(trackId: string, index: number): void;\n}\n\nconst EMPTY: TrackSoundHistory = { entries: [], cursor: -1 };\n\nfunction sameDescriptor(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n}\n\nexport function useSoundHistory(\n applySound: (trackId: string, descriptor: unknown) => Promise<void>,\n opts: UseSoundHistoryOptions = {},\n): UseSoundHistoryResult {\n const max = Math.max(2, opts.max ?? 24);\n\n // Read callbacks through refs so the returned API stays referentially stable\n // even if the caller passes a fresh closure each render.\n const applyRef = useRef(applySound);\n applyRef.current = applySound;\n const onChangeRef = useRef(opts.onChange);\n onChangeRef.current = opts.onChange;\n\n // Authoritative store in a ref (async callbacks read latest); version forces re-render.\n const dataRef = useRef<Record<string, TrackSoundHistory>>({});\n const [, setVersion] = useState(0);\n const bump = useCallback((): void => setVersion((v) => v + 1), []);\n\n // Single writer: update store, re-render, optionally notify for persistence.\n const commit = useCallback(\n (trackId: string, next: TrackSoundHistory, notify: boolean): void => {\n dataRef.current = { ...dataRef.current, [trackId]: next };\n bump();\n if (notify) onChangeRef.current?.(trackId, next);\n },\n [bump],\n );\n\n const record = useCallback(\n (trackId: string, descriptor: unknown, label: string): void => {\n const h = dataRef.current[trackId];\n const current = h && h.cursor >= 0 ? h.entries[h.cursor] : undefined;\n // Ignore re-applying the same sound (no-op shuffles, scene re-seeds).\n if (current && sameDescriptor(current.descriptor, descriptor)) return;\n const entries: SoundHistoryEntry[] = [...(h ? h.entries : []), { descriptor, label }];\n // Cap: evict the OLDEST NON-FAVORITED entry when over the limit (favorites survive).\n while (entries.length > max) {\n const victim = entries.findIndex((e) => !e.favorite);\n if (victim === -1) break; // everything is favorited — keep it all\n entries.splice(victim, 1);\n }\n commit(trackId, { entries, cursor: entries.length - 1 }, true);\n },\n [max, commit],\n );\n\n const restoreTo = useCallback(\n async (trackId: string, index: number): Promise<boolean> => {\n const h = dataRef.current[trackId];\n if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;\n await applyRef.current(trackId, h.entries[index].descriptor);\n commit(trackId, { entries: h.entries, cursor: index }, true);\n return true;\n },\n [commit],\n );\n\n const undo = useCallback(\n (trackId: string): Promise<boolean> => {\n const h = dataRef.current[trackId];\n if (!h || h.cursor <= 0) return Promise.resolve(false);\n return restoreTo(trackId, h.cursor - 1);\n },\n [restoreTo],\n );\n\n const toggleFavorite = useCallback(\n (trackId: string, index: number): void => {\n const h = dataRef.current[trackId];\n if (!h || index < 0 || index >= h.entries.length) return;\n const entries = h.entries.map((e, i) => (i === index ? { ...e, favorite: !e.favorite } : e));\n commit(trackId, { entries, cursor: h.cursor }, true);\n },\n [commit],\n );\n\n const restore = useCallback(\n (\n trackId: string,\n state: { entries?: readonly SoundHistoryEntry[]; cursor?: number } | null | undefined,\n ): void => {\n const entries: SoundHistoryEntry[] = Array.isArray(state?.entries) ? [...state!.entries] : [];\n const raw = typeof state?.cursor === 'number' ? state!.cursor : entries.length - 1;\n const cursor = entries.length === 0 ? -1 : Math.min(Math.max(raw, 0), entries.length - 1);\n commit(trackId, { entries, cursor }, false);\n },\n [commit],\n );\n\n const list = useCallback(\n (trackId: string): TrackSoundHistory => dataRef.current[trackId] ?? EMPTY,\n [],\n );\n\n const canUndo = useCallback((trackId: string): boolean => {\n const h = dataRef.current[trackId];\n return !!h && h.cursor > 0;\n }, []);\n\n const clear = useCallback(\n (trackId: string): void => {\n if (dataRef.current[trackId]) {\n const next = { ...dataRef.current };\n delete next[trackId];\n dataRef.current = next;\n bump();\n }\n onChangeRef.current?.(trackId, EMPTY); // persist the cleared state\n },\n [bump],\n );\n\n const reset = useCallback((): void => {\n dataRef.current = {};\n bump();\n }, [bump]);\n\n // Stable object so consumers can safely list it in useCallback/useEffect deps.\n return useMemo(\n () => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),\n [record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite],\n );\n}\n","/**\n * Per-track state model shared by every generator panel built on the\n * panel-core. Verbatim generalization of the synth panel's SynthTrackState\n * (SynthGeneratorPanel.tsx:67–100) — field names, defaults, and semantics are\n * frozen by the Phase-0 behavior pin.\n *\n * @since SDK 2.35.0\n */\n\nimport type {\n PluginTrackHandle,\n PluginTrackRuntimeState,\n PluginMidiNote,\n} from '../types/plugin-sdk.types';\nimport { EMPTY_FX_DETAIL_STATE, type TrackFxDetailState } from '../types/fx-toggle.types';\nimport type { DrawerTab } from '../components/TrackDrawer';\n\n/** Internal track state combining handle + runtime state + prompt. */\nexport interface GeneratorTrackState {\n handle: PluginTrackHandle;\n prompt: string;\n role: string;\n runtimeState: PluginTrackRuntimeState;\n fxDetailState: TrackFxDetailState;\n // Unified drawer state (fx / pick / edit / history tabs).\n drawerOpen: boolean;\n drawerTab: DrawerTab;\n editorStage: boolean;\n isGenerating: boolean;\n error: string | null;\n hasMidi: boolean;\n generationProgress: number;\n // Piano-roll edit state. `editNotes` is the live, editable copy of the\n // track's MIDI (loaded lazily when the Edit tab is first opened, or seeded\n // from a fresh generation). `editBars`/`editBpm` size the grid + the save\n // span.\n editNotes: PluginMidiNote[];\n editBars: number;\n editBpm: number;\n instrumentPluginId: string | null;\n instrumentName: string | null;\n instrumentMissing: boolean;\n /**\n * Per-track shuffle history: sound/preset names already handed back since\n * the track was created OR since the history was reset (which happens\n * automatically when the pool is exhausted — the family shuffle adapter\n * reports \"exhausted\" and the core wipes the history and retries). Cycle\n * pattern: cycle through everything before any repeat.\n */\n shuffleHistory: Set<string>;\n}\n\n/**\n * Fresh track state with the panel defaults (the add-track literal at\n * SynthGeneratorPanel.tsx:634–654). `overrides` lets loadTracks hydrate\n * prompt/role/runtime/fx/etc. from fetched state in one construction.\n */\nexport function newTrackState(\n handle: PluginTrackHandle,\n overrides: Partial<Omit<GeneratorTrackState, 'handle'>> = {},\n): GeneratorTrackState {\n return {\n handle,\n prompt: '',\n role: '',\n runtimeState: { id: handle.id, muted: false, solo: false, volume: 0.75, pan: 0 },\n fxDetailState: { ...EMPTY_FX_DETAIL_STATE },\n drawerOpen: false,\n drawerTab: 'fx',\n editorStage: false,\n isGenerating: false,\n error: null,\n hasMidi: false,\n generationProgress: 0,\n editNotes: [],\n editBars: 4,\n editBpm: 120,\n instrumentPluginId: handle.instrumentPluginId ?? null,\n instrumentName: handle.instrumentName ?? null,\n instrumentMissing: false,\n shuffleHistory: new Set<string>(),\n ...overrides,\n };\n}\n","/**\n * Small pure helpers shared by every generator panel — moved verbatim out of\n * the three panel monoliths (synth/drum/instrument each carried a copy of\n * pluginFxToToggleFx and an LLM note-response parser).\n *\n * @since SDK 2.35.0\n */\n\nimport type {\n PluginTrackFxDetailState,\n PluginFxCategoryDetailState,\n PluginMidiNote,\n} from '../types/plugin-sdk.types';\nimport { EMPTY_FX_DETAIL_STATE, type TrackFxDetailState } from '../types/fx-toggle.types';\n\n/**\n * Build a scene plugin_data key for a track-scoped value. Scene-data keys are\n * ALWAYS constructed from the stable DB UUID (`handle.dbId`) — never the\n * engine id, which changes on project reload. This is the ONLY key builder\n * panels and generation strategies should use.\n */\nexport function trackDataKey(dbId: string, suffix: string): string {\n return `track:${dbId}:${suffix}`;\n}\n\n/** Convert SDK PluginTrackFxDetailState to the FxToggleBar's expected TrackFxDetailState. */\nexport function pluginFxToToggleFx(sdkState: PluginTrackFxDetailState): TrackFxDetailState {\n const result = { ...EMPTY_FX_DETAIL_STATE };\n for (const category of ['eq', 'compressor', 'chorus', 'phaser', 'delay', 'reverb'] as const) {\n const sdkCat = sdkState[category] as PluginFxCategoryDetailState | undefined;\n if (sdkCat) {\n result[category] = {\n enabled: sdkCat.enabled,\n presetIndex: sdkCat.presetIndex,\n dryWet: sdkCat.dryWet,\n };\n }\n }\n return result;\n}\n\n/** Shape of the parsed flat LLM JSON note response. */\nexport interface LLMNoteResponse {\n notes: PluginMidiNote[];\n role?: string;\n}\n\n/**\n * Parse the LLM JSON response and extract valid MIDI notes (flat\n * `{notes:[...], role?}` schema). Handles markdown code fences; silently\n * filters invalid notes; returns null when nothing parses.\n */\nexport function parseLLMNoteResponse(content: string): LLMNoteResponse | null {\n try {\n // Try to extract JSON from the response (handle markdown code fences)\n let jsonStr = content.trim();\n const fenceMatch = jsonStr.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)```/);\n if (fenceMatch) {\n jsonStr = fenceMatch[1].trim();\n }\n\n const parsed: unknown = JSON.parse(jsonStr);\n if (typeof parsed !== 'object' || parsed === null || !('notes' in parsed)) {\n return null;\n }\n\n const obj = parsed as Record<string, unknown>;\n if (!Array.isArray(obj.notes)) {\n return null;\n }\n\n const validNotes: PluginMidiNote[] = [];\n for (const raw of obj.notes) {\n if (typeof raw !== 'object' || raw === null) continue;\n const note = raw as Record<string, unknown>;\n\n const pitch = typeof note.pitch === 'number' ? note.pitch : NaN;\n const startBeat = typeof note.startBeat === 'number' ? note.startBeat : NaN;\n const durationBeats = typeof note.durationBeats === 'number' ? note.durationBeats : NaN;\n const velocity = typeof note.velocity === 'number' ? note.velocity : NaN;\n\n if (\n !isNaN(pitch) && pitch >= 0 && pitch <= 127 &&\n !isNaN(startBeat) && startBeat >= 0 &&\n !isNaN(durationBeats) && durationBeats > 0 &&\n !isNaN(velocity) && velocity >= 1 && velocity <= 127\n ) {\n validNotes.push({\n pitch: Math.round(pitch),\n startBeat,\n durationBeats,\n velocity: Math.round(velocity),\n });\n }\n }\n\n const role = typeof obj.role === 'string' ? obj.role : undefined;\n\n return { notes: validNotes, role };\n } catch {\n return null;\n }\n}\n","/**\n * Generic multi-track group seam — the crossfade-pair pattern, family- and\n * meta-parameterized.\n *\n * A \"track group\" is N normal tracks linked by a shared `groupId` persisted in\n * scene plugin_data under one key PER MEMBER: `track:<dbId>:<metaKey>`. Groups\n * are never stored as a group-level record; they are assembled by scanning the\n * member keys (single source of truth, survives member deletion gracefully).\n * The panel-core resolves parsed groups against live tracks each render:\n * complete groups render through a custom group row and their members are\n * excluded from the normal row list; incomplete groups degrade per the\n * extension's `isComplete` policy (crossfade: both members required; a bass\n * voice-group: anchor required).\n *\n * This is the seam the crossfade/fade metas established (crossfade-meta.ts);\n * new group families (e.g. the bass plugin's voice groups) ride it without\n * panel-core changes.\n *\n * @since SDK 2.35.0\n */\n\n/** One parsed member: the scene-data key's dbId + its narrowed meta value. */\nexport interface TrackGroupMember<M> {\n dbId: string;\n meta: M;\n}\n\n/** One parsed group (members in `sortMembers` order when provided). */\nexport interface TrackGroupMeta<M> {\n groupId: string;\n members: TrackGroupMember<M>[];\n}\n\n/** How to scan + narrow one group family out of scene plugin_data. */\nexport interface GroupParseSpec<M> {\n /** Scene-data key suffix: scans `track:<dbId>:<metaKey>`. */\n metaKey: string;\n /** Defensive narrow (the `asCrossfadeMeta` pattern) — return null to skip. */\n asMeta(val: unknown): M | null;\n /** Extract the shared group id from a member meta. */\n groupIdOf(meta: M): string;\n /** Stable member order (e.g. by voiceIndex). Omit = scene-data scan order. */\n sortMembers?(a: TrackGroupMember<M>, b: TrackGroupMember<M>): number;\n}\n\n/**\n * Scan all `track:<dbId>:<metaKey>` keys in a scene's plugin_data and assemble\n * groups. Pure — no I/O; caller passes the already-fetched scene data map.\n */\nexport function parseTrackGroups<M>(\n sceneData: Record<string, unknown>,\n spec: GroupParseSpec<M>,\n): TrackGroupMeta<M>[] {\n const pattern = new RegExp(`^track:(.+):${spec.metaKey}$`);\n const groups = new Map<string, TrackGroupMember<M>[]>();\n for (const [key, val] of Object.entries(sceneData)) {\n const match = pattern.exec(key);\n if (!match) continue;\n const meta = spec.asMeta(val);\n if (!meta) continue;\n const groupId = spec.groupIdOf(meta);\n const list = groups.get(groupId) ?? [];\n list.push({ dbId: match[1], meta });\n groups.set(groupId, list);\n }\n const out: TrackGroupMeta<M>[] = [];\n for (const [groupId, members] of groups) {\n if (spec.sortMembers) members.sort(spec.sortMembers);\n out.push({ groupId, members });\n }\n return out;\n}\n\n/** A group resolved against live tracks (only members whose track exists). */\nexport interface ResolvedTrackGroup<M, T> {\n groupId: string;\n members: Array<{ dbId: string; meta: M; track: T }>;\n}\n\nexport interface ResolveGroupsOptions<M, T> {\n /**\n * Group completeness policy. A group failing this renders as loose normal\n * rows instead (its members are NOT excluded). Default: every PARSED member\n * resolved a live track — the crossfade rule (partner deleted ⇒ degrade).\n */\n isComplete?(group: ResolvedTrackGroup<M, T>, parsed: TrackGroupMeta<M>): boolean;\n}\n\nexport interface ResolvedGroupsResult<M, T> {\n /** Complete groups, ready for the group row renderer. */\n resolved: ResolvedTrackGroup<M, T>[];\n /** dbIds of members of COMPLETE groups — exclude these from normal rows. */\n memberDbIds: Set<string>;\n /**\n * dbIds whose member key exists but whose track is gone (deleted\n * out-of-band) — candidates for lazy scene-data cleanup.\n */\n staleMemberDbIds: string[];\n}\n\n/**\n * Resolve parsed groups against live track state. Pure; call from a useMemo\n * keyed on [tracks, parsedGroups] (fresh array identities per call are\n * expected — do NOT key effects on the arrays without a string-key guard,\n * see the drift-resync `lastResyncKeyRef` pattern).\n */\nexport function resolveTrackGroups<M, T>(\n parsedGroups: TrackGroupMeta<M>[],\n tracks: readonly T[],\n getDbId: (track: T) => string,\n opts: ResolveGroupsOptions<M, T> = {},\n): ResolvedGroupsResult<M, T> {\n const byDbId = new Map<string, T>();\n for (const t of tracks) byDbId.set(getDbId(t), t);\n\n const resolved: ResolvedTrackGroup<M, T>[] = [];\n const memberDbIds = new Set<string>();\n const staleMemberDbIds: string[] = [];\n\n for (const parsed of parsedGroups) {\n const live: ResolvedTrackGroup<M, T> = { groupId: parsed.groupId, members: [] };\n for (const member of parsed.members) {\n const track = byDbId.get(member.dbId);\n if (track) live.members.push({ dbId: member.dbId, meta: member.meta, track });\n else staleMemberDbIds.push(member.dbId);\n }\n if (live.members.length === 0) continue;\n const complete = opts.isComplete\n ? opts.isComplete(live, parsed)\n : live.members.length === parsed.members.length;\n if (!complete) continue; // degrade: members render as normal rows\n resolved.push(live);\n for (const m of live.members) memberDbIds.add(m.dbId);\n }\n\n return { resolved, memberDbIds, staleMemberDbIds };\n}\n","/**\n * Transition-scene machinery for panel-core panels — crossfade pair + fade\n * creation, group controls, sliders, drift re-sync, and fade curve re-apply.\n * Moved VERBATIM from the synth panel (SynthGeneratorPanel.tsx 715–987,\n * 1148–1235, 1787–1847) with the family-specific pieces routed through the\n * GeneratorPanelAdapter (sound copy/persist, system prompt, note parsing,\n * track naming). Semantics are frozen by the Phase-0 behavior pin.\n *\n * Deliberately NOT rewritten onto the generic group seam (group-meta.ts) —\n * that seam is additive for new families (bass voice groups); migrating\n * crossfades onto it is a contained follow-up.\n *\n * @since SDK 2.35.0\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type {\n PluginHost,\n PluginTrackHandle,\n PluginSceneContext,\n MidiClipData,\n} from '../types/plugin-sdk.types';\nimport {\n EQUAL_POWER_GAIN,\n asCrossfadeMeta,\n soundIdentity,\n buildCrossfadeVolumeCurves,\n type CrossfadeMeta,\n type CrossfadePairMeta,\n} from '../crossfade-meta';\nimport { buildCrossfadeInpaintPrompt } from '../crossfade-inpaint';\nimport {\n asFadeMeta,\n buildFadeVolumeCurve,\n type FadeDirection,\n type FadeGesture,\n type FadeMeta,\n type FadeEntry,\n} from '../fade-meta';\nimport type { CrossfadeSelection } from '../components/CrossfadeModal';\nimport type { FadeSelection } from '../components/FadeModal';\nimport type { GeneratorTrackState } from './track-state';\nimport type { GeneratorPanelAdapter } from './adapter.types';\n\n/** A crossfade pair resolved against live track state (both members present). */\nexport interface ResolvedCrossfadePair extends CrossfadePairMeta {\n origin: GeneratorTrackState;\n target: GeneratorTrackState;\n}\n\n/** A fade (transition orphan) resolved against live track state. */\nexport interface ResolvedFade extends FadeEntry {\n track: GeneratorTrackState;\n}\n\nexport interface UseTransitionOpsInputs {\n host: PluginHost;\n adapter: GeneratorPanelAdapter;\n activeSceneId: string | null;\n isConnected: boolean;\n isAuthenticated: boolean;\n sceneContext: PluginSceneContext | null | undefined;\n tracks: GeneratorTrackState[];\n setTracks: React.Dispatch<React.SetStateAction<GeneratorTrackState[]>>;\n loadTracks(incremental?: boolean): Promise<void>;\n setCrossfadePairsMeta: React.Dispatch<React.SetStateAction<CrossfadePairMeta[]>>;\n setFadesMeta: React.Dispatch<React.SetStateAction<FadeEntry[]>>;\n resolvedCrossfadePairs: ResolvedCrossfadePair[];\n resolvedFades: ResolvedFade[];\n}\n\nexport interface TransitionOps {\n isCreatingCrossfade: boolean;\n isCreatingFade: boolean;\n handleCreateCrossfade(origin: CrossfadeSelection, target: CrossfadeSelection): Promise<void>;\n handleCreateFade(\n selection: FadeSelection,\n direction: FadeDirection,\n gesture: FadeGesture,\n ): Promise<void>;\n handleCrossfadeMute(pair: ResolvedCrossfadePair): void;\n handleCrossfadeSolo(pair: ResolvedCrossfadePair): void;\n handleCrossfadeDelete(pair: ResolvedCrossfadePair): Promise<void>;\n handleCrossfadeSlider(pair: ResolvedCrossfadePair, pos: number): void;\n handleFadeDelete(fade: ResolvedFade): Promise<void>;\n handleFadeSlider(fade: ResolvedFade, pos: number): void;\n}\n\nexport function useTransitionOps({\n host,\n adapter,\n activeSceneId,\n isConnected,\n isAuthenticated,\n sceneContext,\n tracks,\n setTracks,\n loadTracks,\n setCrossfadePairsMeta,\n setFadesMeta,\n resolvedCrossfadePairs,\n resolvedFades,\n}: UseTransitionOpsInputs): TransitionOps {\n const { identity } = adapter;\n\n // Engine track ids whose fade volume curve has been applied this session\n // (keyed by engine id so reopen → new ids re-applies; curve isn't engine-persisted).\n const appliedFadeAutomationRef = useRef<Set<string>>(new Set());\n\n // Apply the crossfade volume automation: origin fades out, target fades in\n // across the loop (equal-power, crossover at sliderPos). Falls back to a static\n // equal-power blend on hosts without setTrackVolumeAutomation.\n const applyCrossfadeAutomation = useCallback(\n async (\n originTrackId: string,\n targetTrackId: string,\n bars: number,\n bpm: number,\n sliderPos: number,\n ): Promise<void> => {\n if (host.setTrackVolumeAutomation) {\n const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);\n await host.setTrackVolumeAutomation(originTrackId, curves.origin).catch(() => {});\n await host.setTrackVolumeAutomation(targetTrackId, curves.target).catch(() => {});\n } else {\n await host.setTrackVolume(originTrackId, EQUAL_POWER_GAIN).catch(() => {});\n await host.setTrackVolume(targetTrackId, EQUAL_POWER_GAIN).catch(() => {});\n }\n },\n [host],\n );\n\n // Apply a fade's one-sided volume curve (volume gesture ramps; build stays flat\n // at unity so the notes carry the fade). No-op on hosts without automation.\n const applyFadeAutomation = useCallback(\n async (\n trackId: string,\n direction: FadeDirection,\n bars: number,\n bpm: number,\n sliderPos: number,\n gesture: FadeGesture,\n ): Promise<void> => {\n if (!host.setTrackVolumeAutomation) return;\n const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);\n await host.setTrackVolumeAutomation(trackId, points).catch(() => {});\n },\n [host],\n );\n\n // --- Create a crossfade pair (transition scenes) ----------------------\n // Two tracks share ONE generated MIDI clip: the top wears the ORIGIN scene\n // track's preset, the bottom wears the TARGET's. One-action: generate →\n // create both → write same MIDI → copy sounds → equal-power volumes →\n // persist pairing. LIFO rollback on any failure. Throws so the designer\n // surfaces it.\n const [isCreatingCrossfade, setIsCreatingCrossfade] = useState(false);\n const handleCreateCrossfade = useCallback(\n async (origin: CrossfadeSelection, target: CrossfadeSelection): Promise<void> => {\n const scene = activeSceneId;\n const fromSceneId = sceneContext?.transitionFromSceneId ?? '';\n const toSceneId = sceneContext?.transitionToSceneId ?? '';\n if (!scene) throw new Error('No active scene.');\n if (!isConnected) throw new Error('Systems not connected.');\n if (!isAuthenticated) throw new Error('Please sign in to generate the bridge.');\n if (tracks.length + 2 > identity.maxTracks) {\n throw new Error('Not enough track slots for a crossfade.');\n }\n\n setIsCreatingCrossfade(true);\n const created: PluginTrackHandle[] = [];\n try {\n const role = target.role ?? origin.role ?? ''; // bridge heads toward the target\n\n // 1. Generate ONE bridge clip via MIDI INPAINTING: morph the ORIGIN part\n // into the TARGET part across the transition. The harmonic frame is\n // auto-prefixed by generateWithLLM; we add the two endpoint patterns +\n // the morph instruction. Read both patterns before creating the layers.\n const mc = await host.getMusicalContext();\n const [originMidi, targetMidi, originKey, targetKey] = await Promise.all([\n host.readImportableTrackMidi\n ? host.readImportableTrackMidi(origin.dbId)\n : Promise.resolve({ clips: [] }),\n host.readImportableTrackMidi\n ? host.readImportableTrackMidi(target.dbId)\n : Promise.resolve({ clips: [] }),\n host.getSceneKey ? host.getSceneKey(fromSceneId) : Promise.resolve(null),\n host.getSceneKey ? host.getSceneKey(toSceneId) : Promise.resolve(null),\n ]);\n const userPrompt = buildCrossfadeInpaintPrompt({\n role,\n bars: mc.bars,\n originName: origin.name,\n targetName: target.name,\n originKey: originKey ? `${originKey.key} ${originKey.mode}` : null,\n targetKey: targetKey ? `${targetKey.key} ${targetKey.mode}` : null,\n originNotes: originMidi.clips[0]?.notes ?? [],\n targetNotes: targetMidi.clips[0]?.notes ?? [],\n });\n const llm = await host.generateWithLLM({\n system: adapter.buildSystemPrompt(host.getValidRoles()),\n user: userPrompt,\n responseFormat: 'json',\n });\n const parsed = adapter.parseNotesResponse(llm.content);\n if (!parsed || parsed.notes.length === 0) {\n throw new Error('The bridge generator returned no notes.');\n }\n const notes = await host.postProcessMidi(parsed.notes, {\n quantize: true,\n removeOverlaps: true,\n });\n const clip: MidiClipData = {\n startTime: 0,\n endTime: (mc.bars * 4 * 60) / mc.bpm,\n tempo: mc.bpm,\n notes,\n };\n\n // 2. Create the two layer tracks (family default instrument; sound copied below).\n const top = await host.createTrack({\n name: `${identity.trackNamePrefix}-${Date.now()}-xf-o`,\n ...adapter.createTrackOptions(),\n });\n created.push(top);\n const bottom = await host.createTrack({\n name: `${identity.trackNamePrefix}-${Date.now()}-xf-t`,\n ...adapter.createTrackOptions(),\n });\n created.push(bottom);\n if (role) {\n await host.setTrackRole(top.id, role).catch(() => {});\n await host.setTrackRole(bottom.id, role).catch(() => {});\n }\n\n // 3. SAME MIDI on both layers.\n await host.writeMidiClip(top.id, clip);\n await host.writeMidiClip(bottom.id, clip);\n\n // 4. Copy each source sound onto its layer (exact sound — no shuffle).\n // The adapter's copySnapshot persists the copy as the layer's durable\n // identity — an unpersisted copy reads as \"no sound\" forever and gets\n // re-pushed to the engine by the drift re-sync on every panel load.\n const copySound = async (newTrackId: string, sourceDbId: string): Promise<string> => {\n if (!host.getTrackSound) return 'default';\n const snap = await host.getTrackSound(sourceDbId);\n if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) return 'default';\n return adapter.sound.copySnapshot(newTrackId, snap);\n };\n const originLabel = await copySound(top.id, origin.dbId);\n const targetLabel = await copySound(bottom.id, target.dbId);\n\n // 5. Crossfade volume automation (centered slider). Leave unmuted —\n // the point is to hear it.\n await applyCrossfadeAutomation(top.id, bottom.id, mc.bars, mc.bpm, 0.5);\n\n // 6. Persist the pairing (one key per member, shared groupId).\n const groupId = top.dbId;\n const originMeta: CrossfadeMeta = {\n groupId,\n slot: 'origin',\n partnerDbId: bottom.dbId,\n sourceTrackDbId: origin.dbId,\n sourceSceneId: fromSceneId,\n sourceName: origin.name,\n soundLabel: originLabel,\n sliderPos: 0.5,\n };\n const targetMeta: CrossfadeMeta = {\n groupId,\n slot: 'target',\n partnerDbId: top.dbId,\n sourceTrackDbId: target.dbId,\n sourceSceneId: toSceneId,\n sourceName: target.name,\n soundLabel: targetLabel,\n sliderPos: 0.5,\n };\n await host.setSceneData(scene, `track:${top.dbId}:crossfade`, originMeta);\n await host.setSceneData(scene, `track:${bottom.dbId}:crossfade`, targetMeta);\n\n await loadTracks(true);\n host.showToast('success', 'Crossfade created', `${origin.name} → ${target.name}`);\n } catch (err: unknown) {\n // LIFO rollback — delete any track we created.\n for (const h of [...created].reverse()) {\n try {\n await host.deleteTrack(h.id);\n } catch {\n /* best effort */\n }\n }\n throw err instanceof Error ? err : new Error(String(err));\n } finally {\n setIsCreatingCrossfade(false);\n }\n },\n [\n host,\n adapter,\n identity,\n activeSceneId,\n isConnected,\n isAuthenticated,\n tracks.length,\n sceneContext,\n applyCrossfadeAutomation,\n loadTracks,\n ],\n );\n\n // --- Create a fade (transition orphan) --------------------------------\n // A fade is a crossfade with one empty endpoint: ONE generated track that\n // either fades in (target-only) or out (origin-only) across the transition.\n const [isCreatingFade, setIsCreatingFade] = useState(false);\n const handleCreateFade = useCallback(\n async (\n selection: FadeSelection,\n direction: FadeDirection,\n gesture: FadeGesture,\n ): Promise<void> => {\n const scene = activeSceneId;\n const fromSceneId = sceneContext?.transitionFromSceneId ?? '';\n const toSceneId = sceneContext?.transitionToSceneId ?? '';\n if (!scene) throw new Error('No active scene.');\n if (!isConnected) throw new Error('Systems not connected.');\n if (!isAuthenticated) throw new Error('Please sign in to generate the fade.');\n if (tracks.length + 1 > identity.maxTracks) {\n throw new Error('Not enough track slots for a fade.');\n }\n\n setIsCreatingFade(true);\n const created: PluginTrackHandle[] = [];\n try {\n const role = selection.role ?? '';\n // The source lives in the FROM scene for a fade-out, the TO scene for a fade-in.\n const sourceSceneId = direction === 'out' ? fromSceneId : toSceneId;\n\n // 1. Generate the part via inpainting with ONE empty endpoint.\n const mc = await host.getMusicalContext();\n const [srcMidi, srcKey] = await Promise.all([\n host.readImportableTrackMidi\n ? host.readImportableTrackMidi(selection.dbId)\n : Promise.resolve({ clips: [] }),\n host.getSceneKey ? host.getSceneKey(sourceSceneId) : Promise.resolve(null),\n ]);\n const srcNotes = srcMidi.clips[0]?.notes ?? [];\n const keyStr = srcKey ? `${srcKey.key} ${srcKey.mode}` : null;\n const userPrompt = buildCrossfadeInpaintPrompt({\n role,\n bars: mc.bars,\n originName: direction === 'out' ? selection.name : 'silence',\n targetName: direction === 'in' ? selection.name : 'silence',\n originKey: direction === 'out' ? keyStr : null,\n targetKey: direction === 'in' ? keyStr : null,\n originNotes: direction === 'out' ? srcNotes : [],\n targetNotes: direction === 'in' ? srcNotes : [],\n });\n const llm = await host.generateWithLLM({\n system: adapter.buildSystemPrompt(host.getValidRoles()),\n user: userPrompt,\n responseFormat: 'json',\n });\n const parsed = adapter.parseNotesResponse(llm.content);\n if (!parsed || parsed.notes.length === 0) {\n throw new Error('The fade generator returned no notes.');\n }\n const notes = await host.postProcessMidi(parsed.notes, {\n quantize: true,\n removeOverlaps: true,\n });\n const clip: MidiClipData = {\n startTime: 0,\n endTime: (mc.bars * 4 * 60) / mc.bpm,\n tempo: mc.bpm,\n notes,\n };\n\n // 2. Create ONE track (family default instrument; sound copied below).\n const track = await host.createTrack({\n name: `${identity.trackNamePrefix}-${Date.now()}-fade-${direction}`,\n ...adapter.createTrackOptions(),\n });\n created.push(track);\n if (role) await host.setTrackRole(track.id, role).catch(() => {});\n\n // 3. MIDI.\n await host.writeMidiClip(track.id, clip);\n\n // 4. Copy the source sound (exact sound — no shuffle). copySnapshot\n // persists it as the layer's durable identity for the drift re-sync.\n let soundLabel = 'default';\n if (host.getTrackSound) {\n const snap = await host.getTrackSound(selection.dbId);\n if (snap && snap.kind === adapter.sound.acceptedSnapshotKind) {\n soundLabel = await adapter.sound.copySnapshot(track.id, snap);\n }\n }\n\n // 5. One-sided volume curve (centered slider). Mark applied so the load\n // effect doesn't redundantly re-apply.\n await applyFadeAutomation(track.id, direction, mc.bars, mc.bpm, 0.5, gesture);\n appliedFadeAutomationRef.current.add(track.id);\n\n // 6. Persist the fade metadata (one key for the lone track).\n const meta: FadeMeta = {\n direction,\n gesture,\n sourceTrackDbId: selection.dbId,\n sourceSceneId,\n sourceName: selection.name,\n soundLabel,\n sliderPos: 0.5,\n };\n await host.setSceneData(scene, `track:${track.dbId}:fade`, meta);\n\n await loadTracks(true);\n host.showToast(\n 'success',\n direction === 'in' ? 'Fade in created' : 'Fade out created',\n selection.name,\n );\n } catch (err: unknown) {\n for (const h of [...created].reverse()) {\n try {\n await host.deleteTrack(h.id);\n } catch {\n /* best effort */\n }\n }\n throw err instanceof Error ? err : new Error(String(err));\n } finally {\n setIsCreatingFade(false);\n }\n },\n [\n host,\n adapter,\n identity,\n activeSceneId,\n isConnected,\n isAuthenticated,\n tracks.length,\n sceneContext,\n applyFadeAutomation,\n loadTracks,\n ],\n );\n\n // --- Crossfade group controls -----------------------------------------\n // Mute/solo act on BOTH layers together (group); per-layer volume/pan reuse\n // the normal handlers (members are normal tracks). Delete removes the whole\n // pair + its scene-data keys.\n const handleCrossfadeMute = useCallback(\n (pair: ResolvedCrossfadePair): void => {\n const newMuted = !pair.origin.runtimeState.muted;\n for (const id of [pair.origin.handle.id, pair.target.handle.id]) {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === id\n ? { ...t, runtimeState: { ...t.runtimeState, muted: newMuted } }\n : t,\n ),\n );\n host.setTrackMute(id, newMuted).catch(() => {});\n }\n },\n [host, setTracks],\n );\n\n const handleCrossfadeSolo = useCallback(\n (pair: ResolvedCrossfadePair): void => {\n const newSolo = !pair.origin.runtimeState.solo;\n for (const id of [pair.origin.handle.id, pair.target.handle.id]) {\n setTracks((prev) =>\n prev.map((t) =>\n t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, solo: newSolo } } : t,\n ),\n );\n host.setTrackSolo(id, newSolo).catch(() => {});\n }\n },\n [host, setTracks],\n );\n\n const handleCrossfadeDelete = useCallback(\n async (pair: ResolvedCrossfadePair): Promise<void> => {\n try {\n for (const member of [pair.origin, pair.target]) {\n await host.deleteTrack(member.handle.id);\n if (activeSceneId) {\n await host.deleteSceneData(activeSceneId, `track:${member.handle.dbId}:crossfade`);\n }\n }\n setCrossfadePairsMeta((prev) => prev.filter((p) => p.groupId !== pair.groupId));\n setTracks((prev) =>\n prev.filter(\n (t) =>\n t.handle.id !== pair.origin.handle.id && t.handle.id !== pair.target.handle.id,\n ),\n );\n host.showToast('success', 'Crossfade removed');\n } catch (err: unknown) {\n host.showToast(\n 'error',\n 'Failed to delete crossfade',\n err instanceof Error ? err.message : String(err),\n );\n }\n },\n [host, activeSceneId, setCrossfadePairsMeta, setTracks],\n );\n\n // Drag the crossfade fader: optimistic UI now, debounced engine apply + persist\n // of sliderPos (recomputes the equal-power curves at the new crossover point).\n const crossfadeSliderTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\n const handleCrossfadeSlider = useCallback(\n (pair: ResolvedCrossfadePair, pos: number): void => {\n setCrossfadePairsMeta((prev) =>\n prev.map((p) => (p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)),\n );\n if (crossfadeSliderTimers.current[pair.groupId]) {\n clearTimeout(crossfadeSliderTimers.current[pair.groupId]);\n }\n crossfadeSliderTimers.current[pair.groupId] = setTimeout(() => {\n void (async () => {\n const mc = await host.getMusicalContext();\n await applyCrossfadeAutomation(\n pair.origin.handle.id,\n pair.target.handle.id,\n mc.bars,\n mc.bpm,\n pos,\n );\n if (activeSceneId) {\n const sceneData = (await host.getAllSceneData(activeSceneId)) as Record<\n string,\n unknown\n >;\n for (const dbId of [pair.originDbId, pair.targetDbId]) {\n const meta = asCrossfadeMeta(sceneData[`track:${dbId}:crossfade`]);\n if (meta) {\n host\n .setSceneData(activeSceneId, `track:${dbId}:crossfade`, { ...meta, sliderPos: pos })\n .catch(() => {});\n }\n }\n }\n })();\n }, 200);\n },\n [host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta],\n );\n\n // --- Fade controls ----------------------------------------------------\n const handleFadeDelete = useCallback(\n async (fade: ResolvedFade): Promise<void> => {\n try {\n await host.deleteTrack(fade.track.handle.id);\n if (activeSceneId) {\n await host.deleteSceneData(activeSceneId, `track:${fade.dbId}:fade`);\n }\n setFadesMeta((prev) => prev.filter((f) => f.dbId !== fade.dbId));\n setTracks((prev) => prev.filter((t) => t.handle.id !== fade.track.handle.id));\n host.showToast('success', 'Fade removed');\n } catch (err: unknown) {\n host.showToast(\n 'error',\n 'Failed to delete fade',\n err instanceof Error ? err.message : String(err),\n );\n }\n },\n [host, activeSceneId, setFadesMeta, setTracks],\n );\n\n // Drag the fade slider: optimistic UI now, debounced engine apply + persist of\n // sliderPos (recomputes the one-sided curve at the new fade position).\n const fadeSliderTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\n const handleFadeSlider = useCallback(\n (fade: ResolvedFade, pos: number): void => {\n setFadesMeta((prev) =>\n prev.map((f) => (f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)),\n );\n if (fadeSliderTimers.current[fade.dbId]) clearTimeout(fadeSliderTimers.current[fade.dbId]);\n fadeSliderTimers.current[fade.dbId] = setTimeout(() => {\n void (async () => {\n const mc = await host.getMusicalContext();\n await applyFadeAutomation(\n fade.track.handle.id,\n fade.meta.direction,\n mc.bars,\n mc.bpm,\n pos,\n fade.meta.gesture,\n );\n if (activeSceneId) {\n const sceneData = (await host.getAllSceneData(activeSceneId)) as Record<\n string,\n unknown\n >;\n const meta = asFadeMeta(sceneData[`track:${fade.dbId}:fade`]);\n if (meta) {\n host\n .setSceneData(activeSceneId, `track:${fade.dbId}:fade`, { ...meta, sliderPos: pos })\n .catch(() => {});\n }\n }\n })();\n }, 200);\n },\n [host, activeSceneId, applyFadeAutomation, setFadesMeta],\n );\n\n // Auto re-sync drifted source sounds. A crossfade/fade COPIES each source's\n // sound onto its layer at creation; if the user later changes the source\n // track's sound, the transition is stale. Re-read source + layer sounds and,\n // if the source's state-aware identity differs, re-copy it onto the layer\n // AND persist it so the check converges. Runs once per layer↔source\n // membership, not per render — the resolved memos get fresh array identities\n // on EVERY tracks change (volume tick, mute, …).\n const lastResyncKeyRef = useRef('');\n useEffect(() => {\n if (\n !host.getTrackSound ||\n (resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0)\n ) {\n return;\n }\n const resyncKey = [\n ...resolvedCrossfadePairs.map(\n (p) =>\n `${p.origin.handle.dbId}<${p.originSourceDbId}|${p.target.handle.dbId}<${p.targetSourceDbId}`,\n ),\n ...resolvedFades.map((f) => `${f.track.handle.dbId}<${f.meta.sourceTrackDbId}`),\n ].join(',');\n if (resyncKey === lastResyncKeyRef.current) return;\n lastResyncKeyRef.current = resyncKey;\n let cancelled = false;\n const reapplyIfDrifted = async (\n layerTrackId: string,\n layerDbId: string,\n sourceDbId: string,\n ): Promise<void> => {\n if (!host.getTrackSound || cancelled) return;\n const [sourceSnap, layerSnap] = await Promise.all([\n host.getTrackSound(sourceDbId),\n host.getTrackSound(layerDbId),\n ]);\n if (cancelled || !sourceSnap || sourceSnap.kind !== adapter.sound.acceptedSnapshotKind) {\n return;\n }\n if (soundIdentity(sourceSnap) === soundIdentity(layerSnap)) return;\n try {\n await adapter.sound.copySnapshot(layerTrackId, sourceSnap);\n } catch {\n /* best effort — retried on next membership change/reopen */\n }\n };\n void (async () => {\n for (const pair of resolvedCrossfadePairs) {\n await reapplyIfDrifted(pair.origin.handle.id, pair.origin.handle.dbId, pair.originSourceDbId);\n await reapplyIfDrifted(pair.target.handle.id, pair.target.handle.dbId, pair.targetSourceDbId);\n }\n for (const fade of resolvedFades) {\n await reapplyIfDrifted(fade.track.handle.id, fade.track.handle.dbId, fade.meta.sourceTrackDbId);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [resolvedCrossfadePairs, resolvedFades, host, adapter]);\n\n // Re-apply each fade's one-sided volume curve on load (it is NOT engine-\n // persisted; recompute from the persisted sliderPos + gesture). Keyed by engine\n // track id so it fires once per resolve — including after reopen (new ids).\n useEffect(() => {\n if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;\n void (async () => {\n const mc = await host.getMusicalContext();\n for (const fade of resolvedFades) {\n const id = fade.track.handle.id;\n if (appliedFadeAutomationRef.current.has(id)) continue;\n appliedFadeAutomationRef.current.add(id);\n await applyFadeAutomation(\n id,\n fade.meta.direction,\n mc.bars,\n mc.bpm,\n fade.meta.sliderPos,\n fade.meta.gesture,\n );\n }\n })();\n }, [resolvedFades, host, applyFadeAutomation]);\n\n return {\n isCreatingCrossfade,\n isCreatingFade,\n handleCreateCrossfade,\n handleCreateFade,\n handleCrossfadeMute,\n handleCrossfadeSolo,\n handleCrossfadeDelete,\n handleCrossfadeSlider,\n handleFadeDelete,\n handleFadeSlider,\n };\n}\n","/**\n * GeneratorPanelShell — the shared render skeleton for panel-core panels.\n *\n * Verbatim extraction of the synth panel's render phases\n * (SynthGeneratorPanel.tsx:1849–2195): no-scene gate → no-contract gate →\n * COMPOSING → placeholder hybrid → normal (modals, mounted-but-hidden\n * TransitionDesigner, crossfade/fade rows, generic group rows, normal rows,\n * export button). The ~50-prop TrackRow plumbing that the monolith duplicated\n * across the hybrid + normal phases lives here ONCE (`buildRowProps`), pinned\n * by the Phase-0 props snapshot.\n *\n * @since SDK 2.35.0\n */\n\nimport React, { useCallback } from 'react';\nimport type { ReactNode } from 'react';\nimport type { BulkAddPlaceholderTrack } from '../types/plugin-sdk.types';\nimport type { FxCategory } from '../types/fx-toggle.types';\nimport { TrackRow, type SDKTrackRowProps } from '../components/TrackRow';\nimport { CrossfadeTrackRow } from '../components/CrossfadeTrackRow';\nimport { FadeTrackRow } from '../components/FadeTrackRow';\nimport { ImportTrackModal } from '../components/ImportTrackModal';\nimport { TransitionDesigner } from '../components/TransitionDesigner';\nimport { SorceryProgressBar } from '../components/SorceryProgressBar';\nimport type { CrossfadeSlot } from '../crossfade-meta';\nimport type { TrackRowDragProps } from '../hooks/useTrackReorder';\nimport type { GeneratorTrackState } from './track-state';\nimport type { GeneratorPanelCore } from './useGeneratorPanelCore';\nimport type { GeneratorPanelSlots, GroupRenderContext } from './adapter.types';\n\nexport interface GeneratorPanelShellProps {\n core: GeneratorPanelCore;\n slots?: GeneratorPanelSlots;\n}\n\nexport function GeneratorPanelShell({ core, slots }: GeneratorPanelShellProps): React.ReactElement {\n const {\n ui,\n adapter,\n tracks,\n isLoadingTracks,\n supportsMeters,\n trackLevels,\n anySolo,\n reorder,\n soundHistory,\n isComposing,\n placeholders,\n designerView,\n canCrossfade,\n xfFromId,\n xfToId,\n importOpen,\n setImportOpen,\n soundImportTarget,\n setSoundImportTarget,\n handleSoundImportPick,\n handlePortTrack,\n transition,\n crossfadePairsMeta,\n fadesMeta,\n resolvedCrossfadePairs,\n crossfadeMemberDbIds,\n resolvedFades,\n fadeMemberDbIds,\n resolvedGenericGroups,\n genericGroupMemberDbIds,\n availableInstruments,\n instrumentsLoading,\n handlers,\n isExportingMidi,\n handleExportMidi,\n handleFxToggle,\n handleFxPresetChange,\n handleFxDryWetChange,\n handleInstrumentSelect,\n handleShowEditor,\n handleBackToInstruments,\n handleRefreshInstruments,\n onAuditionNote,\n loadTracks,\n makeServices,\n setGroupMute,\n setGroupSolo,\n deleteGroup,\n } = core;\n const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;\n const { identity, features } = adapter;\n\n // --- The ONE default TrackRow props builder ------------------------------\n // Byte-identical to the monolith's duplicated prop blocks; the Phase-0\n // snapshot (sas-app synth-panel-behavior.test.tsx) pins its output.\n const buildRowProps = useCallback(\n (track: GeneratorTrackState, drag?: TrackRowDragProps): SDKTrackRowProps => {\n const id = track.handle.id;\n const pickerProps = features.instrumentPicker\n ? {\n instrumentName: track.instrumentName,\n instrumentMissing: track.instrumentMissing,\n onToggleDrawer: () => handlers.toggleDrawer(id),\n availableInstruments,\n currentInstrumentPluginId: track.instrumentPluginId,\n onInstrumentSelect: (pluginId: string) => handleInstrumentSelect(id, pluginId),\n instrumentsLoading,\n onRefreshInstruments: handleRefreshInstruments,\n editorStage: track.editorStage,\n onShowEditor: () => handleShowEditor(id),\n onBackToInstruments: () => handleBackToInstruments(id),\n }\n : {};\n const importSoundProps = features.importTracks\n ? {\n onImportSound: () => setSoundImportTarget(track),\n importSoundLabel: adapter.sound.importSoundLabel,\n }\n : {};\n const props: SDKTrackRowProps = {\n ...(drag ? { drag } : {}),\n track: { id, name: track.handle.name, role: track.role },\n levels: supportsMeters ? trackLevels : undefined,\n prompt: track.prompt,\n runtimeState: {\n muted: track.runtimeState.muted,\n solo: track.runtimeState.solo,\n volume: track.runtimeState.volume,\n pan: track.runtimeState.pan,\n },\n soloedOut: anySolo && !track.runtimeState.solo,\n fxDetailState: track.fxDetailState,\n drawerOpen: track.drawerOpen,\n drawerTab: track.drawerTab,\n onTabChange: (tab) => handlers.tabChange(id, tab),\n isGenerating: track.isGenerating,\n isAuthenticated,\n error: track.error,\n hasMidi: track.hasMidi,\n generationProgress: track.generationProgress,\n estimatedGenerationMs: identity.estimatedGenerationMs,\n onPromptChange: (prompt: string) => handlers.promptChange(id, prompt),\n onGenerate: () => handlers.generate(id),\n onShuffle: () => handlers.shuffle(id),\n onCopy: () => handlers.copy(id),\n onDelete: () => handlers.delete(id),\n onMuteToggle: () => handlers.muteToggle(id),\n onSoloToggle: () => handlers.soloToggle(id),\n onVolumeChange: (vol: number) => handlers.volumeChange(id, vol),\n onPanChange: (pan: number) => handlers.panChange(id, pan),\n onFxToggle: (cat: FxCategory, enabled: boolean) => handleFxToggle(id, cat, enabled),\n onFxPresetChange: (cat: FxCategory, idx: number) => handleFxPresetChange(id, cat, idx),\n onFxDryWetChange: (cat: FxCategory, val: number) => handleFxDryWetChange(id, cat, val),\n onToggleFxDrawer: () => handlers.toggleFxDrawer(id),\n onProgressChange: (pct: number) => handlers.progressChange(id, pct),\n accentColor: identity.accentColor,\n ...pickerProps,\n soundHistory: soundHistory.list(id).entries,\n soundHistoryCursor: soundHistory.list(id).cursor,\n onRestoreSound: (i: number) => {\n void soundHistory.restoreTo(id, i);\n },\n onToggleFavorite: (i: number) => soundHistory.toggleFavorite(id, i),\n ...importSoundProps,\n editNotes: track.editNotes,\n onNotesChange: (notes) => handlers.notesChange(id, notes),\n editBars: track.editBars,\n editBpm: track.editBpm,\n editSnap: 0.25,\n onAuditionNote: (pitch, vel, ms) => onAuditionNote(id, pitch, vel, ms),\n };\n return adapter.mapTrackRowProps ? adapter.mapTrackRowProps(track, props) : props;\n },\n [\n features.instrumentPicker,\n features.importTracks,\n adapter,\n supportsMeters,\n trackLevels,\n anySolo,\n isAuthenticated,\n identity,\n handlers,\n availableInstruments,\n instrumentsLoading,\n handleInstrumentSelect,\n handleRefreshInstruments,\n handleShowEditor,\n handleBackToInstruments,\n setSoundImportTarget,\n soundHistory,\n handleFxToggle,\n handleFxPresetChange,\n handleFxDryWetChange,\n onAuditionNote,\n ],\n );\n\n // --- Render -----------------------------------------------------------\n\n // No scene selected\n if (!activeSceneId) {\n return (\n <div\n data-testid={`no-scene-placeholder-${identity.familyKey}`}\n className=\"flex items-center justify-center py-8\"\n >\n <button\n onClick={() => onSelectScene?.()}\n className=\"text-sas-muted text-xs hover:text-sas-accent transition-colors underline underline-offset-2\"\n >\n Select a Scene\n </button>\n </div>\n );\n }\n\n // Scene selected but no contract generated yet.\n if (!sceneContext?.hasContract) {\n return (\n <div\n data-testid={`no-contract-placeholder-${identity.familyKey}`}\n className=\"flex items-center justify-center py-8\"\n >\n <button\n onClick={() => onOpenContract?.()}\n className=\"text-sas-muted text-xs hover:text-sas-accent transition-colors underline underline-offset-2\"\n >\n Generate a Contract\n </button>\n </div>\n );\n }\n\n // Phase 1: COMPOSING — single progress bar during LLM planning\n if (features.bulkComposePlaceholders && isComposing) {\n return (\n <div data-testid={`${identity.familyKey}-section`} className=\"p-2\">\n <SorceryProgressBar isLoading={true} statusText=\"COMPOSING...\" heightClass=\"h-10\" />\n </div>\n );\n }\n\n // Phase 2: HYBRID — completed tracks show full TrackRow, in-progress show bars\n const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];\n if (activePlaceholders.length > 0) {\n // Build lookup from DB ID → loaded track state for completed tracks\n const tracksByDbId = new Map<string, GeneratorTrackState>();\n for (const t of tracks) {\n tracksByDbId.set(t.handle.dbId, t);\n if (t.handle.id !== t.handle.dbId) {\n tracksByDbId.set(t.handle.id, t);\n }\n }\n\n return (\n <div data-testid={`${identity.familyKey}-section`} className=\"p-2 space-y-2\">\n {activePlaceholders.map((ph: BulkAddPlaceholderTrack) => {\n const loadedTrack = ph.status === 'completed' ? tracksByDbId.get(ph.id) : undefined;\n\n // Completed AND loaded → full TrackRow UI\n if (loadedTrack) {\n return <TrackRow key={ph.id} {...buildRowProps(loadedTrack)} />;\n }\n\n // In-progress, planned, failed, or completed-but-not-yet-loaded → bar\n return (\n <div\n key={ph.id}\n data-testid=\"bulk-placeholder-track\"\n className=\"relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt\"\n style={{ borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: '3px' }}\n >\n <SorceryProgressBar isLoading={true} statusText=\"CONJURING MIDI...\" heightClass=\"h-10\" />\n </div>\n );\n })}\n </div>\n );\n }\n\n // Group render context for generic extensions.\n const groupCtx: GroupRenderContext = {\n services: makeServices(),\n anySolo,\n supportsMeters,\n levels: supportsMeters ? trackLevels : undefined,\n handlers,\n renderDefaultTrackRow: (\n track: GeneratorTrackState,\n overrides?: Partial<SDKTrackRowProps>,\n drag?: TrackRowDragProps,\n ): ReactNode => (\n <TrackRow key={track.handle.id} {...{ ...buildRowProps(track, drag), ...(overrides ?? {}) }} />\n ),\n setGroupMute,\n setGroupSolo,\n deleteGroup,\n };\n\n // Phase 3: NORMAL — real tracks using SDK TrackRow\n return (\n <div data-testid={`${identity.familyKey}-section`} className=\"p-2 space-y-2\">\n {features.importTracks && host.listImportableTracks && (\n <ImportTrackModal\n host={host}\n open={importOpen}\n onClose={() => setImportOpen(false)}\n onImported={() => {\n void loadTracks(true);\n }}\n onPortTrack={host.readImportableTrackMidi ? handlePortTrack : undefined}\n testIdPrefix={`${identity.familyKey}-import`}\n />\n )}\n {features.importTracks && host.listImportableTracks && host.getTrackSound && (\n <ImportTrackModal\n host={host}\n mode=\"sound\"\n open={!!soundImportTarget}\n title={adapter.sound.importSoundLabel}\n onClose={() => setSoundImportTarget(null)}\n onImported={() => {}}\n onPick={handleSoundImportPick}\n testIdPrefix={`${identity.familyKey}-sound-import`}\n />\n )}\n {slots?.modals}\n {canCrossfade && xfFromId && xfToId && (\n <div className={designerView ? 'contents' : 'hidden'}>\n <TransitionDesigner\n host={host}\n fromSceneId={xfFromId}\n toSceneId={xfToId}\n transitionSceneId={activeSceneId ?? ''}\n excludeSourceDbIds={[\n ...crossfadePairsMeta.flatMap((p) => [p.originSourceDbId, p.targetSourceDbId]),\n ...fadesMeta.map((f) => f.meta.sourceTrackDbId),\n ]}\n onCreateCrossfade={transition.handleCreateCrossfade}\n onCreateFade={transition.handleCreateFade}\n familyLabel={identity.familyLabel}\n testIdPrefix={`${identity.familyKey}-transition-designer`}\n />\n </div>\n )}\n {!(designerView && canCrossfade) &&\n (isLoadingTracks ? (\n <div className=\"text-sas-muted text-xs text-center py-4\">Loading tracks...</div>\n ) : (\n <>\n {slots?.beforeRows}\n {resolvedCrossfadePairs.map((pair) => (\n <CrossfadeTrackRow\n key={pair.groupId}\n accentColor={identity.transitionAccentColor}\n levels={supportsMeters ? trackLevels : undefined}\n sliderPos={pair.sliderPos}\n origin={{\n trackId: pair.origin.handle.id,\n name: pair.origin.handle.name,\n role: pair.origin.role,\n sourceName: pair.originSourceName,\n soundLabel: pair.originSoundLabel,\n runtimeState: pair.origin.runtimeState,\n }}\n target={{\n trackId: pair.target.handle.id,\n name: pair.target.handle.name,\n role: pair.target.role,\n sourceName: pair.targetSourceName,\n soundLabel: pair.targetSoundLabel,\n runtimeState: pair.target.runtimeState,\n }}\n onMuteToggle={() => transition.handleCrossfadeMute(pair)}\n onSoloToggle={() => transition.handleCrossfadeSolo(pair)}\n onVolumeChange={(slot: CrossfadeSlot, vol: number) =>\n handlers.volumeChange(\n slot === 'origin' ? pair.origin.handle.id : pair.target.handle.id,\n vol,\n )\n }\n onPanChange={(slot: CrossfadeSlot, pan: number) =>\n handlers.panChange(\n slot === 'origin' ? pair.origin.handle.id : pair.target.handle.id,\n pan,\n )\n }\n onSliderChange={(pos: number) => transition.handleCrossfadeSlider(pair, pos)}\n onDelete={() => transition.handleCrossfadeDelete(pair)}\n />\n ))}\n {resolvedFades.map((fade) => (\n <FadeTrackRow\n key={fade.dbId}\n accentColor={identity.transitionAccentColor}\n levels={supportsMeters ? trackLevels : undefined}\n direction={fade.meta.direction}\n gesture={fade.meta.gesture}\n sliderPos={fade.meta.sliderPos}\n layer={{\n trackId: fade.track.handle.id,\n name: fade.track.handle.name,\n role: fade.track.role,\n sourceName: fade.meta.sourceName,\n soundLabel: fade.meta.soundLabel,\n runtimeState: fade.track.runtimeState,\n }}\n onMuteToggle={() => handlers.muteToggle(fade.track.handle.id)}\n onSoloToggle={() => handlers.soloToggle(fade.track.handle.id)}\n onVolumeChange={(vol: number) => handlers.volumeChange(fade.track.handle.id, vol)}\n onPanChange={(pan: number) => handlers.panChange(fade.track.handle.id, pan)}\n onSliderChange={(pos: number) => transition.handleFadeSlider(fade, pos)}\n onDelete={() => transition.handleFadeDelete(fade)}\n />\n ))}\n {(adapter.groupExtensions ?? []).flatMap((ext) =>\n (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => (\n <React.Fragment key={`${ext.metaKey}:${group.groupId}`}>\n {ext.renderGroup(group, groupCtx)}\n </React.Fragment>\n )),\n )}\n {tracks.map((track: GeneratorTrackState, index: number) => {\n if (\n crossfadeMemberDbIds.has(track.handle.dbId) ||\n fadeMemberDbIds.has(track.handle.dbId) ||\n genericGroupMemberDbIds.has(track.handle.dbId)\n ) {\n return null;\n }\n return <TrackRow key={track.handle.id} {...buildRowProps(track, reorder.dragPropsFor(index))} />;\n })}\n {slots?.afterRows}\n </>\n ))}\n\n {/* Export Tracks — bundle all tracks' MIDI as a ZIP */}\n {features.exportMidi &&\n !designerView &&\n !isLoadingTracks &&\n tracks.length > 0 &&\n (() => {\n const hasAnyMidi = tracks.some((t) => t.hasMidi);\n const exportDisabled = isExportingMidi || !hasAnyMidi;\n return (\n <div className=\"pt-2\">\n <button\n data-testid=\"export-midi-tracks-button\"\n onClick={handleExportMidi}\n disabled={exportDisabled}\n title={\n isExportingMidi\n ? 'Exporting...'\n : !hasAnyMidi\n ? 'Generate MIDI on at least one track first'\n : 'Export all tracks as a ZIP of .mid files'\n }\n className={`w-full px-2 py-1.5 text-[10px] uppercase tracking-wide rounded-sm border transition-colors ${\n exportDisabled\n ? 'text-sas-muted/40 border-transparent hover:border-sas-accent cursor-not-allowed'\n : 'text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent'\n }`}\n >\n {isExportingMidi ? 'Exporting...' : 'Export Tracks'}\n </button>\n </div>\n );\n })()}\n </div>\n );\n}\n","/**\n * Surge XT sound adapter — the PanelSoundAdapter shared by every family whose\n * tracks host Surge XT (or a user-picked third-party VST3) as the instrument:\n * synth and bass today.\n *\n * A \"sound\" is the INSTRUMENT plugin's state: default Surge XT round-trips\n * through the Tracktion ValueTree (get/setPluginState); third-party\n * instruments (u-he Diva, Serum, …) need their RAW VST3 state\n * (get/setRawPluginState), which the ValueTree wrapper does not faithfully\n * preserve. The instrument is the first non-utility plugin on the track.\n * Matching only 'Surge' silently broke history for custom-instrument tracks\n * pre-split — hence the dual path.\n *\n * @since SDK 2.35.0\n */\n\nimport type { PluginHost, TrackSoundSnapshot } from '../types/plugin-sdk.types';\nimport type { PanelSoundAdapter } from './adapter.types';\n\n/**\n * Resolve the track's instrument (first non-utility plugin) plus how its\n * state serializes (raw VST3 vs Tracktion ValueTree).\n */\nasync function getInstrument(\n host: PluginHost,\n trackId: string,\n): Promise<{ index: number; isRaw: boolean } | null> {\n try {\n const plugins = await host.getTrackPlugins(trackId);\n const instrument = plugins.find(\n (p) => !p.name.includes('Volume') && !p.name.includes('Pan') && !p.name.includes('Level'),\n );\n if (!instrument) return null;\n return { index: instrument.index, isRaw: !instrument.name.includes('Surge') };\n } catch {\n return null;\n }\n}\n\nexport interface SurgeSoundAdapterOverrides {\n /** Sound-history cap (default 12 — Surge state blobs are large). */\n historyMax?: number;\n /** Drawer action label (default 'Import Preset'). */\n importSoundLabel?: string;\n}\n\nexport function createSurgeSoundAdapter(\n host: PluginHost,\n overrides: SurgeSoundAdapterOverrides = {},\n): PanelSoundAdapter {\n const applySound = async (trackId: string, descriptor: unknown): Promise<void> => {\n const { state, stateType } = descriptor as { state: string; stateType?: 'raw' | 'valuetree' };\n const inst = await getInstrument(host, trackId);\n if (!inst) return;\n // Restore through the setter matching how the sound was captured. Absent\n // stateType ⇒ ValueTree (history recorded before the raw/ValueTree split).\n if (stateType === 'raw') await host.setRawPluginState(trackId, inst.index, state);\n else await host.setPluginState(trackId, inst.index, state);\n };\n return {\n applySound,\n captureSoundDescriptor: async (trackId: string) => {\n const inst = await getInstrument(host, trackId);\n if (!inst) return null;\n // Capture in the instrument's native serialization so restore is faithful.\n const state = inst.isRaw\n ? await host.getRawPluginState(trackId, inst.index)\n : await host.getPluginState(trackId, inst.index);\n return { descriptor: { state, stateType: inst.isRaw ? 'raw' : 'valuetree' } };\n },\n copySnapshot: async (trackId: string, snap: TrackSoundSnapshot) => {\n if (snap.kind !== 'preset') return 'default';\n await applySound(trackId, { state: snap.state, stateType: snap.stateType });\n // Persist the copy as the track's durable preset identity — getTrackSound\n // reads it, and the transition drift re-sync compares identities (an\n // unpersisted copy reads as \"no sound\" and gets re-pushed every load).\n // Absent stateType ⇒ ValueTree (same fallback applySound uses).\n await host\n .persistTrackPresetState?.(trackId, {\n state: snap.state,\n stateType: snap.stateType ?? 'valuetree',\n name: snap.label,\n })\n .catch(() => {});\n return snap.label;\n },\n descriptorFromSnapshot: (snap: TrackSoundSnapshot) => {\n const preset = snap as Extract<TrackSoundSnapshot, { kind: 'preset' }>;\n return { state: preset.state, stateType: preset.stateType };\n },\n acceptedSnapshotKind: 'preset',\n historyMax: overrides.historyMax ?? 12,\n importSoundLabel: overrides.importSoundLabel ?? 'Import Preset',\n importNoun: 'preset',\n previousSoundLabel: 'Previous preset',\n };\n}\n","/**\n * Plugin SDK Version\n *\n * Semver version of the plugin SDK contract.\n * Plugins declare minSdkVersion in their manifest.\n * Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)\n * during activation and marks incompatible plugins accordingly.\n */\nexport const PLUGIN_SDK_VERSION = '2.35.0';\n","/**\n * Format the cross-plugin concurrent-track context into a prose block\n * that's safe to drop straight into an LLM user-prompt. Both the synth\n * and drum builtin panels use this so the rendered prompt stays\n * consistent across generators — and so a single change here propagates\n * to every plugin that calls `host.getGenerationContext()`.\n *\n * Per-track payload follows the user's preferred shape (raw note JSON\n * grouped by chord) so the model sees velocity / start-beat /\n * duration / pitch verbatim and can reason about feel + harmony.\n *\n * Returns the empty string when there are no concurrent tracks — call\n * sites can `if (block) push(block)` rather than baking in a placeholder.\n */\n\nimport type {\n PluginGenerationContext,\n PluginChordSegment,\n PluginMidiNote,\n} from '../types/plugin-sdk.types';\n\nexport function formatConcurrentTracks(ctx: PluginGenerationContext): string {\n const tracks = ctx.concurrentTracks;\n if (!tracks || tracks.length === 0) return '';\n\n const lines: string[] = [`Concurrent tracks in scene (already generated):`];\n\n for (const track of tracks) {\n const promptStr = track.prompt\n ? ` prompt=\"${escapeQuotes(track.prompt)}\"`\n : '';\n lines.push(` - role=${track.role ?? 'unknown'}${promptStr}`);\n\n if (track.notesByChord.length === 0) {\n lines.push(` (no notes)`);\n } else {\n for (const segment of track.notesByChord) {\n if (segment.notes.length === 0) continue;\n lines.push(` ${formatChordSegment(segment)}`);\n }\n }\n\n if (track.truncated && typeof track.originalNoteCount === 'number') {\n const dropped = track.originalNoteCount - sumKeptNotes(track.notesByChord);\n if (dropped > 0) {\n lines.push(` … (${dropped} more notes truncated)`);\n }\n }\n }\n\n if (ctx.truncatedTrackCount && ctx.truncatedTrackCount > 0) {\n lines.push(\n ` … (${ctx.truncatedTrackCount} additional track${ctx.truncatedTrackCount === 1 ? '' : 's'} omitted to fit token budget)`,\n );\n }\n\n return lines.join('\\n');\n}\n\nfunction formatChordSegment(segment: PluginChordSegment): string {\n const [start, end] = segment.chordRangeQn;\n const notesJson = JSON.stringify(segment.notes.map(compactNote));\n return `${segment.chord} (beats ${start}-${end}): ${notesJson}`;\n}\n\n/**\n * Strip channel and other rarely-relevant fields so the LLM sees only\n * the four properties that drive perception: pitch, startBeat,\n * durationBeats, velocity.\n */\nfunction compactNote(n: PluginMidiNote): {\n pitch: number;\n startBeat: number;\n durationBeats: number;\n velocity: number;\n} {\n return {\n pitch: n.pitch,\n startBeat: n.startBeat,\n durationBeats: n.durationBeats,\n velocity: n.velocity,\n };\n}\n\nfunction escapeQuotes(s: string): string {\n return s.replace(/\"/g, '\\\\\"');\n}\n\nfunction sumKeptNotes(segments: PluginChordSegment[]): number {\n let total = 0;\n for (const s of segments) total += s.notes.length;\n return total;\n}\n","/**\n * Lightweight, dependency-free semantic matching for sample selection.\n *\n * Sample generators (drums, instruments) ship a short StableAudio text\n * prompt next to every sample (\"tight 909-style kick one shot, hard click\n * transient, short punchy body, dry, no hi hats, no loop\"). When the user\n * asks for \"a 1950s style boom bap kick\" we want to pick the sample whose\n * prompt is closest to that intent — instead of a uniform random draw —\n * while still preserving variety so a vague \"give me a kick\" doesn't return\n * the identical sample every time.\n *\n * Design notes:\n * - Pure functions, no I/O, no SDK-type dependencies → trivially unit\n * testable with an injected `rng`, and safe to call from either the\n * main or renderer process.\n * - Scoring is IDF-weighted query-coverage (a TF-IDF / BM25-lite). The\n * IDF is derived from the candidate pool itself, so it is STRUCTURAL —\n * no hand-maintained synonym tables. Rare, discriminating tokens in the\n * prompts (\"909\", \"dusty\", \"tube\") dominate; corpus-universal filler\n * (\"one\", \"shot\", \"dry\") washes out to ~zero IDF on its own.\n * - The near-universal negative clauses StableAudio prompts carry\n * (\"no hi hats\", \"no loop\", \"no melody\") are stripped before tokenizing;\n * they are pure noise for matching.\n * - Selection is softmax-weighted random among the top-k. Flat scores →\n * ~uniform (≈ the old random behavior); a clear winner → tight\n * convergence. The all-zero (no-signal) case is intentionally left to\n * the caller to fall back to its existing random path over the full\n * pool — see `scorePromptMatch`'s contract below.\n */\n\n/**\n * Function words + a few imperative-request fillers that should never count\n * as matchable intent. Kept deliberately SMALL — IDF already neutralizes\n * corpus-universal words, and query tokens that appear in no candidate are\n * dropped during scoring, so this list only needs the words that would\n * otherwise be both query-frequent AND coincidentally present in prompts.\n */\nconst STOP_WORDS: ReadonlySet<string> = new Set([\n 'a', 'an', 'the', 'and', 'or', 'but', 'with', 'for', 'to', 'of', 'in', 'on',\n 'at', 'by', 'is', 'it', 'this', 'that', 'i', 'my', 'me', 'make', 'please',\n 'give', 'want', 'need', 'some', 'like', 'get', 'something',\n]);\n\n/**\n * Tokenize a prompt or query into matchable lowercase tokens.\n *\n * 1. Drop comma-delimited negative clauses (\"no hi hats\", \"no loop\").\n * 2. Lowercase, split on any non-alphanumeric run.\n * 3. Drop stop-words and 1–2 digit numeric noise (\"01\", \"02\") while\n * keeping meaningful numerics (\"808\", \"909\", \"1950\").\n */\nexport function tokenizePrompt(text: string): string[] {\n if (!text) return [];\n const withoutNegatives = text\n .split(',')\n .map((clause) => clause.trim())\n .filter((clause) => clause.length > 0 && !/^no\\s/i.test(clause))\n .join(' ');\n\n return withoutNegatives\n .toLowerCase()\n .split(/[^a-z0-9]+/u)\n .filter((tok) => {\n if (!tok) return false;\n if (STOP_WORDS.has(tok)) return false;\n if (/^\\d{1,2}$/.test(tok)) return false; // \"01\", \"02\" — sequence noise\n return true;\n });\n}\n\n/**\n * Score each candidate prompt against the query, returning a parallel array\n * of scores in [0, 1] (1 = the candidate covers all of the query's\n * discriminating intent).\n *\n * Contract: a returned max of 0 means the query shares NO matchable token\n * with any candidate (no signal). Callers should treat that as \"fall back to\n * the existing uniform-random pick over the full pool\" so vague queries keep\n * today's variety rather than biasing toward an arbitrary top-k slice.\n */\nexport function scorePromptMatch(\n query: string,\n candidatePrompts: ReadonlyArray<string>,\n): number[] {\n const n = candidatePrompts.length;\n if (n === 0) return [];\n\n const queryTokens = Array.from(new Set(tokenizePrompt(query)));\n if (queryTokens.length === 0) return candidatePrompts.map(() => 0);\n\n const candidateTokenSets = candidatePrompts.map((p) => new Set(tokenizePrompt(p)));\n\n // IDF for each query token, derived from the candidate pool. Tokens that\n // appear in no candidate are unmatchable → excluded from both the score\n // numerator and the normalization denominator.\n const idf = new Map<string, number>();\n for (const token of queryTokens) {\n let df = 0;\n for (const set of candidateTokenSets) {\n if (set.has(token)) df += 1;\n }\n if (df > 0) idf.set(token, Math.log(1 + n / df));\n }\n\n let denominator = 0;\n for (const weight of idf.values()) denominator += weight;\n if (denominator === 0) return candidatePrompts.map(() => 0);\n\n return candidateTokenSets.map((set) => {\n let numerator = 0;\n for (const [token, weight] of idf) {\n if (set.has(token)) numerator += weight;\n }\n return numerator / denominator;\n });\n}\n\n/** One scored candidate. `key` (if present) is what `excludeKeys` matches on. */\nexport interface ScoredCandidate<T> {\n item: T;\n score: number;\n key?: string;\n}\n\nexport interface PickTopKOptions {\n /** Consider only the top-k by score (default 5). */\n k?: number;\n /**\n * Softmax temperature (default 0.3). Lower → sharper preference for the\n * top match; higher → flatter (more variety). Scores are in [0, 1].\n */\n temperature?: number;\n /** Candidate keys to exclude (e.g. shuffle history). */\n excludeKeys?: ReadonlySet<string>;\n /** Injectable RNG in [0, 1) for deterministic tests (default Math.random). */\n rng?: () => number;\n}\n\n/**\n * Pick one candidate via softmax-weighted random selection among the top-k\n * by score. Returns null only when the pool is empty after exclusion.\n *\n * Equal scores → equal weights → uniform pick among the top-k, so this\n * degrades gracefully toward random when the query gives no preference.\n */\nexport function pickTopKWeighted<T>(\n scored: ReadonlyArray<ScoredCandidate<T>>,\n options: PickTopKOptions = {},\n): T | null {\n const { k = 5, temperature = 0.3, excludeKeys, rng = Math.random } = options;\n\n let pool = scored;\n if (excludeKeys && excludeKeys.size > 0) {\n pool = pool.filter((c) => c.key === undefined || !excludeKeys.has(c.key));\n }\n if (pool.length === 0) return null;\n\n const sorted = [...pool].sort((a, b) => b.score - a.score);\n const top = sorted.slice(0, Math.max(1, k));\n\n // Softmax with a max-subtraction for numerical stability.\n const maxScore = top[0].score;\n const safeTemp = Math.max(1e-6, temperature);\n const weights = top.map((c) => Math.exp((c.score - maxScore) / safeTemp));\n const totalWeight = weights.reduce((sum, w) => sum + w, 0);\n\n let threshold = rng() * totalWeight;\n for (let i = 0; i < top.length; i += 1) {\n threshold -= weights[i];\n if (threshold <= 0) return top[i].item;\n }\n return top[top.length - 1].item;\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","next","import_jsx_runtime","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","useDebouncedCallback","import_react","import_jsx_runtime","import_jsx_runtime","React","import_react","import_jsx_runtime","React","import_react","import_jsx_runtime","React","import_react","import_jsx_runtime","import_react","import_jsx_runtime","scenes","import_react","import_jsx_runtime","shortId","import_react","import_react","import_jsx_runtime","shortId","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_react","import_react","import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime","React","compactNote"]}