{"version":3,"sources":["../src/WaveformPlaylistContext.tsx","../src/playout/resolvePlayoutAdapter.ts","../src/waveformDataLoader.ts","../src/soundFontSync.ts","../src/hooks/useTimeFormat.ts","../src/hooks/useZoomControls.ts","../src/hooks/useMasterVolume.ts","../src/hooks/useSelectionState.ts","../src/hooks/useLoopState.ts","../src/hooks/useSelectedTrack.ts","../src/hooks/useUndoState.ts","../src/hooks/useClipDragHandlers.ts","../src/utils/boundaryTrim.ts","../src/hooks/useAnnotationDragHandlers.ts","../src/hooks/useDragSensors.ts","../src/hooks/useClipSplitting.ts","../src/hooks/useKeyboardShortcuts.ts","../src/hooks/usePlaybackShortcuts.ts","../src/hooks/useAnnotationKeyboardControls.ts","../src/hooks/useAnimationFrameLoop.ts","../src/hooks/useWaveformDataCache.ts","../src/workers/peaksWorker.ts","../src/MediaElementPlaylistContext.tsx","../src/playout/resolveMediaElementPlayout.ts","../src/components/PlaybackControls.tsx","../src/components/ZoomControls.tsx","../src/components/ContextualControls.tsx","../src/AnnotationIntegrationContext.tsx","../src/components/AnnotationControls.tsx","../src/contexts/ClipInteractionContext.tsx","../src/components/PlaylistVisualization.tsx","../src/components/SortableTrackControls.tsx","../src/utils/dragPopoverHealer.ts","../src/utils/trackOrderPreview.ts","../src/hooks/useTrailingActive.ts","../src/components/AnimatedPlayhead.tsx","../src/components/ChannelWithProgress.tsx","../src/SpectrogramIntegrationContext.tsx","../src/components/PlaylistAnnotationList.tsx","../src/components/Waveform.tsx","../src/components/MediaElementPlaylist.tsx","../src/plugins/noDropAnimationPlugins.ts","../src/components/AnimatedMediaElementPlayhead.tsx","../src/components/ChannelWithMediaElementProgress.tsx","../src/components/MediaElementAnnotationList.tsx","../src/components/MediaElementWaveform.tsx","../src/components/KeyboardShortcuts.tsx","../src/components/ClipInteractionProvider.tsx","../src/modifiers/ClipCollisionModifier.ts","../src/modifiers/SnapToGridModifier.ts"],"sourcesContent":["import React, {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  useRef,\n  useCallback,\n  useMemo,\n  type ReactNode,\n} from 'react';\nimport { ThemeProvider } from 'styled-components';\nimport type {\n  EffectsFunction,\n  TrackEffectsFunction,\n  SoundFontCache,\n} from '@waveform-playlist/playout';\nimport { resolvePlayoutAdapter } from './playout/resolvePlayoutAdapter';\nimport { PlaylistEngine, type EngineState, type PlayoutAdapter } from '@waveform-playlist/engine';\nimport {\n  type ClipTrack,\n  type Fade,\n  type AnnotationAction,\n  type MidiNoteData,\n} from '@waveform-playlist/core';\nimport {\n  type TimeFormat,\n  type WaveformPlaylistTheme,\n  defaultTheme,\n} from '@waveform-playlist/ui-components';\nimport { extractPeaksFromWaveformDataFull } from './waveformDataLoader';\nimport { syncSoundFontCacheToAdapter } from './soundFontSync';\nimport type { TrackDragPreview } from './utils/trackOrderPreview';\nimport type WaveformData from 'waveform-data';\nimport type { PeakData } from '@waveform-playlist/core';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport {\n  useTimeFormat,\n  useZoomControls,\n  useMasterVolume,\n  useSelectionState,\n  useLoopState,\n  useSelectedTrack,\n  useUndoState,\n  useAnimationFrameLoop,\n  useWaveformDataCache,\n} from './hooks';\n\n// Types\nexport interface ClipPeaks {\n  clipId: string;\n  trackName: string;\n  peaks: PeakData;\n  startSample: number;\n  durationSamples: number;\n  fadeIn?: Fade;\n  fadeOut?: Fade;\n  midiNotes?: MidiNoteData[];\n  sampleRate?: number;\n  offsetSamples?: number;\n}\n\nexport type TrackClipPeaks = ClipPeaks[];\n\nexport type { TrackDragPreview };\n\n// Legacy WaveformTrack type - kept for reference but deprecated\n// @deprecated Use ClipTrack from @waveform-playlist/core instead\nexport interface WaveformTrack {\n  src: string | AudioBuffer;\n  name?: string;\n  effects?: TrackEffectsFunction;\n}\n\nexport interface TrackState {\n  name: string;\n  muted: boolean;\n  soloed: boolean;\n  volume: number;\n  pan: number;\n}\n\n// Split contexts for performance optimization\n// Animation context contains playback state and timing refs — no per-frame state updates\n\n/** Per-frame data passed to registered animation callbacks. */\nexport interface FrameData {\n  /** Raw engine time (for state/logic — NOT for visual positioning). */\n  readonly time: number;\n  /** Visually-aligned time for DOM positioning: engine.getAudibleTime() while\n   *  playing (matches speaker output), raw time when resting. */\n  readonly visualTime: number;\n  readonly sampleRate: number;\n  readonly samplesPerPixel: number;\n}\n\nexport interface PlaybackAnimationContextValue {\n  isPlaying: boolean;\n  currentTime: number;\n  currentTimeRef: React.RefObject<number>;\n  /**\n   * Visually-aligned playback time (engine.getAudibleTime() while playing;\n   * raw resting time otherwise). Kept current by the animation loop during playback\n   * and by pause/seek/stop paths when stopped. Read from this for any visual\n   * positioning that should match the audible output.\n   */\n  visualTimeRef: React.RefObject<number>;\n  // Refs for direct time calculation in animated components (avoids timing drift)\n  playbackStartTimeRef: React.RefObject<number>; // context.currentTime when playback started\n  audioStartPositionRef: React.RefObject<number>; // Audio position when playback started\n  /** Returns raw playback time from engine (auto-wraps at loop boundaries). */\n  getPlaybackTime: () => number;\n  /** Current time of the adapter's AudioContext, in seconds. */\n  getAudioContextTime: () => number;\n  /**\n   * Returns the current adapter scheduler lookahead (Tone ~0.1s, native 0).\n   * Use this for any audible-latency calculation that must match the playhead\n   * (e.g., recording live-preview peak slicing).\n   */\n  getLookAhead: () => number;\n  /**\n   * Returns the adapter AudioContext's output latency in seconds.\n   * Use this for audible-latency calculations (e.g., recording live-preview peak slicing).\n   */\n  getOutputLatency: () => number;\n  /** Register a per-frame callback driven by the single animation loop. */\n  registerFrameCallback: (id: string, cb: (data: FrameData) => void) => void;\n  /** Unregister a per-frame callback. */\n  unregisterFrameCallback: (id: string) => void;\n}\n\nexport interface PlaylistStateContextValue {\n  continuousPlay: boolean;\n  linkEndpoints: boolean;\n  annotationsEditable: boolean;\n  isAutomaticScroll: boolean;\n  isLoopEnabled: boolean;\n  annotations: AnnotationData[];\n  activeAnnotationId: string | null;\n  selectionStart: number;\n  selectionEnd: number;\n  selectedTrackId: string | null; // ID of currently selected track for editing operations\n  // Loop region (separate from selection) - Audacity-style loop points\n  loopStart: number;\n  loopEnd: number;\n  /** Whether playback continues past the end of loaded audio instead of\n   *  auto-stopping (implies the fillViewport layout) */\n  indefinitePlayback: boolean;\n  /** Whether the timeline visually fills the scroll container even when the\n   *  audio is shorter (layout only — no effect on playback) */\n  fillViewport: boolean;\n  /** Whether undo is available */\n  canUndo: boolean;\n  /** Whether redo is available */\n  canRedo: boolean;\n}\n\nexport interface PlaylistControlsContextValue {\n  // Playback controls\n  play: (startTime?: number, playDuration?: number) => Promise<void>;\n  pause: () => void;\n  stop: () => void;\n  seekTo: (time: number) => void;\n  setCurrentTime: (time: number) => void;\n\n  // Track controls\n  setTrackMute: (trackIndex: number, muted: boolean) => void;\n  setTrackSolo: (trackIndex: number, soloed: boolean) => void;\n  setTrackVolume: (trackIndex: number, volume: number) => void;\n  setTrackPan: (trackIndex: number, pan: number) => void;\n\n  /** Move a track to a new index in the vertical track order. Purely\n   *  organizational — playback is not interrupted. The reordered tracks\n   *  array flows back through onTracksChange. */\n  reorderTrack: (trackId: string, toIndex: number) => void;\n\n  /** Internal: call after every track-reorder DRAG interaction ends\n   *  (committed or canceled) to force PlaylistVisualization to remount the\n   *  track-controls slots, discarding any @dnd-kit sortable-plugin DOM\n   *  corruption. Not needed for button-driven reorderTrack calls (they never\n   *  touch dnd-kit's DOM). See bumpTrackReorderEpoch in the provider. */\n  bumpTrackReorderEpoch: () => void;\n\n  /** INTERNAL wiring for ClipInteractionProvider: publishes/clears the live\n   *  track-reorder drag preview. Custom drag integrations may call it, but\n   *  most consumers only ever READ trackDragPreview from usePlaylistData(). */\n  setTrackDragPreview: React.Dispatch<React.SetStateAction<TrackDragPreview | null>>;\n\n  // Selection\n  setSelection: (start: number, end: number) => void;\n  setSelectedTrackId: (trackId: string | null) => void;\n\n  // Time format\n  setTimeFormat: (format: TimeFormat) => void;\n  formatTime: (seconds: number) => string;\n\n  // Zoom\n  zoomIn: () => void;\n  zoomOut: () => void;\n\n  // Master volume\n  setMasterVolume: (volume: number) => void;\n\n  // Automatic scroll\n  setAutomaticScroll: (enabled: boolean) => void;\n  setScrollContainer: (element: HTMLDivElement | null) => void;\n  scrollContainerRef: React.RefObject<HTMLDivElement | null>;\n\n  // Annotation controls\n  setContinuousPlay: (enabled: boolean) => void;\n  setLinkEndpoints: (enabled: boolean) => void;\n  setAnnotationsEditable: (enabled: boolean) => void;\n  setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>>;\n  setActiveAnnotationId: (id: string | null) => void;\n\n  // Loop controls\n  setLoopEnabled: (enabled: boolean) => void;\n  setLoopRegion: (start: number, end: number) => void;\n  setLoopRegionFromSelection: () => void;\n  clearLoopRegion: () => void;\n\n  // Undo/redo\n  undo: () => void;\n  redo: () => void;\n\n  // Recording\n  /** Mark a recording session active/inactive. While active: (1) the\n   *  end-of-audio auto-stop is suppressed so overdub playback runs past the\n   *  end of existing material, and (2) with an `armedTrackId`, that track's\n   *  existing content is transiently muted — punch-in recording replaces\n   *  whatever the take overlaps (#579), so the doomed material must not play\n   *  under the overdub; its previous mute state is restored when the session\n   *  ends (audio-only, the UI mute control is untouched). Wired automatically\n   *  from the Waveform / PlaylistVisualization `recordingState` prop; overdub\n   *  flows should also call it eagerly (before `play()`). */\n  setRecordingActive: (active: boolean, armedTrackId?: string | null) => void;\n}\n\nexport interface PlaylistDataContextValue {\n  duration: number;\n  audioBuffers: AudioBuffer[];\n  peaksDataArray: TrackClipPeaks[]; // Array of tracks, each containing array of clip peaks\n  trackStates: TrackState[];\n  tracks: ClipTrack[]; // Original tracks array with IDs\n  sampleRate: number;\n  waveHeight: number;\n  timeScaleHeight: number;\n  minimumPlaylistHeight: number;\n  controls: { show: boolean; width: number };\n  playoutRef: React.RefObject<PlaylistEngine | null>;\n  samplesPerPixel: number;\n  timeFormat: TimeFormat;\n  masterVolume: number;\n  canZoomIn: boolean;\n  canZoomOut: boolean;\n  barWidth: number;\n  barGap: number;\n  /** Draw bars with pill-shaped rounded caps (radius barWidth/2). */\n  roundedBars: boolean;\n  /** Width in pixels of progress bars. Defaults to barWidth + barGap (fills gaps). */\n  progressBarWidth: number;\n  /** Whether the playlist has finished loading all tracks */\n  isReady: boolean;\n  /** Internal: incremented after every track-reorder drag interaction ends.\n   *  PlaylistVisualization folds this into the track-controls slot `key` to\n   *  force a remount, working around @dnd-kit sortable-plugin DOM corruption\n   *  (see bumpTrackReorderEpoch in PlaylistControlsContextValue). */\n  trackReorderEpoch: number;\n  /** Non-null while a track-reorder drag is live: the dragged track and the\n   *  index it would drop at. Both playlist columns derive their display\n   *  layout from it; custom renderTrackControls consumers can read it to\n   *  mirror the preview in their own UI. */\n  trackDragPreview: TrackDragPreview | null;\n  /** Whether tracks are rendered in mono mode */\n  mono: boolean;\n  /** Ref set by useClipDragHandlers during boundary trim drags.\n   *  When true, loadAudio skips engine rebuild — visual updates flow via React state only. */\n  isDraggingRef: React.MutableRefObject<boolean>;\n  onTracksChange: ((tracks: ClipTrack[]) => void) | undefined;\n}\n\n// Create the 4 separate contexts\nconst PlaybackAnimationContext = createContext<PlaybackAnimationContextValue | null>(null);\nconst PlaylistStateContext = createContext<PlaylistStateContextValue | null>(null);\nconst PlaylistControlsContext = createContext<PlaylistControlsContextValue | null>(null);\nconst PlaylistDataContext = createContext<PlaylistDataContextValue | null>(null);\n\nexport interface WaveformPlaylistProviderProps {\n  tracks: ClipTrack[]; // Updated to use clip-based model\n  timescale?: boolean;\n  mono?: boolean;\n  waveHeight?: number;\n  samplesPerPixel?: number;\n  zoomLevels?: number[]; // Array of zoom levels in samples per pixel (lower = more zoomed in)\n  automaticScroll?: boolean;\n  theme?: Partial<WaveformPlaylistTheme>;\n  controls?: {\n    show: boolean;\n    width: number;\n  };\n  annotationList?: {\n    annotations?: AnnotationData[];\n    editable?: boolean;\n    isContinuousPlay?: boolean;\n    linkEndpoints?: boolean;\n    controls?: AnnotationAction[];\n  };\n  effects?: EffectsFunction;\n  onReady?: () => void;\n  /** Called when audio/engine initialization fails (e.g. a missing optional peer,\n   *  a throwing `createAdapter`, or an invalid `zoomLevels`/`samplesPerPixel`). The\n   *  provider already logs the error; use this to surface it in your UI. */\n  onError?: (err: Error) => void;\n  /** @deprecated Use onAnnotationsChange instead */\n  onAnnotationUpdate?: (annotations: AnnotationData[]) => void;\n  /** Callback when annotations are changed (drag, edit, etc.) */\n  onAnnotationsChange?: (annotations: AnnotationData[]) => void;\n  /** Width in pixels of waveform bars. Default: 1 */\n  barWidth?: number;\n  /** Spacing in pixels between waveform bars. Default: 0 */\n  barGap?: number;\n  /** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */\n  roundedBars?: boolean;\n  /** Width in pixels of progress bars. Default: barWidth + barGap (fills gaps). */\n  progressBarWidth?: number;\n  /** Callback when engine clip operations (move, trim, split) change tracks.\n   * The provider calls this so the parent can update its tracks state without\n   * triggering a full engine rebuild.\n   *\n   * **Important:** The parent must pass the received `tracks` reference back as\n   * the `tracks` prop (i.e. `setState(tracks)`). The provider uses reference\n   * identity (`tracks === engineTracksRef.current`) to detect engine-originated\n   * updates and skip the expensive `loadAudio` rebuild. */\n  onTracksChange?: (tracks: ClipTrack[]) => void;\n  /** SoundFont cache for sample-based MIDI playback. When provided, MIDI clips\n   *  use SoundFont samples instead of PolySynth synthesis. */\n  soundFontCache?: SoundFontCache;\n  /** When true, tracks render visually but the engine build is deferred.\n   *  Use this during progressive loading to avoid rebuilding the engine for\n   *  each track — flip to false when all tracks are ready for a single build. */\n  deferEngineRebuild?: boolean;\n  /** Disable the automatic stop when the cursor reaches the end of the\n   *  longest track — the transport rolls until an explicit stop/pause\n   *  (DAW-style). Implies the fillViewport layout. Recording sessions\n   *  suppress the auto-stop automatically, so most recording UIs don't\n   *  need this. */\n  indefinitePlayback?: boolean;\n  /** Extend the timeline (background + timescale) to fill the visible scroll\n   *  container even when the audio is shorter. Layout only. Recording UIs\n   *  typically want this so the empty timeline doesn't collapse to the audio\n   *  width. */\n  fillViewport?: boolean;\n  /** Desired AudioContext sample rate. Creates a cross-browser AudioContext at\n   *  this rate via standardized-audio-context. Pre-computed peaks (.dat files)\n   *  render instantly when they match. On mismatch, falls back to worker. */\n  sampleRate?: number;\n  /** Factory for a custom PlayoutAdapter. When omitted, the Tone.js engine\n   *  (@waveform-playlist/playout) is dynamically imported — so a custom adapter\n   *  lets a consumer use neither @waveform-playlist/playout nor tone. Called once\n   *  per engine rebuild; the provider owns and disposes the returned instance.\n   *  Pass a stable reference (module-level or useCallback) — it is read directly\n   *  inside the curated `loadAudio` effect, not via its dependency array. */\n  createAdapter?: () => PlayoutAdapter;\n  children: ReactNode;\n}\n\nexport const WaveformPlaylistProvider: React.FC<WaveformPlaylistProviderProps> = ({\n  tracks,\n  timescale = false,\n  mono = false,\n  waveHeight = 80,\n  samplesPerPixel: initialSamplesPerPixel = 1024,\n  zoomLevels,\n  automaticScroll = false,\n  theme: userTheme,\n  controls = { show: false, width: 0 },\n  annotationList,\n  effects,\n  onReady,\n  onError,\n  onAnnotationUpdate: _onAnnotationUpdate,\n  onAnnotationsChange,\n  barWidth = 1,\n  barGap = 0,\n  roundedBars = false,\n  progressBarWidth: progressBarWidthProp,\n  onTracksChange,\n  soundFontCache,\n  deferEngineRebuild = false,\n  indefinitePlayback = false,\n  fillViewport = false,\n  sampleRate: sampleRateProp,\n  createAdapter,\n  children,\n}) => {\n  // Default progressBarWidth to barWidth + barGap (fills gaps)\n  const progressBarWidth = progressBarWidthProp ?? barWidth + barGap;\n\n  // Stabilize zoomLevels reference — inline arrays (e.g. zoomLevels={[256, 512]})\n  // create a new reference every render, which would trigger engine rebuild via\n  // loadAudio deps. Content-based comparison avoids spurious rebuilds.\n  const stableZoomLevels = useMemo(\n    () => zoomLevels,\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [zoomLevels?.join(',')]\n  );\n\n  // Annotations are derived from prop (single source of truth in parent)\n  // In v6, annotations must be pre-parsed (numeric start/end). Use parseAeneas() from @waveform-playlist/annotations before passing.\n  const annotations = useMemo(() => {\n    if (!annotationList?.annotations) return [];\n    if (process.env.NODE_ENV !== 'production' && annotationList.annotations.length > 0) {\n      const first = annotationList.annotations[0] as unknown as Record<string, unknown>;\n      if (typeof first.start !== 'number' || typeof first.end !== 'number') {\n        console.error(\n          '[waveform-playlist] Annotations must have numeric start/end values. ' +\n            'In v6, use parseAeneas() from @waveform-playlist/annotations before passing annotations. ' +\n            'Received start type: ' +\n            typeof first.start\n        );\n        return [];\n      }\n    }\n    return annotationList.annotations;\n  }, [annotationList?.annotations]);\n\n  // Ref for animation loop (avoids restarting loop on annotation change)\n  const annotationsRef = useRef<AnnotationData[]>(annotations);\n  annotationsRef.current = annotations;\n\n  // State\n  const [activeAnnotationId, setActiveAnnotationIdState] = useState<string | null>(null);\n  const [isPlaying, setIsPlaying] = useState(false);\n  const [currentTime, setCurrentTime] = useState(0);\n  const [duration, setDuration] = useState(0);\n  const [audioBuffers, setAudioBuffers] = useState<AudioBuffer[]>([]);\n  const [peaksDataArray, setPeaksDataArray] = useState<TrackClipPeaks[]>([]); // Updated for clip-based peaks\n  const [trackStates, setTrackStates] = useState<TrackState[]>([]);\n  const [isAutomaticScroll, setIsAutomaticScroll] = useState(automaticScroll);\n  const [continuousPlay, setContinuousPlayState] = useState(\n    annotationList?.isContinuousPlay ?? false\n  );\n  const [linkEndpoints, setLinkEndpoints] = useState(annotationList?.linkEndpoints ?? false);\n  const [annotationsEditable, setAnnotationsEditable] = useState(annotationList?.editable ?? false);\n  const [isReady, setIsReady] = useState(false);\n  // Bumped after every track-reorder DRAG interaction (committed or not) —\n  // see bumpTrackReorderEpoch below for the full rationale.\n  const [trackReorderEpoch, setTrackReorderEpoch] = useState(0);\n  // Live track-reorder drag preview (view-level only; see the\n  // \"Track Reordering\" section of packages/browser/CLAUDE.md). Set by\n  // ClipInteractionProvider's onDragOver, cleared on drag end/cancel.\n  // The engine's committed order is untouched until drop.\n  const [trackDragPreview, setTrackDragPreview] = useState<TrackDragPreview | null>(null);\n\n  // Refs\n  // Engine owns selection, loop, selectedTrackId, zoom, and masterVolume state.\n  // React subscribes to engine statechange and mirrors into useState/refs.\n  // Playback timing (currentTime, isPlaying) remains in React for animation loop.\n  const engineRef = useRef<PlaylistEngine | null>(null);\n  const adapterRef = useRef<PlayoutAdapter | null>(null);\n  const audioInitializedRef = useRef<boolean>(false);\n\n  // Ref for animation loop access (avoids adding the prop to useCallback deps)\n  const indefinitePlaybackRef = useRef(indefinitePlayback);\n  indefinitePlaybackRef.current = indefinitePlayback;\n\n  // Recording session state. While active:\n  // 1. The animation loop's end-of-audio auto-stop is suppressed so overdub\n  //    playback runs past the end of existing material (#589/#590).\n  // 2. The armed track's existing content is transiently muted: punch-in\n  //    replaces whatever the take overlaps (#579), so the doomed material\n  //    must not play under the overdub. Engine-level mute (survives adapter\n  //    rebuilds), captured/restored around the session; the track's UI mute\n  //    state is untouched.\n  // Auto-wired from PlaylistVisualization's recordingState prop; overdub\n  // flows also call it eagerly before play() — a render round-trip loses the\n  // race against the first animation frame (empty timeline: time 0 >=\n  // duration 0 would stop playback instantly).\n  const recordingActiveRef = useRef(false);\n  const armedTrackMuteRef = useRef<{ trackId: string; prevMuted: boolean } | null>(null);\n  const setRecordingActive = useCallback((active: boolean, armedTrackId?: string | null) => {\n    recordingActiveRef.current = active;\n    const engine = engineRef.current;\n    if (!engine) return;\n    if (active && armedTrackId) {\n      if (armedTrackMuteRef.current?.trackId === armedTrackId) return; // already armed\n      if (armedTrackMuteRef.current) {\n        // Armed track changed mid-session — restore the previous one first.\n        engine.setTrackMute(armedTrackMuteRef.current.trackId, armedTrackMuteRef.current.prevMuted);\n      }\n      const prevMuted = engine.getState().tracks.find((t) => t.id === armedTrackId)?.muted ?? false;\n      armedTrackMuteRef.current = { trackId: armedTrackId, prevMuted };\n      engine.setTrackMute(armedTrackId, true);\n    } else if (!active && armedTrackMuteRef.current) {\n      engine.setTrackMute(armedTrackMuteRef.current.trackId, armedTrackMuteRef.current.prevMuted);\n      armedTrackMuteRef.current = null;\n    }\n  }, []);\n  const isPlayingRef = useRef<boolean>(false);\n  isPlayingRef.current = isPlaying;\n  const playStartPositionRef = useRef<number>(0);\n  const currentTimeRef = useRef<number>(0);\n  // Visually-aligned playback time (see toVisualTime / getAudibleTime). The\n  // animation loop updates this per frame. Pause/seek/stop paths sync it\n  // explicitly so the static playhead reflects the commanded resting position.\n  const visualTimeRef = useRef<number>(0);\n  const tracksRef = useRef<ClipTrack[]>(tracks);\n  const soundFontCacheRef = useRef(soundFontCache);\n  soundFontCacheRef.current = soundFontCache;\n  const trackStatesRef = useRef<TrackState[]>(trackStates);\n  const playbackStartTimeRef = useRef<number>(0); // context.currentTime when playback started\n  const audioStartPositionRef = useRef<number>(0); // Audio position when playback started\n  const playbackEndTimeRef = useRef<number | null>(null); // Audio position where playback should stop (for selections)\n  // Suppresses the engine 'stop' mirror (below) around provider-initiated\n  // engine.stop() calls that immediately seek/restart playback in the same\n  // synchronous block (play(), setSelection, reschedulePlayback, the\n  // engine-rebuild-with-resume path). Without this, the mirror's ref writes\n  // would race the restart's own writes and briefly show the pre-restart\n  // position/isPlaying=false. Stop paths that don't restart afterward don't\n  // need suppression — see the mirror's own comment for why.\n  const suppressEngineStopMirrorRef = useRef(false);\n  const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n  const isAutomaticScrollRef = useRef<boolean>(false);\n  // Animation frame callback registry — components register per-frame DOM update\n  // callbacks instead of running their own rAF loops. Single loop drives all.\n  const frameCallbacksRef = useRef<Map<string, (data: FrameData) => void>>(new Map());\n  const continuousPlayRef = useRef<boolean>(annotationList?.isContinuousPlay ?? false);\n  const activeAnnotationIdRef = useRef<string | null>(null);\n  // Engine clip operations guard: tracks reference from the last engine statechange.\n  // When engine methods (moveClip, trimClip, splitClip) mutate tracks, the statechange\n  // handler stores state.tracks here and calls onTracksChange with the same reference.\n  // loadAudio checks tracks === engineTracksRef.current to skip the full rebuild.\n  const engineTracksRef = useRef<ClipTrack[] | null>(null);\n  // Monotonic counter from engine.getState().tracksVersion — used to detect\n  // track mutations (move/trim/split/add/remove) vs other statechange events.\n  const lastTracksVersionRef = useRef(0);\n  // Flag set during render to prevent the previous effect cleanup from disposing the\n  // engine when we're skipping the rebuild. React runs previous cleanup before the\n  // current effect body, so the flag must be set during render (which precedes cleanup).\n  const skipEngineDisposeRef = useRef(false);\n  // Set by useClipDragHandlers during boundary trim drags. When true,\n  // loadAudio skips the full engine rebuild — visual updates flow via React\n  // state only. On drag end, engine.trimClip() commits the final delta.\n  const isDraggingRef = useRef(false);\n  // Snapshot of tracks from the previous loadAudio run, used to detect\n  // additive-only changes (new tracks appended, existing unchanged).\n  const prevTracksRef = useRef<ClipTrack[]>([]);\n  // Provider-level ref for scroll-position math and animation loop pixel\n  // calculation. Distinct from useZoomControls's internal ref (statechange guard).\n  const samplesPerPixelRef = useRef<number>(initialSamplesPerPixel);\n  // Provisional sample rate until the adapter (which owns the AudioContext) is\n  // created in loadAudio. Reconciled from adapter.audioContext.sampleRate there.\n  // (#510: avoids a static @waveform-playlist/playout import at mount.)\n  const [initialSampleRate] = useState<number>(() => sampleRateProp ?? 48000);\n  const sampleRateRef = useRef<number>(initialSampleRate);\n\n  // Custom hooks — engine-owned state delegated to hooks with onEngineState() pattern\n  const { timeFormat, setTimeFormat, formatTime } = useTimeFormat();\n  const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });\n  const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;\n  const volume = useMasterVolume({ engineRef, initialVolume: 1.0 });\n  const {\n    masterVolume,\n    setMasterVolume,\n    masterVolumeRef,\n    onEngineState: onVolumeEngineState,\n  } = volume;\n  const {\n    selectionStart,\n    selectionEnd,\n    setSelection: setSelectionEngine,\n    selectionStartRef,\n    selectionEndRef,\n    onEngineState: onSelectionEngineState,\n  } = useSelectionState({ engineRef });\n  const {\n    isLoopEnabled,\n    loopStart,\n    loopEnd,\n    setLoopEnabled,\n    setLoopRegion,\n    clearLoopRegion,\n    isLoopEnabledRef,\n    loopStartRef,\n    loopEndRef,\n    onEngineState: onLoopEngineState,\n  } = useLoopState({ engineRef });\n  const {\n    selectedTrackId,\n    setSelectedTrackId: setSelectedTrackIdControl,\n    onEngineState: onSelectedTrackEngineState,\n    selectedTrackIdRef,\n  } = useSelectedTrack({ engineRef });\n  const {\n    canUndo,\n    canRedo,\n    undo,\n    redo,\n    onEngineState: onUndoEngineState,\n  } = useUndoState({ engineRef });\n  const { animationFrameRef, startAnimationFrameLoop, stopAnimationFrameLoop } =\n    useAnimationFrameLoop();\n\n  // Worker-based WaveformData cache for fast zoom resampling\n  const baseScale = useMemo(\n    () => Math.min(...(stableZoomLevels ?? [256, 512, 1024, 2048, 4096, 8192])),\n    [stableZoomLevels]\n  );\n  const { cache: waveformDataCache } = useWaveformDataCache(tracks, baseScale);\n\n  // Custom setter for continuousPlay that updates BOTH state and ref synchronously\n  // This ensures the ref is updated immediately, before the animation loop can read it\n  const setContinuousPlay = useCallback((value: boolean) => {\n    continuousPlayRef.current = value; // Update ref synchronously\n    setContinuousPlayState(value); // Update state (triggers re-render)\n  }, []);\n\n  // Custom setter for activeAnnotationId that updates BOTH state and ref synchronously\n  const setActiveAnnotationId = useCallback((value: string | null) => {\n    activeAnnotationIdRef.current = value; // Update ref synchronously\n    setActiveAnnotationIdState(value); // Update state (triggers re-render)\n  }, []);\n\n  // Cross-hook concern: reads selection refs to set loop region\n  const setLoopRegionFromSelection = useCallback(() => {\n    const start = selectionStartRef.current ?? 0;\n    const end = selectionEndRef.current ?? 0;\n    if (start !== end && end > start) {\n      setLoopRegion(start, end);\n    }\n  }, [setLoopRegion, selectionStartRef, selectionEndRef]);\n\n  // Keep refs in sync with state\n  useEffect(() => {\n    isAutomaticScrollRef.current = isAutomaticScroll;\n  }, [isAutomaticScroll]);\n\n  useEffect(() => {\n    trackStatesRef.current = trackStates;\n  }, [trackStates]);\n\n  tracksRef.current = tracks;\n\n  // Render-phase guard: detect when tracks prop came from an engine clip operation.\n  // Must be set before effects run so the previous effect cleanup can skip disposal.\n  // Also skip disposal during active boundary trim drags — onDragMove updates React\n  // tracks per-frame, triggering effect re-runs whose cleanup would dispose the engine.\n  const isEngineTracks = tracks === engineTracksRef.current;\n\n  // Detect additive-only track changes: new tracks appended, existing tracks unchanged.\n  // Uses reference equality on individual tracks — if any existing track object changed\n  // (e.g., clip added to it), this is false and triggers a full rebuild.\n  const prevTracks = prevTracksRef.current;\n  const isIncrementalAdd =\n    engineRef.current !== null &&\n    tracks.length > prevTracks.length &&\n    prevTracks.every((pt) => {\n      const current = tracks.find((t) => t.id === pt.id);\n      return current === pt; // reference equality — existing tracks must be untouched\n    });\n  // Note: prevTracks may be empty here (e.g. recording's first track on a\n  // persistent empty-timeline engine) — that's a valid 0→1 incremental add, so\n  // it must NOT require prevTracks.length > 0. The engineRef !== null guard keeps\n  // it correct: a lazy/no-engine consumer still takes the full build path.\n\n  skipEngineDisposeRef.current = isEngineTracks || isDraggingRef.current || isIncrementalAdd;\n\n  // Adjust scroll position proportionally when zoom changes\n  useEffect(() => {\n    if (!scrollContainerRef.current || duration === 0) return;\n\n    const container = scrollContainerRef.current;\n    const oldSamplesPerPixel = samplesPerPixelRef.current;\n    const newSamplesPerPixel = samplesPerPixel;\n\n    if (oldSamplesPerPixel === newSamplesPerPixel) return;\n\n    // Calculate the current center time in the viewport\n    const containerWidth = container.clientWidth;\n    const currentScrollLeft = container.scrollLeft;\n    const centerPixel = currentScrollLeft + containerWidth / 2;\n    const sr = sampleRateRef.current;\n    const centerTime = (centerPixel * oldSamplesPerPixel) / sr;\n\n    // Calculate new scroll position to keep the same center time\n    const newCenterPixel = (centerTime * sr) / newSamplesPerPixel;\n    const newScrollLeft = Math.max(0, newCenterPixel - containerWidth / 2);\n\n    container.scrollLeft = newScrollLeft;\n    samplesPerPixelRef.current = newSamplesPerPixel;\n  }, [samplesPerPixel, duration]);\n\n  // Track pending playback resume after tracks change\n  const pendingResumeRef = useRef<{ position: number } | null>(null);\n\n  // Load audio from clips (only when tracks change)\n  useEffect(() => {\n    // Guard: skip full rebuild when tracks came from an engine clip operation\n    // (moveClip, trimClip, splitClip). The engine and adapter already have the\n    // correct tracks — we just need to update duration for the timeline.\n    // Also skip during active boundary trim drags — visual updates flow via\n    // React state only; engine.trimClip() commits the final delta on drag end.\n    if (isEngineTracks || isDraggingRef.current) {\n      if (isEngineTracks) {\n        engineTracksRef.current = null;\n      }\n\n      // Recalculate duration from updated clip positions\n      let maxDuration = 0;\n      tracks.forEach((track) => {\n        track.clips.forEach((clip) => {\n          const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n          maxDuration = Math.max(maxDuration, clipEnd);\n        });\n      });\n      setDuration(maxDuration);\n      prevTracksRef.current = tracks;\n      return;\n    }\n\n    // Guard: incremental track addition — only new tracks appended, existing\n    // tracks unchanged. Add each new track to the existing engine without\n    // tearing down the playout. This avoids disposing and recreating all\n    // audio nodes when dropping a file or finishing a recording.\n    if (isIncrementalAdd && engineRef.current) {\n      try {\n        // Use prevTracks captured during render (not prevTracksRef.current) to ensure\n        // the guard condition and inner logic operate on the same snapshot.\n        const prevIds = new Set(prevTracks.map((t) => t.id));\n        const addedTracks = tracks.filter((t) => !prevIds.has(t.id));\n\n        // Merge current UI state into new tracks before adding to engine\n        const currentTrackStates = trackStatesRef.current;\n        for (const track of addedTracks) {\n          const trackIndex = tracks.indexOf(track);\n          const trackState = currentTrackStates[trackIndex];\n          const trackWithState = {\n            ...track,\n            volume: trackState?.volume ?? track.volume,\n            muted: trackState?.muted ?? track.muted,\n            soloed: trackState?.soloed ?? track.soloed,\n            pan: trackState?.pan ?? track.pan,\n          };\n          engineRef.current!.addTrack(trackWithState);\n        }\n\n        // Update duration from all tracks (including newly added)\n        let maxDuration = 0;\n        tracks.forEach((track) => {\n          track.clips.forEach((clip) => {\n            const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n            maxDuration = Math.max(maxDuration, clipEnd);\n          });\n        });\n        setDuration(maxDuration);\n\n        // Initialize track states for the new tracks (preserve existing)\n        setTrackStates((prev) => {\n          if (prev.length === tracks.length) return prev;\n          const newStates = [...prev];\n          for (const track of addedTracks) {\n            newStates.push({\n              name: track.name,\n              muted: track.muted,\n              soloed: track.soloed,\n              volume: track.volume,\n              pan: track.pan,\n            });\n          }\n          return newStates;\n        });\n\n        // Extract audio buffers from all clips across all tracks\n        const buffers: AudioBuffer[] = [];\n        tracks.forEach((track) => {\n          track.clips.forEach((clip) => {\n            if (clip.audioBuffer) {\n              buffers.push(clip.audioBuffer);\n            }\n          });\n        });\n        setAudioBuffers(buffers);\n\n        prevTracksRef.current = tracks;\n        return;\n      } catch (err) {\n        console.warn(\n          '[waveform-playlist] Incremental add failed, falling through to full rebuild:',\n          String(err)\n        );\n        // Fall through to full loadAudio path\n      }\n    }\n\n    // Defer engine rebuild during progressive loading — tracks render visually\n    // (controls, clip shapes, peaks via worker) but the engine isn't built yet.\n    // When deferEngineRebuild flips to false, this effect re-runs and builds once.\n    if (deferEngineRebuild) {\n      // Still update duration so the timeline renders at the correct width\n      let maxDuration = 0;\n      tracks.forEach((track) => {\n        track.clips.forEach((clip) => {\n          const clipEnd = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n          maxDuration = Math.max(maxDuration, clipEnd);\n        });\n      });\n      setDuration(maxDuration);\n      return;\n    }\n\n    // Reset ready state for full rebuild\n    setIsReady(false);\n\n    if (tracks.length === 0) {\n      // Clear content state when all tracks are removed.\n      setAudioBuffers([]);\n      setDuration(0);\n      setTrackStates([]);\n      setPeaksDataArray([]);\n\n      // Always keep a persistent engine alive — even with no tracks — so the\n      // Transport is ready before playback/recording starts. Without this the\n      // engine is rebuilt asynchronously when the first track is added, and\n      // play() races that rebuild — intermittently no-opping (frozen\n      // playhead/time) and paying first-record latency. The engine is still\n      // created via dynamic import (lazy module load preserved, #510); only its\n      // lifecycle is eager.\n      if (engineRef.current) {\n        // Keep the existing engine; just clear its tracks.\n        engineRef.current.setTracks([]);\n        prevTracksRef.current = tracks;\n        return;\n      }\n      // No engine yet (initial mount with no tracks): fall through to the\n      // loadAudio path below, which builds a persistent engine for 0 tracks.\n    }\n\n    // Capture playback state before rebuilding playout (read from ref, not state,\n    // so isPlaying can be excluded from the dep array — play/pause must not trigger rebuilds)\n    const wasPlaying = isPlayingRef.current;\n    const resumePosition = currentTimeRef.current;\n\n    // Stop current playback and animation before disposing\n    if (engineRef.current && wasPlaying) {\n      // Suppressed: resumePlayback() (below) restarts from resumePosition once\n      // the rebuilt engine is ready — an unguarded mirror would clobber the\n      // refs with this (old, about-to-be-replaced) engine's rewound position\n      // in the meantime. See suppressEngineStopMirrorRef declaration.\n      suppressEngineStopMirrorRef.current = true;\n      engineRef.current.stop();\n      suppressEngineStopMirrorRef.current = false;\n      stopAnimationFrameLoop();\n      // Mark that we need to resume playback after playout is rebuilt\n      pendingResumeRef.current = { position: resumePosition };\n    }\n\n    let cancelled = false;\n\n    const loadAudio = async () => {\n      try {\n        // Extract all audio buffers from all clips across all tracks\n        const buffers: AudioBuffer[] = [];\n\n        tracks.forEach((track) => {\n          track.clips.forEach((clip) => {\n            if (clip.audioBuffer) {\n              buffers.push(clip.audioBuffer);\n            }\n          });\n        });\n\n        // Calculate total timeline duration from all clips across all tracks\n        // Use clip.sampleRate which is always defined (works for peaks-only clips too)\n        let maxDuration = 0;\n        tracks.forEach((track) => {\n          track.clips.forEach((clip) => {\n            const sampleRate = clip.sampleRate;\n            const clipEndSample = clip.startSample + clip.durationSamples;\n            const clipEnd = clipEndSample / sampleRate;\n            maxDuration = Math.max(maxDuration, clipEnd);\n          });\n        });\n\n        setAudioBuffers(buffers);\n        setDuration(maxDuration);\n\n        // Initialize or update track states, preserving existing UI state (mute/solo/volume/pan)\n        // Only initialize from ClipTrack props when trackStates is empty or track count changes\n        setTrackStates((prevStates) => {\n          if (prevStates.length === tracks.length) {\n            // Same number of tracks - preserve existing UI state, just update names\n            return prevStates.map((state, i) => ({\n              ...state,\n              name: tracks[i].name,\n            }));\n          }\n          // Track count changed - reinitialize from ClipTrack properties\n          return tracks.map((track) => ({\n            name: track.name,\n            muted: track.muted,\n            soloed: track.soloed,\n            volume: track.volume,\n            pan: track.pan,\n          }));\n        });\n\n        // Dispose old engine before creating new one\n        if (engineRef.current) {\n          engineRef.current.dispose();\n        }\n\n        // Reset tracks version tracking for the new engine\n        lastTracksVersionRef.current = 0;\n        engineTracksRef.current = null;\n\n        // Create engine adapter — custom if provided, else Tone.js (dynamically imported).\n        // Reset init flag — new adapter needs AudioContext resume on first play.\n        audioInitializedRef.current = false;\n        const adapter = await resolvePlayoutAdapter({\n          createAdapter,\n          effects,\n          soundFontCache: soundFontCacheRef.current,\n          sampleRate: sampleRateProp,\n        });\n        if (cancelled) {\n          adapter.dispose();\n          return;\n        }\n        // Reconcile sample rate from the adapter's own AudioContext. The setState\n        // calls later in loadAudio trigger a re-render that propagates this via\n        // `const sampleRate = sampleRateRef.current` at render.\n        sampleRateRef.current = adapter.audioContext.sampleRate;\n        adapterRef.current = adapter;\n        const engine = new PlaylistEngine({\n          adapter,\n          samplesPerPixel: samplesPerPixelRef.current,\n          zoomLevels: stableZoomLevels,\n        });\n\n        // Seed engine with current UI state so a fresh engine doesn't\n        // reset selection/loop/selectedTrack to defaults on the first statechange emission.\n        engine.setSelection(selectionStartRef.current ?? 0, selectionEndRef.current ?? 0);\n        engine.setLoopRegion(loopStartRef.current ?? 0, loopEndRef.current ?? 0);\n        if (isLoopEnabledRef.current) engine.setLoopEnabled(true);\n        engine.setMasterVolume(masterVolumeRef.current ?? 1.0);\n        if (selectedTrackIdRef.current) engine.selectTrack(selectedTrackIdRef.current);\n\n        // Merge current UI state into tracks before passing to engine\n        const currentTrackStates = trackStatesRef.current;\n        const tracksWithState = tracks.map((track, index) => {\n          const trackState = currentTrackStates[index];\n          return {\n            ...track,\n            volume: trackState?.volume ?? track.volume,\n            muted: trackState?.muted ?? track.muted,\n            soloed: trackState?.soloed ?? track.soloed,\n            pan: trackState?.pan ?? track.pan,\n          };\n        });\n\n        // Subscribe to engine statechange BEFORE setTracks() so the first\n        // emission (which carries correct canZoomIn/canZoomOut) is captured.\n        // Seeding statechanges fired above are harmless — they contain values\n        // that already match the hooks' current state, so onEngineState()\n        // ref guards skip them.\n        // Suppress tracks mirroring during the initial setTracks — the parent\n        // already has these tracks. The flag is local so it becomes false\n        // after setTracks returns (statechange fires synchronously).\n        let suppressTracksMirroring = true;\n        engine.on('statechange', (state: EngineState) => {\n          onSelectionEngineState(state);\n          onLoopEngineState(state);\n          onSelectedTrackEngineState(state);\n          onZoomEngineState(state);\n          onVolumeEngineState(state);\n          onUndoEngineState(state);\n\n          // Mirror engine tracks changes to parent via onTracksChange.\n          // tracksVersion only increments on structural track mutations (move,\n          // trim, split, setTracks, addTrack, removeTrack), not on\n          // selection/zoom/volume changes. Per-track mixer edits bump the\n          // engine's separate mixerVersion (#501) and are persisted here via\n          // trackStates, so they intentionally do not flow through onTracksChange.\n          if (!suppressTracksMirroring && state.tracksVersion !== lastTracksVersionRef.current) {\n            lastTracksVersionRef.current = state.tracksVersion;\n            engineTracksRef.current = state.tracks;\n\n            // trackStates is positionally indexed (no track id of its own) and\n            // is otherwise never touched by reorderTrack — the engine reorder is\n            // \"purely organizational\" (see PlaylistEngine.reorderTrack) and never\n            // rebuilds. Without this, a track's mixer identity (name/mute/solo/\n            // volume/pan) stays pinned to its OLD index after a reorder, so the\n            // control panel silently shows/controls the wrong track. Re-derive an\n            // id->state map from the pre-reorder pairing (tracksRef/trackStatesRef\n            // reflect the last committed render, i.e. before this mutation) and\n            // re-project it onto the new track order. Guarded to same-length\n            // arrays (addTrack/removeTrack already flow through a full rebuild\n            // that reinitializes trackStates) and to an actual order change (so\n            // non-reorder structural mutations like moveClip/trimClip/splitClip\n            // — which don't change track order — are a no-op here).\n            const oldTracks = tracksRef.current;\n            const oldStates = trackStatesRef.current;\n            if (oldTracks.length === oldStates.length && oldTracks.length === state.tracks.length) {\n              const idToState = new Map(oldTracks.map((t, i) => [t.id, oldStates[i]]));\n              const reordered = state.tracks.map((t, i) => idToState.get(t.id) ?? oldStates[i]);\n              const orderChanged = reordered.some((s, i) => s !== oldStates[i]);\n              if (orderChanged) {\n                setTrackStates(reordered);\n              }\n            }\n\n            if (onTracksChange) {\n              onTracksChange(state.tracks);\n            } else {\n              console.warn(\n                '[waveform-playlist] Engine tracks changed but onTracksChange prop is not set — ' +\n                  'UI will revert on next render. Pass onTracksChange to WaveformPlaylistProvider.'\n              );\n            }\n          }\n        });\n\n        // Engine-initiated stop mirror (final-review CRITICAL 1, #608 follow-up).\n        // PlaylistEngine self-stops when a bounded play(start, end) completes on\n        // an adapter that reports onPlaybackEnded (Tone) — see engine CLAUDE.md\n        // \"onPlaybackEnded hook\". That self-stop rewinds the engine's own current\n        // time to playStartPosition BEFORE the rAF loop's own end-of-playback\n        // checks (below) can observe it, so the loop's `time >= playbackEndTimeRef`\n        // / `time >= duration` comparisons never fire — React isPlaying stays\n        // true and the loop spins forever at the rewound position. Mirroring the\n        // engine's 'stop' event makes React authoritative-from-engine for ANY\n        // engine-initiated stop, closing that race. The engine already rewound\n        // its own time (`_currentTime = _playStartPosition`) — we only mirror\n        // that into React's refs/state, we never seek() again here.\n        //\n        // Suppressed via suppressEngineStopMirrorRef around provider-initiated\n        // engine.stop() calls that immediately seek/restart playback (play(),\n        // setSelection, reschedulePlayback, engine-rebuild-with-resume) — those\n        // already set the correct target position themselves; an unguarded\n        // mirror would clobber it mid-transition with the pre-restart position.\n        // Stop paths that terminate playback (stop(), the rAF loop's own\n        // annotation/selection/duration-end branches) don't need suppression:\n        // they either write the identical value the mirror would (both read\n        // _playStartPosition), or run their own setCurrentTimeRefs call\n        // synchronously right after this listener, which — as plain refs, not\n        // batched state — simply overwrites the mirror's write with no other\n        // code observing the transient value in between.\n        engine.on('stop', () => {\n          if (suppressEngineStopMirrorRef.current) return;\n          setIsPlaying(false);\n          stopAnimationFrameLoop();\n          const stoppedAt = engine.getCurrentTime();\n          setCurrentTimeRefs(stoppedAt);\n          setCurrentTime(stoppedAt);\n        });\n\n        engine.setTracks(tracksWithState);\n        suppressTracksMirroring = false;\n        lastTracksVersionRef.current = engine.getState().tracksVersion;\n        engineRef.current = engine;\n\n        setIsReady(true);\n\n        // Dispatch custom event for external listeners\n        const event = new CustomEvent('waveform-playlist:ready', {\n          detail: {\n            trackCount: tracks.length,\n            duration: maxDuration,\n          },\n        });\n        window.dispatchEvent(event);\n\n        prevTracksRef.current = tracks;\n\n        onReady?.();\n      } catch (error) {\n        // Dispose an adapter created before the engine failed to build (e.g. invalid\n        // zoomLevels make `new PlaylistEngine` throw) — the cleanup skips it because\n        // engineRef was never assigned, so it would otherwise leak a live AudioContext.\n        if (!engineRef.current && adapterRef.current) {\n          try {\n            adapterRef.current.dispose();\n          } catch (disposeErr) {\n            console.warn(\n              '[waveform-playlist] adapter dispose after load failure threw: ' + String(disposeErr)\n            );\n          }\n          adapterRef.current = null;\n        }\n        console.warn('[waveform-playlist] Error loading audio: ' + String(error));\n        onError?.(error instanceof Error ? error : new Error(String(error)));\n      }\n    };\n\n    loadAudio();\n\n    return () => {\n      cancelled = true;\n      // Skip disposal when the next render's guard will keep the engine alive.\n      // skipEngineDisposeRef is set during the render phase (before this cleanup runs).\n      if (skipEngineDisposeRef.current) {\n        skipEngineDisposeRef.current = false;\n        return;\n      }\n      stopAnimationFrameLoop();\n      if (engineRef.current) {\n        engineRef.current.dispose();\n        adapterRef.current = null;\n      }\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [\n    tracks,\n    // isEngineTracks is intentionally excluded — it's a render-phase guard read\n    // inside the effect body, not a trigger. Including it causes a spurious re-run\n    // when it flips from true→false after engineTracksRef is cleared.\n    // isPlaying is intentionally excluded — read from isPlayingRef inside the\n    // effect body. Including it causes a full engine+playout rebuild on every\n    // play/pause/stop, destroying and recreating all audio Players.\n    // soundFontCache is deliberately excluded — late cache changes are forwarded\n    // to the live adapter by the sync effect below; only adapter creation reads it\n    // (via soundFontCacheRef).\n    onReady,\n    onError,\n    effects,\n    stopAnimationFrameLoop,\n    onSelectionEngineState,\n    onLoopEngineState,\n    onSelectedTrackEngineState,\n    onZoomEngineState,\n    onVolumeEngineState,\n    onUndoEngineState,\n    onTracksChange,\n    masterVolumeRef,\n    selectionStartRef,\n    selectionEndRef,\n    loopStartRef,\n    loopEndRef,\n    isLoopEnabledRef,\n    stableZoomLevels,\n    deferEngineRebuild,\n  ]);\n\n  // Forward late-arriving / swapped SoundFontCache to the live adapter so\n  // MIDI tracks upgrade from PolySynth without an engine rebuild. On mount\n  // this may run against a just-created adapter (the loadAudio effect is\n  // declared earlier, runs first, and assigns adapterRef synchronously) or\n  // against null (empty tracks). Both are safe: the helper no-ops on null,\n  // and the adapter skips MIDI tracks whose routing is unchanged.\n  useEffect(() => {\n    syncSoundFontCacheToAdapter(adapterRef.current, soundFontCache);\n  }, [soundFontCache]);\n\n  // Regenerate peaks when zoom, mono, or waveformDataCache changes (without reloading audio)\n  // Peak sources in priority order:\n  //   A) clip.waveformData — external pre-computed peaks (e.g. from BBC audiowaveform)\n  //   B) waveformDataCache — worker-generated WaveformData (fast resample on zoom)\n  //   C) empty peaks — clip is still loading or has no audio data\n  useEffect(() => {\n    if (tracks.length === 0) return;\n\n    const allTrackPeaks: TrackClipPeaks[] = tracks.map((track) => {\n      const clipPeaks: ClipPeaks[] = track.clips.map((clip) => {\n        let peaks: PeakData | undefined;\n\n        // Path A: External pre-computed waveform data (e.g. from audiowaveform .dat file)\n        // When sample rates match, use clip offsets directly.\n        // When rates mismatch (e.g., waveformData at 48000 Hz, clip rebuilt at 44100 Hz\n        // after audio decode), convert offsets to the waveformData's sample space and\n        // adjust samplesPerPixel so peaks fill the correct container width.\n        // Path B (worker) replaces these on next render once cache is populated.\n        if (clip.waveformData) {\n          try {\n            const wdRate = clip.waveformData.sample_rate;\n            const clipRate = clip.sampleRate;\n            let peakOffset = clip.offsetSamples;\n            let peakDuration = clip.durationSamples;\n            let peakSpp = samplesPerPixel;\n            if (wdRate !== clipRate && clipRate > 0 && wdRate > 0) {\n              const ratio = wdRate / clipRate;\n              peakOffset = Math.round(clip.offsetSamples * ratio);\n              peakDuration = Math.round(clip.durationSamples * ratio);\n              peakSpp = Math.max(1, Math.round(samplesPerPixel * ratio));\n            }\n            peaks = extractPeaksFromWaveformDataFull(\n              clip.waveformData as WaveformData,\n              peakSpp,\n              mono,\n              peakOffset,\n              peakDuration\n            );\n          } catch (err) {\n            console.warn(\n              '[waveform-playlist] Failed to extract peaks from waveformData: ' + String(err)\n            );\n          }\n        }\n\n        // Path B: Worker-generated WaveformData cache (fast resample on zoom)\n        if (!peaks) {\n          const cached = waveformDataCache.get(clip.id);\n          if (cached) {\n            try {\n              peaks = extractPeaksFromWaveformDataFull(\n                cached,\n                samplesPerPixel,\n                mono,\n                clip.offsetSamples,\n                clip.durationSamples\n              );\n            } catch (err) {\n              console.warn(\n                '[waveform-playlist] Failed to extract peaks from cache: ' + String(err)\n              );\n            }\n          }\n        }\n\n        // Path C: No peaks data available yet — render empty while worker processes.\n        // Use correct channel count from audioBuffer to prevent track height shift\n        // when peaks arrive (mono mode collapses to 1 channel).\n        if (!peaks) {\n          if (!deferEngineRebuild && !clip.audioBuffer && !clip.waveformData && !clip.midiNotes) {\n            console.warn(\n              `[waveform-playlist] Clip \"${clip.id}\" has no audio data or waveform data`\n            );\n          }\n          const numChannels = mono ? 1 : (clip.audioBuffer?.numberOfChannels ?? 1);\n          // MIDI clips: derive pixel width from sample duration so the clip renders\n          // at the correct width instead of zero-width blank tracks.\n          const pixelLength = clip.midiNotes\n            ? Math.ceil(clip.durationSamples / samplesPerPixel)\n            : 0;\n          peaks = {\n            length: pixelLength,\n            data: Array.from({ length: numChannels }, () => new Int16Array(pixelLength * 2)),\n            bits: 16,\n          };\n        }\n\n        return {\n          clipId: clip.id,\n          trackName: track.name,\n          peaks,\n          startSample: clip.startSample,\n          durationSamples: clip.durationSamples,\n          fadeIn: clip.fadeIn,\n          fadeOut: clip.fadeOut,\n          midiNotes: clip.midiNotes,\n          sampleRate: clip.sampleRate,\n          offsetSamples: clip.offsetSamples,\n        };\n      });\n\n      return clipPeaks;\n    });\n\n    setPeaksDataArray(allTrackPeaks);\n  }, [tracks, samplesPerPixel, mono, waveformDataCache, deferEngineRebuild]);\n\n  // Audio clock from the adapter's own AudioContext (#510 — replaces tone's\n  // getContext()). Stable; reads a ref so identity never changes.\n  const getAudioContextTime = useCallback(\n    () => adapterRef.current?.audioContext.currentTime ?? 0,\n    []\n  );\n\n  // Returns current playback time from engine (auto-wraps at loop boundaries).\n  // Falls back to manual calculation when engine is unavailable.\n  const getPlaybackTimeFallbackWarnedRef = useRef(false);\n  const getPlaybackTime = useCallback(() => {\n    if (engineRef.current) {\n      return engineRef.current.getCurrentTime();\n    }\n    // Fallback: manual calculation (does not handle loop wrapping)\n    if (!getPlaybackTimeFallbackWarnedRef.current) {\n      getPlaybackTimeFallbackWarnedRef.current = true;\n      console.warn(\n        '[waveform-playlist] getPlaybackTime called without engine. ' +\n          'Falling back to manual elapsed time (loop wrapping will not work).'\n      );\n    }\n    const elapsed = getAudioContextTime() - (playbackStartTimeRef.current ?? 0);\n    return (audioStartPositionRef.current ?? 0) + elapsed;\n  }, [getAudioContextTime]);\n\n  // Resting (non-playing) cursor positions display the raw time unchanged.\n  // Latency compensation (outputLatency + lookAhead) models the gap between\n  // the Transport's scheduling position and audible output — a property of\n  // *playing*, not of position. A seeked/paused/stopped cursor has no audible\n  // counterpart; the commanded position is the truth. During playback the\n  // animation loop uses engine.getAudibleTime() instead.\n  const toVisualTime = useCallback((rawTime: number): number => {\n    return Number.isFinite(rawTime) ? Math.max(0, rawTime) : 0;\n  }, []);\n\n  // Pair-write helper for the dual-ref pattern. Storage stays raw on\n  // `currentTimeRef`; display gets the guard-clamped raw version on `visualTimeRef`\n  // (compensation happens in the animation loop via engine.getAudibleTime() while playing).\n  // Use this at every pause/seek/stop/loop site — never assign one ref alone\n  // (browser/CLAUDE.md: \"Display uses visualTimeRef\").\n  const setCurrentTimeRefs = useCallback(\n    (rawTime: number) => {\n      currentTimeRef.current = rawTime;\n      visualTimeRef.current = toVisualTime(rawTime);\n    },\n    [toVisualTime]\n  );\n\n  // Read the engine's scheduler lookahead (Tone adapter ~0.1s, native 0).\n  // Exposed through PlaybackAnimationContextValue so consumers (e.g., recording\n  // live preview) compute audible-latency from the same source as the playhead.\n  const getLookAhead = useCallback((): number => {\n    return engineRef.current?.lookAhead ?? 0;\n  }, []);\n\n  const getOutputLatency = useCallback((): number => {\n    const audioCtx = adapterRef.current?.audioContext;\n    return audioCtx && 'outputLatency' in audioCtx ? (audioCtx as AudioContext).outputLatency : 0;\n  }, []);\n\n  const registerFrameCallback = useCallback((id: string, cb: (data: FrameData) => void) => {\n    frameCallbacksRef.current.set(id, cb);\n  }, []);\n\n  const unregisterFrameCallback = useCallback((id: string) => {\n    frameCallbacksRef.current.delete(id);\n  }, []);\n\n  // Animation loop\n  const startAnimationLoop = useCallback(() => {\n    const updateTime = () => {\n      // Get current time from engine (auto-wraps at loop boundaries via Transport.seconds)\n      const time = getPlaybackTime();\n      currentTimeRef.current = time;\n\n      // Compute visual time once — all visual consumers use this same value.\n      // engine.getAudibleTime() subtracts outputLatency (hardware DAC delay)\n      // and adapter.lookAhead (Tone.js Transport runs ~100ms ahead of audible),\n      // holding at the play-start position during the pre-roll window. Native\n      // adapters report lookAhead 0, so this is a near no-op there.\n      const visualRaw = engineRef.current ? engineRef.current.getAudibleTime() : time;\n      const visualTime = Number.isFinite(visualRaw) ? Math.max(0, visualRaw) : 0;\n      visualTimeRef.current = visualTime;\n\n      // Drive registered per-frame callbacks BEFORE stop checks so the\n      // final frame renders at the correct stop position (not one frame behind).\n      const sr = sampleRateRef.current;\n      const spp = samplesPerPixelRef.current;\n      const frameData: FrameData = {\n        time,\n        visualTime,\n        sampleRate: sr,\n        samplesPerPixel: spp,\n      };\n      for (const cb of frameCallbacksRef.current.values()) {\n        cb(frameData);\n      }\n\n      // Handle annotation playback based on continuous play mode\n      const currentAnnotations = annotationsRef.current;\n      if (currentAnnotations.length > 0) {\n        const currentAnnotation = currentAnnotations.find(\n          (ann) => time >= ann.start && time < ann.end\n        );\n\n        if (continuousPlayRef.current) {\n          // Continuous play ON: update active annotation, let audio play to the end\n          if (currentAnnotation && currentAnnotation.id !== activeAnnotationIdRef.current) {\n            setActiveAnnotationId(currentAnnotation.id);\n          } else if (!currentAnnotation && activeAnnotationIdRef.current !== null) {\n            // Clear the active annotation when we're past it, but don't stop playback\n            // Let playback continue until the audio actually ends (handled by duration check)\n            setActiveAnnotationId(null);\n          }\n        } else {\n          // Continuous play OFF: stop at end of current annotation\n          if (activeAnnotationIdRef.current) {\n            const activeAnnotation = currentAnnotations.find(\n              (ann) => ann.id === activeAnnotationIdRef.current\n            );\n            if (activeAnnotation && time >= activeAnnotation.end) {\n              // Stop playback at end of current annotation\n              if (engineRef.current) {\n                engineRef.current.stop();\n              }\n              setIsPlaying(false);\n              setCurrentTimeRefs(playStartPositionRef.current);\n              setCurrentTime(playStartPositionRef.current);\n              return;\n            }\n          } else {\n            // If no active annotation ID is set, use the current annotation\n            if (currentAnnotation) {\n              setActiveAnnotationId(currentAnnotation.id);\n            }\n          }\n        }\n      }\n\n      // Handle automatic scroll — uses shared visualTime for alignment with playhead\n      if (isAutomaticScrollRef.current && scrollContainerRef.current && duration > 0) {\n        const container = scrollContainerRef.current;\n        const pixelPosition = (visualTime * sr) / spp;\n        const containerWidth = container.clientWidth;\n\n        // Continuously scroll to keep playhead centered.\n        // Math.round prevents sub-pixel jitter from browser integer rounding.\n        const targetScrollLeft = Math.round(Math.max(0, pixelPosition - containerWidth / 2));\n        container.scrollLeft = targetScrollLeft;\n      }\n\n      // Check if we've reached the playback end time (for selection playback)\n      if (playbackEndTimeRef.current !== null && time >= playbackEndTimeRef.current) {\n        // Stop playback at selection end (selection playback is separate from looping)\n        if (engineRef.current) {\n          engineRef.current.stop();\n        }\n        setIsPlaying(false);\n        setCurrentTimeRefs(playbackEndTimeRef.current);\n        setCurrentTime(playbackEndTimeRef.current);\n        playbackEndTimeRef.current = null; // Clear the end time\n        return;\n      }\n\n      // Loop is handled natively by Tone.js Transport (Transport.loop/loopStart/loopEnd).\n      // Transport.seconds auto-wraps at loop boundaries, so getPlaybackTime() returns\n      // the correct position without manual detection here.\n\n      // Auto-stop at the end of the timeline (player-style default), unless:\n      // - indefinitePlayback: the transport rolls until an explicit\n      //   stop/pause (DAW-style, matching dawcore's indefinite-playback), or\n      // - a recording session is active: overdub playback must run past the\n      //   end of existing material (#589/#590).\n      // Explicit ends (selection playback, annotation boundaries) are\n      // handled above regardless.\n      if (time >= duration && !indefinitePlaybackRef.current && !recordingActiveRef.current) {\n        // Stop playback - inline to avoid circular dependency\n        if (engineRef.current) {\n          engineRef.current.stop();\n        }\n        setIsPlaying(false);\n        setCurrentTimeRefs(playStartPositionRef.current);\n        setCurrentTime(playStartPositionRef.current);\n        setActiveAnnotationId(null);\n        return;\n      }\n      startAnimationFrameLoop(updateTime);\n    };\n    startAnimationFrameLoop(updateTime);\n  }, [\n    duration,\n    setActiveAnnotationId,\n    startAnimationFrameLoop,\n    getPlaybackTime,\n    setCurrentTimeRefs,\n  ]);\n\n  const stopAnimationLoop = stopAnimationFrameLoop;\n\n  // Restart animation loop and reschedule playout when continuousPlay changes during playback\n  // This ensures the loop always has the current continuousPlay value\n  // and removes duration limits when switching to continuous play\n  useEffect(() => {\n    const reschedulePlayback = async () => {\n      if (isPlaying && animationFrameRef.current && engineRef.current) {\n        // When toggling continuous play ON, reschedule playout without duration limit\n        // so audio continues past the current annotation boundary\n        if (continuousPlay) {\n          const currentPos = currentTimeRef.current;\n\n          // Stop current playout (which may have duration limit + pause callback).\n          // Suppressed: this immediately restarts playback below — see\n          // suppressEngineStopMirrorRef declaration.\n          suppressEngineStopMirrorRef.current = true;\n          engineRef.current.stop();\n          suppressEngineStopMirrorRef.current = false;\n          stopAnimationLoop();\n\n          const timeNow = getAudioContextTime();\n          playbackStartTimeRef.current = timeNow;\n          audioStartPositionRef.current = currentPos;\n\n          // Play without duration - will play to end of track\n          engineRef.current.play(currentPos);\n          startAnimationLoop();\n        } else {\n          // Just restart animation loop for continuous play OFF\n          stopAnimationLoop();\n          startAnimationLoop();\n        }\n      }\n    };\n\n    reschedulePlayback().catch((err) => {\n      console.warn('[waveform-playlist] Failed to reschedule playback:', err);\n      setIsPlaying(false);\n      stopAnimationLoop();\n    });\n  }, [\n    continuousPlay,\n    isPlaying,\n    startAnimationLoop,\n    stopAnimationLoop,\n    animationFrameRef,\n    getAudioContextTime,\n  ]);\n\n  // Resume playback after tracks change (e.g., after splitting a clip during playback)\n  useEffect(() => {\n    const resumePlayback = async () => {\n      if (pendingResumeRef.current && engineRef.current) {\n        const { position } = pendingResumeRef.current;\n        pendingResumeRef.current = null;\n\n        const timeNow = getAudioContextTime();\n        playbackStartTimeRef.current = timeNow;\n        audioStartPositionRef.current = position;\n\n        if (!audioInitializedRef.current) {\n          await engineRef.current.init();\n          audioInitializedRef.current = true;\n        }\n\n        engineRef.current.play(position);\n        setIsPlaying(true);\n        startAnimationLoop();\n      }\n    };\n\n    resumePlayback().catch((err) => {\n      console.warn('[waveform-playlist] Failed to resume playback after track change:', err);\n      setIsPlaying(false);\n      stopAnimationLoop();\n    });\n  }, [tracks, startAnimationLoop, stopAnimationLoop, getAudioContextTime]);\n\n  // Playback controls\n  const play = useCallback(\n    async (startTime?: number, playDuration?: number) => {\n      if (!engineRef.current) return;\n\n      const actualStartTime = startTime ?? currentTimeRef.current;\n      playStartPositionRef.current = actualStartTime;\n\n      // Update currentTimeRef to match the actual start position\n      // This ensures the animation loop starts from the correct position\n      setCurrentTimeRefs(actualStartTime);\n\n      // Stop existing playback and animation loop before restarting.\n      // engine.stop() resets engine._currentTime to _playStartPosition (could be 0)\n      // and sets _isPlaying=false. AnimatedPlayhead's RAF may fire during this\n      // window and call engine.getCurrentTime() which would return 0.\n      // The seek() immediately sets _currentTime to the intended start position,\n      // preventing the cursor flash.\n      // Suppressed: seek()+play() below immediately re-establish the correct\n      // position — see suppressEngineStopMirrorRef declaration.\n      suppressEngineStopMirrorRef.current = true;\n      engineRef.current.stop();\n      suppressEngineStopMirrorRef.current = false;\n      engineRef.current.seek(actualStartTime);\n      stopAnimationLoop();\n\n      // Record timing for accurate position tracking using the adapter's AudioContext\n      const startTimeNow = getAudioContextTime();\n      playbackStartTimeRef.current = startTimeNow;\n      audioStartPositionRef.current = actualStartTime;\n\n      // Set playback end time if playing with duration (e.g., selection playback)\n      playbackEndTimeRef.current =\n        playDuration !== undefined ? actualStartTime + playDuration : null;\n\n      // Don't set up playback complete callback for annotations\n      // The animation loop handles stopping at annotation boundaries\n      // This avoids callback timing issues when switching between annotations\n\n      // Ensure AudioContext is resumed (requires user gesture, no-op after first call).\n      // Guarded by ref so subsequent plays skip the await entirely.\n      if (!audioInitializedRef.current) {\n        await engineRef.current.init();\n        audioInitializedRef.current = true;\n      }\n\n      const endTime = playDuration !== undefined ? actualStartTime + playDuration : undefined;\n      try {\n        engineRef.current.play(actualStartTime, endTime);\n      } catch (err) {\n        console.warn('[waveform-playlist] Playback failed to start:', err);\n        stopAnimationLoop();\n        return;\n      }\n      setIsPlaying(true);\n      startAnimationLoop();\n    },\n    [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs, getAudioContextTime]\n  );\n\n  const pause = useCallback(() => {\n    if (!engineRef.current) return;\n\n    // Get current position from engine (auto-wraps at loop boundaries)\n    const pauseTime = getPlaybackTime();\n\n    engineRef.current.pause();\n    setIsPlaying(false);\n    stopAnimationLoop();\n\n    // Update to the calculated pause position\n    setCurrentTimeRefs(pauseTime);\n    setCurrentTime(pauseTime);\n  }, [stopAnimationLoop, getPlaybackTime, setCurrentTimeRefs]);\n\n  const stop = useCallback(() => {\n    if (!engineRef.current) return;\n\n    engineRef.current.stop();\n    setIsPlaying(false);\n    stopAnimationLoop();\n\n    // Return cursor to where playback started (Audacity-style)\n    setCurrentTimeRefs(playStartPositionRef.current);\n    setCurrentTime(playStartPositionRef.current);\n    setActiveAnnotationId(null);\n  }, [stopAnimationLoop, setActiveAnnotationId, setCurrentTimeRefs]);\n\n  // Seek to a specific time - works whether playing or stopped\n  const seekTo = useCallback(\n    (time: number) => {\n      // Clamp time to valid range\n      const clampedTime = Math.max(0, Math.min(time, duration));\n\n      // Update the current time state\n      setCurrentTimeRefs(clampedTime);\n      setCurrentTime(clampedTime);\n\n      // If currently playing, restart at the new position.\n      // play() handles stop+seek+restart internally.\n      if (isPlaying && engineRef.current) {\n        play(clampedTime);\n      }\n    },\n    [duration, isPlaying, play, setCurrentTimeRefs]\n  );\n\n  // Track controls\n  const setTrackMute = useCallback(\n    (trackIndex: number, muted: boolean) => {\n      const trackId = tracksRef.current[trackIndex]?.id;\n      if (!trackId) return;\n\n      const newStates = [...trackStates];\n      newStates[trackIndex] = { ...newStates[trackIndex], muted };\n      setTrackStates(newStates);\n\n      if (engineRef.current) {\n        engineRef.current.setTrackMute(trackId, muted);\n      }\n    },\n    [trackStates]\n  );\n\n  const setTrackSolo = useCallback(\n    (trackIndex: number, soloed: boolean) => {\n      const trackId = tracksRef.current[trackIndex]?.id;\n      if (!trackId) return;\n\n      const newStates = [...trackStates];\n      newStates[trackIndex] = { ...newStates[trackIndex], soloed };\n      setTrackStates(newStates);\n\n      if (engineRef.current) {\n        engineRef.current.setTrackSolo(trackId, soloed);\n      }\n    },\n    [trackStates]\n  );\n\n  const setTrackVolume = useCallback(\n    (trackIndex: number, volume: number) => {\n      const trackId = tracksRef.current[trackIndex]?.id;\n      if (!trackId) return;\n\n      const newStates = [...trackStates];\n      newStates[trackIndex] = { ...newStates[trackIndex], volume };\n      setTrackStates(newStates);\n\n      if (engineRef.current) {\n        engineRef.current.setTrackVolume(trackId, volume);\n      }\n    },\n    [trackStates]\n  );\n\n  const setTrackPan = useCallback(\n    (trackIndex: number, pan: number) => {\n      const trackId = tracksRef.current[trackIndex]?.id;\n      if (!trackId) return;\n\n      const newStates = [...trackStates];\n      newStates[trackIndex] = { ...newStates[trackIndex], pan };\n      setTrackStates(newStates);\n\n      if (engineRef.current) {\n        engineRef.current.setTrackPan(trackId, pan);\n      }\n    },\n    [trackStates]\n  );\n\n  const reorderTrack = useCallback(\n    (trackId: string, toIndex: number) => {\n      if (!engineRef.current) {\n        console.warn('[waveform-playlist] reorderTrack: engine not ready, call ignored');\n        return;\n      }\n      engineRef.current.reorderTrack(trackId, toIndex);\n    },\n    [engineRef]\n  );\n\n  // @dnd-kit/react's Feedback and OptimisticSortingPlugin sortable plugins\n  // perform raw, React-bypassing DOM node moves (insertAdjacentElement /\n  // replaceWith) as part of their live drag-preview and collision-commit\n  // mechanics — confirmed via MutationObserver instrumentation to physically\n  // relocate the React-owned track-controls row during a track-reorder drag,\n  // leaving its parent/nextSibling different from what React's fiber tree\n  // still believes. That desync is permanent for the session: every LATER\n  // React-driven reorder (button OR drag) computes correct data but silently\n  // fails to patch the DOM for these rows. `SortableTrackControls` already\n  // sets `feedback: 'move'` to avoid the placeholder-splice half of this (see\n  // its comment), but OptimisticSortingPlugin's own `reorder()` DOM splice\n  // fires independently on any drag that commits a real index change and\n  // isn't configurable away without losing live index tracking. Bumping this\n  // epoch forces PlaylistVisualization to re-key (remount) the track-controls\n  // slots after every track-reorder drag ends — committed or canceled, since\n  // corruption can occur before a final index is even decided — discarding\n  // any corrupted fiber state so the NEXT reorder (drag or button) starts\n  // from a clean DOM. Called from ClipInteractionProvider's onDragEnd.\n  const bumpTrackReorderEpoch = useCallback(() => {\n    setTrackReorderEpoch((epoch) => epoch + 1);\n  }, []);\n\n  // Selection — wraps hook setter with playback side-effects (provider concern).\n  const setSelection = useCallback(\n    (start: number, end: number) => {\n      setSelectionEngine(start, end);\n      setCurrentTimeRefs(start);\n      setCurrentTime(start);\n\n      if (isPlaying && engineRef.current) {\n        // Stop + seek before restarting playback from selection start.\n        // engine.stop() resets _currentTime and _isPlaying, creating a window\n        // where getCurrentTime() returns wrong value. seek() immediately\n        // corrects _currentTime so any RAF firing in between reads the\n        // intended position. engine.play() then restarts everything.\n        // Suppressed: seek()+play() below immediately re-establish the correct\n        // position — see suppressEngineStopMirrorRef declaration. Unguarded,\n        // the mirror would also flip isPlaying to false with nothing here to\n        // set it back true (isPlaying was already true on entry to this branch).\n        suppressEngineStopMirrorRef.current = true;\n        engineRef.current.stop();\n        suppressEngineStopMirrorRef.current = false;\n        engineRef.current.seek(start);\n        engineRef.current.play(start);\n      }\n    },\n    [isPlaying, setSelectionEngine, setCurrentTimeRefs]\n  );\n\n  // Memoize setScrollContainer callback\n  const setScrollContainer = useCallback((element: HTMLDivElement | null) => {\n    scrollContainerRef.current = element;\n  }, []);\n\n  // Stable callback ref for onAnnotationsChange to avoid re-creating controls context\n  const onAnnotationsChangeRef = useRef(onAnnotationsChange);\n  onAnnotationsChangeRef.current = onAnnotationsChange;\n\n  const setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>> = useCallback(\n    (action) => {\n      const updated = typeof action === 'function' ? action(annotationsRef.current) : action;\n      if (!onAnnotationsChangeRef.current) {\n        if (process.env.NODE_ENV !== 'production') {\n          console.warn(\n            'waveform-playlist: setAnnotations was called but no onAnnotationsChange callback is provided. ' +\n              'Annotation edits will not persist. Pass onAnnotationsChange to WaveformPlaylistProvider to handle annotation updates.'\n          );\n        }\n        return;\n      }\n      onAnnotationsChangeRef.current(updated);\n    },\n    []\n  );\n\n  const sampleRate = sampleRateRef.current;\n  const timeScaleHeight = timescale ? 30 : 0;\n  const minimumPlaylistHeight = tracks.length * waveHeight + timeScaleHeight;\n\n  // Split context values for performance optimization\n  // Animation context only re-renders consumers on discrete events\n  // (play/pause/stop/seek), never during the animation loop itself\n\n  const animationValue: PlaybackAnimationContextValue = useMemo(\n    () => ({\n      isPlaying,\n      currentTime,\n      currentTimeRef,\n      visualTimeRef,\n      playbackStartTimeRef,\n      audioStartPositionRef,\n      getPlaybackTime,\n      getAudioContextTime,\n      getLookAhead,\n      getOutputLatency,\n      registerFrameCallback,\n      unregisterFrameCallback,\n    }),\n    [\n      isPlaying,\n      currentTime,\n      currentTimeRef,\n      visualTimeRef,\n      playbackStartTimeRef,\n      audioStartPositionRef,\n      getPlaybackTime,\n      getAudioContextTime,\n      getLookAhead,\n      getOutputLatency,\n      registerFrameCallback,\n      unregisterFrameCallback,\n    ]\n  );\n\n  const stateValue: PlaylistStateContextValue = useMemo(\n    () => ({\n      continuousPlay,\n      linkEndpoints,\n      annotationsEditable,\n      isAutomaticScroll,\n      isLoopEnabled,\n      annotations,\n      activeAnnotationId,\n      selectionStart,\n      selectionEnd,\n      selectedTrackId,\n      loopStart,\n      loopEnd,\n      indefinitePlayback,\n      fillViewport,\n      canUndo,\n      canRedo,\n    }),\n    [\n      continuousPlay,\n      linkEndpoints,\n      annotationsEditable,\n      isAutomaticScroll,\n      isLoopEnabled,\n      annotations,\n      activeAnnotationId,\n      selectionStart,\n      selectionEnd,\n      selectedTrackId,\n      loopStart,\n      loopEnd,\n      indefinitePlayback,\n      fillViewport,\n      canUndo,\n      canRedo,\n    ]\n  );\n\n  const setCurrentTimeControl = useCallback(\n    (time: number) => {\n      setCurrentTimeRefs(time);\n      setCurrentTime(time);\n    },\n    [setCurrentTimeRefs]\n  );\n\n  const setAutomaticScrollControl = useCallback((enabled: boolean) => {\n    setIsAutomaticScroll(enabled);\n  }, []);\n\n  const controlsValue: PlaylistControlsContextValue = useMemo(\n    () => ({\n      // Playback controls\n      play,\n      pause,\n      stop,\n      seekTo,\n      setCurrentTime: setCurrentTimeControl,\n\n      // Track controls\n      setTrackMute,\n      setTrackSolo,\n      setTrackVolume,\n      setTrackPan,\n      reorderTrack,\n      bumpTrackReorderEpoch,\n      setTrackDragPreview,\n\n      // Selection\n      setSelection,\n      setSelectedTrackId: setSelectedTrackIdControl,\n\n      // Time format\n      setTimeFormat,\n      formatTime,\n\n      // Zoom\n      zoomIn: zoom.zoomIn,\n      zoomOut: zoom.zoomOut,\n\n      // Master volume\n      setMasterVolume,\n\n      // Automatic scroll\n      setAutomaticScroll: setAutomaticScrollControl,\n      setScrollContainer,\n      scrollContainerRef,\n\n      // Annotation controls\n      setContinuousPlay,\n      setLinkEndpoints,\n      setAnnotationsEditable,\n      setAnnotations,\n      setActiveAnnotationId,\n\n      // Loop controls\n      setLoopEnabled,\n      setLoopRegion,\n      setLoopRegionFromSelection,\n      clearLoopRegion,\n\n      // Undo/redo\n      undo,\n      redo,\n\n      // Recording\n      setRecordingActive,\n    }),\n    [\n      play,\n      pause,\n      stop,\n      seekTo,\n      setCurrentTimeControl,\n      setTrackMute,\n      setTrackSolo,\n      setTrackVolume,\n      setTrackPan,\n      reorderTrack,\n      bumpTrackReorderEpoch,\n      setTrackDragPreview,\n      setSelection,\n      setSelectedTrackIdControl,\n      setTimeFormat,\n      formatTime,\n      zoom.zoomIn,\n      zoom.zoomOut,\n      setMasterVolume,\n      setAutomaticScrollControl,\n      setScrollContainer,\n      scrollContainerRef,\n      setContinuousPlay,\n      setLinkEndpoints,\n      setAnnotationsEditable,\n      setAnnotations,\n      setActiveAnnotationId,\n      setLoopEnabled,\n      setLoopRegion,\n      setLoopRegionFromSelection,\n      clearLoopRegion,\n      undo,\n      redo,\n      setRecordingActive,\n    ]\n  );\n\n  const dataValue: PlaylistDataContextValue = useMemo(\n    () => ({\n      duration,\n      audioBuffers,\n      peaksDataArray,\n      trackStates,\n      tracks,\n      sampleRate,\n      waveHeight,\n      timeScaleHeight,\n      minimumPlaylistHeight,\n      controls,\n      playoutRef: engineRef,\n      samplesPerPixel,\n      timeFormat,\n      masterVolume,\n      canZoomIn: zoom.canZoomIn,\n      canZoomOut: zoom.canZoomOut,\n      barWidth,\n      barGap,\n      roundedBars,\n      progressBarWidth,\n      isReady,\n      trackReorderEpoch,\n      trackDragPreview,\n      mono,\n      isDraggingRef,\n      onTracksChange,\n    }),\n    [\n      duration,\n      audioBuffers,\n      peaksDataArray,\n      trackStates,\n      tracks,\n      sampleRate,\n      waveHeight,\n      timeScaleHeight,\n      minimumPlaylistHeight,\n      controls,\n      engineRef,\n      samplesPerPixel,\n      timeFormat,\n      masterVolume,\n      zoom.canZoomIn,\n      zoom.canZoomOut,\n      barWidth,\n      barGap,\n      roundedBars,\n      progressBarWidth,\n      isReady,\n      trackReorderEpoch,\n      trackDragPreview,\n      mono,\n      isDraggingRef,\n      onTracksChange,\n    ]\n  );\n\n  // Merge user theme with default theme\n  const mergedTheme = { ...defaultTheme, ...userTheme };\n\n  return (\n    <ThemeProvider theme={mergedTheme}>\n      <PlaybackAnimationContext.Provider value={animationValue}>\n        <PlaylistStateContext.Provider value={stateValue}>\n          <PlaylistControlsContext.Provider value={controlsValue}>\n            <PlaylistDataContext.Provider value={dataValue}>\n              {children}\n            </PlaylistDataContext.Provider>\n          </PlaylistControlsContext.Provider>\n        </PlaylistStateContext.Provider>\n      </PlaybackAnimationContext.Provider>\n    </ThemeProvider>\n  );\n};\n\n// Individual hooks for each context - use these for optimal performance\n// Components only re-render when their specific context data changes\n\nexport const usePlaybackAnimation = () => {\n  const context = useContext(PlaybackAnimationContext);\n  if (!context) {\n    throw new Error('usePlaybackAnimation must be used within WaveformPlaylistProvider');\n  }\n  return context;\n};\n\nexport const usePlaylistState = () => {\n  const context = useContext(PlaylistStateContext);\n  if (!context) {\n    throw new Error('usePlaylistState must be used within WaveformPlaylistProvider');\n  }\n  return context;\n};\n\nexport const usePlaylistControls = () => {\n  const context = useContext(PlaylistControlsContext);\n  if (!context) {\n    throw new Error('usePlaylistControls must be used within WaveformPlaylistProvider');\n  }\n  return context;\n};\n\nexport const usePlaylistData = () => {\n  const context = useContext(PlaylistDataContext);\n  if (!context) {\n    throw new Error('usePlaylistData must be used within WaveformPlaylistProvider');\n  }\n  return context;\n};\n\n/**\n * Like {@link usePlaylistData} but returns `null` instead of throwing when there\n * is no WaveformPlaylistProvider ancestor. Use this in hooks/components that may\n * also run in the MediaElement path (MediaElementPlaylistProvider), where the\n * WebAudio playlist context is absent.\n */\nexport const usePlaylistDataOptional = () => useContext(PlaylistDataContext);\n","import type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport type { EffectsFunction, SoundFontCache } from '@waveform-playlist/playout';\n\n/** Options for {@link resolvePlayoutAdapter}. */\nexport interface ResolvePlayoutAdapterOptions {\n  /** Consumer-supplied factory. When present, the default engine is never imported. */\n  createAdapter?: () => PlayoutAdapter;\n  /** Master effects function — forwarded to the default Tone.js adapter only. */\n  effects?: EffectsFunction;\n  /** SoundFont cache — forwarded to the default Tone.js adapter only. */\n  soundFontCache?: SoundFontCache;\n  /** Desired AudioContext sample rate — configures the global Tone context (default path only). */\n  sampleRate?: number;\n}\n\nconst INSTALL_HINT =\n  '@waveform-playlist/playout (and its peer `tone`) is required for the default WebAudio engine. ' +\n  'Install with: npm install @waveform-playlist/playout tone — or pass a custom `createAdapter`.';\n\n/**\n * Resolve a {@link PlayoutAdapter}. With `createAdapter`, returns its result and\n * never touches the default engine — so consumers with a custom adapter install\n * neither `@waveform-playlist/playout` nor `tone`. Otherwise dynamically imports\n * the Tone.js engine, rethrowing a friendly install hint (and console.warn-ing the\n * original error) when the optional peer is absent.\n */\nexport async function resolvePlayoutAdapter(\n  opts: ResolvePlayoutAdapterOptions\n): Promise<PlayoutAdapter> {\n  if (opts.createAdapter) {\n    return opts.createAdapter();\n  }\n\n  let mod: typeof import('@waveform-playlist/playout');\n  try {\n    mod = await import('@waveform-playlist/playout');\n  } catch (originalErr) {\n    console.warn(\n      '[waveform-playlist] @waveform-playlist/playout dynamic import failed: ' + String(originalErr)\n    );\n    throw new Error(INSTALL_HINT);\n  }\n\n  if (opts.sampleRate !== undefined) {\n    // Non-fatal: a failed context configuration falls back to the default rate\n    // (restores pre-#510 behavior — it was caught in the old mount-time initializer).\n    try {\n      mod.configureGlobalContext({ sampleRate: opts.sampleRate });\n    } catch (ctxErr) {\n      console.warn(\n        '[waveform-playlist] configureGlobalContext failed (continuing with default rate): ' +\n          String(ctxErr)\n      );\n    }\n  }\n  return mod.createToneAdapter({ effects: opts.effects, soundFontCache: opts.soundFontCache });\n}\n","/**\n * Waveform Data Loader\n *\n * Utilities for loading pre-computed waveform data in waveform-data.js format.\n * Supports both binary (.dat) and JSON formats from BBC's audiowaveform tool.\n */\n\nimport WaveformData from 'waveform-data';\nimport type { PeakData, Peaks } from '@waveform-playlist/core';\n\n/**\n * Load waveform data from a .dat or .json file\n *\n * @param src - URL to waveform data file (.dat or .json)\n * @returns WaveformData instance\n */\nexport async function loadWaveformData(src: string): Promise<WaveformData> {\n  const response = await fetch(src);\n\n  if (!response.ok) {\n    throw new Error(`Failed to fetch waveform data: ${response.statusText}`);\n  }\n\n  // Detect binary format from pathname (ignores query string and fragment)\n  const { pathname } = new URL(src, globalThis.location?.href ?? 'http://localhost');\n  const isBinary = pathname.toLowerCase().endsWith('.dat');\n\n  if (isBinary) {\n    const arrayBuffer = await response.arrayBuffer();\n    return WaveformData.create(arrayBuffer);\n  } else {\n    const json = await response.json();\n    return WaveformData.create(json);\n  }\n}\n\n/**\n * Convert WaveformData to our internal Peaks format\n *\n * @param waveformData - WaveformData instance from waveform-data.js\n * @param channelIndex - Channel index (0 for mono/left, 1 for right)\n * @returns Peaks data with alternating min/max values, preserving original bit depth\n */\nexport function waveformDataToPeaks(\n  waveformData: WaveformData,\n  channelIndex: number = 0\n): { data: Int8Array | Int16Array; bits: 8 | 16; length: number; sampleRate: number } {\n  const channel = waveformData.channel(channelIndex);\n  const bits = waveformData.bits as 8 | 16;\n\n  // Get the min/max arrays to determine length\n  const minArray = channel.min_array();\n  const maxArray = channel.max_array();\n  const length = minArray.length;\n\n  // Use appropriate typed array based on source file bit depth\n  // 8-bit: values range from -128 to 127\n  // 16-bit: values range from -32768 to 32767\n  const peaks = bits === 8 ? new Int8Array(length * 2) : new Int16Array(length * 2);\n\n  // Interleave min/max pairs\n  for (let i = 0; i < length; i++) {\n    peaks[i * 2] = minArray[i];\n    peaks[i * 2 + 1] = maxArray[i];\n  }\n\n  return {\n    data: peaks,\n    bits,\n    length,\n    sampleRate: waveformData.sample_rate,\n  };\n}\n\n/**\n * Load waveform data file and convert to Peaks format in one step\n *\n * @param src - URL to waveform data file (.dat or .json)\n * @param channelIndex - Channel index (default: 0)\n * @returns Peaks data ready for rendering\n */\nexport async function loadPeaksFromWaveformData(\n  src: string,\n  channelIndex: number = 0\n): Promise<{ data: Int8Array | Int16Array; bits: 8 | 16; length: number; sampleRate: number }> {\n  const waveformData = await loadWaveformData(src);\n  return waveformDataToPeaks(waveformData, channelIndex);\n}\n\n/**\n * Get metadata from waveform data file without converting to peaks\n *\n * @param src - URL to waveform data file\n * @returns Metadata (sample rate, channels, duration, bits, etc.)\n */\nexport async function getWaveformDataMetadata(src: string): Promise<{\n  sampleRate: number;\n  channels: number;\n  duration: number;\n  samplesPerPixel: number;\n  length: number;\n  bits: 8 | 16;\n}> {\n  const waveformData = await loadWaveformData(src);\n\n  return {\n    sampleRate: waveformData.sample_rate,\n    channels: waveformData.channels,\n    duration: waveformData.duration,\n    samplesPerPixel: waveformData.scale,\n    length: waveformData.length,\n    bits: waveformData.bits as 8 | 16,\n  };\n}\n\n/**\n * Slice and resample WaveformData with aligned source indices.\n *\n * Handles three cases:\n * 1. Scale differs + offset/duration given → aligned slice then resample\n * 2. Same scale + offset/duration given → direct slice\n * 3. No offset/duration → full-file resample (or identity)\n *\n * @returns Processed WaveformData ready for peak extraction\n */\nfunction sliceAndResample(\n  waveformData: WaveformData,\n  samplesPerPixel: number,\n  offsetSamples?: number,\n  durationSamples?: number\n): WaveformData | null {\n  let processedData = waveformData;\n\n  if (offsetSamples !== undefined && durationSamples !== undefined) {\n    if (processedData.scale !== samplesPerPixel) {\n      const sourceScale = waveformData.scale;\n      const ratio = samplesPerPixel / sourceScale;\n\n      // Compute clip's pixel range in target-scale bins\n      const targetStart = Math.floor(offsetSamples / samplesPerPixel);\n      const targetEnd = Math.ceil((offsetSamples + durationSamples) / samplesPerPixel);\n\n      // Convert to source indices using floor/ceil for inclusive slicing.\n      // For integer ratios (power-of-two scales), this gives exact alignment.\n      // For non-integer ratios, floor/ceil ensures we always include all source\n      // bins that contribute to the target bins — never underrepresenting peaks.\n      const sourceStart = Math.floor(targetStart * ratio);\n      const sourceEnd = Math.min(waveformData.length, Math.ceil(targetEnd * ratio));\n\n      if (sourceStart >= sourceEnd) {\n        return null; // Clip offset beyond waveform data\n      }\n\n      processedData = processedData.slice({\n        startIndex: sourceStart,\n        endIndex: sourceEnd,\n      });\n      processedData = processedData.resample({ scale: samplesPerPixel });\n    } else {\n      // No resampling needed — slice directly at base scale\n      const startIndex = Math.floor(offsetSamples / samplesPerPixel);\n      const endIndex = Math.ceil((offsetSamples + durationSamples) / samplesPerPixel);\n      processedData = processedData.slice({ startIndex, endIndex });\n    }\n  } else if (processedData.scale !== samplesPerPixel) {\n    processedData = processedData.resample({ scale: samplesPerPixel });\n  }\n\n  return processedData;\n}\n\n/**\n * Extract peaks from a WaveformData object at a specific scale (samplesPerPixel)\n * and optionally slice to a sample range.\n *\n * @param waveformData - WaveformData instance from waveform-data.js\n * @param samplesPerPixel - Target samples per pixel (will resample if different)\n * @param channelIndex - Channel index (default: 0)\n * @param offsetSamples - Optional start offset in samples (for clip trimming)\n * @param durationSamples - Optional duration in samples (for clip trimming)\n * @returns Peaks data ready for rendering\n */\nexport function extractPeaksFromWaveformData(\n  waveformData: WaveformData,\n  samplesPerPixel: number,\n  channelIndex: number = 0,\n  offsetSamples?: number,\n  durationSamples?: number\n): { data: Int8Array | Int16Array; bits: 8 | 16; length: number } {\n  const processedData = sliceAndResample(\n    waveformData,\n    samplesPerPixel,\n    offsetSamples,\n    durationSamples\n  );\n\n  if (processedData === null) {\n    const bits = waveformData.bits as 8 | 16;\n    const emptyPeaks = bits === 8 ? new Int8Array(0) : new Int16Array(0);\n    return { data: emptyPeaks, bits, length: 0 };\n  }\n\n  // Convert to our peaks format\n  const channel = processedData.channel(channelIndex);\n  const bits = processedData.bits as 8 | 16;\n  const minArray = channel.min_array();\n  const maxArray = channel.max_array();\n  const length = minArray.length;\n\n  const peaks = bits === 8 ? new Int8Array(length * 2) : new Int16Array(length * 2);\n\n  for (let i = 0; i < length; i++) {\n    peaks[i * 2] = minArray[i];\n    peaks[i * 2 + 1] = maxArray[i];\n  }\n\n  return { data: peaks, bits, length };\n}\n\n/**\n * Extract peaks from a WaveformData object, handling ALL channels, mono merging,\n * slicing, and resampling.\n *\n * Bit depth is determined by the WaveformData source — all typed arrays match\n * the source's bit depth for consistent data/metadata.\n *\n * @param waveformData - WaveformData instance (should be generated with split_channels: true)\n * @param samplesPerPixel - Target samples per pixel\n * @param isMono - Whether to merge channels to mono\n * @param offsetSamples - Optional start offset in samples (for clip trimming)\n * @param durationSamples - Optional duration in samples (for clip trimming)\n * @returns PeakData matching the interface from @waveform-playlist/core\n */\nexport function extractPeaksFromWaveformDataFull(\n  waveformData: WaveformData,\n  samplesPerPixel: number,\n  isMono: boolean,\n  offsetSamples?: number,\n  durationSamples?: number\n): PeakData {\n  const processedData = sliceAndResample(\n    waveformData,\n    samplesPerPixel,\n    offsetSamples,\n    durationSamples\n  );\n\n  if (processedData === null) {\n    const bits = waveformData.bits as 8 | 16;\n    return { length: 0, data: [], bits };\n  }\n\n  const numChannels = processedData.channels;\n  const bits = processedData.bits as 8 | 16;\n\n  // Extract peaks for all channels\n  const channelPeaks: Peaks[] = [];\n  for (let c = 0; c < numChannels; c++) {\n    const channel = processedData.channel(c);\n    const minArray = channel.min_array();\n    const maxArray = channel.max_array();\n    const len = minArray.length;\n\n    const peaks: Peaks = bits === 8 ? new Int8Array(len * 2) : new Int16Array(len * 2);\n\n    for (let i = 0; i < len; i++) {\n      peaks[i * 2] = minArray[i];\n      peaks[i * 2 + 1] = maxArray[i];\n    }\n    channelPeaks.push(peaks);\n  }\n\n  // Handle mono merging (same algorithm as makeMono in webaudio-peaks)\n  if (isMono && channelPeaks.length > 1) {\n    const weight = 1 / channelPeaks.length;\n    const numPeaks = channelPeaks[0].length / 2;\n    const monoPeaks: Peaks =\n      bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);\n\n    for (let i = 0; i < numPeaks; i++) {\n      let min = 0;\n      let max = 0;\n      for (let c = 0; c < channelPeaks.length; c++) {\n        min += weight * channelPeaks[c][i * 2];\n        max += weight * channelPeaks[c][i * 2 + 1];\n      }\n      monoPeaks[i * 2] = min;\n      monoPeaks[i * 2 + 1] = max;\n    }\n\n    return {\n      length: numPeaks,\n      data: [monoPeaks],\n      bits,\n    };\n  }\n\n  const peakLength = channelPeaks.length > 0 ? channelPeaks[0].length / 2 : 0;\n\n  return {\n    length: peakLength,\n    data: channelPeaks,\n    bits,\n  };\n}\n","import type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport type { SoundFontCache } from '@waveform-playlist/playout';\n\n/** Adapters that accept a SoundFontCache (e.g. the Tone.js adapter). */\ninterface SoundFontCapableAdapter {\n  setSoundFontCache(cache: SoundFontCache | undefined): void;\n}\n\n/**\n * Structural check for soundfont support — avoids a runtime import of\n * `@waveform-playlist/playout`. `isToneAdapter` from playout does the same\n * structural check (via optional chaining); our local `supportsSoundFont`\n * adds an explicit `adapter != null` guard. Keeping it structural lets the\n * core barrel stay engine-free (#510) and works for any custom adapter that\n * exposes `setSoundFontCache`.\n */\nfunction supportsSoundFont(\n  adapter: PlayoutAdapter | null\n): adapter is PlayoutAdapter & SoundFontCapableAdapter {\n  return (\n    adapter != null &&\n    typeof (adapter as Partial<SoundFontCapableAdapter>).setSoundFontCache === 'function'\n  );\n}\n\n/**\n * Forward a (possibly late-loaded or swapped) SoundFontCache to the live\n * adapter. Safe no-op when the adapter is absent or doesn't support\n * soundfonts. The adapter itself skips MIDI tracks whose routing is\n * unchanged, so redundant calls (e.g. on mount) cause no rebuild churn.\n */\nexport function syncSoundFontCacheToAdapter(\n  adapter: PlayoutAdapter | null,\n  cache: SoundFontCache | undefined\n): void {\n  if (!supportsSoundFont(adapter)) return;\n  adapter.setSoundFontCache(cache);\n}\n","import { useState } from 'react';\nimport {\n  formatTime as formatTimeUtil,\n  parseTime as parseTimeUtil,\n  type TimeFormat,\n} from '@waveform-playlist/ui-components';\n\nexport interface TimeFormatControls {\n  timeFormat: TimeFormat;\n  setTimeFormat: (format: TimeFormat) => void;\n  formatTime: (seconds: number) => string;\n  parseTime: (timeString: string) => number;\n}\n\n/**\n * Hook to manage time format state\n *\n * @example\n * ```tsx\n * const { timeFormat, setTimeFormat, formatTime, parseTime } = useTimeFormat();\n *\n * <TimeFormatSelect\n *   value={timeFormat}\n *   onChange={setTimeFormat}\n * />\n * <span>{formatTime(currentTime)}</span>\n * <input onChange={(e) => seekTo(parseTime(e.target.value))} />\n * ```\n */\nexport function useTimeFormat(): TimeFormatControls {\n  const [timeFormat, setTimeFormat] = useState<TimeFormat>('hh:mm:ss.uuu');\n\n  const formatTime = (seconds: number) => {\n    return formatTimeUtil(seconds, timeFormat);\n  };\n\n  const parseTime = (timeString: string) => {\n    return parseTimeUtil(timeString, timeFormat);\n  };\n\n  return {\n    timeFormat,\n    setTimeFormat,\n    formatTime,\n    parseTime,\n  };\n}\n","import { useState, useCallback, useRef, startTransition, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface ZoomControls {\n  samplesPerPixel: number;\n  zoomIn: () => void;\n  zoomOut: () => void;\n  canZoomIn: boolean;\n  canZoomOut: boolean;\n}\n\nexport interface UseZoomControlsProps {\n  engineRef: RefObject<PlaylistEngine | null>;\n  initialSamplesPerPixel: number;\n}\n\n/**\n * Hook for managing zoom controls via PlaylistEngine delegation.\n *\n * zoomIn/zoomOut delegate to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * samplesPerPixel updates use startTransition so React treats them as\n * non-urgent — during playback, animation RAF callbacks interleave\n * with the zoom re-render instead of being blocked.\n */\nexport function useZoomControls({\n  engineRef,\n  initialSamplesPerPixel,\n}: UseZoomControlsProps): ZoomControls & {\n  onEngineState: (state: EngineState) => void;\n} {\n  const [samplesPerPixel, setSamplesPerPixel] = useState(initialSamplesPerPixel);\n  const [canZoomIn, setCanZoomIn] = useState(true);\n  const [canZoomOut, setCanZoomOut] = useState(true);\n\n  // Internal refs for statechange guards — prevent redundant setState\n  // calls during high-frequency engine events (clip drags, play/pause).\n  const canZoomInRef = useRef(true);\n  const canZoomOutRef = useRef(true);\n  const samplesPerPixelRef = useRef(initialSamplesPerPixel);\n\n  const zoomIn = useCallback(() => {\n    engineRef.current?.zoomIn();\n  }, [engineRef]);\n\n  const zoomOut = useCallback(() => {\n    engineRef.current?.zoomOut();\n  }, [engineRef]);\n\n  // Called by the provider's statechange handler to mirror engine state.\n  const onEngineState = useCallback((state: EngineState) => {\n    if (state.samplesPerPixel !== samplesPerPixelRef.current) {\n      samplesPerPixelRef.current = state.samplesPerPixel;\n      startTransition(() => {\n        setSamplesPerPixel(state.samplesPerPixel);\n      });\n    }\n    if (state.canZoomIn !== canZoomInRef.current) {\n      canZoomInRef.current = state.canZoomIn;\n      setCanZoomIn(state.canZoomIn);\n    }\n    if (state.canZoomOut !== canZoomOutRef.current) {\n      canZoomOutRef.current = state.canZoomOut;\n      setCanZoomOut(state.canZoomOut);\n    }\n  }, []);\n\n  return {\n    samplesPerPixel,\n    zoomIn,\n    zoomOut,\n    canZoomIn,\n    canZoomOut,\n    onEngineState,\n  };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseMasterVolumeProps {\n  engineRef: RefObject<PlaylistEngine | null>;\n  initialVolume?: number; // 0-1.0 (linear gain, consistent with Web Audio API)\n}\n\nexport interface MasterVolumeControls {\n  masterVolume: number;\n  setMasterVolume: (volume: number) => void;\n  /** Ref holding the current masterVolume for seeding a fresh engine. */\n  masterVolumeRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing master volume via PlaylistEngine delegation.\n *\n * setMasterVolume delegates to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n */\nexport function useMasterVolume({\n  engineRef,\n  initialVolume = 1.0,\n}: UseMasterVolumeProps): MasterVolumeControls & {\n  onEngineState: (state: EngineState) => void;\n} {\n  const [masterVolume, setMasterVolumeState] = useState(initialVolume);\n\n  // Internal ref for statechange guard + engine seeding on rebuild.\n  const masterVolumeRef = useRef(initialVolume);\n\n  const setMasterVolume = useCallback(\n    (volume: number) => {\n      if (engineRef.current) {\n        // Engine exists — delegate; statechange will update ref + React state.\n        engineRef.current.setMasterVolume(volume);\n      } else {\n        // No engine yet — persist locally so loadAudio() seeds correctly.\n        masterVolumeRef.current = volume;\n        setMasterVolumeState(volume);\n      }\n    },\n    [engineRef]\n  );\n\n  // Called by the provider's statechange handler to mirror engine state.\n  const onEngineState = useCallback((state: EngineState) => {\n    if (state.masterVolume !== masterVolumeRef.current) {\n      masterVolumeRef.current = state.masterVolume;\n      setMasterVolumeState(state.masterVolume);\n    }\n  }, []);\n\n  return {\n    masterVolume,\n    setMasterVolume,\n    masterVolumeRef,\n    onEngineState,\n  };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseSelectionStateProps {\n  engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface SelectionControls {\n  selectionStart: number;\n  selectionEnd: number;\n  setSelection: (start: number, end: number) => void;\n  selectionStartRef: React.RefObject<number>;\n  selectionEndRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing selection state via PlaylistEngine delegation.\n *\n * setSelection delegates to the engine. State is mirrored back from\n * the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * Note: The provider wraps this setter with playback side-effects\n * (updating currentTime, restarting playback). This hook only handles\n * the engine delegation + state mirroring.\n */\nexport function useSelectionState({ engineRef }: UseSelectionStateProps): SelectionControls & {\n  onEngineState: (state: EngineState) => void;\n} {\n  const [selectionStart, setSelectionStartState] = useState(0);\n  const [selectionEnd, setSelectionEndState] = useState(0);\n\n  // Internal refs for statechange guard + engine seeding on rebuild.\n  const selectionStartRef = useRef(0);\n  const selectionEndRef = useRef(0);\n\n  const setSelection = useCallback(\n    (start: number, end: number) => {\n      engineRef.current?.setSelection(start, end);\n    },\n    [engineRef]\n  );\n\n  // Called by the provider's statechange handler to mirror engine state.\n  const onEngineState = useCallback((state: EngineState) => {\n    if (state.selectionStart !== selectionStartRef.current) {\n      selectionStartRef.current = state.selectionStart;\n      setSelectionStartState(state.selectionStart);\n    }\n    if (state.selectionEnd !== selectionEndRef.current) {\n      selectionEndRef.current = state.selectionEnd;\n      setSelectionEndState(state.selectionEnd);\n    }\n  }, []);\n\n  return {\n    selectionStart,\n    selectionEnd,\n    setSelection,\n    selectionStartRef,\n    selectionEndRef,\n    onEngineState,\n  };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseLoopStateProps {\n  engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface LoopControls {\n  isLoopEnabled: boolean;\n  loopStart: number;\n  loopEnd: number;\n  setLoopEnabled: (value: boolean) => void;\n  setLoopRegion: (start: number, end: number) => void;\n  clearLoopRegion: () => void;\n  isLoopEnabledRef: React.RefObject<boolean>;\n  loopStartRef: React.RefObject<number>;\n  loopEndRef: React.RefObject<number>;\n}\n\n/**\n * Hook for managing loop state via PlaylistEngine delegation.\n *\n * setLoopEnabled, setLoopRegion, and clearLoopRegion delegate to the\n * engine. State is mirrored back from the engine via onEngineState(),\n * which the provider's statechange handler calls on every engine event.\n */\nexport function useLoopState({ engineRef }: UseLoopStateProps): LoopControls & {\n  onEngineState: (state: EngineState) => void;\n} {\n  const [isLoopEnabled, setIsLoopEnabledState] = useState(false);\n  const [loopStart, setLoopStartState] = useState(0);\n  const [loopEnd, setLoopEndState] = useState(0);\n\n  // Internal refs for statechange guard + animation loop reads.\n  const isLoopEnabledRef = useRef(false);\n  const loopStartRef = useRef(0);\n  const loopEndRef = useRef(0);\n\n  const setLoopEnabled = useCallback(\n    (value: boolean) => {\n      engineRef.current?.setLoopEnabled(value);\n    },\n    [engineRef]\n  );\n\n  const setLoopRegion = useCallback(\n    (start: number, end: number) => {\n      engineRef.current?.setLoopRegion(start, end);\n    },\n    [engineRef]\n  );\n\n  const clearLoopRegion = useCallback(() => {\n    engineRef.current?.setLoopRegion(0, 0);\n  }, [engineRef]);\n\n  // Called by the provider's statechange handler to mirror engine state.\n  const onEngineState = useCallback((state: EngineState) => {\n    if (state.isLoopEnabled !== isLoopEnabledRef.current) {\n      isLoopEnabledRef.current = state.isLoopEnabled;\n      setIsLoopEnabledState(state.isLoopEnabled);\n    }\n    if (state.loopStart !== loopStartRef.current) {\n      loopStartRef.current = state.loopStart;\n      setLoopStartState(state.loopStart);\n    }\n    if (state.loopEnd !== loopEndRef.current) {\n      loopEndRef.current = state.loopEnd;\n      setLoopEndState(state.loopEnd);\n    }\n  }, []);\n\n  return {\n    isLoopEnabled,\n    loopStart,\n    loopEnd,\n    setLoopEnabled,\n    setLoopRegion,\n    clearLoopRegion,\n    isLoopEnabledRef,\n    loopStartRef,\n    loopEndRef,\n    onEngineState,\n  };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseSelectedTrackProps {\n  engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface SelectedTrackControls {\n  selectedTrackId: string | null;\n  setSelectedTrackId: (trackId: string | null) => void;\n}\n\n/**\n * Hook for managing selected track state via PlaylistEngine delegation.\n *\n * setSelectedTrackId delegates to the engine's selectTrack method.\n * State is mirrored back from the engine via onEngineState(), which\n * the provider's statechange handler calls on every engine event.\n */\nexport function useSelectedTrack({ engineRef }: UseSelectedTrackProps): SelectedTrackControls & {\n  onEngineState: (state: EngineState) => void;\n  selectedTrackIdRef: React.RefObject<string | null>;\n} {\n  const [selectedTrackId, setSelectedTrackIdState] = useState<string | null>(null);\n\n  // Internal ref for statechange guard.\n  const selectedTrackIdRef = useRef<string | null>(null);\n\n  const setSelectedTrackId = useCallback(\n    (trackId: string | null) => {\n      engineRef.current?.selectTrack(trackId);\n    },\n    [engineRef]\n  );\n\n  // Called by the provider's statechange handler to mirror engine state.\n  const onEngineState = useCallback((state: EngineState) => {\n    if (state.selectedTrackId !== selectedTrackIdRef.current) {\n      selectedTrackIdRef.current = state.selectedTrackId;\n      setSelectedTrackIdState(state.selectedTrackId);\n    }\n  }, []);\n\n  return {\n    selectedTrackId,\n    setSelectedTrackId,\n    onEngineState,\n    selectedTrackIdRef,\n  };\n}\n","import { useState, useCallback, useRef, type RefObject } from 'react';\nimport type { PlaylistEngine, EngineState } from '@waveform-playlist/engine';\n\nexport interface UseUndoStateProps {\n  engineRef: RefObject<PlaylistEngine | null>;\n}\n\nexport interface UndoControls {\n  canUndo: boolean;\n  canRedo: boolean;\n  undo: () => void;\n  redo: () => void;\n}\n\n/**\n * Hook for managing undo/redo state via PlaylistEngine delegation.\n *\n * undo/redo delegate to the engine. canUndo/canRedo are mirrored back\n * from the engine via onEngineState(), which the provider's statechange\n * handler calls on every engine event.\n *\n * No refs are exposed for engine seeding — undo history intentionally\n * resets on engine rebuild (setTracks calls clearHistory).\n */\nexport function useUndoState({ engineRef }: UseUndoStateProps): UndoControls & {\n  onEngineState: (state: EngineState) => void;\n} {\n  const [canUndo, setCanUndo] = useState(false);\n  const [canRedo, setCanRedo] = useState(false);\n\n  // Internal refs for statechange guard (same pattern as useSelectionState etc.)\n  const canUndoRef = useRef(false);\n  const canRedoRef = useRef(false);\n\n  const undo = useCallback(() => {\n    if (!engineRef.current) {\n      console.warn('[waveform-playlist] undo: engine not ready, call ignored');\n      return;\n    }\n    engineRef.current.undo();\n  }, [engineRef]);\n\n  const redo = useCallback(() => {\n    if (!engineRef.current) {\n      console.warn('[waveform-playlist] redo: engine not ready, call ignored');\n      return;\n    }\n    engineRef.current.redo();\n  }, [engineRef]);\n\n  // Called by the provider's statechange handler to mirror engine state.\n  const onEngineState = useCallback((state: EngineState) => {\n    if (state.canUndo !== canUndoRef.current) {\n      canUndoRef.current = state.canUndo;\n      setCanUndo(state.canUndo);\n    }\n    if (state.canRedo !== canRedoRef.current) {\n      canRedoRef.current = state.canRedo;\n      setCanRedo(state.canRedo);\n    }\n  }, []);\n\n  return {\n    canUndo,\n    canRedo,\n    undo,\n    redo,\n    onEngineState,\n  };\n}\n","import React from 'react';\nimport type {\n  DragStartEvent as DragStartCallback,\n  DragMoveEvent as DragMoveCallback,\n  DragEndEvent as DragEndCallback,\n} from '@dnd-kit/abstract';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport type { PlaylistEngine } from '@waveform-playlist/engine';\nimport { calculateBoundaryTrim } from '../utils/boundaryTrim';\nimport { usePlaylistData } from '../WaveformPlaylistContext';\n\ninterface UseClipDragHandlersOptions {\n  tracks: ClipTrack[];\n  onTracksChange: (tracks: ClipTrack[]) => void;\n  samplesPerPixel: number;\n  engineRef: React.RefObject<PlaylistEngine | null>;\n  /** Ref toggled during boundary trim drags. When true, the provider's loadAudio\n   *  skips engine rebuilds so engine keeps original clip positions. On drag end,\n   *  engine.trimClip() commits the final delta. Obtain from usePlaylistData(). */\n  isDraggingRef: React.MutableRefObject<boolean>;\n  /** Optional function that snaps a sample position to the nearest grid position.\n   *  Used for boundary trim snapping (move snapping is handled by the SnapToGridModifier). */\n  snapSamplePosition?: (samplePosition: number) => number;\n}\n\n/**\n * Custom hook for handling clip drag operations (movement and trimming)\n *\n * Provides drag handlers for use with @dnd-kit/react DragDropProvider.\n * Handles both clip movement (dragging entire clips) and boundary trimming (adjusting clip edges).\n *\n * Collision detection for clip moves is handled by `ClipCollisionModifier` (passed to DragDropProvider).\n *\n * **Move:** `onDragEnd` delegates to `engine.moveClip()` in one shot.\n *\n * **Trim:** `onDragMove` updates React state per-frame via `onTracksChange` for smooth\n * visual feedback (using cumulative deltas from the original clip snapshot). `isDraggingRef`\n * prevents loadAudio from rebuilding the engine during the drag, so the engine keeps the\n * original clip positions. On drag end, `engine.trimClip()` commits the final delta.\n *\n * @example\n * ```tsx\n * const { onDragStart, onDragMove, onDragEnd } = useClipDragHandlers({\n *   tracks,\n *   onTracksChange: setTracks,\n *   samplesPerPixel,\n *   engineRef: playoutRef,\n *   isDraggingRef,\n * });\n *\n * return (\n *   <DragDropProvider\n *     onDragStart={onDragStart}\n *     onDragMove={onDragMove}\n *     onDragEnd={onDragEnd}\n *     modifiers={[RestrictToHorizontalAxis, ClipCollisionModifier.configure({ tracks, samplesPerPixel })]}\n *   >\n *     <Waveform showClipHeaders={true} />\n *   </DragDropProvider>\n * );\n * ```\n */\nexport function useClipDragHandlers({\n  tracks,\n  onTracksChange,\n  samplesPerPixel,\n  engineRef,\n  isDraggingRef,\n  snapSamplePosition,\n}: UseClipDragHandlersOptions) {\n  const { sampleRate } = usePlaylistData();\n  // Store snap function in ref so onDragMove doesn't need it as a dependency\n  const snapSamplePositionRef = React.useRef(snapSamplePosition);\n  snapSamplePositionRef.current = snapSamplePosition;\n  // Store original clip state when drag starts (for cumulative delta application)\n  const originalClipStateRef = React.useRef<{\n    offsetSamples: number;\n    durationSamples: number;\n    startSample: number;\n  } | null>(null);\n\n  // Store the last boundary trim delta (samples) so onDragEnd uses the same value\n  // as the last onDragMove visual update, avoiding stale-position mismatches.\n  const lastBoundaryDeltaRef = React.useRef(0);\n\n  const onDragStart = React.useCallback(\n    (event: Parameters<DragStartCallback>[0]) => {\n      const data = event.operation.source?.data as\n        | {\n            kind?: string;\n            boundary?: 'left' | 'right';\n            trackIndex: number;\n            clipIndex: number;\n          }\n        | undefined;\n\n      if (!data || data.kind === 'track-reorder') return;\n\n      // Only store state for boundary trimming operations\n      if (!data.boundary) {\n        originalClipStateRef.current = null;\n        // Group move into one undo step\n        if (engineRef.current) {\n          engineRef.current.beginTransaction();\n        } else {\n          console.warn(\n            '[waveform-playlist] onDragStart: engine not ready, move will not be grouped for undo'\n          );\n        }\n        return;\n      }\n\n      const track = tracks[data.trackIndex];\n      const clip = track?.clips[data.clipIndex];\n\n      if (clip) {\n        // Store original clip state for cumulative delta application\n        originalClipStateRef.current = {\n          offsetSamples: clip.offsetSamples,\n          durationSamples: clip.durationSamples,\n          startSample: clip.startSample,\n        };\n        // Signal provider to skip loadAudio rebuilds during the drag\n        isDraggingRef.current = true;\n        // Group the drag into one undo step\n        if (engineRef.current) {\n          engineRef.current.beginTransaction();\n        } else {\n          console.warn(\n            '[waveform-playlist] onDragStart: engine not ready, trim will not be grouped for undo'\n          );\n        }\n      }\n    },\n    [tracks, isDraggingRef, engineRef]\n  );\n\n  const onDragMove = React.useCallback(\n    (event: Parameters<DragMoveCallback>[0]) => {\n      const data = event.operation.source?.data as\n        | {\n            kind?: string;\n            boundary?: 'left' | 'right';\n            trackIndex: number;\n            clipIndex: number;\n          }\n        | undefined;\n\n      if (!data || data.kind === 'track-reorder') return;\n\n      // Only update for boundary trimming operations (not clip movement)\n      if (!data.boundary) return;\n\n      // Need original clip state to apply cumulative delta\n      if (!originalClipStateRef.current) return;\n\n      const { boundary, trackIndex, clipIndex } = data;\n\n      // The dragmove event is dispatched BEFORE position.current is updated (happens\n      // in a microtask), so the snapshot's position.current is stale. Use event.to\n      // (the pointer's current coordinates from the sensor) for the correct position.\n      const currentX = event.to?.x ?? event.operation.position.current.x;\n      const rawDeltaX = currentX - event.operation.position.initial.x;\n      const sampleDelta = rawDeltaX * samplesPerPixel;\n\n      // Get original clip state (stored on drag start)\n      const originalClip = originalClipStateRef.current;\n      const snapFn = snapSamplePositionRef.current;\n\n      // Track the effective delta after snapping (for engine.trimClip on drag end)\n      let effectiveDelta = sampleDelta;\n\n      // Update tracks in real-time during drag\n      const newTracks = tracks.map((track, tIdx) => {\n        if (tIdx !== trackIndex) return track;\n\n        const sortedClips = [...track.clips].sort((a, b) => a.startSample - b.startSample);\n        const sortedIndex = sortedClips.findIndex((clip) => clip === track.clips[clipIndex]);\n\n        const newClips = track.clips.map((clip, cIdx) => {\n          if (cIdx !== clipIndex) return clip;\n\n          let trimResult = calculateBoundaryTrim({\n            originalClip,\n            clip,\n            pixelDelta: rawDeltaX,\n            samplesPerPixel,\n            sampleRate,\n            boundary,\n            sortedClips,\n            sortedIndex,\n          });\n\n          // Snap the boundary position to the grid if a snap function is provided\n          if (snapFn) {\n            if (boundary === 'left') {\n              const snappedStart = snapFn(trimResult.startSample);\n              const delta = snappedStart - trimResult.startSample;\n              trimResult = {\n                startSample: snappedStart,\n                durationSamples: trimResult.durationSamples - delta,\n                offsetSamples: trimResult.offsetSamples + delta,\n              };\n              // Effective delta = how far the start moved from original\n              effectiveDelta = snappedStart - originalClip.startSample;\n            } else {\n              const endSample = trimResult.startSample + trimResult.durationSamples;\n              const snappedEnd = snapFn(endSample);\n              trimResult = {\n                ...trimResult,\n                durationSamples: snappedEnd - trimResult.startSample,\n              };\n              // Effective delta = how much the duration changed from original\n              effectiveDelta = trimResult.durationSamples - originalClip.durationSamples;\n            }\n          }\n\n          return {\n            ...clip,\n            startSample: trimResult.startSample,\n            durationSamples: trimResult.durationSamples,\n            offsetSamples: trimResult.offsetSamples,\n          };\n        });\n\n        return { ...track, clips: newClips };\n      });\n\n      lastBoundaryDeltaRef.current = effectiveDelta;\n      onTracksChange(newTracks);\n    },\n    [tracks, onTracksChange, samplesPerPixel, sampleRate]\n  );\n\n  const onDragEnd = React.useCallback(\n    (event: Parameters<DragEndCallback>[0]) => {\n      const kindData = event.operation.source?.data as { kind?: string } | undefined;\n      if (kindData?.kind === 'track-reorder') return;\n\n      // Handle canceled drags (focus loss, Escape key, component unmount).\n      // Without this, isDraggingRef stays true and loadAudio skips rebuilds permanently.\n      if (event.canceled) {\n        // Revert React state for boundary trims — onDragMove updated tracks per-frame,\n        // but the engine still has original positions (isDraggingRef blocked rebuilds).\n        // For clip moves, originalClipStateRef is null so this block is skipped —\n        // no React state revert needed because the Feedback plugin handles the visual\n        // position (CSS translate removed on cancel) and engine was never mutated.\n        if (originalClipStateRef.current) {\n          const cancelData = event.operation.source?.data as\n            | { trackIndex: number; clipIndex: number }\n            | undefined;\n          if (cancelData) {\n            const { trackIndex, clipIndex } = cancelData;\n            const original = originalClipStateRef.current;\n            const revertedTracks = tracks.map((track, tIdx) => {\n              if (tIdx !== trackIndex) return track;\n              const newClips = track.clips.map((clip, cIdx) => {\n                if (cIdx !== clipIndex) return clip;\n                return {\n                  ...clip,\n                  offsetSamples: original.offsetSamples,\n                  durationSamples: original.durationSamples,\n                  startSample: original.startSample,\n                };\n              });\n              return { ...track, clips: newClips };\n            });\n            onTracksChange(revertedTracks);\n          }\n        }\n        isDraggingRef.current = false;\n        originalClipStateRef.current = null;\n        lastBoundaryDeltaRef.current = 0;\n        // Cancel aborts the transaction — no undo step pushed\n        engineRef.current?.abortTransaction();\n        return;\n      }\n\n      const data = event.operation.source?.data as\n        | {\n            clipId: string;\n            trackIndex: number;\n            boundary?: 'left' | 'right';\n          }\n        | undefined;\n\n      if (!data) {\n        // Transaction was opened in onDragStart — abort to prevent leak\n        isDraggingRef.current = false;\n        engineRef.current?.abortTransaction();\n        return;\n      }\n\n      const { trackIndex, clipId, boundary } = data;\n\n      // Boundary trims: use the last delta stored by onDragMove (avoids stale snapshot position).\n      // Clip moves: use transform.x (modifier-constrained, cached as plain object in snapshot).\n      const sampleDelta = boundary\n        ? lastBoundaryDeltaRef.current\n        : event.operation.transform.x * samplesPerPixel;\n\n      const trackId = tracks[trackIndex]?.id;\n\n      // Boundary trim: onDragMove updated React state per-frame for visuals.\n      // isDraggingRef prevented loadAudio from rebuilding the engine, so the\n      // engine still has the original (pre-drag) clip positions. Commit the\n      // final delta via engine.trimClip() so the adapter has correct positions.\n      if (boundary) {\n        isDraggingRef.current = false;\n        if (!trackId) {\n          console.warn(\n            `[waveform-playlist] onDragEnd: track at index ${trackIndex} not found — trim not synced to adapter`\n          );\n          engineRef.current?.abortTransaction();\n        } else if (!engineRef.current) {\n          console.warn('[waveform-playlist] engineRef is null — trim not synced to adapter');\n        } else {\n          engineRef.current.trimClip(trackId, clipId, boundary, Math.floor(sampleDelta));\n          engineRef.current.commitTransaction();\n        }\n        originalClipStateRef.current = null;\n        lastBoundaryDeltaRef.current = 0;\n        return;\n      }\n\n      // Clip move: delegate to engine in one shot\n      if (!trackId) {\n        console.warn(\n          `[waveform-playlist] onDragEnd: track at index ${trackIndex} not found — move not synced to adapter`\n        );\n        engineRef.current?.abortTransaction();\n      } else if (!engineRef.current) {\n        console.warn('[waveform-playlist] engineRef is null — move not synced to adapter');\n      } else {\n        engineRef.current.moveClip(trackId, clipId, Math.floor(sampleDelta));\n        engineRef.current.commitTransaction();\n      }\n    },\n    [tracks, onTracksChange, samplesPerPixel, engineRef, isDraggingRef]\n  );\n\n  return {\n    onDragStart,\n    onDragMove,\n    onDragEnd,\n  };\n}\n","import type { AudioClip } from '@waveform-playlist/core';\nimport { constrainBoundaryTrim } from '@waveform-playlist/engine';\n\n/**\n * Result of a boundary trim calculation — the new clip dimensions\n * after dragging a left or right boundary by a given pixel delta.\n */\nexport interface BoundaryTrimResult {\n  startSample: number;\n  durationSamples: number;\n  offsetSamples: number;\n}\n\nexport interface CalculateBoundaryTrimParams {\n  /** The original clip state captured at drag start */\n  originalClip: Pick<AudioClip, 'startSample' | 'durationSamples' | 'offsetSamples'>;\n  /** The current clip with full AudioClip shape (used for constraint calculation) */\n  clip: AudioClip;\n  /** Raw pixel delta from drag start (positive = rightward) */\n  pixelDelta: number;\n  /** Samples per pixel (zoom level) */\n  samplesPerPixel: number;\n  /** Audio sample rate (used to compute minimum duration) */\n  sampleRate: number;\n  /** Which boundary is being dragged */\n  boundary: 'left' | 'right';\n  /** Clips on the same track, sorted by startSample (for collision detection) */\n  sortedClips: AudioClip[];\n  /** Index of the clip being trimmed within sortedClips */\n  sortedIndex: number;\n}\n\n/**\n * Pure function that calculates new clip dimensions after a boundary trim drag.\n *\n * Converts a pixel delta to sample delta, constrains it via the engine's\n * `constrainBoundaryTrim`, and applies the constrained delta to the original\n * clip snapshot to produce new startSample, durationSamples, and offsetSamples.\n *\n * For left boundary: startSample and offsetSamples increase by the constrained delta,\n * durationSamples decreases by the same amount.\n *\n * For right boundary: only durationSamples changes (increases by the constrained delta).\n */\nexport function calculateBoundaryTrim({\n  originalClip,\n  clip,\n  pixelDelta,\n  samplesPerPixel,\n  sampleRate,\n  boundary,\n  sortedClips,\n  sortedIndex,\n}: CalculateBoundaryTrimParams): BoundaryTrimResult {\n  const sampleDelta = pixelDelta * samplesPerPixel;\n  const MIN_DURATION_SAMPLES = Math.floor(0.1 * sampleRate);\n\n  // Build a temporary clip with original state for constraint calculation\n  const tempClip: AudioClip = {\n    ...clip,\n    startSample: originalClip.startSample,\n    offsetSamples: originalClip.offsetSamples,\n    durationSamples: originalClip.durationSamples,\n  };\n\n  const constrainedDelta = constrainBoundaryTrim(\n    tempClip,\n    Math.floor(sampleDelta),\n    boundary,\n    sortedClips,\n    sortedIndex,\n    MIN_DURATION_SAMPLES\n  );\n\n  if (boundary === 'left') {\n    return {\n      startSample: originalClip.startSample + constrainedDelta,\n      durationSamples: originalClip.durationSamples - constrainedDelta,\n      offsetSamples: originalClip.offsetSamples + constrainedDelta,\n    };\n  } else {\n    return {\n      startSample: originalClip.startSample,\n      durationSamples: originalClip.durationSamples + constrainedDelta,\n      offsetSamples: originalClip.offsetSamples,\n    };\n  }\n}\n","import React from 'react';\nimport type {\n  DragStartEvent as DragStartCallback,\n  DragMoveEvent as DragMoveCallback,\n  DragEndEvent as DragEndCallback,\n} from '@dnd-kit/abstract';\nimport type { AnnotationData } from '@waveform-playlist/core';\n\nconst LINK_THRESHOLD = 0.01; // Consider edges \"linked\" if within 10ms\n\ninterface UseAnnotationDragHandlersOptions {\n  annotations: AnnotationData[];\n  onAnnotationsChange: (annotations: AnnotationData[]) => void;\n  samplesPerPixel: number;\n  /** Sample rate for pixel-to-time conversion. Providers pass the real rate from\n   *  context; defaults to 48000 (the engine default) for out-of-provider usage. */\n  sampleRate?: number;\n  duration: number;\n  linkEndpoints: boolean;\n}\n\n/**\n * Custom hook for handling annotation drag operations (boundary trimming)\n *\n * Provides drag handlers for use with @dnd-kit/react DragDropProvider.\n * Handles annotation boundary resizing with linked endpoints support.\n *\n * @example\n * ```tsx\n * const { onDragStart, onDragMove, onDragEnd } = useAnnotationDragHandlers({\n *   annotations,\n *   onAnnotationsChange: setAnnotations,\n *   samplesPerPixel,\n *   duration,\n *   linkEndpoints,\n * });\n *\n * return (\n *   <DragDropProvider\n *     onDragStart={onDragStart}\n *     onDragMove={onDragMove}\n *     onDragEnd={onDragEnd}\n *     modifiers={[RestrictToHorizontalAxis]}\n *   >\n *     {renderAnnotations()}\n *   </DragDropProvider>\n * );\n * ```\n */\nexport function useAnnotationDragHandlers({\n  annotations,\n  onAnnotationsChange,\n  samplesPerPixel,\n  // Default mirrors the engine default; the providers always pass the real rate from context (#510).\n  sampleRate = 48000,\n  duration,\n  linkEndpoints,\n}: UseAnnotationDragHandlersOptions) {\n  // Store original annotation state when drag starts (for cumulative delta application)\n  const originalAnnotationStateRef = React.useRef<{\n    start: number;\n    end: number;\n    annotationIndex: number;\n  } | null>(null);\n\n  const onDragStart = React.useCallback(\n    (event: Parameters<DragStartCallback>[0]) => {\n      const data = event.operation.source?.data as\n        | {\n            annotationId: string;\n            annotationIndex: number;\n            edge: 'start' | 'end';\n          }\n        | undefined;\n\n      if (!data || data.annotationIndex === undefined) {\n        originalAnnotationStateRef.current = null;\n        return;\n      }\n\n      const annotation = annotations[data.annotationIndex];\n      if (annotation) {\n        originalAnnotationStateRef.current = {\n          start: annotation.start,\n          end: annotation.end,\n          annotationIndex: data.annotationIndex,\n        };\n      }\n    },\n    [annotations]\n  );\n\n  const onDragMove = React.useCallback(\n    (event: Parameters<DragMoveCallback>[0]) => {\n      if (!originalAnnotationStateRef.current) {\n        return;\n      }\n\n      const data = event.operation.source?.data as\n        | {\n            annotationId: string;\n            annotationIndex: number;\n            edge: 'start' | 'end';\n          }\n        | undefined;\n\n      if (!data) return;\n\n      const { edge, annotationIndex } = data;\n      const originalState = originalAnnotationStateRef.current;\n\n      // The dragmove event is dispatched BEFORE position.current is updated (happens\n      // in a microtask), so the snapshot's position.current is stale. Use event.to\n      // (the pointer's current coordinates from the sensor) for the correct position.\n      const currentX = event.to?.x ?? event.operation.position.current.x;\n      const rawDeltaX = currentX - event.operation.position.initial.x;\n      const timeDelta = (rawDeltaX * samplesPerPixel) / sampleRate;\n\n      // Apply delta to original state\n      const newTime =\n        edge === 'start' ? originalState.start + timeDelta : originalState.end + timeDelta;\n\n      // Update annotations using the boundary logic\n      const updatedAnnotations = updateAnnotationBoundaries({\n        annotationIndex,\n        newTime,\n        isDraggingStart: edge === 'start',\n        annotations,\n        duration,\n        linkEndpoints,\n      });\n\n      onAnnotationsChange(updatedAnnotations);\n    },\n    [annotations, onAnnotationsChange, samplesPerPixel, sampleRate, duration, linkEndpoints]\n  );\n\n  const onDragEnd = React.useCallback(\n    (event: Parameters<DragEndCallback>[0]) => {\n      // Handle canceled drags — revert annotations to pre-drag state\n      if (event.canceled && originalAnnotationStateRef.current) {\n        const { annotationIndex, start, end } = originalAnnotationStateRef.current;\n        const revertedAnnotations = annotations.map((annotation, idx) => {\n          if (idx !== annotationIndex) return annotation;\n          return { ...annotation, start, end };\n        });\n        onAnnotationsChange(revertedAnnotations);\n      }\n      originalAnnotationStateRef.current = null;\n    },\n    [annotations, onAnnotationsChange]\n  );\n\n  return {\n    onDragStart,\n    onDragMove,\n    onDragEnd,\n  };\n}\n\n/**\n * Updates annotation boundaries based on drag operations.\n * Handles linked endpoints and collision detection.\n */\nfunction updateAnnotationBoundaries({\n  annotationIndex,\n  newTime,\n  isDraggingStart,\n  annotations,\n  duration,\n  linkEndpoints: shouldLinkEndpoints,\n}: {\n  annotationIndex: number;\n  newTime: number;\n  isDraggingStart: boolean;\n  annotations: AnnotationData[];\n  duration: number;\n  linkEndpoints: boolean;\n}): AnnotationData[] {\n  const updatedAnnotations = [...annotations];\n  const annotation = annotations[annotationIndex];\n\n  if (isDraggingStart) {\n    // Dragging start edge\n    const constrainedStart = Math.min(annotation.end - 0.1, Math.max(0, newTime));\n    const delta = constrainedStart - annotation.start;\n\n    updatedAnnotations[annotationIndex] = {\n      ...annotation,\n      start: constrainedStart,\n    };\n\n    if (shouldLinkEndpoints && annotationIndex > 0) {\n      // Link Endpoints mode: handle both already-linked and collision scenarios\n      const prevAnnotation = updatedAnnotations[annotationIndex - 1];\n\n      if (Math.abs(prevAnnotation.end - annotation.start) < LINK_THRESHOLD) {\n        // Already linked: move previous annotation's end together with this start\n        updatedAnnotations[annotationIndex - 1] = {\n          ...prevAnnotation,\n          end: Math.max(prevAnnotation.start + 0.1, prevAnnotation.end + delta),\n        };\n      } else if (constrainedStart <= prevAnnotation.end) {\n        // Dragged past previous annotation: snap to link them together\n        updatedAnnotations[annotationIndex] = {\n          ...updatedAnnotations[annotationIndex],\n          start: prevAnnotation.end,\n        };\n      }\n    } else if (\n      !shouldLinkEndpoints &&\n      annotationIndex > 0 &&\n      constrainedStart < updatedAnnotations[annotationIndex - 1].end\n    ) {\n      // Collision detection: push previous annotation's end back\n      updatedAnnotations[annotationIndex - 1] = {\n        ...updatedAnnotations[annotationIndex - 1],\n        end: constrainedStart,\n      };\n    }\n  } else {\n    // Dragging end edge\n    const constrainedEnd = Math.max(annotation.start + 0.1, Math.min(newTime, duration));\n    const delta = constrainedEnd - annotation.end;\n\n    updatedAnnotations[annotationIndex] = {\n      ...annotation,\n      end: constrainedEnd,\n    };\n\n    if (shouldLinkEndpoints && annotationIndex < updatedAnnotations.length - 1) {\n      // Link Endpoints mode: handle both already-linked and collision scenarios\n      const nextAnnotation = updatedAnnotations[annotationIndex + 1];\n\n      if (Math.abs(nextAnnotation.start - annotation.end) < LINK_THRESHOLD) {\n        // Already linked: move next annotation's start together with this end\n        const newStart = nextAnnotation.start + delta;\n        updatedAnnotations[annotationIndex + 1] = {\n          ...nextAnnotation,\n          start: Math.min(nextAnnotation.end - 0.1, newStart),\n        };\n\n        // Cascade linked endpoints\n        let currentIndex = annotationIndex + 1;\n        while (currentIndex < updatedAnnotations.length - 1) {\n          const current = updatedAnnotations[currentIndex];\n          const next = updatedAnnotations[currentIndex + 1];\n\n          if (Math.abs(next.start - current.end) < LINK_THRESHOLD) {\n            const nextDelta = current.end - annotations[currentIndex].end;\n            updatedAnnotations[currentIndex + 1] = {\n              ...next,\n              start: Math.min(next.end - 0.1, next.start + nextDelta),\n            };\n            currentIndex++;\n          } else {\n            break; // No more linked endpoints\n          }\n        }\n      } else if (constrainedEnd >= nextAnnotation.start) {\n        // Dragged past next annotation: snap to link them together\n        updatedAnnotations[annotationIndex] = {\n          ...updatedAnnotations[annotationIndex],\n          end: nextAnnotation.start,\n        };\n      }\n    } else if (\n      !shouldLinkEndpoints &&\n      annotationIndex < updatedAnnotations.length - 1 &&\n      constrainedEnd > updatedAnnotations[annotationIndex + 1].start\n    ) {\n      // Collision detection: push next annotation's start forward\n      const nextAnnotation = updatedAnnotations[annotationIndex + 1];\n\n      updatedAnnotations[annotationIndex + 1] = {\n        ...nextAnnotation,\n        start: constrainedEnd,\n      };\n\n      // Cascade collisions\n      let currentIndex = annotationIndex + 1;\n      while (currentIndex < updatedAnnotations.length - 1) {\n        const current = updatedAnnotations[currentIndex];\n        const next = updatedAnnotations[currentIndex + 1];\n\n        if (current.end > next.start) {\n          updatedAnnotations[currentIndex + 1] = {\n            ...next,\n            start: current.end,\n          };\n          currentIndex++;\n        } else {\n          break; // No more collisions\n        }\n      }\n    }\n  }\n\n  return updatedAnnotations;\n}\n","/**\n * Hook for configuring @dnd-kit sensors for clip dragging\n *\n * Provides consistent drag activation behavior across all examples.\n * Always overrides PointerSensor defaults with custom activation constraints:\n * - Default mode: distance-based activation (1px) for all pointer types\n * - Touch-optimized mode: delay-based activation for touch (250ms),\n *   distance-based for mouse/pen\n */\n\nimport { useMemo } from 'react';\nimport { PointerSensor, PointerActivationConstraints } from '@dnd-kit/dom';\nimport type { PluginDescriptor } from '@dnd-kit/abstract';\n\nexport interface DragSensorOptions {\n  /**\n   * Enable mobile-optimized touch handling with delay-based activation.\n   * When true, touch events get delay-based activation while mouse/pen get distance-based.\n   * When false (default), all pointer types use distance-based activation (1px).\n   */\n  touchOptimized?: boolean;\n  /**\n   * Delay in milliseconds before touch drag activates (only when touchOptimized is true).\n   * Default: 250ms - long enough to distinguish from scroll intent\n   */\n  touchDelay?: number;\n  /**\n   * Distance tolerance during touch delay (only when touchOptimized is true).\n   * If finger moves more than this during delay, drag is cancelled.\n   * Default: 5px - allows slight finger movement\n   */\n  touchTolerance?: number;\n  /**\n   * Distance in pixels before mouse drag activates.\n   * Default: 1px for immediate feedback on desktop\n   */\n  mouseDistance?: number;\n}\n\n/**\n * Returns configured sensors for @dnd-kit drag operations\n *\n * @param options - Configuration options for drag sensors\n * @returns Array of sensor constructors/descriptors for DragDropProvider's sensors prop\n *\n * @example\n * // Desktop-optimized (default — 1px distance activation for all pointer types)\n * const sensors = useDragSensors();\n *\n * @example\n * // Mobile-optimized with custom touch delay\n * const sensors = useDragSensors({ touchOptimized: true, touchDelay: 300 });\n */\nexport function useDragSensors(\n  options: DragSensorOptions = {}\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (typeof PointerSensor | PluginDescriptor<any, any, any>)[] {\n  const {\n    touchOptimized = false,\n    touchDelay = 250,\n    touchTolerance = 5,\n    mouseDistance = 1,\n  } = options;\n\n  return useMemo(() => {\n    if (touchOptimized) {\n      // Custom activation constraints for touch-optimized mode\n      return [\n        PointerSensor.configure({\n          activationConstraints(event) {\n            // Touch events get delay-based activation\n            if (event.pointerType === 'touch') {\n              return [\n                new PointerActivationConstraints.Delay({\n                  value: touchDelay,\n                  tolerance: touchTolerance,\n                }),\n              ];\n            }\n            // Mouse/pen get distance-based activation\n            return [new PointerActivationConstraints.Distance({ value: mouseDistance })];\n          },\n        }),\n      ];\n    }\n\n    // Default: PointerSensor with distance-based activation\n    return [\n      PointerSensor.configure({\n        activationConstraints: [\n          new PointerActivationConstraints.Distance({ value: mouseDistance }),\n        ],\n      }),\n    ];\n  }, [touchOptimized, touchDelay, touchTolerance, mouseDistance]);\n}\n","import React, { useCallback } from 'react';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport { calculateSplitPoint, canSplitAt } from '@waveform-playlist/engine';\nimport type { PlaylistEngine } from '@waveform-playlist/engine';\nimport {\n  usePlaybackAnimation,\n  usePlaylistData,\n  usePlaylistState,\n} from '../WaveformPlaylistContext';\n\nexport interface UseClipSplittingOptions {\n  tracks: ClipTrack[];\n  samplesPerPixel: number;\n  engineRef: React.RefObject<PlaylistEngine | null>;\n}\n\nexport interface UseClipSplittingResult {\n  splitClipAtPlayhead: () => boolean;\n  splitClipAt: (trackIndex: number, clipIndex: number, splitTime: number) => boolean;\n}\n\n/**\n * Hook for splitting clips at the playhead or at a specific time\n *\n * Splitting delegates to `engine.splitClip()` — the engine handles clip creation,\n * adapter sync, and emits statechange. The provider's statechange handler propagates\n * the updated tracks to the parent via `onTracksChange`.\n *\n * @param options - Configuration options\n * @returns Object with split functions\n *\n * @example\n * ```tsx\n * const { splitClipAtPlayhead } = useClipSplitting({\n *   tracks,\n *   samplesPerPixel,\n *   engineRef: playoutRef,\n * });\n *\n * // In keyboard handler\n * const handleKeyPress = (e: KeyboardEvent) => {\n *   if (e.key === 's' || e.key === 'S') {\n *     splitClipAtPlayhead();\n *   }\n * };\n * ```\n */\nexport const useClipSplitting = (options: UseClipSplittingOptions): UseClipSplittingResult => {\n  const { tracks, engineRef } = options;\n  const { sampleRate } = usePlaylistData();\n  const { currentTimeRef } = usePlaybackAnimation();\n  const { selectedTrackId } = usePlaylistState();\n\n  /**\n   * Split a specific clip at a given time\n   *\n   * @param trackIndex - Index of the track containing the clip\n   * @param clipIndex - Index of the clip within the track\n   * @param splitTime - Timeline position where to split (in seconds)\n   * @returns true if pre-validation passed and the engine was called, false otherwise.\n   * Note: engine.splitClip() returns void so a true here assumes the engine accepted\n   * the split (it performs its own canSplitAt check internally).\n   */\n  const splitClipAt = useCallback(\n    (trackIndex: number, clipIndex: number, splitTime: number): boolean => {\n      const { samplesPerPixel } = options;\n\n      const track = tracks[trackIndex];\n      if (!track) return false;\n\n      const clip = track.clips[clipIndex];\n      if (!clip) return false;\n\n      // Convert split time to sample position, snapped to pixel boundary\n      const splitSample = Math.round(splitTime * sampleRate);\n      const snappedSplitSample = calculateSplitPoint(splitSample, samplesPerPixel);\n\n      // Pre-flight validation before sending to engine (engine also validates internally).\n      // Duplicated here to provide early user feedback via console.warn.\n      const MIN_DURATION_SAMPLES = Math.floor(0.1 * sampleRate);\n      if (!canSplitAt(clip, snappedSplitSample, MIN_DURATION_SAMPLES)) {\n        console.warn('Split point is invalid (outside bounds or too close to edge)');\n        return false;\n      }\n\n      // Delegate to engine — handles clip creation, adapter sync, and emits statechange\n      const engine = engineRef.current;\n      if (!engine) {\n        console.warn('[waveform-playlist] engineRef is null — split not synced to adapter');\n        return false;\n      }\n\n      engine.splitClip(track.id, clip.id, snappedSplitSample);\n      return true;\n    },\n    [tracks, options, engineRef, sampleRate]\n  );\n\n  /**\n   * Split clip at the current playhead position on the selected track\n   * If no track is selected, does nothing\n   *\n   * @returns true if a clip was split, false otherwise\n   */\n  const splitClipAtPlayhead = useCallback((): boolean => {\n    // If no track is selected, cannot split\n    if (!selectedTrackId) {\n      console.warn('[waveform-playlist] No track selected — click a clip to select a track first');\n      return false;\n    }\n\n    // Find the selected track\n    const trackIndex = tracks.findIndex((track) => track.id === selectedTrackId);\n    if (trackIndex === -1) {\n      console.warn('Selected track not found');\n      return false;\n    }\n\n    const track = tracks[trackIndex];\n\n    // Use ref for real-time position during playback (state updates are throttled)\n    const currentTime = currentTimeRef.current ?? 0;\n\n    // Find clip at current time on the selected track\n    for (let clipIndex = 0; clipIndex < track.clips.length; clipIndex++) {\n      const clip = track.clips[clipIndex];\n      const clipStartTime = clip.startSample / sampleRate;\n      const clipEndTime = (clip.startSample + clip.durationSamples) / sampleRate;\n\n      // Check if currentTime is within this clip (not at boundaries)\n      if (currentTime > clipStartTime && currentTime < clipEndTime) {\n        // Found a clip! Split it\n        return splitClipAt(trackIndex, clipIndex, currentTime);\n      }\n    }\n\n    console.warn(`[waveform-playlist] No clip found at playhead position on track \"${track.name}\"`);\n    return false;\n  }, [tracks, currentTimeRef, selectedTrackId, splitClipAt, sampleRate]);\n\n  return {\n    splitClipAtPlayhead,\n    splitClipAt,\n  };\n};\n","import { useEffect, useCallback } from 'react';\nimport { handleKeyboardEvent } from '@waveform-playlist/core';\nimport type { KeyboardShortcut } from '@waveform-playlist/core';\n\n// Re-export from core for backwards compatibility\nexport type { KeyboardShortcut } from '@waveform-playlist/core';\nexport { handleKeyboardEvent, getShortcutLabel } from '@waveform-playlist/core';\n\nexport interface UseKeyboardShortcutsOptions {\n  shortcuts: KeyboardShortcut[];\n  enabled?: boolean;\n}\n\n/**\n * Hook for managing keyboard shortcuts\n *\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * useKeyboardShortcuts({\n *   shortcuts: [\n *     {\n *       key: ' ',\n *       action: togglePlayPause,\n *       description: 'Play/Pause',\n *       preventDefault: true,\n *     },\n *     {\n *       key: 's',\n *       action: splitClipAtPlayhead,\n *       description: 'Split clip at playhead',\n *       preventDefault: true,\n *     },\n *   ],\n * });\n * ```\n */\nexport const useKeyboardShortcuts = (options: UseKeyboardShortcutsOptions): void => {\n  const { shortcuts, enabled = true } = options;\n\n  const handleKeyDown = useCallback(\n    (event: KeyboardEvent) => handleKeyboardEvent(event, shortcuts, enabled),\n    [shortcuts, enabled]\n  );\n\n  useEffect(() => {\n    if (!enabled) return;\n\n    window.addEventListener('keydown', handleKeyDown);\n\n    return () => {\n      window.removeEventListener('keydown', handleKeyDown);\n    };\n  }, [handleKeyDown, enabled]);\n};\n","import { useCallback } from 'react';\nimport { usePlaybackAnimation, usePlaylistControls } from '../WaveformPlaylistContext';\nimport { useKeyboardShortcuts, type KeyboardShortcut } from './useKeyboardShortcuts';\n\nexport interface UsePlaybackShortcutsOptions {\n  /**\n   * Enable the shortcuts. Defaults to true.\n   */\n  enabled?: boolean;\n  /**\n   * Additional shortcuts to include alongside the default playback shortcuts.\n   */\n  additionalShortcuts?: KeyboardShortcut[];\n  /**\n   * Override default shortcuts. If provided, only these shortcuts will be used.\n   */\n  shortcuts?: KeyboardShortcut[];\n}\n\nexport interface UsePlaybackShortcutsReturn {\n  /** Rewind to the beginning (time = 0) */\n  rewindToStart: () => void;\n  /** Toggle play/pause */\n  togglePlayPause: () => void;\n  /** Stop playback and return to start position */\n  stopPlayback: () => void;\n  /** The list of active keyboard shortcuts */\n  shortcuts: KeyboardShortcut[];\n}\n\n/**\n * Hook that provides common playback keyboard shortcuts for the playlist.\n *\n * Default shortcuts:\n * - `Space` - Toggle play/pause\n * - `Escape` - Stop playback\n * - `0` - Rewind to start (seek to time 0)\n *\n * @example\n * ```tsx\n * // Basic usage - enables default shortcuts\n * usePlaybackShortcuts();\n *\n * // With additional custom shortcuts\n * usePlaybackShortcuts({\n *   additionalShortcuts: [\n *     { key: 's', action: splitClipAtPlayhead, description: 'Split clip' },\n *   ],\n * });\n *\n * // Completely override shortcuts\n * usePlaybackShortcuts({\n *   shortcuts: [\n *     { key: 'Home', action: rewindToStart, description: 'Go to start' },\n *   ],\n * });\n * ```\n */\nexport const usePlaybackShortcuts = (\n  options: UsePlaybackShortcutsOptions = {}\n): UsePlaybackShortcutsReturn => {\n  const { enabled = true, additionalShortcuts = [], shortcuts: overrideShortcuts } = options;\n\n  const { isPlaying } = usePlaybackAnimation();\n  const { setCurrentTime, play, pause, stop } = usePlaylistControls();\n\n  /**\n   * Toggle between play and pause.\n   */\n  const togglePlayPause = useCallback(() => {\n    if (isPlaying) {\n      pause();\n    } else {\n      play();\n    }\n  }, [isPlaying, play, pause]);\n\n  /**\n   * Stop playback and return to start position.\n   */\n  const stopPlayback = useCallback(() => {\n    stop();\n  }, [stop]);\n\n  /**\n   * Rewind to the beginning of the timeline.\n   * If playing, stops and restarts playback from the beginning.\n   */\n  const rewindToStart = useCallback(() => {\n    setCurrentTime(0);\n\n    if (isPlaying) {\n      play(0);\n    }\n  }, [isPlaying, setCurrentTime, play]);\n\n  // Default playback shortcuts\n  const defaultShortcuts: KeyboardShortcut[] = [\n    {\n      key: ' ',\n      action: togglePlayPause,\n      description: 'Play/Pause',\n      preventDefault: true,\n    },\n    {\n      key: 'Escape',\n      action: stopPlayback,\n      description: 'Stop',\n      preventDefault: true,\n    },\n    {\n      key: '0',\n      action: rewindToStart,\n      description: 'Rewind to start',\n      preventDefault: true,\n    },\n  ];\n\n  // Use override shortcuts if provided, otherwise combine defaults with additional\n  const activeShortcuts = overrideShortcuts ?? [...defaultShortcuts, ...additionalShortcuts];\n\n  // Register the keyboard shortcuts\n  useKeyboardShortcuts({\n    shortcuts: activeShortcuts,\n    enabled,\n  });\n\n  return {\n    rewindToStart,\n    togglePlayPause,\n    stopPlayback,\n    shortcuts: activeShortcuts,\n  };\n};\n","import { useCallback, useMemo, useEffect } from 'react';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport { usePlaylistDataOptional } from '../WaveformPlaylistContext';\nimport { useKeyboardShortcuts } from './useKeyboardShortcuts';\n\nconst LINK_THRESHOLD = 0.01; // Consider edges \"linked\" if within 10ms\nconst TIME_DELTA = 0.01; // 10ms adjustment per keypress\n\ninterface UseAnnotationKeyboardControlsOptions {\n  annotations: AnnotationData[];\n  activeAnnotationId: string | null;\n  onAnnotationsChange: (annotations: AnnotationData[]) => void;\n  /** Callback to set the active annotation ID for selection */\n  onActiveAnnotationChange?: (id: string | null) => void;\n  duration: number;\n  linkEndpoints: boolean;\n  /** Whether continuous play is enabled (affects playback duration) */\n  continuousPlay?: boolean;\n  enabled?: boolean;\n  /** Optional: scroll container ref for auto-scrolling to annotation */\n  scrollContainerRef?: React.RefObject<HTMLDivElement | null>;\n  /** Optional: callback to start playback at a time with optional duration */\n  onPlay?: (startTime: number, duration?: number) => void;\n  /**\n   * Pixels-per-sample for auto-scroll positioning. Falls back to the\n   * WaveformPlaylistProvider context when omitted (WebAudio path). Pass\n   * explicitly when used outside that provider (e.g. the MediaElement path).\n   */\n  samplesPerPixel?: number;\n  /** Sample rate for auto-scroll positioning. Falls back to context. */\n  sampleRate?: number;\n}\n\n/**\n * Hook for keyboard-based annotation navigation and boundary editing\n *\n * Navigation Shortcuts:\n * - ArrowUp / ArrowLeft = Select previous annotation\n * - ArrowDown / ArrowRight = Select next annotation\n * - Home = Select first annotation\n * - End = Select last annotation\n * - Escape = Deselect annotation\n * - Enter = Play selected annotation\n *\n * Boundary Editing Shortcuts (requires active annotation):\n * - [ = Move start boundary earlier (left)\n * - ] = Move start boundary later (right)\n * - Shift+[ = Move end boundary earlier (left)\n * - Shift+] = Move end boundary later (right)\n *\n * Respects linkEndpoints and continuousPlay settings.\n *\n * @example\n * ```tsx\n * useAnnotationKeyboardControls({\n *   annotations,\n *   activeAnnotationId,\n *   onAnnotationsChange: setAnnotations,\n *   onActiveAnnotationChange: setActiveAnnotationId,\n *   duration,\n *   linkEndpoints,\n * });\n * ```\n */\nexport function useAnnotationKeyboardControls({\n  annotations,\n  activeAnnotationId,\n  onAnnotationsChange,\n  onActiveAnnotationChange,\n  duration,\n  linkEndpoints,\n  continuousPlay = false,\n  enabled = true,\n  scrollContainerRef,\n  onPlay,\n  samplesPerPixel: samplesPerPixelProp,\n  sampleRate: sampleRateProp,\n}: UseAnnotationKeyboardControlsOptions) {\n  // Auto-scroll positioning needs samplesPerPixel/sampleRate. Prefer explicit\n  // props (works in the MediaElement path); otherwise read them from the\n  // WaveformPlaylistProvider context when present. usePlaylistDataOptional()\n  // returns null instead of throwing, so this hook is safe outside that provider.\n  const playlistData = usePlaylistDataOptional();\n  const samplesPerPixel = samplesPerPixelProp ?? playlistData?.samplesPerPixel;\n  const sampleRate = sampleRateProp ?? playlistData?.sampleRate;\n\n  const activeIndex = useMemo(() => {\n    if (!activeAnnotationId) return -1;\n    return annotations.findIndex((a) => a.id === activeAnnotationId);\n  }, [annotations, activeAnnotationId]);\n\n  // Scroll waveform to show a specific annotation\n  const scrollToAnnotation = useCallback(\n    (annotationId: string) => {\n      if (!scrollContainerRef?.current || !samplesPerPixel || !sampleRate) return;\n\n      const annotation = annotations.find((a) => a.id === annotationId);\n      if (!annotation) return;\n\n      const container = scrollContainerRef.current;\n      const containerWidth = container.clientWidth;\n\n      // Calculate pixel positions for annotation start and center\n      const startPixel = (annotation.start * sampleRate) / samplesPerPixel;\n      const endPixel = (annotation.end * sampleRate) / samplesPerPixel;\n      const annotationCenter = (startPixel + endPixel) / 2;\n\n      // Check if annotation is currently visible\n      const scrollLeft = container.scrollLeft;\n      const visibleStart = scrollLeft;\n      const visibleEnd = scrollLeft + containerWidth;\n\n      // If annotation is not fully visible, scroll to center it\n      if (startPixel < visibleStart || endPixel > visibleEnd) {\n        const targetScrollLeft = Math.max(0, annotationCenter - containerWidth / 2);\n        container.scrollTo({\n          left: targetScrollLeft,\n          behavior: 'smooth',\n        });\n      }\n    },\n    [annotations, scrollContainerRef, samplesPerPixel, sampleRate]\n  );\n\n  // Auto-scroll when active annotation changes via keyboard navigation\n  useEffect(() => {\n    if (activeAnnotationId && scrollContainerRef?.current && samplesPerPixel && sampleRate) {\n      scrollToAnnotation(activeAnnotationId);\n    }\n  }, [activeAnnotationId, scrollToAnnotation, scrollContainerRef, samplesPerPixel, sampleRate]);\n\n  const moveStartBoundary = useCallback(\n    (delta: number) => {\n      if (activeIndex < 0) return;\n\n      const annotation = annotations[activeIndex];\n      const newStart = Math.max(0, Math.min(annotation.end - 0.1, annotation.start + delta));\n      const actualDelta = newStart - annotation.start;\n\n      const updatedAnnotations = [...annotations];\n      updatedAnnotations[activeIndex] = {\n        ...annotation,\n        start: newStart,\n      };\n\n      // Handle linked endpoints\n      if (linkEndpoints && activeIndex > 0) {\n        const prevAnnotation = updatedAnnotations[activeIndex - 1];\n        if (Math.abs(prevAnnotation.end - annotation.start) < LINK_THRESHOLD) {\n          // Already linked: move previous annotation's end together\n          updatedAnnotations[activeIndex - 1] = {\n            ...prevAnnotation,\n            end: Math.max(prevAnnotation.start + 0.1, prevAnnotation.end + actualDelta),\n          };\n        }\n      } else if (!linkEndpoints && activeIndex > 0) {\n        // Non-linked mode: don't overlap previous annotation\n        const prevAnnotation = updatedAnnotations[activeIndex - 1];\n        if (newStart < prevAnnotation.end) {\n          // Push back previous annotation's end\n          updatedAnnotations[activeIndex - 1] = {\n            ...prevAnnotation,\n            end: newStart,\n          };\n        }\n      }\n\n      onAnnotationsChange(updatedAnnotations);\n    },\n    [annotations, activeIndex, linkEndpoints, onAnnotationsChange]\n  );\n\n  const moveEndBoundary = useCallback(\n    (delta: number) => {\n      if (activeIndex < 0) return;\n\n      const annotation = annotations[activeIndex];\n      const newEnd = Math.max(annotation.start + 0.1, Math.min(duration, annotation.end + delta));\n      const actualDelta = newEnd - annotation.end;\n\n      const updatedAnnotations = [...annotations];\n      updatedAnnotations[activeIndex] = {\n        ...annotation,\n        end: newEnd,\n      };\n\n      // Handle linked endpoints\n      if (linkEndpoints && activeIndex < annotations.length - 1) {\n        const nextAnnotation = updatedAnnotations[activeIndex + 1];\n        if (Math.abs(nextAnnotation.start - annotation.end) < LINK_THRESHOLD) {\n          // Already linked: move next annotation's start together\n          const newNextStart = Math.min(\n            nextAnnotation.end - 0.1,\n            nextAnnotation.start + actualDelta\n          );\n          updatedAnnotations[activeIndex + 1] = {\n            ...nextAnnotation,\n            start: newNextStart,\n          };\n\n          // Cascade linked endpoints\n          let currentIndex = activeIndex + 1;\n          while (currentIndex < updatedAnnotations.length - 1) {\n            const current = updatedAnnotations[currentIndex];\n            const next = updatedAnnotations[currentIndex + 1];\n\n            if (Math.abs(next.start - annotations[currentIndex].end) < LINK_THRESHOLD) {\n              const nextDelta = current.end - annotations[currentIndex].end;\n              updatedAnnotations[currentIndex + 1] = {\n                ...next,\n                start: Math.min(next.end - 0.1, next.start + nextDelta),\n              };\n              currentIndex++;\n            } else {\n              break;\n            }\n          }\n        }\n      } else if (!linkEndpoints && activeIndex < annotations.length - 1) {\n        // Non-linked mode: don't overlap next annotation\n        const nextAnnotation = updatedAnnotations[activeIndex + 1];\n        if (newEnd > nextAnnotation.start) {\n          // Push forward next annotation's start\n          updatedAnnotations[activeIndex + 1] = {\n            ...nextAnnotation,\n            start: newEnd,\n          };\n\n          // Cascade collisions\n          let currentIndex = activeIndex + 1;\n          while (currentIndex < updatedAnnotations.length - 1) {\n            const current = updatedAnnotations[currentIndex];\n            const next = updatedAnnotations[currentIndex + 1];\n\n            if (current.end > next.start) {\n              updatedAnnotations[currentIndex + 1] = {\n                ...next,\n                start: current.end,\n              };\n              currentIndex++;\n            } else {\n              break;\n            }\n          }\n        }\n      }\n\n      onAnnotationsChange(updatedAnnotations);\n    },\n    [annotations, activeIndex, duration, linkEndpoints, onAnnotationsChange]\n  );\n\n  // Navigation functions\n  const selectPrevious = useCallback(() => {\n    if (!onActiveAnnotationChange || annotations.length === 0) return;\n\n    if (activeIndex <= 0) {\n      // If no selection or at first, select last annotation\n      onActiveAnnotationChange(annotations[annotations.length - 1].id);\n    } else {\n      onActiveAnnotationChange(annotations[activeIndex - 1].id);\n    }\n  }, [annotations, activeIndex, onActiveAnnotationChange]);\n\n  const selectNext = useCallback(() => {\n    if (!onActiveAnnotationChange || annotations.length === 0) return;\n\n    if (activeIndex < 0 || activeIndex >= annotations.length - 1) {\n      // If no selection or at last, select first annotation\n      onActiveAnnotationChange(annotations[0].id);\n    } else {\n      onActiveAnnotationChange(annotations[activeIndex + 1].id);\n    }\n  }, [annotations, activeIndex, onActiveAnnotationChange]);\n\n  const selectFirst = useCallback(() => {\n    if (!onActiveAnnotationChange || annotations.length === 0) return;\n    onActiveAnnotationChange(annotations[0].id);\n  }, [annotations, onActiveAnnotationChange]);\n\n  const selectLast = useCallback(() => {\n    if (!onActiveAnnotationChange || annotations.length === 0) return;\n    onActiveAnnotationChange(annotations[annotations.length - 1].id);\n  }, [annotations, onActiveAnnotationChange]);\n\n  const clearSelection = useCallback(() => {\n    if (!onActiveAnnotationChange) return;\n    onActiveAnnotationChange(null);\n  }, [onActiveAnnotationChange]);\n\n  // Play the currently selected annotation\n  const playActiveAnnotation = useCallback(() => {\n    if (activeIndex < 0 || !onPlay) return;\n\n    const annotation = annotations[activeIndex];\n    // If continuous play is off, play just this annotation's duration\n    const playDuration = !continuousPlay ? annotation.end - annotation.start : undefined;\n    onPlay(annotation.start, playDuration);\n  }, [annotations, activeIndex, continuousPlay, onPlay]);\n\n  // Shortcuts that require an active annotation (boundary editing + playback)\n  const activeAnnotationShortcuts = useMemo(\n    () => [\n      {\n        key: '[',\n        action: () => moveStartBoundary(-TIME_DELTA),\n        description: 'Move annotation start earlier',\n        preventDefault: true,\n      },\n      {\n        key: ']',\n        action: () => moveStartBoundary(TIME_DELTA),\n        description: 'Move annotation start later',\n        preventDefault: true,\n      },\n      {\n        key: '{',\n        shiftKey: true,\n        action: () => moveEndBoundary(-TIME_DELTA),\n        description: 'Move annotation end earlier',\n        preventDefault: true,\n      },\n      {\n        key: '}',\n        shiftKey: true,\n        action: () => moveEndBoundary(TIME_DELTA),\n        description: 'Move annotation end later',\n        preventDefault: true,\n      },\n      {\n        key: 'Enter',\n        action: playActiveAnnotation,\n        description: 'Play selected annotation',\n        preventDefault: true,\n      },\n    ],\n    [moveStartBoundary, moveEndBoundary, playActiveAnnotation]\n  );\n\n  // Navigation shortcuts (always active when enabled and there are annotations)\n  const navigationShortcuts = useMemo(\n    () => [\n      {\n        key: 'ArrowUp',\n        action: selectPrevious,\n        description: 'Select previous annotation',\n        preventDefault: true,\n      },\n      {\n        key: 'ArrowLeft',\n        action: selectPrevious,\n        description: 'Select previous annotation',\n        preventDefault: true,\n      },\n      {\n        key: 'ArrowDown',\n        action: selectNext,\n        description: 'Select next annotation',\n        preventDefault: true,\n      },\n      {\n        key: 'ArrowRight',\n        action: selectNext,\n        description: 'Select next annotation',\n        preventDefault: true,\n      },\n      {\n        key: 'Home',\n        action: selectFirst,\n        description: 'Select first annotation',\n        preventDefault: true,\n      },\n      {\n        key: 'End',\n        action: selectLast,\n        description: 'Select last annotation',\n        preventDefault: true,\n      },\n      {\n        key: 'Escape',\n        action: clearSelection,\n        description: 'Deselect annotation',\n        preventDefault: true,\n      },\n    ],\n    [selectPrevious, selectNext, selectFirst, selectLast, clearSelection]\n  );\n\n  // Active annotation shortcuts only work when an annotation is selected\n  useKeyboardShortcuts({\n    shortcuts: activeAnnotationShortcuts,\n    enabled: enabled && activeIndex >= 0,\n  });\n\n  // Navigation shortcuts work whenever there are annotations\n  useKeyboardShortcuts({\n    shortcuts: navigationShortcuts,\n    enabled: enabled && annotations.length > 0 && !!onActiveAnnotationChange,\n  });\n\n  return {\n    moveStartBoundary,\n    moveEndBoundary,\n    selectPrevious,\n    selectNext,\n    selectFirst,\n    selectLast,\n    clearSelection,\n    scrollToAnnotation,\n    playActiveAnnotation,\n  };\n}\n","import { useCallback, useEffect, useRef } from 'react';\n\nexport interface AnimationFrameLoopControls {\n  animationFrameRef: React.MutableRefObject<number | null>;\n  startAnimationFrameLoop: (callback: () => void) => void;\n  stopAnimationFrameLoop: () => void;\n}\n\n/**\n * Shared RAF loop controller used by playlist providers.\n * Always guarantees a single in-flight animation frame.\n */\nexport const useAnimationFrameLoop = (): AnimationFrameLoopControls => {\n  const animationFrameRef = useRef<number | null>(null);\n\n  const stopAnimationFrameLoop = useCallback(() => {\n    if (animationFrameRef.current !== null) {\n      cancelAnimationFrame(animationFrameRef.current);\n      animationFrameRef.current = null;\n    }\n  }, []);\n\n  const startAnimationFrameLoop = useCallback(\n    (callback: () => void) => {\n      stopAnimationFrameLoop();\n      animationFrameRef.current = requestAnimationFrame(callback);\n    },\n    [stopAnimationFrameLoop]\n  );\n\n  useEffect(() => {\n    return () => {\n      stopAnimationFrameLoop();\n    };\n  }, [stopAnimationFrameLoop]);\n\n  return {\n    animationFrameRef,\n    startAnimationFrameLoop,\n    stopAnimationFrameLoop,\n  };\n};\n","/**\n * useWaveformDataCache\n *\n * Manages a Map<clipId, WaveformData> that stores pre-computed WaveformData\n * objects generated by a web worker. These enable near-instant zoom changes\n * via WaveformData.resample() instead of re-scanning raw audio samples.\n *\n * Behavior:\n * - Creates the worker lazily on first clip needing generation\n * - Generates waveform data once per unique AudioBuffer\n * - Fans out cached/generated waveform data to all clips sharing that buffer\n * - Uses a cancelled flag to ignore state updates after effect cleanup\n * - Terminates the worker on unmount\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { ClipTrack } from '@waveform-playlist/core';\nimport type WaveformData from 'waveform-data';\nimport { createPeaksWorker, type PeaksWorkerApi } from '../workers/peaksWorker';\n\nexport interface UseWaveformDataCacheReturn {\n  cache: ReadonlyMap<string, WaveformData>;\n  isGenerating: boolean;\n}\n\nexport function useWaveformDataCache(\n  tracks: ClipTrack[],\n  baseScale: number\n): UseWaveformDataCacheReturn {\n  const [cache, setCache] = useState<Map<string, WaveformData>>(() => new Map());\n  const [isGenerating, setIsGenerating] = useState(false);\n\n  const workerRef = useRef<PeaksWorkerApi | null>(null);\n  const generatedByBufferRef = useRef<WeakMap<AudioBuffer, WaveformData>>(new WeakMap());\n  const inflightByBufferRef = useRef<WeakMap<AudioBuffer, Promise<void>>>(new WeakMap());\n  const subscribersByBufferRef = useRef<WeakMap<AudioBuffer, Set<string>>>(new WeakMap());\n  const pendingCountRef = useRef(0);\n\n  // Stable callback to get or create the worker.\n  // Also replaces a crashed worker (onerror marks it terminated) — reusing\n  // it would reject every later generation until the component remounts.\n  const getWorker = useCallback(() => {\n    if (!workerRef.current || workerRef.current.isTerminated()) {\n      workerRef.current = createPeaksWorker();\n    }\n    return workerRef.current;\n  }, []);\n\n  useEffect(() => {\n    let cancelled = false;\n    const generatedByBuffer = generatedByBufferRef.current;\n    const inflightByBuffer = inflightByBufferRef.current;\n    const subscribersByBuffer = subscribersByBufferRef.current;\n    const immediateAssignments: Array<{ clipId: string; waveformData: WaveformData }> = [];\n    const buffersToProcess = new Map<AudioBuffer, Set<string>>();\n\n    for (const track of tracks) {\n      for (const clip of track.clips) {\n        if (!clip.audioBuffer || clip.waveformData) {\n          continue;\n        }\n\n        const existingWaveformData = generatedByBuffer.get(clip.audioBuffer);\n        if (existingWaveformData) {\n          immediateAssignments.push({\n            clipId: clip.id,\n            waveformData: existingWaveformData,\n          });\n          continue;\n        }\n\n        if (inflightByBuffer.get(clip.audioBuffer)) {\n          const existingSubscribers = subscribersByBuffer.get(clip.audioBuffer);\n          if (existingSubscribers) {\n            existingSubscribers.add(clip.id);\n          } else {\n            subscribersByBuffer.set(clip.audioBuffer, new Set([clip.id]));\n          }\n          continue;\n        }\n\n        const clipsForBuffer = buffersToProcess.get(clip.audioBuffer);\n        if (clipsForBuffer) {\n          clipsForBuffer.add(clip.id);\n        } else {\n          buffersToProcess.set(clip.audioBuffer, new Set([clip.id]));\n        }\n      }\n    }\n\n    if (immediateAssignments.length > 0) {\n      setCache((prev) => {\n        const next = new Map(prev);\n        for (const assignment of immediateAssignments) {\n          next.set(assignment.clipId, assignment.waveformData);\n        }\n        return next;\n      });\n    }\n\n    if (buffersToProcess.size === 0) return;\n\n    pendingCountRef.current += buffersToProcess.size;\n    setIsGenerating(true);\n\n    const worker = getWorker();\n\n    for (const [audioBuffer, clipIds] of buffersToProcess) {\n      subscribersByBuffer.set(audioBuffer, new Set(clipIds));\n\n      // .slice() channel buffers to avoid detaching the original AudioBuffer views\n      const channels: ArrayBuffer[] = [];\n      for (let c = 0; c < audioBuffer.numberOfChannels; c++) {\n        channels.push(audioBuffer.getChannelData(c).slice().buffer as ArrayBuffer);\n      }\n\n      const id = `buffer-${Math.random().toString(36).slice(2, 11)}`;\n      const job = worker\n        .generate({\n          id,\n          channels,\n          length: audioBuffer.length,\n          sampleRate: audioBuffer.sampleRate,\n          scale: baseScale,\n          bits: 16,\n          splitChannels: true,\n        })\n        .then((waveformData) => {\n          generatedByBuffer.set(audioBuffer, waveformData);\n          const subscribers = subscribersByBuffer.get(audioBuffer) ?? new Set<string>();\n          subscribersByBuffer.delete(audioBuffer);\n          inflightByBuffer.delete(audioBuffer);\n          if (cancelled) return;\n\n          setCache((prev) => {\n            const next = new Map(prev);\n            for (const clipId of subscribers) {\n              next.set(clipId, waveformData);\n            }\n            return next;\n          });\n\n          pendingCountRef.current--;\n          if (pendingCountRef.current <= 0) {\n            pendingCountRef.current = 0;\n            setIsGenerating(false);\n          }\n        })\n        .catch((err) => {\n          subscribersByBuffer.delete(audioBuffer);\n          inflightByBuffer.delete(audioBuffer);\n          if (cancelled) return;\n          console.warn('[waveform-playlist] Worker peak generation failed:', err);\n          pendingCountRef.current--;\n          if (pendingCountRef.current <= 0) {\n            pendingCountRef.current = 0;\n            setIsGenerating(false);\n          }\n        });\n\n      inflightByBuffer.set(audioBuffer, job);\n    }\n\n    return () => {\n      cancelled = true;\n      inflightByBufferRef.current = new WeakMap();\n      subscribersByBufferRef.current = new WeakMap();\n      pendingCountRef.current = 0;\n      setIsGenerating(false);\n    };\n  }, [tracks, baseScale, getWorker]);\n\n  // Terminate worker on unmount\n  useEffect(() => {\n    return () => {\n      workerRef.current?.terminate();\n      workerRef.current = null;\n    };\n  }, []);\n\n  return { cache, isGenerating };\n}\n","/**\n * Inline Web Worker for generating WaveformData binary format from AudioBuffer channels.\n *\n * Uses a Blob URL approach (portable across all bundlers) with the generateWaveformData\n * algorithm adapted from BBC's waveform-data.js (MIT licensed, adapted from Audacity).\n *\n * The worker generates peaks at a base scale (finest zoom level). The main thread\n * then uses WaveformData.resample() for near-instant zoom changes.\n */\n\nimport WaveformData from 'waveform-data';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Worker code (runs inside the Blob URL worker)\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Self-contained worker source.\n * Contains the generateWaveformData function from waveform-data.js/src/waveform-generator.js\n * (MIT License, BBC). Pure JS, zero dependencies.\n */\nconst workerSource = `\n\"use strict\";\n\nvar INT8_MAX = 127;\nvar INT8_MIN = -128;\nvar INT16_MAX = 32767;\nvar INT16_MIN = -32768;\n\nfunction calculateWaveformDataLength(audio_sample_count, scale) {\n  var data_length = Math.floor(audio_sample_count / scale);\n  var samples_remaining = audio_sample_count - (data_length * scale);\n  if (samples_remaining > 0) {\n    data_length++;\n  }\n  return data_length;\n}\n\nfunction generateWaveformData(options) {\n  var scale = options.scale;\n  var amplitude_scale = options.amplitude_scale;\n  var split_channels = options.split_channels;\n  var length = options.length;\n  var sample_rate = options.sample_rate;\n  var channels = options.channels.map(function(channel) {\n    return new Float32Array(channel);\n  });\n  var output_channels = split_channels ? channels.length : 1;\n  var header_size = 24;\n  var data_length = calculateWaveformDataLength(length, scale);\n  var bytes_per_sample = options.bits === 8 ? 1 : 2;\n  var total_size = header_size + data_length * 2 * bytes_per_sample * output_channels;\n  var buffer = new ArrayBuffer(total_size);\n  var data_view = new DataView(buffer);\n\n  var scale_counter = 0;\n  var offset = header_size;\n\n  var min_value = new Array(output_channels);\n  var max_value = new Array(output_channels);\n\n  for (var channel = 0; channel < output_channels; channel++) {\n    min_value[channel] = Infinity;\n    max_value[channel] = -Infinity;\n  }\n\n  var range_min = options.bits === 8 ? INT8_MIN : INT16_MIN;\n  var range_max = options.bits === 8 ? INT8_MAX : INT16_MAX;\n\n  data_view.setInt32(0, 2, true);\n  data_view.setUint32(4, options.bits === 8, true);\n  data_view.setInt32(8, sample_rate, true);\n  data_view.setInt32(12, scale, true);\n  data_view.setInt32(16, data_length, true);\n  data_view.setInt32(20, output_channels, true);\n\n  for (var i = 0; i < length; i++) {\n    var sample = 0;\n\n    if (output_channels === 1) {\n      for (var ch = 0; ch < channels.length; ++ch) {\n        sample += channels[ch][i];\n      }\n      sample = Math.floor(range_max * sample * amplitude_scale / channels.length);\n\n      if (sample < min_value[0]) {\n        min_value[0] = sample;\n        if (min_value[0] < range_min) {\n          min_value[0] = range_min;\n        }\n      }\n      if (sample > max_value[0]) {\n        max_value[0] = sample;\n        if (max_value[0] > range_max) {\n          max_value[0] = range_max;\n        }\n      }\n    }\n    else {\n      for (var ch2 = 0; ch2 < output_channels; ++ch2) {\n        sample = Math.floor(range_max * channels[ch2][i] * amplitude_scale);\n\n        if (sample < min_value[ch2]) {\n          min_value[ch2] = sample;\n          if (min_value[ch2] < range_min) {\n            min_value[ch2] = range_min;\n          }\n        }\n        if (sample > max_value[ch2]) {\n          max_value[ch2] = sample;\n          if (max_value[ch2] > range_max) {\n            max_value[ch2] = range_max;\n          }\n        }\n      }\n    }\n\n    if (++scale_counter === scale) {\n      for (var ch3 = 0; ch3 < output_channels; ch3++) {\n        if (options.bits === 8) {\n          data_view.setInt8(offset++, min_value[ch3]);\n          data_view.setInt8(offset++, max_value[ch3]);\n        }\n        else {\n          data_view.setInt16(offset, min_value[ch3], true);\n          data_view.setInt16(offset + 2, max_value[ch3], true);\n          offset += 4;\n        }\n        min_value[ch3] = Infinity;\n        max_value[ch3] = -Infinity;\n      }\n      scale_counter = 0;\n    }\n  }\n\n  if (scale_counter > 0) {\n    for (var ch4 = 0; ch4 < output_channels; ch4++) {\n      if (options.bits === 8) {\n        data_view.setInt8(offset++, min_value[ch4]);\n        data_view.setInt8(offset++, max_value[ch4]);\n      }\n      else {\n        data_view.setInt16(offset, min_value[ch4], true);\n        data_view.setInt16(offset + 2, max_value[ch4], true);\n      }\n    }\n  }\n\n  return buffer;\n}\n\nself.onmessage = function(e) {\n  var msg = e.data;\n  try {\n    var result = generateWaveformData({\n      scale: msg.scale,\n      bits: msg.bits,\n      amplitude_scale: msg.amplitude_scale,\n      split_channels: msg.split_channels,\n      length: msg.length,\n      sample_rate: msg.sample_rate,\n      channels: msg.channels\n    });\n    self.postMessage({ id: msg.id, buffer: result }, [result]);\n  } catch (err) {\n    self.postMessage({ id: msg.id, error: err.message || String(err) });\n  }\n};\n`;\n\n// ────────────────────────────────────────────────────────────────────────────\n// Promise-based worker API (runs on the main thread)\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface PeaksWorkerApi {\n  generate(params: {\n    id: string;\n    channels: ArrayBuffer[];\n    length: number;\n    sampleRate: number;\n    scale: number;\n    bits: 8 | 16;\n    splitChannels: boolean;\n  }): Promise<WaveformData>;\n  /** True once the worker has crashed (onerror) or been terminated — every\n   * generate() will reject. Callers should discard this instance and create\n   * a fresh worker. */\n  isTerminated(): boolean;\n  terminate(): void;\n}\n\ninterface PendingEntry {\n  resolve: (value: WaveformData) => void;\n  reject: (reason: unknown) => void;\n}\n\nlet idCounter = 0;\n\nexport function createPeaksWorker(): PeaksWorkerApi {\n  let worker: Worker;\n  try {\n    const blob = new Blob([workerSource], { type: 'application/javascript' });\n    const url = URL.createObjectURL(blob);\n    worker = new Worker(url);\n    URL.revokeObjectURL(url);\n  } catch (err) {\n    // Worker creation can fail in CSP-restricted environments that block blob: URLs.\n    // Return a no-op API that rejects all generate calls gracefully.\n    console.warn('[waveform-playlist] Failed to create peaks worker (CSP restriction?):', err);\n    return {\n      generate() {\n        return Promise.reject(new Error('Worker creation failed'));\n      },\n      // Reports NOT terminated: CSP blocking is permanent for the page, so\n      // recreating on every call would just re-attempt (and re-warn about)\n      // Worker construction each time.\n      isTerminated() {\n        return false;\n      },\n      terminate() {\n        /* no-op */\n      },\n    };\n  }\n\n  const pending = new Map<string, PendingEntry>();\n  let terminated = false;\n\n  worker.onmessage = (e: MessageEvent) => {\n    const msg = e.data;\n    const entry = pending.get(msg.id);\n    if (!entry) return;\n    pending.delete(msg.id);\n\n    if (msg.error) {\n      entry.reject(new Error(msg.error));\n    } else {\n      try {\n        const waveformData = WaveformData.create(msg.buffer);\n        entry.resolve(waveformData);\n      } catch (err) {\n        entry.reject(err);\n      }\n    }\n  };\n\n  worker.onerror = (e: ErrorEvent) => {\n    terminated = true;\n    worker.terminate();\n    for (const [, entry] of pending) {\n      entry.reject(e.error ?? new Error(e.message));\n    }\n    pending.clear();\n  };\n\n  return {\n    isTerminated() {\n      return terminated;\n    },\n\n    generate(params) {\n      if (terminated) return Promise.reject(new Error('Worker terminated'));\n      const messageId = String(++idCounter);\n\n      return new Promise<WaveformData>((resolve, reject) => {\n        pending.set(messageId, { resolve, reject });\n\n        worker.postMessage(\n          {\n            id: messageId,\n            scale: params.scale,\n            bits: params.bits,\n            amplitude_scale: 1.0,\n            split_channels: params.splitChannels,\n            length: params.length,\n            sample_rate: params.sampleRate,\n            channels: params.channels,\n          },\n          params.channels // Transfer ownership\n        );\n      });\n    },\n\n    terminate() {\n      terminated = true;\n      worker.terminate();\n      for (const [, entry] of pending) {\n        entry.reject(new Error('Worker terminated'));\n      }\n      pending.clear();\n    },\n  };\n}\n","import React, {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  useRef,\n  useCallback,\n  useMemo,\n  type ReactNode,\n} from 'react';\nimport { ThemeProvider } from 'styled-components';\nimport type { MediaElementPlayout, FadeConfig } from '@waveform-playlist/media-element-playout';\nimport { resolveMediaElementPlayout } from './playout/resolveMediaElementPlayout';\nimport { type WaveformDataObject } from '@waveform-playlist/core';\nimport { type WaveformPlaylistTheme, defaultTheme } from '@waveform-playlist/ui-components';\nimport type { AnnotationData } from '@waveform-playlist/core';\nimport { extractPeaksFromWaveformData } from './waveformDataLoader';\nimport type WaveformData from 'waveform-data';\nimport type { PeakData } from '@waveform-playlist/core';\nimport type { ClipPeaks, TrackClipPeaks } from './WaveformPlaylistContext';\nimport { useAnimationFrameLoop } from './hooks/useAnimationFrameLoop';\n\n// Configuration for a single media element track\nexport interface MediaElementTrackConfig {\n  /** Audio source URL or Blob URL */\n  source: string;\n  /** Pre-computed waveform data (required for visualization) */\n  waveformData: WaveformDataObject;\n  /** Track name for display */\n  name?: string;\n  /** Fade in configuration (requires audioContext on provider) */\n  fadeIn?: FadeConfig;\n  /** Fade out configuration (requires audioContext on provider) */\n  fadeOut?: FadeConfig;\n}\n\n// Context values for animation (high-frequency updates)\nexport interface MediaElementAnimationContextValue {\n  isPlaying: boolean;\n  currentTime: number;\n  currentTimeRef: React.RefObject<number>;\n}\n\n// Context values for playlist state\nexport interface MediaElementStateContextValue {\n  continuousPlay: boolean;\n  annotations: AnnotationData[];\n  activeAnnotationId: string | null;\n  playbackRate: number;\n  isAutomaticScroll: boolean;\n}\n\n// Context values for controls\nexport interface MediaElementControlsContextValue {\n  play: (startTime?: number) => void;\n  pause: () => void;\n  stop: () => void;\n  seekTo: (time: number) => void;\n  setPlaybackRate: (rate: number) => void;\n  setContinuousPlay: (enabled: boolean) => void;\n  setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>>;\n  setActiveAnnotationId: (id: string | null) => void;\n  setAutomaticScroll: (enabled: boolean) => void;\n  setScrollContainer: (element: HTMLDivElement | null) => void;\n  scrollContainerRef: React.RefObject<HTMLDivElement | null>;\n}\n\n// Context values for playlist data\nexport interface MediaElementDataContextValue {\n  duration: number;\n  peaksDataArray: TrackClipPeaks[];\n  sampleRate: number;\n  waveHeight: number;\n  timeScaleHeight: number;\n  samplesPerPixel: number;\n  playoutRef: React.RefObject<MediaElementPlayout | null>;\n  controls: { show: boolean; width: number };\n  barWidth: number;\n  barGap: number;\n  /** Draw bars with pill-shaped rounded caps (radius barWidth/2). */\n  roundedBars: boolean;\n  progressBarWidth: number;\n  fadeIn?: FadeConfig;\n  fadeOut?: FadeConfig;\n}\n\n// Create contexts\nconst MediaElementAnimationContext = createContext<MediaElementAnimationContextValue | null>(null);\nconst MediaElementStateContext = createContext<MediaElementStateContextValue | null>(null);\nconst MediaElementControlsContext = createContext<MediaElementControlsContextValue | null>(null);\nconst MediaElementDataContext = createContext<MediaElementDataContextValue | null>(null);\n\nexport interface MediaElementPlaylistProviderProps {\n  /** Single track configuration with source URL and waveform data */\n  track: MediaElementTrackConfig;\n  /** Initial samples per pixel (zoom level) */\n  samplesPerPixel?: number;\n  /** Height of each waveform track */\n  waveHeight?: number;\n  /** Show timescale */\n  timescale?: boolean;\n  /** Initial playback rate (0.5 to 2.0) */\n  playbackRate?: number;\n  /** Whether to preserve pitch when changing playback rate (default: true).\n   *  Set to false when using an external pitch processor like SoundTouch. */\n  preservesPitch?: boolean;\n  /** Enable automatic scroll to keep playhead centered */\n  automaticScroll?: boolean;\n  /** Theme configuration */\n  theme?: Partial<WaveformPlaylistTheme>;\n  /** Track controls configuration */\n  controls?: { show: boolean; width: number };\n  /** Annotations */\n  annotationList?: {\n    annotations?: AnnotationData[];\n    isContinuousPlay?: boolean;\n  };\n  /** Width of waveform bars */\n  barWidth?: number;\n  /** Gap between waveform bars */\n  barGap?: number;\n  /** Draw bars with pill-shaped rounded caps (radius barWidth/2). Default: false */\n  roundedBars?: boolean;\n  /** Width of progress bars */\n  progressBarWidth?: number;\n  /** Callback when annotations are changed (drag, edit, etc.) */\n  onAnnotationsChange?: (annotations: AnnotationData[]) => void;\n  /**\n   * AudioContext for Web Audio routing (fades, effects).\n   * When provided, audio routes through Web Audio nodes:\n   *   HTMLAudioElement → MediaElementSourceNode → fadeGain → volumeGain → destination\n   *\n   * Without this, playback uses HTMLAudioElement directly (no fades or effects).\n   * Each provider instance should use its own AudioContext or share one —\n   * createMediaElementSource() is called once per audio element.\n   */\n  audioContext?: AudioContext;\n  /** Callback when audio is ready */\n  onReady?: () => void;\n  /** Called when playout initialization fails (e.g. a missing optional peer,\n   *  a throwing `createPlayout`, or an `addTrack` error). The provider already\n   *  logs the error; use this to surface it in your UI. */\n  onError?: (err: Error) => void;\n  /** Factory for a custom MediaElement playout. When omitted, the bundled engine\n   *  (@waveform-playlist/media-element-playout) is dynamically imported. */\n  createPlayout?: () => MediaElementPlayout;\n  children: ReactNode;\n}\n\n/**\n * MediaElementPlaylistProvider\n *\n * A simplified playlist provider for single-track playback using HTMLAudioElement.\n * Key features:\n * - Pitch-preserving playback rate (0.5x - 2.0x)\n * - Pre-computed peaks visualization (no AudioBuffer needed)\n * - Simpler API than full WaveformPlaylistProvider\n *\n * Use this for:\n * - Language learning apps (speed control)\n * - Podcast players\n * - Single-track audio viewers\n *\n * For multi-track editing, use WaveformPlaylistProvider instead.\n */\nexport const MediaElementPlaylistProvider: React.FC<MediaElementPlaylistProviderProps> = ({\n  track,\n  samplesPerPixel: initialSamplesPerPixel = 1024,\n  waveHeight = 100,\n  timescale = false,\n  playbackRate: initialPlaybackRate = 1,\n  preservesPitch = true,\n  automaticScroll = false,\n  theme: userTheme,\n  controls = { show: false, width: 0 },\n  annotationList,\n  barWidth = 1,\n  barGap = 0,\n  roundedBars = false,\n  progressBarWidth: progressBarWidthProp,\n  audioContext,\n  onAnnotationsChange,\n  onReady,\n  onError,\n  createPlayout,\n  children,\n}) => {\n  const progressBarWidth = progressBarWidthProp ?? barWidth + barGap;\n\n  // State\n  const [isPlaying, setIsPlaying] = useState(false);\n  const [currentTime, setCurrentTime] = useState(0);\n  const [duration, setDuration] = useState(0);\n  const [peaksDataArray, setPeaksDataArray] = useState<TrackClipPeaks[]>([]);\n  const [playbackRate, setPlaybackRateState] = useState(initialPlaybackRate);\n  // Annotations are derived from prop (single source of truth in parent)\n  // In v6, annotations must be pre-parsed (numeric start/end). Use parseAeneas() from @waveform-playlist/annotations before passing.\n  const annotations = useMemo(() => {\n    if (!annotationList?.annotations) return [];\n    if (process.env.NODE_ENV !== 'production' && annotationList.annotations.length > 0) {\n      const first = annotationList.annotations[0] as unknown as Record<string, unknown>;\n      if (typeof first.start !== 'number' || typeof first.end !== 'number') {\n        console.error(\n          '[waveform-playlist] Annotations must have numeric start/end values. ' +\n            'In v6, use parseAeneas() from @waveform-playlist/annotations before passing annotations. ' +\n            'Received start type: ' +\n            typeof first.start\n        );\n        return [];\n      }\n    }\n    return annotationList.annotations;\n  }, [annotationList?.annotations]);\n\n  // Ref for animation loop (avoids restarting loop on annotation change)\n  const annotationsRef = useRef<AnnotationData[]>(annotations);\n  annotationsRef.current = annotations;\n\n  const [activeAnnotationId, setActiveAnnotationIdState] = useState<string | null>(null);\n  const [continuousPlay, setContinuousPlayState] = useState(\n    annotationList?.isContinuousPlay ?? false\n  );\n  const [samplesPerPixel] = useState(initialSamplesPerPixel);\n  const [isAutomaticScroll, setIsAutomaticScroll] = useState(automaticScroll);\n\n  // Refs\n  const playoutRef = useRef<MediaElementPlayout | null>(null);\n  const currentTimeRef = useRef<number>(0);\n  const continuousPlayRef = useRef<boolean>(continuousPlay);\n  const activeAnnotationIdRef = useRef<string | null>(null);\n  const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n  const isAutomaticScrollRef = useRef<boolean>(automaticScroll);\n  const samplesPerPixelRef = useRef<number>(initialSamplesPerPixel);\n  const { startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();\n\n  // Sync refs\n  useEffect(() => {\n    continuousPlayRef.current = continuousPlay;\n  }, [continuousPlay]);\n\n  useEffect(() => {\n    isAutomaticScrollRef.current = isAutomaticScroll;\n  }, [isAutomaticScroll]);\n\n  // Custom setter for activeAnnotationId\n  const setActiveAnnotationId = useCallback((value: string | null) => {\n    activeAnnotationIdRef.current = value;\n    setActiveAnnotationIdState(value);\n  }, []);\n\n  const setContinuousPlay = useCallback((value: boolean) => {\n    continuousPlayRef.current = value;\n    setContinuousPlayState(value);\n  }, []);\n\n  // Memoize setScrollContainer callback\n  const setScrollContainer = useCallback((element: HTMLDivElement | null) => {\n    scrollContainerRef.current = element;\n  }, []);\n\n  // Get sample rate from waveform data\n  const sampleRate = track.waveformData.sample_rate;\n\n  // Initialize playout and load track\n  useEffect(() => {\n    let cancelled = false;\n    let createdPlayout: MediaElementPlayout | null = null;\n\n    (async () => {\n      try {\n        const playout = await resolveMediaElementPlayout({\n          createPlayout,\n          playbackRate: initialPlaybackRate,\n          preservesPitch,\n        });\n        if (cancelled) {\n          playout.dispose();\n          return;\n        }\n        createdPlayout = playout;\n\n        playout.addTrack({\n          source: track.source,\n          peaks: track.waveformData,\n          name: track.name,\n          audioContext,\n          fadeIn: track.fadeIn,\n          fadeOut: track.fadeOut,\n        });\n\n        // Set up time update callback\n        const mediaTrack = playout.getTrack(playout['track']?.id ?? '');\n        if (mediaTrack) {\n          mediaTrack.setOnTimeUpdateCallback((time) => {\n            currentTimeRef.current = time;\n          });\n        }\n\n        // Set up playback complete callback\n        playout.setOnPlaybackComplete(() => {\n          stopAnimationFrameLoop();\n          setIsPlaying(false);\n          setActiveAnnotationId(null);\n          currentTimeRef.current = 0;\n          setCurrentTime(0);\n        });\n\n        playoutRef.current = playout;\n        setDuration(track.waveformData.duration);\n        onReady?.();\n      } catch (err) {\n        console.warn('[waveform-playlist] MediaElement playout init failed: ' + String(err));\n        onError?.(err instanceof Error ? err : new Error(String(err)));\n      }\n    })();\n\n    return () => {\n      cancelled = true;\n      stopAnimationFrameLoop();\n      if (createdPlayout) {\n        createdPlayout.dispose();\n      }\n      playoutRef.current = null;\n    };\n  }, [\n    track.source,\n    track.waveformData,\n    track.name,\n    track.fadeIn,\n    track.fadeOut,\n    audioContext,\n    initialPlaybackRate,\n    preservesPitch,\n    onReady,\n    onError,\n    stopAnimationFrameLoop,\n    setActiveAnnotationId,\n    createPlayout,\n  ]);\n\n  // Generate peaks from waveform data\n  useEffect(() => {\n    try {\n      const extractedPeaks = extractPeaksFromWaveformData(\n        track.waveformData as WaveformData,\n        samplesPerPixel,\n        0, // channel index\n        0, // offset\n        Math.ceil(track.waveformData.duration * sampleRate) // duration in samples\n      );\n\n      const clipPeaks: ClipPeaks = {\n        clipId: 'media-element-clip',\n        trackName: track.name ?? 'Track',\n        peaks: {\n          length: extractedPeaks.length,\n          data: [extractedPeaks.data],\n          bits: extractedPeaks.bits,\n        } as PeakData,\n        startSample: 0,\n        durationSamples: Math.ceil(track.waveformData.duration * sampleRate),\n      };\n\n      setPeaksDataArray([[clipPeaks]]);\n    } catch (err) {\n      console.warn('[waveform-playlist] Failed to extract peaks from waveform data:', err);\n    }\n  }, [track.waveformData, track.name, samplesPerPixel, sampleRate]);\n\n  // Animation loop\n  const startAnimationLoop = useCallback(() => {\n    const updateTime = () => {\n      const time = playoutRef.current?.getCurrentTime() ?? 0;\n      currentTimeRef.current = time;\n\n      // Handle annotation playback\n      const currentAnnotations = annotationsRef.current;\n      if (currentAnnotations.length > 0) {\n        const currentAnnotation = currentAnnotations.find(\n          (ann) => time >= ann.start && time < ann.end\n        );\n\n        if (continuousPlayRef.current) {\n          if (currentAnnotation && currentAnnotation.id !== activeAnnotationIdRef.current) {\n            setActiveAnnotationId(currentAnnotation.id);\n          } else if (!currentAnnotation && activeAnnotationIdRef.current !== null) {\n            // Clear the active annotation when we're past it, but don't stop playback\n            // Let playback continue until the audio element fires its 'ended' event\n            setActiveAnnotationId(null);\n          }\n        } else {\n          if (activeAnnotationIdRef.current) {\n            const activeAnnotation = currentAnnotations.find(\n              (ann) => ann.id === activeAnnotationIdRef.current\n            );\n            if (activeAnnotation && time >= activeAnnotation.end) {\n              playoutRef.current?.stop();\n              setIsPlaying(false);\n              return;\n            }\n          } else if (currentAnnotation) {\n            setActiveAnnotationId(currentAnnotation.id);\n          }\n        }\n      }\n\n      // Handle automatic scroll - continuously center the playhead\n      if (isAutomaticScrollRef.current && scrollContainerRef.current) {\n        const container = scrollContainerRef.current;\n        const pixelPosition = (time * sampleRate) / samplesPerPixelRef.current;\n        const containerWidth = container.clientWidth;\n\n        // Continuously scroll to keep playhead centered\n        const targetScrollLeft = Math.max(0, pixelPosition - containerWidth / 2);\n        container.scrollLeft = targetScrollLeft;\n      }\n\n      startAnimationFrameLoop(updateTime);\n    };\n\n    startAnimationFrameLoop(updateTime);\n  }, [setActiveAnnotationId, sampleRate, startAnimationFrameLoop]);\n\n  const stopAnimationLoop = stopAnimationFrameLoop;\n\n  // Playback controls\n  const play = useCallback(\n    (startTime?: number) => {\n      if (!playoutRef.current) return;\n\n      const actualStartTime = startTime ?? currentTimeRef.current;\n      playoutRef.current.play(undefined, actualStartTime);\n      setIsPlaying(true);\n      startAnimationLoop();\n    },\n    [startAnimationLoop]\n  );\n\n  const pause = useCallback(() => {\n    if (!playoutRef.current) return;\n\n    playoutRef.current.pause();\n    setIsPlaying(false);\n    stopAnimationLoop();\n    setCurrentTime(playoutRef.current.getCurrentTime());\n  }, [stopAnimationLoop]);\n\n  const stop = useCallback(() => {\n    if (!playoutRef.current) return;\n\n    playoutRef.current.stop();\n    setIsPlaying(false);\n    stopAnimationLoop();\n    currentTimeRef.current = 0;\n    setCurrentTime(0);\n    setActiveAnnotationId(null);\n  }, [stopAnimationLoop, setActiveAnnotationId]);\n\n  const seekTo = useCallback(\n    (time: number) => {\n      const clampedTime = Math.max(0, Math.min(time, duration));\n      currentTimeRef.current = clampedTime;\n      setCurrentTime(clampedTime);\n\n      if (playoutRef.current) {\n        playoutRef.current.seekTo(clampedTime);\n      }\n    },\n    [duration]\n  );\n\n  const setPlaybackRate = useCallback((rate: number) => {\n    const clampedRate = Math.max(0.5, Math.min(2.0, rate));\n    setPlaybackRateState(clampedRate);\n    if (playoutRef.current) {\n      playoutRef.current.setPlaybackRate(clampedRate);\n    }\n  }, []);\n\n  const timeScaleHeight = timescale ? 30 : 0;\n\n  // Context values\n  const animationValue: MediaElementAnimationContextValue = useMemo(\n    () => ({\n      isPlaying,\n      currentTime,\n      currentTimeRef,\n    }),\n    [isPlaying, currentTime]\n  );\n\n  const stateValue: MediaElementStateContextValue = useMemo(\n    () => ({\n      continuousPlay,\n      annotations,\n      activeAnnotationId,\n      playbackRate,\n      isAutomaticScroll,\n    }),\n    [continuousPlay, annotations, activeAnnotationId, playbackRate, isAutomaticScroll]\n  );\n\n  // Stable callback ref for onAnnotationsChange to avoid re-creating controls context\n  const onAnnotationsChangeRef = useRef(onAnnotationsChange);\n  onAnnotationsChangeRef.current = onAnnotationsChange;\n\n  const setAnnotations: React.Dispatch<React.SetStateAction<AnnotationData[]>> = useCallback(\n    (action) => {\n      const updated = typeof action === 'function' ? action(annotationsRef.current) : action;\n      if (!onAnnotationsChangeRef.current) {\n        if (process.env.NODE_ENV !== 'production') {\n          console.warn(\n            'waveform-playlist: setAnnotations was called but no onAnnotationsChange callback is provided. ' +\n              'Annotation edits will not persist. Pass onAnnotationsChange to MediaElementPlaylistProvider to handle annotation updates.'\n          );\n        }\n        return;\n      }\n      onAnnotationsChangeRef.current(updated);\n    },\n    []\n  );\n\n  const controlsValue: MediaElementControlsContextValue = useMemo(\n    () => ({\n      play,\n      pause,\n      stop,\n      seekTo,\n      setPlaybackRate,\n      setContinuousPlay,\n      setAnnotations,\n      setActiveAnnotationId,\n      setAutomaticScroll: (enabled: boolean) => {\n        setIsAutomaticScroll(enabled);\n      },\n      setScrollContainer,\n      scrollContainerRef,\n    }),\n    [\n      play,\n      pause,\n      stop,\n      seekTo,\n      setPlaybackRate,\n      setContinuousPlay,\n      setAnnotations,\n      setActiveAnnotationId,\n      setScrollContainer,\n    ]\n  );\n\n  const dataValue: MediaElementDataContextValue = useMemo(\n    () => ({\n      duration,\n      peaksDataArray,\n      sampleRate,\n      waveHeight,\n      timeScaleHeight,\n      samplesPerPixel,\n      playoutRef,\n      controls,\n      barWidth,\n      barGap,\n      roundedBars,\n      progressBarWidth,\n      fadeIn: track.fadeIn,\n      fadeOut: track.fadeOut,\n    }),\n    [\n      duration,\n      peaksDataArray,\n      sampleRate,\n      waveHeight,\n      timeScaleHeight,\n      samplesPerPixel,\n      controls,\n      barWidth,\n      barGap,\n      roundedBars,\n      progressBarWidth,\n      track.fadeIn,\n      track.fadeOut,\n    ]\n  );\n\n  const mergedTheme = { ...defaultTheme, ...userTheme };\n\n  return (\n    <ThemeProvider theme={mergedTheme}>\n      <MediaElementAnimationContext.Provider value={animationValue}>\n        <MediaElementStateContext.Provider value={stateValue}>\n          <MediaElementControlsContext.Provider value={controlsValue}>\n            <MediaElementDataContext.Provider value={dataValue}>\n              {children}\n            </MediaElementDataContext.Provider>\n          </MediaElementControlsContext.Provider>\n        </MediaElementStateContext.Provider>\n      </MediaElementAnimationContext.Provider>\n    </ThemeProvider>\n  );\n};\n\n// Hooks\nexport const useMediaElementAnimation = () => {\n  const context = useContext(MediaElementAnimationContext);\n  if (!context) {\n    throw new Error('useMediaElementAnimation must be used within MediaElementPlaylistProvider');\n  }\n  return context;\n};\n\nexport const useMediaElementState = () => {\n  const context = useContext(MediaElementStateContext);\n  if (!context) {\n    throw new Error('useMediaElementState must be used within MediaElementPlaylistProvider');\n  }\n  return context;\n};\n\nexport const useMediaElementControls = () => {\n  const context = useContext(MediaElementControlsContext);\n  if (!context) {\n    throw new Error('useMediaElementControls must be used within MediaElementPlaylistProvider');\n  }\n  return context;\n};\n\nexport const useMediaElementData = () => {\n  const context = useContext(MediaElementDataContext);\n  if (!context) {\n    throw new Error('useMediaElementData must be used within MediaElementPlaylistProvider');\n  }\n  return context;\n};\n","import type { MediaElementPlayout } from '@waveform-playlist/media-element-playout';\n\n/** Options for {@link resolveMediaElementPlayout}. */\nexport interface ResolveMediaElementPlayoutOptions {\n  /** Consumer-supplied factory. When present, the default engine is never imported. */\n  createPlayout?: () => MediaElementPlayout;\n  /** Initial playback rate forwarded to the default engine. */\n  playbackRate?: number;\n  /** Whether to preserve pitch on rate change (default engine only). */\n  preservesPitch?: boolean;\n}\n\nconst INSTALL_HINT =\n  '@waveform-playlist/media-element-playout is required for the default MediaElement engine. ' +\n  'Install with: npm install @waveform-playlist/media-element-playout — or pass a custom `createPlayout`.';\n\n/**\n * Resolve a MediaElement playout. With `createPlayout`, returns its result and\n * never imports the default engine. Otherwise dynamically imports\n * `@waveform-playlist/media-element-playout`, rethrowing a friendly install hint\n * (and console.warn-ing the original error) when the optional peer is absent.\n */\nexport async function resolveMediaElementPlayout(\n  opts: ResolveMediaElementPlayoutOptions\n): Promise<MediaElementPlayout> {\n  if (opts.createPlayout) {\n    return opts.createPlayout();\n  }\n\n  let mod: typeof import('@waveform-playlist/media-element-playout');\n  try {\n    mod = await import('@waveform-playlist/media-element-playout');\n  } catch (originalErr) {\n    console.warn(\n      '[waveform-playlist] @waveform-playlist/media-element-playout dynamic import failed: ' +\n        String(originalErr)\n    );\n    throw new Error(INSTALL_HINT);\n  }\n\n  return new mod.MediaElementPlayout({\n    playbackRate: opts.playbackRate,\n    preservesPitch: opts.preservesPitch,\n  });\n}\n","import React, { useCallback } from 'react';\nimport { BaseControlButton } from '@waveform-playlist/ui-components';\nimport {\n  usePlaybackAnimation,\n  usePlaylistState,\n  usePlaylistControls,\n  usePlaylistData,\n} from '../WaveformPlaylistContext';\n\nexport const PlayButton: React.FC<{ className?: string }> = ({ className }) => {\n  const { isPlaying, currentTimeRef } = usePlaybackAnimation();\n  const { selectionStart, selectionEnd, isLoopEnabled } = usePlaylistState();\n  const { play } = usePlaylistControls();\n\n  const handleClick = async () => {\n    const hasSelection = selectionStart !== selectionEnd && selectionEnd > selectionStart;\n\n    if (hasSelection && !isLoopEnabled) {\n      // Without loop: Play selection region only, then stop\n      const duration = selectionEnd - selectionStart;\n      await play(selectionStart, duration);\n    } else {\n      // With loop or no selection: Play from current cursor position.\n      // Transport loop handles boundaries when loop is enabled.\n      await play(currentTimeRef.current ?? 0);\n    }\n  };\n\n  return (\n    <BaseControlButton onClick={handleClick} disabled={isPlaying} className={className}>\n      Play\n    </BaseControlButton>\n  );\n};\n\nexport const PauseButton: React.FC<{ className?: string }> = ({ className }) => {\n  const { isPlaying } = usePlaybackAnimation();\n  const { pause } = usePlaylistControls();\n\n  return (\n    <BaseControlButton onClick={pause} disabled={!isPlaying} className={className}>\n      Pause\n    </BaseControlButton>\n  );\n};\n\nexport const StopButton: React.FC<{ className?: string }> = ({ className }) => {\n  const { isPlaying } = usePlaybackAnimation();\n  const { stop } = usePlaylistControls();\n\n  return (\n    <BaseControlButton onClick={stop} disabled={!isPlaying} className={className}>\n      Stop\n    </BaseControlButton>\n  );\n};\n\nexport const RewindButton: React.FC<{ className?: string }> = ({ className }) => {\n  const { isPlaying } = usePlaybackAnimation();\n  const { play, setCurrentTime } = usePlaylistControls();\n\n  const handleClick = () => {\n    setCurrentTime(0);\n\n    if (isPlaying) {\n      play(0);\n    }\n  };\n\n  return (\n    <BaseControlButton onClick={handleClick} className={className}>\n      Rewind\n    </BaseControlButton>\n  );\n};\n\nexport const FastForwardButton: React.FC<{ className?: string }> = ({ className }) => {\n  const { isPlaying } = usePlaybackAnimation();\n  const { play, setCurrentTime } = usePlaylistControls();\n  const { duration } = usePlaylistData();\n\n  const handleClick = () => {\n    setCurrentTime(duration);\n\n    if (isPlaying) {\n      play(duration);\n    }\n  };\n\n  return (\n    <BaseControlButton onClick={handleClick} className={className}>\n      Fast Forward\n    </BaseControlButton>\n  );\n};\n\nexport const SkipBackwardButton: React.FC<{ skipAmount?: number; className?: string }> = ({\n  skipAmount = 5,\n  className,\n}) => {\n  const { currentTimeRef, isPlaying } = usePlaybackAnimation();\n  const { play, setCurrentTime } = usePlaylistControls();\n\n  const handleClick = () => {\n    const newTime = Math.max(0, (currentTimeRef.current ?? 0) - skipAmount);\n    setCurrentTime(newTime);\n\n    if (isPlaying) {\n      play(newTime);\n    }\n  };\n\n  return (\n    <BaseControlButton onClick={handleClick} className={className}>\n      Skip Backward\n    </BaseControlButton>\n  );\n};\n\nexport const SkipForwardButton: React.FC<{ skipAmount?: number; className?: string }> = ({\n  skipAmount = 5,\n  className,\n}) => {\n  const { currentTimeRef, isPlaying } = usePlaybackAnimation();\n  const { play, setCurrentTime } = usePlaylistControls();\n  const { duration } = usePlaylistData();\n\n  const handleClick = () => {\n    const newTime = Math.min(duration, (currentTimeRef.current ?? 0) + skipAmount);\n    setCurrentTime(newTime);\n\n    if (isPlaying) {\n      play(newTime);\n    }\n  };\n\n  return (\n    <BaseControlButton onClick={handleClick} className={className}>\n      Skip Forward\n    </BaseControlButton>\n  );\n};\n\nexport const LoopButton: React.FC<{ className?: string }> = ({ className }) => {\n  const { isLoopEnabled, loopStart, loopEnd } = usePlaylistState();\n  const { setLoopEnabled, setLoopRegion } = usePlaylistControls();\n  const { duration } = usePlaylistData();\n\n  const hasValidLoopRegion = loopStart !== loopEnd && loopEnd > loopStart;\n\n  const handleClick = () => {\n    if (!isLoopEnabled && !hasValidLoopRegion) {\n      // Create a default loop region when enabling loop without one\n      // Default to first 10 seconds or 25% of duration, whichever is smaller\n      const defaultEnd = Math.min(10, duration * 0.25);\n      setLoopRegion(0, Math.max(1, defaultEnd)); // At least 1 second\n    }\n    setLoopEnabled(!isLoopEnabled);\n  };\n\n  return (\n    <BaseControlButton\n      onClick={handleClick}\n      className={className}\n      title={isLoopEnabled ? 'Disable loop' : 'Enable loop'}\n    >\n      {isLoopEnabled ? 'Loop On' : 'Loop Off'}\n    </BaseControlButton>\n  );\n};\n\nexport const SetLoopRegionButton: React.FC<{ className?: string }> = ({ className }) => {\n  const { selectionStart, selectionEnd, loopStart, loopEnd } = usePlaylistState();\n  const { setLoopRegionFromSelection, clearLoopRegion } = usePlaylistControls();\n\n  const hasValidSelection = selectionStart !== selectionEnd && selectionEnd > selectionStart;\n  const hasLoopRegion = loopStart !== loopEnd && loopEnd > loopStart;\n\n  const handleClick = () => {\n    if (hasLoopRegion) {\n      clearLoopRegion();\n    } else {\n      setLoopRegionFromSelection();\n    }\n  };\n\n  return (\n    <BaseControlButton\n      onClick={handleClick}\n      disabled={!hasValidSelection && !hasLoopRegion}\n      className={className}\n      title={\n        hasLoopRegion\n          ? 'Clear loop region'\n          : hasValidSelection\n            ? 'Set loop region from selection'\n            : 'Create a selection first'\n      }\n    >\n      {hasLoopRegion ? 'Clear Loop' : 'Set Loop'}\n    </BaseControlButton>\n  );\n};\n\nexport interface ClearAllButtonProps {\n  onClearAll: () => void;\n  label?: string;\n  className?: string;\n}\n\nexport const ClearAllButton: React.FC<ClearAllButtonProps> = ({\n  onClearAll,\n  label = 'Clear All',\n  className,\n}) => {\n  const { stop } = usePlaylistControls();\n\n  const handleClick = useCallback(() => {\n    stop();\n    onClearAll();\n  }, [stop, onClearAll]);\n\n  return (\n    <BaseControlButton onClick={handleClick} className={className} title=\"Remove all tracks\">\n      {label}\n    </BaseControlButton>\n  );\n};\n","import React from 'react';\nimport { BaseControlButton } from '@waveform-playlist/ui-components';\nimport { usePlaylistControls, usePlaylistData } from '../WaveformPlaylistContext';\n\nexport const ZoomInButton: React.FC<{ className?: string; disabled?: boolean }> = ({\n  className,\n  disabled,\n}) => {\n  const { zoomIn } = usePlaylistControls();\n  const { canZoomIn } = usePlaylistData();\n\n  return (\n    <BaseControlButton onClick={zoomIn} disabled={disabled || !canZoomIn} className={className}>\n      Zoom In\n    </BaseControlButton>\n  );\n};\n\nexport const ZoomOutButton: React.FC<{ className?: string; disabled?: boolean }> = ({\n  className,\n  disabled,\n}) => {\n  const { zoomOut } = usePlaylistControls();\n  const { canZoomOut } = usePlaylistData();\n\n  return (\n    <BaseControlButton onClick={zoomOut} disabled={disabled || !canZoomOut} className={className}>\n      Zoom Out\n    </BaseControlButton>\n  );\n};\n","import React, { useRef, useEffect } from 'react';\nimport {\n  MasterVolumeControl as BaseMasterVolumeControl,\n  TimeFormatSelect as BaseTimeFormatSelect,\n  AutomaticScrollCheckbox as BaseAutomaticScrollCheckbox,\n  SelectionTimeInputs as BaseSelectionTimeInputs,\n  formatTime,\n} from '@waveform-playlist/ui-components';\nimport styled from 'styled-components';\nimport {\n  usePlaybackAnimation,\n  usePlaylistState,\n  usePlaylistControls,\n  usePlaylistData,\n} from '../WaveformPlaylistContext';\n\n/**\n * Master volume control that uses the playlist context\n */\nexport const MasterVolumeControl: React.FC<{ className?: string }> = ({ className }) => {\n  const { masterVolume } = usePlaylistData();\n  const { setMasterVolume } = usePlaylistControls();\n\n  return (\n    <BaseMasterVolumeControl\n      volume={masterVolume}\n      onChange={setMasterVolume}\n      className={className}\n    />\n  );\n};\n\n/**\n * Time format selector that uses the playlist context\n */\nexport const TimeFormatSelect: React.FC<{ className?: string }> = ({ className }) => {\n  const { timeFormat } = usePlaylistData();\n  const { setTimeFormat } = usePlaylistControls();\n\n  return <BaseTimeFormatSelect value={timeFormat} onChange={setTimeFormat} className={className} />;\n};\n\nconst PositionDisplay = styled.span`\n  font-family: 'Courier New', Monaco, monospace;\n  font-size: 1rem;\n  font-weight: 600;\n  color: ${(props) => props.theme?.textColor || '#333'};\n  user-select: none;\n`;\n\n/**\n * Audio position display that uses the playlist context.\n * Updates via the shared animation frame registry — no own rAF loop.\n * Direct DOM manipulation avoids React re-renders.\n */\nexport const AudioPosition: React.FC<{ className?: string }> = ({ className }) => {\n  const timeRef = useRef<HTMLSpanElement>(null);\n  const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } =\n    usePlaybackAnimation();\n  const { timeFormat: format } = usePlaylistData();\n\n  // Register per-frame callback during playback — uses raw time for display\n  useEffect(() => {\n    const id = 'audio-position';\n    if (isPlaying) {\n      registerFrameCallback(id, ({ time }) => {\n        if (timeRef.current) {\n          timeRef.current.textContent = formatTime(time, format);\n        }\n      });\n    }\n    return () => unregisterFrameCallback(id);\n  }, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);\n\n  // Update when stopped (for seeks)\n  useEffect(() => {\n    if (!isPlaying && timeRef.current) {\n      timeRef.current.textContent = formatTime(currentTimeRef.current ?? 0, format);\n    }\n  });\n\n  return (\n    <PositionDisplay ref={timeRef} className={className} aria-label=\"Audio position\">\n      {formatTime(currentTimeRef.current ?? 0, format)}\n    </PositionDisplay>\n  );\n};\n\n/**\n * Selection time inputs that use the playlist context\n */\nexport const SelectionTimeInputs: React.FC<{ className?: string }> = ({ className }) => {\n  const { selectionStart, selectionEnd } = usePlaylistState();\n  const { setSelection } = usePlaylistControls();\n\n  return (\n    <BaseSelectionTimeInputs\n      selectionStart={selectionStart}\n      selectionEnd={selectionEnd}\n      onSelectionChange={setSelection}\n      className={className}\n    />\n  );\n};\n\n/**\n * Automatic scroll checkbox that uses the playlist context\n * Uses split contexts to avoid re-rendering during animation\n */\nexport const AutomaticScrollCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n  const { isAutomaticScroll } = usePlaylistState();\n  const { setAutomaticScroll } = usePlaylistControls();\n\n  return (\n    <BaseAutomaticScrollCheckbox\n      checked={isAutomaticScroll}\n      onChange={setAutomaticScroll}\n      className={className}\n    />\n  );\n};\n","import { createContext, useContext } from 'react';\nimport type {\n  AnnotationData,\n  AnnotationAction,\n  AnnotationActionOptions,\n  RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\n\n/**\n * Props the browser package passes to the AnnotationText component.\n * Mirrors what PlaylistAnnotationList and MediaElementAnnotationList actually use.\n */\nexport interface AnnotationTextIntegrationProps {\n  annotations: AnnotationData[];\n  activeAnnotationId?: string;\n  shouldScrollToActive?: boolean;\n  scrollActivePosition?: ScrollLogicalPosition;\n  scrollActiveContainer?: 'nearest' | 'all';\n  editable?: boolean;\n  controls?: AnnotationAction[];\n  annotationListConfig?: AnnotationActionOptions;\n  height?: number;\n  onAnnotationUpdate?: (updatedAnnotations: AnnotationData[]) => void;\n  renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n}\n\n/**\n * Props the browser package passes to the AnnotationBox component.\n * Mirrors what PlaylistVisualization and MediaElementPlaylist actually use.\n */\nexport interface AnnotationBoxIntegrationProps {\n  annotationId: string;\n  annotationIndex: number;\n  startPosition: number;\n  endPosition: number;\n  label?: string;\n  color?: string;\n  isActive?: boolean;\n  onClick?: () => void;\n  editable?: boolean;\n}\n\n/**\n * Props the browser package passes to the AnnotationBoxesWrapper component.\n * Mirrors what PlaylistVisualization and MediaElementPlaylist actually use.\n */\nexport interface AnnotationBoxesWrapperIntegrationProps {\n  children?: React.ReactNode;\n  height?: number;\n  width?: number;\n}\n\n/**\n * Interface for annotation integration provided by @waveform-playlist/annotations.\n *\n * The browser package defines what it needs, and the optional annotations package\n * provides it via <AnnotationProvider>.\n */\nexport interface AnnotationIntegration {\n  // Parser functions\n  parseAeneas: (data: unknown) => AnnotationData;\n  serializeAeneas: (annotation: AnnotationData) => unknown;\n\n  // Visualization components (typed with the props the browser package actually passes)\n  AnnotationText: React.ComponentType<AnnotationTextIntegrationProps>;\n  AnnotationBox: React.ComponentType<AnnotationBoxIntegrationProps>;\n  AnnotationBoxesWrapper: React.ComponentType<AnnotationBoxesWrapperIntegrationProps>;\n\n  // Control components\n  ContinuousPlayCheckbox: React.ComponentType<{\n    checked: boolean;\n    onChange: (checked: boolean) => void;\n    className?: string;\n  }>;\n  LinkEndpointsCheckbox: React.ComponentType<{\n    checked: boolean;\n    onChange: (checked: boolean) => void;\n    className?: string;\n  }>;\n  EditableCheckbox: React.ComponentType<{\n    checked: boolean;\n    onChange: (checked: boolean) => void;\n    className?: string;\n  }>;\n  DownloadAnnotationsButton: React.ComponentType<{\n    annotations: AnnotationData[];\n    filename?: string;\n    className?: string;\n  }>;\n}\n\nexport const AnnotationIntegrationContext = createContext<AnnotationIntegration | null>(null);\n\nexport const AnnotationIntegrationProvider = AnnotationIntegrationContext.Provider;\n\n/**\n * Hook to access annotation integration provided by @waveform-playlist/annotations.\n * Throws if used without <AnnotationProvider> wrapping the component tree.\n *\n * Follows the Kent C. Dodds pattern:\n * https://kentcdodds.com/blog/how-to-use-react-context-effectively\n */\nexport function useAnnotationIntegration(): AnnotationIntegration {\n  const context = useContext(AnnotationIntegrationContext);\n  if (!context) {\n    throw new Error(\n      'useAnnotationIntegration must be used within <AnnotationProvider>. ' +\n        'Install @waveform-playlist/annotations and wrap your app with <AnnotationProvider>. ' +\n        'See: https://waveform-playlist.naomiaro.com/docs/guides/annotations'\n    );\n  }\n  return context;\n}\n","import React from 'react';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\n\n/**\n * Continuous play checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const ContinuousPlayCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n  const { ContinuousPlayCheckbox: Base } = useAnnotationIntegration();\n  const { continuousPlay } = usePlaylistState();\n  const { setContinuousPlay } = usePlaylistControls();\n\n  return <Base checked={continuousPlay} onChange={setContinuousPlay} className={className} />;\n};\n\n/**\n * Link endpoints checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const LinkEndpointsCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n  const { LinkEndpointsCheckbox: Base } = useAnnotationIntegration();\n  const { linkEndpoints } = usePlaylistState();\n  const { setLinkEndpoints } = usePlaylistControls();\n\n  return <Base checked={linkEndpoints} onChange={setLinkEndpoints} className={className} />;\n};\n\n/**\n * Editable annotations checkbox that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const EditableCheckbox: React.FC<{ className?: string }> = ({ className }) => {\n  const { EditableCheckbox: Base } = useAnnotationIntegration();\n  const { annotationsEditable } = usePlaylistState();\n  const { setAnnotationsEditable } = usePlaylistControls();\n\n  return (\n    <Base checked={annotationsEditable} onChange={setAnnotationsEditable} className={className} />\n  );\n};\n\n/**\n * Download annotations button that uses the playlist context.\n * Must be used within <AnnotationProvider>.\n */\nexport const DownloadAnnotationsButton: React.FC<{ filename?: string; className?: string }> = ({\n  filename,\n  className,\n}) => {\n  const { DownloadAnnotationsButton: Base } = useAnnotationIntegration();\n  const { annotations } = usePlaylistState();\n\n  return <Base annotations={annotations} filename={filename} className={className} />;\n};\n","import { createContext, useContext } from 'react';\n\nconst ClipInteractionContext = createContext(false);\n\nexport const ClipInteractionContextProvider = ClipInteractionContext.Provider;\n\nexport function useClipInteractionEnabled(): boolean {\n  return useContext(ClipInteractionContext);\n}\n","import React, {\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n  useMemo,\n  type ReactNode,\n  useCallback,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport styled from 'styled-components';\nimport {\n  Playlist,\n  Track as TrackComponent,\n  Clip,\n  Selection,\n  TimescaleLoopRegion,\n  PlaylistInfoContext,\n  DevicePixelRatioProvider,\n  SmartScale,\n  CloseButton,\n  Controls,\n  Header,\n  Button,\n  ButtonGroup,\n  Slider,\n  SliderWrapper,\n  VolumeDownIcon,\n  VolumeUpIcon,\n  TrackMenu,\n  useTheme,\n  waveformColorToCss,\n  type RenderPlayheadFunction,\n  SpectrogramLabels,\n  CLIP_HEADER_HEIGHT,\n  ReorderRail,\n} from '@waveform-playlist/ui-components';\nimport { SortableTrackControls } from './SortableTrackControls';\nimport { AnnotationIntegrationContext } from '../AnnotationIntegrationContext';\nimport {\n  usePlaybackAnimation,\n  usePlaylistState,\n  usePlaylistControls,\n  usePlaylistData,\n  type ClipPeaks,\n  type TrackState,\n} from '../WaveformPlaylistContext';\nimport { applyTrackOrderPreview, computeTrackLayout } from '../utils/trackOrderPreview';\nimport { useTrailingActive } from '../hooks/useTrailingActive';\nimport { resolveRecordingOffsetSamples, type Peaks } from '@waveform-playlist/core';\nimport { AnimatedPlayhead } from './AnimatedPlayhead';\nimport { ChannelWithProgress } from './ChannelWithProgress';\nimport type { SpectrogramConfig } from '@waveform-playlist/core';\nimport type { AnnotationAction } from '@waveform-playlist/core';\nimport type { ClipTrack, RenderMode } from '@waveform-playlist/core';\nimport type { AnnotationData, GetAnnotationBoxLabelFn } from '../types/annotations';\nimport {\n  SpectrogramIntegrationContext,\n  type SpectrogramIntegration,\n} from '../SpectrogramIntegrationContext';\n\n// Default duration in seconds for empty tracks (used for recording workflow)\nconst DEFAULT_EMPTY_TRACK_DURATION = 60;\n\n/** Drop-shadow for the floating (fully opaque) drag-source controls row. */\nconst DRAG_SOURCE_SHADOW = '0 4px 12px rgba(0, 0, 0, 0.35)';\n\ninterface PositionedRowProps {\n  readonly $top: number;\n  readonly $animate: boolean;\n}\n\n/**\n * Shared relative container for absolutely-positioned track rows. Explicit\n * height because absolute children don't size their parent.\n */\nconst TracksLayout = styled.div.attrs<{ $height: number }>((props) => ({\n  style: { height: `${props.$height}px` },\n}))<{ $height: number }>`\n  position: relative;\n`;\n\n/**\n * Positions one waveform track row from the shared layout map. Positioned\n * via `top`, NOT `translateY` — a transform (even translateY(0)) creates a\n * stacking context on every row, which flattens the clips' z-index: 105\n * beneath the ClickOverlay (z-index: 1) and blocks all clip interactions\n * (header drag, boundary trim, split). `top` moves the row without a\n * stacking context, so clip z-indexes keep competing globally as before.\n * Transition only while a track drag is active (+trailing window) so\n * unrelated layout changes stay instant.\n */\nconst TrackRowPositioner = styled.div.attrs<PositionedRowProps>((props) => ({\n  style: { top: `${props.$top}px` },\n}))<PositionedRowProps>`\n  position: absolute;\n  left: 0;\n  right: 0;\n  ${(props) => props.$animate && 'transition: top 150ms ease;'}\n  @media (prefers-reduced-motion: reduce) {\n    transition: none;\n  }\n`;\n\ninterface ControlSlotProps {\n  readonly $height: number;\n  readonly $top: number;\n  readonly $animate: boolean;\n  readonly $isSelected?: boolean;\n}\n\n/**\n * Height-synced container for each track's controls in the ControlsColumn.\n * Uses the same height formula as Track: waveHeight * numChannels + clipHeaderHeight.\n */\nconst ControlSlot = styled.div.attrs<ControlSlotProps>((props) => ({\n  style: {\n    height: `${props.$height}px`,\n    top: `${props.$top}px`,\n  },\n}))<ControlSlotProps>`\n  position: absolute;\n  left: 0;\n  right: 0;\n  overflow: hidden;\n  pointer-events: auto;\n  background: ${(props) => props.theme.surfaceColor};\n  transition: background 0.15s ease-in-out;\n  ${(props) => props.$animate && 'transition: top 150ms ease, background 0.15s ease-in-out;'}\n  @media (prefers-reduced-motion: reduce) {\n    transition: background 0.15s ease-in-out;\n  }\n  ${(props) => props.$isSelected && `background: ${props.theme.selectedTrackControlsBackground};`}\n`;\n\nexport interface PlaylistVisualizationProps {\n  renderTrackControls?: (trackIndex: number) => ReactNode;\n  renderTick?: (label: string, pixelPosition: number) => ReactNode;\n  /** Custom playhead render function. Receives position (pixels) and color from theme. */\n  renderPlayhead?: RenderPlayheadFunction;\n  annotationControls?: AnnotationAction[];\n  /**\n   * Custom function to generate the label shown on annotation boxes in the waveform.\n   * Receives the annotation data and its index, returns a string label.\n   * Default: annotation.id\n   */\n  getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n  className?: string;\n  showClipHeaders?: boolean;\n  interactiveClips?: boolean;\n  showFades?: boolean;\n  /**\n   * Enable mobile-optimized touch interactions.\n   * When true, increases touch target sizes for clip boundaries.\n   * Use with useDragSensors({ touchOptimized: true }) for best results.\n   */\n  touchOptimized?: boolean;\n  /** Callback when a track's close button is clicked. Only renders close button when provided. */\n  onRemoveTrack?: (trackIndex: number) => void;\n  // Live recording state for real-time waveform preview\n  recordingState?: {\n    isRecording: boolean;\n    trackId: string;\n    startSample: number;\n    durationSamples: number;\n    peaks: (Int8Array | Int16Array)[];\n    bits: 8 | 16;\n    /**\n     * Latency offset (seconds) to skip in the live preview. Absolute replacement\n     * for the auto-computed outputLatency + lookAhead value. Pass the same value\n     * given to useIntegratedRecording so preview and finalized clip match.\n     */\n    latencyOffset?: number;\n  };\n  /** Enable vertical track reordering: a drag grip + move up/down buttons on\n   *  each default track control panel. Drag requires ClipInteractionProvider\n   *  (the ambient DragDropProvider); the buttons work regardless. Default: false. */\n  trackReordering?: boolean;\n}\n\n/**\n * Wrapper that isolates the custom playhead's hooks from PlaylistVisualization.\n * Calling renderPlayhead() directly would merge its hooks into the parent,\n * causing \"Rendered more hooks\" errors if renderPlayhead is conditionally provided.\n */\nconst CustomPlayhead: React.FC<{\n  renderPlayhead: RenderPlayheadFunction;\n  color: string;\n  samplesPerPixel: number;\n  sampleRate: number;\n}> = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {\n  const {\n    isPlaying,\n    currentTimeRef,\n    visualTimeRef,\n    playbackStartTimeRef,\n    audioStartPositionRef,\n    getPlaybackTime,\n    getAudioContextTime,\n  } = usePlaybackAnimation();\n\n  // Use visualTimeRef so the initial position matches the audible output\n  // (the playhead's rAF loop also reads visualTimeRef for in-sync animation).\n  const visualTime = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n\n  return renderPlayhead({\n    position: (visualTime * sampleRate) / samplesPerPixel,\n    color,\n    isPlaying,\n    currentTimeRef,\n    visualTimeRef,\n    playbackStartTimeRef,\n    audioStartPositionRef,\n    samplesPerPixel,\n    sampleRate,\n    controlsOffset: 0,\n    getAudioContextTime,\n    getPlaybackTime,\n  }) as React.ReactElement;\n};\n\n/** Compute the maximum channel count for a track, considering both clip peaks and live recording. */\nfunction getTrackChannelCount(\n  trackClipPeaks: ClipPeaks[],\n  recordingState: PlaylistVisualizationProps['recordingState'],\n  trackId: string | undefined,\n  mono: boolean\n): number {\n  const clipChannels =\n    trackClipPeaks.length > 0\n      ? Math.max(1, ...trackClipPeaks.map((clip) => clip.peaks.data.length))\n      : 1;\n  const recordingChannels =\n    recordingState?.isRecording && recordingState.trackId === trackId\n      ? mono\n        ? 1\n        : recordingState.peaks.length\n      : 0;\n  return Math.max(clipChannels, recordingChannels);\n}\n\ninterface DefaultTrackControlsProps {\n  trackIndex: number;\n  track: ClipTrack;\n  trackState: TrackState;\n  effectiveRenderMode: RenderMode;\n  selectTrack: (trackIndex: number) => void;\n  onRemoveTrack?: (trackIndex: number) => void;\n  setTrackMute: (trackIndex: number, muted: boolean) => void;\n  setTrackSolo: (trackIndex: number, soloed: boolean) => void;\n  setTrackVolume: (trackIndex: number, volume: number) => void;\n  setTrackPan: (trackIndex: number, pan: number) => void;\n  spectrogram: SpectrogramIntegration | null;\n  setSettingsModalTrackId: (id: string | null) => void;\n  trackReordering: boolean;\n  reorderTrack: (trackId: string, toIndex: number) => void;\n  trackCount: number;\n  /** Attach to the drag grip element (the `handleRef` from SortableTrackControls). */\n  gripRef?: (element: Element | null) => void;\n}\n\n/**\n * Default track control panel: name/close header, mute/solo, volume/pan sliders,\n * plus an optional drag grip and move up/down buttons when `trackReordering` is\n * enabled. Extracted into its own component (used both directly and wrapped in\n * SortableTrackControls) so the trackReordering branch doesn't change the hook\n * count of the shared JSX.\n */\nfunction DefaultTrackControls({\n  trackIndex,\n  track,\n  trackState,\n  effectiveRenderMode,\n  selectTrack,\n  onRemoveTrack,\n  setTrackMute,\n  setTrackSolo,\n  setTrackVolume,\n  setTrackPan,\n  spectrogram,\n  setSettingsModalTrackId,\n  trackReordering,\n  reorderTrack,\n  trackCount,\n  gripRef,\n}: DefaultTrackControlsProps) {\n  return (\n    <Controls\n      onClick={() => selectTrack(trackIndex)}\n      style={\n        trackReordering ? { position: 'relative', paddingLeft: 18, paddingRight: 18 } : undefined\n      }\n    >\n      <Header style={{ justifyContent: 'center', position: 'relative' }}>\n        {onRemoveTrack && (\n          <CloseButton\n            onClick={(e) => {\n              e.stopPropagation();\n              onRemoveTrack(trackIndex);\n            }}\n          />\n        )}\n        <span\n          style={{\n            overflow: 'hidden',\n            textOverflow: 'ellipsis',\n            whiteSpace: 'nowrap',\n            padding: '0 24px',\n            display: 'block',\n          }}\n        >\n          {trackState.name || `Track ${trackIndex + 1}`}\n        </span>\n        {spectrogram?.renderMenuItems && (\n          <span style={{ position: 'absolute', right: 0, top: 0 }}>\n            <TrackMenu\n              items={(onClose) =>\n                spectrogram.renderMenuItems!({\n                  renderMode: effectiveRenderMode,\n                  onRenderModeChange: (mode) => spectrogram.setTrackRenderMode(track.id, mode),\n                  onOpenSettings: () => setSettingsModalTrackId(track.id),\n                  onClose,\n                })\n              }\n            />\n          </span>\n        )}\n      </Header>\n      <ButtonGroup>\n        <Button\n          $variant={trackState.muted ? 'danger' : 'outline'}\n          onClick={() => setTrackMute(trackIndex, !trackState.muted)}\n        >\n          Mute\n        </Button>\n        <Button\n          $variant={trackState.soloed ? 'info' : 'outline'}\n          onClick={() => setTrackSolo(trackIndex, !trackState.soloed)}\n        >\n          Solo\n        </Button>\n      </ButtonGroup>\n      <SliderWrapper>\n        <VolumeDownIcon />\n        <Slider\n          min=\"0\"\n          max=\"1\"\n          step=\"0.01\"\n          value={trackState.volume}\n          onChange={(e) => setTrackVolume(trackIndex, parseFloat(e.target.value))}\n        />\n        <VolumeUpIcon />\n      </SliderWrapper>\n      <SliderWrapper>\n        <span>L</span>\n        <Slider\n          min=\"-1\"\n          max=\"1\"\n          step=\"0.01\"\n          value={trackState.pan}\n          onChange={(e) => setTrackPan(trackIndex, parseFloat(e.target.value))}\n        />\n        <span>R</span>\n      </SliderWrapper>\n      {trackReordering && (\n        <ReorderRail\n          style={{ position: 'absolute', right: 2, top: '50%', transform: 'translateY(-50%)' }}\n          gripRef={gripRef as React.Ref<HTMLButtonElement>}\n          onGripClick={(e) => e.stopPropagation()}\n          upDisabled={trackIndex === 0}\n          downDisabled={trackIndex === trackCount - 1}\n          onMoveUp={(e) => {\n            e.stopPropagation();\n            reorderTrack(track.id, trackIndex - 1);\n          }}\n          onMoveDown={(e) => {\n            e.stopPropagation();\n            reorderTrack(track.id, trackIndex + 1);\n          }}\n        />\n      )}\n    </Controls>\n  );\n}\n\n/**\n * Standalone playlist visualization component (WebAudio version).\n *\n * Renders the waveform tracks, timescale, annotations boxes, selection,\n * playhead, loop regions, and track controls — everything that lives\n * inside <Playlist> plus wrapping providers.\n *\n * Does NOT render AnnotationText (the annotation list below the waveform).\n * Pair with PlaylistAnnotationList for a full annotation editing UI.\n */\nexport const PlaylistVisualization: React.FC<PlaylistVisualizationProps> = ({\n  renderTrackControls,\n  renderTick,\n  renderPlayhead,\n  annotationControls: _annotationControls,\n  getAnnotationBoxLabel,\n  className,\n  showClipHeaders = false,\n  interactiveClips = false,\n  showFades = false,\n  touchOptimized = false,\n  onRemoveTrack,\n  recordingState,\n  trackReordering = false,\n}) => {\n  const theme = useTheme() as import('@waveform-playlist/ui-components').WaveformPlaylistTheme;\n\n  const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();\n  const {\n    selectionStart,\n    selectionEnd,\n    annotations,\n    activeAnnotationId,\n    annotationsEditable,\n    linkEndpoints: _linkEndpoints,\n    continuousPlay,\n    selectedTrackId,\n    loopStart,\n    loopEnd,\n    isLoopEnabled,\n    indefinitePlayback,\n    fillViewport,\n  } = usePlaylistState();\n  const annotationIntegration = useContext(AnnotationIntegrationContext);\n  const {\n    setAnnotations: _setAnnotations,\n    setActiveAnnotationId,\n    setTrackMute,\n    setTrackSolo,\n    setTrackVolume,\n    setTrackPan,\n    setSelection,\n    play,\n    setScrollContainer,\n    setSelectedTrackId,\n    setCurrentTime,\n    setLoopRegion,\n    setRecordingActive,\n    reorderTrack,\n  } = usePlaylistControls();\n  const {\n    peaksDataArray,\n    trackStates,\n    tracks,\n    duration,\n    samplesPerPixel,\n    sampleRate,\n    waveHeight,\n    timeScaleHeight,\n    controls,\n    barWidth,\n    barGap,\n    roundedBars,\n    isReady,\n    trackReorderEpoch,\n    mono,\n    trackDragPreview,\n  } = usePlaylistData();\n\n  // Optional spectrogram integration (only available when SpectrogramProvider is present)\n  const spectrogram = useContext(SpectrogramIntegrationContext);\n\n  // Sync the recording session (and its armed track) to the provider so the\n  // armed track's existing content is transiently muted for the take —\n  // punch-in replaces whatever it overlaps (#579/#589). The cleanup releases\n  // the session on recording end AND on unmount, restoring the track's\n  // previous mute state. Consumers that start playback in the same handler\n  // as the recording should also call setRecordingActive eagerly so the\n  // doomed material never blips before this effect commits.\n  const recordingActive = !!recordingState?.isRecording;\n  const armedTrackId = recordingState?.trackId ?? null;\n  useEffect(() => {\n    if (!recordingActive) return undefined;\n    setRecordingActive(true, armedTrackId);\n    return () => setRecordingActive(false);\n  }, [recordingActive, armedTrackId, setRecordingActive]);\n\n  // Per-track spectrogram rendering helpers (memoized) — only computed when spectrogram is available\n  const perTrackSpectrogramHelpers = useMemo(() => {\n    if (!spectrogram)\n      return new Map<\n        string,\n        {\n          colorLUT: Uint8Array;\n          frequencyScaleFn: (f: number, minF: number, maxF: number) => number;\n          config: SpectrogramConfig | undefined;\n        }\n      >();\n    const helpers = new Map<\n      string,\n      {\n        colorLUT: Uint8Array;\n        frequencyScaleFn: (f: number, minF: number, maxF: number) => number;\n        config: SpectrogramConfig | undefined;\n      }\n    >();\n    tracks.forEach((track) => {\n      const mode =\n        spectrogram.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n        track.renderMode ??\n        'waveform';\n      if (mode === 'waveform') return;\n      const overrides = spectrogram.trackSpectrogramOverrides.get(track.id);\n      const cm =\n        overrides?.colorMap ??\n        track.spectrogramColorMap ??\n        spectrogram.spectrogramColorMap ??\n        'viridis';\n      const cfg = overrides?.config ?? track.spectrogramConfig ?? spectrogram.spectrogramConfig;\n      helpers.set(track.id, {\n        colorLUT: spectrogram.getColorMap(cm),\n        frequencyScaleFn: spectrogram.getFrequencyScale(cfg?.frequencyScale ?? 'mel'),\n        config: cfg,\n      });\n    });\n    return helpers;\n  }, [tracks, spectrogram]);\n\n  // State for spectrogram settings modal\n  const [settingsModalTrackId, setSettingsModalTrackId] = useState<string | null>(null);\n\n  const [isSelecting, setIsSelecting] = useState(false);\n  const mouseDownTimeRef = useRef<number>(0);\n\n  const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n\n  const handleScrollContainerRef = useCallback(\n    (element: HTMLDivElement | null) => {\n      scrollContainerRef.current = element;\n      setScrollContainer(element);\n    },\n    [setScrollContainer]\n  );\n\n  // Calculate dimensions — derive duration directly from tracks prop to prevent width\n  // shift. The `duration` state is set in an effect and lags tracks by at least one render.\n  const tracksMaxDuration = tracks.reduce((max, track) => {\n    return track.clips.reduce((clipMax, clip) => {\n      const end = (clip.startSample + clip.durationSamples) / clip.sampleRate;\n      return Math.max(clipMax, end);\n    }, max);\n  }, 0);\n  let displayDuration =\n    tracksMaxDuration > 0\n      ? tracksMaxDuration\n      : duration > 0\n        ? duration\n        : DEFAULT_EMPTY_TRACK_DURATION;\n\n  // Fill the visible scroll container when requested — indefinitePlayback\n  // implies it (endless playback needs an endless-looking timeline);\n  // fillViewport requests the layout alone.\n  if (indefinitePlayback || fillViewport) {\n    const containerWidth = scrollContainerRef.current?.clientWidth ?? 0;\n    const minContainerDuration = (containerWidth * samplesPerPixel) / sampleRate;\n    displayDuration = Math.max(displayDuration, minContainerDuration);\n  }\n\n  if (recordingState?.isRecording) {\n    const recordingEndSample = recordingState.startSample + recordingState.durationSamples;\n    const recordingEndTime = recordingEndSample / sampleRate;\n    displayDuration = Math.max(displayDuration, recordingEndTime + 10);\n  }\n\n  const tracksFullWidth = Math.floor((displayDuration * sampleRate) / samplesPerPixel);\n\n  const handleAnnotationClick = async (annotation: AnnotationData) => {\n    setActiveAnnotationId(annotation.id);\n    const playDuration = !continuousPlay ? annotation.end - annotation.start : undefined;\n    try {\n      await play(annotation.start, playDuration);\n    } catch (err) {\n      console.error(\n        'waveform-playlist: Failed to start playback for annotation',\n        annotation.id,\n        err\n      );\n    }\n  };\n\n  const selectTrack = useCallback(\n    (trackIndex: number) => {\n      if (trackIndex >= 0 && trackIndex < tracks.length) {\n        const track = tracks[trackIndex];\n        setSelectedTrackId(track.id);\n      }\n    },\n    [tracks, setSelectedTrackId]\n  );\n\n  const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {\n    const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n    const x = e.clientX - rect.left;\n    const clickTime = (x * samplesPerPixel) / sampleRate;\n\n    const y = e.clientY - rect.top;\n    const trackY = y;\n\n    let cumulativeHeight = 0;\n    let clickedTrackIndex = -1;\n\n    for (let i = 0; i < peaksDataArray.length; i++) {\n      const trackClipPeaks = peaksDataArray[i];\n      const rawCh = getTrackChannelCount(trackClipPeaks, recordingState, tracks[i]?.id, mono);\n      const trackMode =\n        spectrogram?.trackSpectrogramOverrides.get(tracks[i]?.id)?.renderMode ??\n        tracks[i]?.renderMode ??\n        'waveform';\n      const effectiveCh = trackMode === 'both' ? rawCh * 2 : rawCh;\n      const trackHeight = effectiveCh * waveHeight + (showClipHeaders ? 22 : 0);\n\n      if (trackY >= cumulativeHeight && trackY < cumulativeHeight + trackHeight) {\n        clickedTrackIndex = i;\n        break;\n      }\n      cumulativeHeight += trackHeight;\n    }\n\n    if (clickedTrackIndex !== -1) {\n      selectTrack(clickedTrackIndex);\n    }\n\n    mouseDownTimeRef.current = clickTime;\n    setIsSelecting(true);\n    setCurrentTime(clickTime);\n    setSelection(clickTime, clickTime);\n  };\n\n  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {\n    if (!isSelecting || isPlaying) return;\n\n    const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n    const x = e.clientX - rect.left;\n    const moveTime = (x * samplesPerPixel) / sampleRate;\n\n    const start = Math.min(selectionStart, moveTime);\n    const end = Math.max(selectionStart, moveTime);\n    setSelection(start, end);\n  };\n\n  const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {\n    if (!isSelecting) return;\n\n    setIsSelecting(false);\n\n    // During playback, use the time captured at mouseDown — auto-scroll shifts the\n    // overlay between mouseDown and mouseUp, so recomputing from getBoundingClientRect()\n    // would produce a wrong (shifted) position.\n    if (isPlaying) {\n      const clickTime = Math.max(0, mouseDownTimeRef.current);\n      setCurrentTime(clickTime);\n      setSelection(clickTime, clickTime);\n      play(clickTime);\n      return;\n    }\n\n    const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n    const x = e.clientX - rect.left;\n    const endTime = (x * samplesPerPixel) / sampleRate;\n\n    const start = Math.min(selectionStart, endTime);\n    const end = Math.max(selectionStart, endTime);\n\n    if (Math.abs(end - start) < 0.1) {\n      setCurrentTime(start);\n    } else {\n      setSelection(start, end);\n    }\n  };\n\n  // Shared vertical layout for BOTH columns (waveform rows + control slots).\n  // Heights use the existing slot formula; order applies the live drag\n  // preview so both columns slide together. Hoisted from the per-slot\n  // computation so the two columns can never disagree. Must run before the\n  // loading-state early return below (rules-of-hooks: no conditional hooks).\n  const trackHeightById = useMemo(() => {\n    const m = new Map<string, number>();\n    peaksDataArray.forEach((trackClipPeaks, trackIndex) => {\n      const track = tracks[trackIndex];\n      if (!track) return;\n      const maxChannels = getTrackChannelCount(trackClipPeaks, recordingState, track.id, mono);\n      m.set(track.id, waveHeight * maxChannels + (showClipHeaders ? CLIP_HEADER_HEIGHT : 0));\n    });\n    return m;\n  }, [peaksDataArray, tracks, recordingState, mono, waveHeight, showClipHeaders]);\n\n  const displayOrderIds = useMemo(\n    () => applyTrackOrderPreview(tracks, trackDragPreview).map((t) => t.id),\n    [tracks, trackDragPreview]\n  );\n\n  const trackLayout = useMemo(\n    () => computeTrackLayout(displayOrderIds, trackHeightById),\n    [displayOrderIds, trackHeightById]\n  );\n\n  const animateTrackLayout = useTrailingActive(trackDragPreview !== null, 200);\n\n  // Only show loading if we have tracks WITH clips but peaks haven't been computed yet.\n  // Don't check audioBuffers — it's set in an effect and can be stale for one or more\n  // renders after tracks change, causing the playlist to unmount and remount (layout shift).\n  // Placeholder tracks (clips: []) bypass this check intentionally.\n  const hasClips = tracks.some((track) => track.clips.length > 0);\n  if (hasClips && peaksDataArray.length === 0) {\n    return <div className={className}>Loading waveform...</div>;\n  }\n\n  // Build track controls slots for the ControlsColumn (outside scroll area)\n  const trackControlsSlots = controls.show\n    ? [\n        <TracksLayout key=\"control-slots-layout\" $height={trackLayout.totalHeight}>\n          {peaksDataArray.map((trackClipPeaks, trackIndex) => {\n            const track = tracks[trackIndex];\n            if (!track) return null;\n\n            const trackState = trackStates[trackIndex] || {\n              name: `Track ${trackIndex + 1}`,\n              muted: false,\n              soloed: false,\n              volume: 1.0,\n              pan: 0,\n            };\n\n            const hasMidiNotes = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);\n            const effectiveRenderMode =\n              spectrogram?.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n              track.renderMode ??\n              (hasMidiNotes ? 'piano-roll' : 'waveform');\n\n            const slotHeight = trackHeightById.get(track.id) ?? 0;\n\n            const defaultControlProps: Omit<DefaultTrackControlsProps, 'gripRef'> = {\n              trackIndex,\n              track,\n              trackState,\n              effectiveRenderMode,\n              selectTrack,\n              onRemoveTrack,\n              setTrackMute,\n              setTrackSolo,\n              setTrackVolume,\n              setTrackPan,\n              spectrogram,\n              setSettingsModalTrackId,\n              trackReordering,\n              reorderTrack,\n              trackCount: peaksDataArray.length,\n            };\n\n            const trackControlContent = renderTrackControls ? (\n              renderTrackControls(trackIndex)\n            ) : trackReordering ? (\n              <SortableTrackControls trackId={track.id} index={trackIndex}>\n                {({ ref, handleRef, isDragSource }) => (\n                  <div\n                    ref={ref as React.Ref<HTMLDivElement>}\n                    style={{\n                      height: '100%',\n                      boxShadow: isDragSource ? DRAG_SOURCE_SHADOW : undefined,\n                      // Opaque background for the floating (top-layer, popover-based)\n                      // drag-source row — without it the row underneath shows through.\n                      // onDragStart selects the dragged track, so its slot always shows\n                      // the selected-track background while lifted.\n                      background: isDragSource ? theme.selectedTrackControlsBackground : undefined,\n                    }}\n                  >\n                    <DefaultTrackControls {...defaultControlProps} gripRef={handleRef} />\n                  </div>\n                )}\n              </SortableTrackControls>\n            ) : (\n              <DefaultTrackControls {...defaultControlProps} />\n            );\n\n            return (\n              // key includes trackReorderEpoch: forces a remount after every\n              // track-reorder drag, discarding @dnd-kit sortable-plugin DOM\n              // corruption (see bumpTrackReorderEpoch's doc comment in\n              // WaveformPlaylistContext). Stays at `-0` — a no-op suffix — for\n              // consumers that never enable trackReordering.\n              <ControlSlot\n                key={`${track.id}-${trackReorderEpoch}`}\n                $height={slotHeight}\n                $top={trackLayout.topById.get(track.id) ?? 0}\n                $animate={animateTrackLayout}\n                $isSelected={track.id === selectedTrackId}\n              >\n                {trackControlContent}\n              </ControlSlot>\n            );\n          })}\n        </TracksLayout>,\n      ]\n    : undefined;\n\n  return (\n    <DevicePixelRatioProvider>\n      <PlaylistInfoContext.Provider\n        value={{\n          samplesPerPixel,\n          sampleRate,\n          zoomLevels: [samplesPerPixel],\n          waveHeight,\n          timeScaleHeight,\n          duration: displayDuration * 1000,\n          controls,\n          barWidth,\n          barGap,\n          roundedBars,\n        }}\n      >\n        <Playlist\n          theme={theme}\n          backgroundColor={\n            theme.playlistBackgroundColor || waveformColorToCss(theme.waveOutlineColor)\n          }\n          timescaleBackgroundColor={theme.timescaleBackgroundColor}\n          timescaleWidth={tracksFullWidth}\n          tracksWidth={tracksFullWidth}\n          controlsWidth={controls.show ? controls.width : 0}\n          onTracksMouseDown={handleMouseDown}\n          onTracksMouseMove={handleMouseMove}\n          onTracksMouseUp={handleMouseUp}\n          scrollContainerRef={handleScrollContainerRef}\n          isSelecting={isSelecting}\n          data-playlist-state={isReady ? 'ready' : 'loading'}\n          trackControlsSlots={trackControlsSlots}\n          timescaleGapHeight={timeScaleHeight > 0 ? timeScaleHeight + 1 : 0}\n          timescale={\n            timeScaleHeight > 0 ? (\n              <>\n                <SmartScale renderTick={renderTick} />\n                {isLoopEnabled && (\n                  <TimescaleLoopRegion\n                    startPosition={(Math.min(loopStart, loopEnd) * sampleRate) / samplesPerPixel}\n                    endPosition={(Math.max(loopStart, loopEnd) * sampleRate) / samplesPerPixel}\n                    markerColor={theme.loopMarkerColor}\n                    regionColor={theme.loopRegionColor}\n                    minPosition={0}\n                    maxPosition={tracksFullWidth}\n                    onLoopRegionChange={(startPixels, endPixels) => {\n                      const startSeconds = (startPixels * samplesPerPixel) / sampleRate;\n                      const endSeconds = (endPixels * samplesPerPixel) / sampleRate;\n                      setLoopRegion(startSeconds, endSeconds);\n                    }}\n                  />\n                )}\n              </>\n            ) : undefined\n          }\n        >\n          <>\n            <TracksLayout $height={trackLayout.totalHeight}>\n              {peaksDataArray.map((trackClipPeaks, trackIndex) => {\n                const track = tracks[trackIndex];\n                if (!track) return null;\n\n                const hasMidiNotes = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);\n                const effectiveRenderMode =\n                  spectrogram?.trackSpectrogramOverrides.get(track.id)?.renderMode ??\n                  track.renderMode ??\n                  (hasMidiNotes ? 'piano-roll' : 'waveform');\n\n                const maxChannels = getTrackChannelCount(\n                  trackClipPeaks,\n                  recordingState,\n                  track.id,\n                  mono\n                );\n\n                return (\n                  <TrackRowPositioner\n                    key={track.id}\n                    $top={trackLayout.topById.get(track.id) ?? 0}\n                    $animate={animateTrackLayout}\n                    data-track-row=\"\"\n                    data-track-id={track.id}\n                    data-track-drag-source={\n                      trackDragPreview?.trackId === track.id ? 'true' : undefined\n                    }\n                  >\n                    <TrackComponent\n                      numChannels={maxChannels}\n                      backgroundColor={\n                        effectiveRenderMode === 'piano-roll'\n                          ? theme.pianoRollBackgroundColor || '#1a1a2e'\n                          : waveformColorToCss(theme.waveOutlineColor)\n                      }\n                      offset={0}\n                      width={tracksFullWidth}\n                      hasClipHeaders={showClipHeaders}\n                      trackId={track.id}\n                      isSelected={track.id === selectedTrackId}\n                    >\n                      {effectiveRenderMode !== 'waveform' &&\n                        (() => {\n                          const helpers = perTrackSpectrogramHelpers.get(track.id);\n                          const trackCfg = helpers?.config;\n                          if (!trackCfg?.labels || !helpers) return null;\n                          return (\n                            <SpectrogramLabels\n                              waveHeight={waveHeight}\n                              numChannels={maxChannels}\n                              frequencyScaleFn={helpers.frequencyScaleFn}\n                              minFrequency={trackCfg.minFrequency ?? 0}\n                              maxFrequency={trackCfg.maxFrequency ?? sampleRate / 2}\n                              labelsColor={trackCfg.labelsColor}\n                              labelsBackground={trackCfg.labelsBackground}\n                              renderMode={effectiveRenderMode as 'spectrogram' | 'both'}\n                              hasClipHeaders={showClipHeaders}\n                            />\n                          );\n                        })()}\n                      {trackClipPeaks.map((clip, clipIndex) => {\n                        const peaksData = clip.peaks;\n                        const width = peaksData.length;\n\n                        return (\n                          <Clip\n                            key={clip.clipId}\n                            clipId={clip.clipId}\n                            trackIndex={trackIndex}\n                            clipIndex={clipIndex}\n                            trackName={clip.trackName}\n                            startSample={clip.startSample}\n                            durationSamples={clip.durationSamples}\n                            samplesPerPixel={samplesPerPixel}\n                            showHeader={showClipHeaders}\n                            disableHeaderDrag={!interactiveClips}\n                            isSelected={track.id === selectedTrackId}\n                            trackId={track.id}\n                            fadeIn={clip.fadeIn}\n                            fadeOut={clip.fadeOut}\n                            sampleRate={sampleRate}\n                            showFades={showFades}\n                            touchOptimized={touchOptimized}\n                            onMouseDown={(e) => {\n                              const target = e.target as HTMLElement;\n                              const isDraggable = target.closest(\n                                '[role=\"button\"][aria-roledescription=\"draggable\"]'\n                              );\n                              if (isDraggable) {\n                                return;\n                              }\n                              selectTrack(trackIndex);\n                            }}\n                          >\n                            {peaksData.data.map((channelPeaks: Peaks, channelIndex: number) => {\n                              return (\n                                <ChannelWithProgress\n                                  key={`${clip.clipId}-${channelIndex}`}\n                                  index={channelIndex}\n                                  data={channelPeaks}\n                                  bits={peaksData.bits}\n                                  length={width}\n                                  isSelected={track.id === selectedTrackId}\n                                  clipStartSample={clip.startSample}\n                                  clipDurationSamples={clip.durationSamples}\n                                  renderMode={clip.midiNotes ? 'piano-roll' : effectiveRenderMode}\n                                  midiNotes={clip.midiNotes}\n                                  clipSampleRate={clip.sampleRate}\n                                  clipOffsetSeconds={\n                                    clip.offsetSamples != null\n                                      ? clip.offsetSamples / (clip.sampleRate || sampleRate)\n                                      : 0\n                                  }\n                                  samplesPerPixel={samplesPerPixel}\n                                  spectrogramClipId={clip.clipId}\n                                  spectrogramOnCanvasRegister={\n                                    spectrogram ? spectrogram.registerSpectrogramCanvas : undefined\n                                  }\n                                  spectrogramOnCanvasUnregister={\n                                    spectrogram\n                                      ? spectrogram.unregisterSpectrogramCanvas\n                                      : undefined\n                                  }\n                                />\n                              );\n                            })}\n                          </Clip>\n                        );\n                      })}\n                      {recordingState?.isRecording &&\n                        recordingState.trackId === track.id &&\n                        recordingState.peaks[0]?.length > 0 &&\n                        (() => {\n                          // Strip leading-silence peaks so the visible preview matches\n                          // audible content. Recorder runs at real time, but the user's\n                          // first reaction lands ~outputLatency+lookAhead seconds into\n                          // the buffer (they hadn't heard the backing track yet). Without\n                          // this, the playhead — which now uses audible time — would\n                          // appear behind the right edge of the recorded waveform.\n                          // Mirrors useIntegratedRecording's finalization compensation\n                          // (shared `resolveRecordingOffsetSamples`) and dawcore's preview-skip\n                          // pattern. `getLookAhead()` reads from the same engine the\n                          // playhead's animation loop uses — keeps the two in lockstep.\n                          const latencyOffsetSamples = resolveRecordingOffsetSamples({\n                            overrideSeconds: recordingState.latencyOffset,\n                            outputLatency: getOutputLatency(),\n                            lookAhead: getLookAhead(),\n                            sampleRate,\n                          });\n                          const latencyPixels = Math.floor(latencyOffsetSamples / samplesPerPixel);\n                          const skipPeakElements = latencyPixels * 2; // each pixel is a min/max pair\n                          const previewDuration = Math.max(\n                            0,\n                            recordingState.durationSamples - latencyOffsetSamples\n                          );\n                          const previewChannels = (\n                            mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks\n                          ).map((channelPeaks) =>\n                            skipPeakElements > 0 && skipPeakElements < channelPeaks.length\n                              ? channelPeaks.subarray(skipPeakElements)\n                              : channelPeaks\n                          );\n                          return (\n                            <Clip\n                              key={`${track.id}-recording`}\n                              clipId=\"recording-preview\"\n                              trackIndex={trackIndex}\n                              clipIndex={trackClipPeaks.length}\n                              trackName=\"Recording...\"\n                              startSample={recordingState.startSample}\n                              durationSamples={previewDuration}\n                              samplesPerPixel={samplesPerPixel}\n                              showHeader={showClipHeaders}\n                              disableHeaderDrag={true}\n                              isSelected={track.id === selectedTrackId}\n                              trackId={track.id}\n                            >\n                              {previewChannels.map((channelPeaks, chIdx) => (\n                                <ChannelWithProgress\n                                  key={`${track.id}-recording-${chIdx}`}\n                                  index={chIdx}\n                                  data={channelPeaks}\n                                  bits={recordingState.bits}\n                                  length={Math.floor(channelPeaks.length / 2)}\n                                  isSelected={track.id === selectedTrackId}\n                                  clipStartSample={recordingState.startSample}\n                                  clipDurationSamples={previewDuration}\n                                />\n                              ))}\n                            </Clip>\n                          );\n                        })()}\n                    </TrackComponent>\n                  </TrackRowPositioner>\n                );\n              })}\n            </TracksLayout>\n            {annotations.length > 0 && annotationIntegration && (\n              <annotationIntegration.AnnotationBoxesWrapper height={30} width={tracksFullWidth}>\n                {annotations.map((annotation, index) => {\n                  const startPosition = (annotation.start * sampleRate) / samplesPerPixel;\n                  const endPosition = (annotation.end * sampleRate) / samplesPerPixel;\n                  const label = getAnnotationBoxLabel\n                    ? getAnnotationBoxLabel(annotation, index)\n                    : annotation.id;\n                  return (\n                    <annotationIntegration.AnnotationBox\n                      key={annotation.id}\n                      annotationId={annotation.id}\n                      annotationIndex={index}\n                      startPosition={startPosition}\n                      endPosition={endPosition}\n                      label={label}\n                      color=\"#ff9800\"\n                      isActive={annotation.id === activeAnnotationId}\n                      onClick={() => handleAnnotationClick(annotation)}\n                      editable={annotationsEditable}\n                    />\n                  );\n                })}\n              </annotationIntegration.AnnotationBoxesWrapper>\n            )}\n            {selectionStart !== selectionEnd && (\n              <Selection\n                startPosition={\n                  (Math.min(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n                }\n                endPosition={\n                  (Math.max(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n                }\n                color={theme.selectionColor}\n              />\n            )}\n            {(isPlaying || selectionStart === selectionEnd) &&\n              (renderPlayhead ? (\n                <CustomPlayhead\n                  renderPlayhead={renderPlayhead}\n                  color={theme.playheadColor}\n                  samplesPerPixel={samplesPerPixel}\n                  sampleRate={sampleRate}\n                />\n              ) : (\n                <AnimatedPlayhead color={theme.playheadColor} />\n              ))}\n          </>\n        </Playlist>\n      </PlaylistInfoContext.Provider>\n      {spectrogram?.SettingsModal &&\n        typeof document !== 'undefined' &&\n        createPortal(\n          <spectrogram.SettingsModal\n            open={settingsModalTrackId !== null}\n            onClose={() => setSettingsModalTrackId(null)}\n            config={\n              settingsModalTrackId !== null\n                ? (spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)?.config ??\n                  tracks.find((t) => t.id === settingsModalTrackId)?.spectrogramConfig ??\n                  spectrogram.spectrogramConfig ??\n                  {})\n                : {}\n            }\n            colorMap={\n              settingsModalTrackId !== null\n                ? (spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)?.colorMap ??\n                  tracks.find((t) => t.id === settingsModalTrackId)?.spectrogramColorMap ??\n                  spectrogram.spectrogramColorMap ??\n                  'viridis')\n                : 'viridis'\n            }\n            onApply={(newConfig, newColorMap) => {\n              if (settingsModalTrackId !== null) {\n                spectrogram.setTrackSpectrogramConfig(settingsModalTrackId, newConfig, newColorMap);\n              }\n            }}\n          />,\n          document.body\n        )}\n    </DevicePixelRatioProvider>\n  );\n};\n","import React, { useCallback, useEffect, useRef } from 'react';\nimport { useSortable } from '@dnd-kit/react/sortable';\nimport { RestrictToVerticalAxis } from '@dnd-kit/abstract/modifiers';\nimport { createDragPopoverHealer } from '../utils/dragPopoverHealer';\n\nexport interface SortableTrackControlsRenderProps {\n  /** Attach to the slot wrapper element (the sortable item). */\n  ref: (element: Element | null) => void;\n  /** Attach to the drag grip element. */\n  handleRef: (element: Element | null) => void;\n  isDragSource: boolean;\n}\n\nexport interface SortableTrackControlsProps {\n  trackId: string;\n  index: number;\n  disabled?: boolean;\n  children: (props: SortableTrackControlsRenderProps) => React.ReactNode;\n}\n\n/**\n * Registers a track-controls row as a vertical sortable item in the ambient\n * DragDropProvider (ClipInteractionProvider). The per-source modifiers\n * REPLACE the provider's clip modifiers for this operation, so track drags\n * are vertical-only and skip clip collision/snap. Data kind 'track-reorder'\n * is the discriminator the shared drag handlers branch on.\n */\nexport const SortableTrackControls: React.FC<SortableTrackControlsProps> = ({\n  trackId,\n  index,\n  disabled = false,\n  children,\n}) => {\n  const { ref, handleRef, isDragSource } = useSortable({\n    id: `track-reorder-${trackId}`,\n    index,\n    group: 'playlist-tracks',\n    type: 'track',\n    accept: ['track'],\n    data: { kind: 'track-reorder', trackId },\n    modifiers: [RestrictToVerticalAxis],\n    disabled,\n    // feedback: 'move' (NOT 'none' — see below) disables the Feedback\n    // plugin's placeholder/clone splicing for this sortable while KEEPING its\n    // dragOperation.shape computation alive. That plugin does raw,\n    // React-bypassing DOM surgery on ANY pointer movement during an activated\n    // drag (insertAdjacentElement to splice in a placeholder, then\n    // `placeholder.replaceWith(element)` on drop) — confirmed via\n    // MutationObserver instrumentation to relocate the React-owned row 6\n    // times during a single one-slot drag, leaving its final parent/sibling\n    // different from what React's fiber tree still believes. That desync is\n    // permanent for the session: every later React-driven reorder (button OR\n    // drag) computes the correct data but silently fails to update the DOM\n    // for these rows. The placeholder splice is created only when\n    // `feedback !== 'move'` (@dnd-kit/dom Feedback.ts) — 'move' keeps the\n    // dragged element in the live DOM (no clone/placeholder) while still\n    // running the *unconditional* `dragOperation.shape = ...` assignment.\n    // `feedback: 'none'` looked like the boundary-trim precedent (Clip.tsx /\n    // AnnotationBox.tsx) but is wrong here: 'none' bails out of Feedback's\n    // render function BEFORE dragOperation.shape is ever set, and shape is\n    // the ONLY input CollisionObserver.computeCollisions() checks — with\n    // 'none', collision (and therefore sortable.index) never updates, so\n    // drag-based reordering silently never commits. Boundary trims don't hit\n    // this because they're plain useDraggable (no collision-based commit) —\n    // sortables need shape. `isDragSource` (box-shadow lift feedback below)\n    // is a reactive signal, unaffected either way. See browser/CLAUDE.md\n    // \"Track Reordering\".\n    feedback: 'move',\n  });\n  // `feedback: 'move'` opts out of dnd-kit's placeholder — and with it the\n  // plugin's own popover-recovery MutationObserver, which is only created\n  // when a placeholder exists. OptimisticSortingPlugin's mid-drag\n  // insertAdjacentElement splices force-dismiss the top-layer popover\n  // (eventlessly, per spec), leaving the dragged row UA-hidden\n  // (`[popover]:not(:popover-open) { display: none }`) for the rest of the\n  // drag. Run our own healer while this row is the drag source.\n  const elementRef = useRef<HTMLElement | null>(null);\n  const composedRef = useCallback(\n    (element: Element | null) => {\n      elementRef.current = element instanceof HTMLElement ? element : null;\n      ref(element);\n    },\n    [ref]\n  );\n  useEffect(() => {\n    if (!isDragSource || !elementRef.current) return undefined;\n    return createDragPopoverHealer(elementRef.current);\n  }, [isDragSource]);\n  return <>{children({ ref: composedRef, handleRef, isDragSource })}</>;\n};\n","/**\n * Keeps a dnd-kit drag-feedback element visible for the duration of a drag.\n *\n * @dnd-kit/dom's Feedback plugin promotes the dragged element into the\n * browser top layer via the Popover API (`popover=\"manual\"` + showPopover()).\n * During a sortable drag, OptimisticSortingPlugin reorders rows with\n * `insertAdjacentElement` — a remove + reinsert that force-dismisses the\n * popover with NO toggle events (per spec, removal-dismissal fires none).\n * A dismissed `[popover]` element falls under the UA rule\n * `[popover]:not(:popover-open) { display: none }`, so the dragged row\n * vanishes the moment it crosses a collision boundary. dnd-kit's own\n * recovery MutationObserver is only created when a placeholder exists\n * (`feedback !== 'move'`), so `feedback: 'move'` sortables never recover.\n *\n * This observer mirrors that upstream recovery for the 'move' case: on any\n * childList mutation, if the element is connected but its popover was\n * dismissed, re-show it. MutationObserver callbacks run as microtasks, so\n * the heal lands in the same frame as the splice — no visible flicker.\n */\nexport function createDragPopoverHealer(element: HTMLElement): () => void {\n  if (typeof element.showPopover !== 'function') {\n    // Browser without the Popover API: dnd-kit falls back to plain\n    // fixed-position feedback, which DOM splices cannot dismiss.\n    return () => {};\n  }\n  const heal = () => {\n    if (!element.isConnected || !element.hasAttribute('popover')) return;\n    let open = false;\n    try {\n      open = element.matches(':popover-open');\n    } catch {\n      // Selector unsupported (older engines): treat as closed and let\n      // showPopover's own InvalidStateError guard below settle it.\n    }\n    if (open) return;\n    try {\n      element.showPopover();\n    } catch {\n      // InvalidStateError (already open / mid-toggle) — nothing to heal.\n    }\n  };\n  const observer = new MutationObserver(heal);\n  observer.observe(element.ownerDocument.body, {\n    childList: true,\n    subtree: true,\n  });\n  return () => observer.disconnect();\n}\n","/**\n * Pure helpers for the track-reorder drag preview.\n *\n * The preview is view-level only: it produces a DISPLAY order/geometry while\n * the engine's committed `tracks` order stays untouched until drop.\n */\n\nexport interface TrackDragPreview {\n  trackId: string;\n  toIndex: number;\n}\n\n/**\n * Returns the display order with the previewed track moved to `toIndex`.\n * Identity (same reference) when there is nothing to do — null preview,\n * unknown id, or an index that doesn't change the order.\n */\nexport function applyTrackOrderPreview<T extends { id: string }>(\n  tracks: readonly T[],\n  preview: TrackDragPreview | null\n): readonly T[] {\n  if (!preview) return tracks;\n  const fromIndex = tracks.findIndex((t) => t.id === preview.trackId);\n  if (fromIndex === -1) return tracks;\n  const toIndex = Math.max(0, Math.min(tracks.length - 1, preview.toIndex));\n  if (toIndex === fromIndex) return tracks;\n  const next = tracks.slice();\n  const [moved] = next.splice(fromIndex, 1);\n  next.splice(toIndex, 0, moved);\n  return next;\n}\n\nexport interface TrackLayout {\n  topById: Map<string, number>;\n  totalHeight: number;\n}\n\n/**\n * Cumulative vertical layout for absolutely-positioned track rows. Both the\n * waveform column and the controls column render from the SAME layout so they\n * can never drift apart mid-preview. Ids missing from `heightById` contribute\n * height 0 (defensive; should not happen in practice).\n */\nexport function computeTrackLayout(\n  orderedIds: readonly string[],\n  heightById: ReadonlyMap<string, number>\n): TrackLayout {\n  const topById = new Map<string, number>();\n  let top = 0;\n  for (const id of orderedIds) {\n    topById.set(id, top);\n    top += heightById.get(id) ?? 0;\n  }\n  return { topById, totalHeight: top };\n}\n","import { useEffect, useState } from 'react';\n\n/**\n * True while `active` is true and for `trailingMs` after it turns false.\n * Used to keep the track-reorder transform transition enabled through the\n * cancel-revert animation (the drag ends but the layout still needs to\n * animate back) without animating unrelated layout changes outside a drag.\n */\nexport function useTrailingActive(active: boolean, trailingMs: number): boolean {\n  const [trailing, setTrailing] = useState(active);\n  useEffect(() => {\n    if (active) {\n      setTrailing(true);\n      return undefined;\n    }\n    const timer = setTimeout(() => setTrailing(false), trailingMs);\n    return () => clearTimeout(timer);\n  }, [active, trailingMs]);\n  return trailing || active;\n}\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { usePlaybackAnimation, usePlaylistData } from '../WaveformPlaylistContext';\n\nconst PlayheadLine = styled.div.attrs<{ $color: string; $width: number }>((props) => ({\n  style: {\n    width: `${props.$width}px`,\n    background: props.$color,\n  },\n}))<{ $color: string; $width: number }>`\n  position: absolute;\n  top: 0;\n  left: 0;\n  height: 100%;\n  z-index: 100; /* Below sticky controls (z-index: 101) so playhead is hidden when scrolled behind controls */\n  pointer-events: none;\n  will-change: transform;\n`;\n\ninterface AnimatedPlayheadProps {\n  color?: string;\n}\n\n/**\n * Animated playhead that updates position via the shared animation frame registry.\n * No own rAF loop — the WaveformPlaylistContext animation loop drives all visual updates.\n */\nexport const AnimatedPlayhead: React.FC<AnimatedPlayheadProps> = ({ color = '#ff0000' }) => {\n  const playheadRef = useRef<HTMLDivElement>(null);\n\n  const {\n    isPlaying,\n    currentTimeRef,\n    visualTimeRef,\n    registerFrameCallback,\n    unregisterFrameCallback,\n  } = usePlaybackAnimation();\n  const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();\n\n  // Register per-frame callback during playback\n  useEffect(() => {\n    const id = 'playhead';\n    if (isPlaying) {\n      registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {\n        if (playheadRef.current) {\n          const px = (visualTime * sr) / spp;\n          playheadRef.current.style.transform = `translate3d(${px}px, 0, 0)`;\n        }\n      });\n    }\n    return () => unregisterFrameCallback(id);\n  }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);\n\n  // Update position when not playing (seeks, stops, etc.) — no rAF needed.\n  // Reads visualTimeRef so the static playhead lines up with audible output\n  // (the pause/seek paths sync visualTimeRef when they update currentTimeRef).\n  useEffect(() => {\n    if (!isPlaying && playheadRef.current) {\n      const time = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n      const position = (time * sampleRate) / samplesPerPixel;\n      playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n    }\n  });\n\n  return <PlayheadLine ref={playheadRef} $color={color} $width={progressBarWidth} data-playhead />;\n};\n","import React, { useId, useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport {\n  clipPixelWidth as computeClipPixelWidth,\n  type MidiNoteData,\n} from '@waveform-playlist/core';\nimport {\n  SmartChannel,\n  type SmartChannelProps,\n  useTheme,\n  usePlaylistInfo,\n  type WaveformPlaylistTheme,\n  waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport { usePlaybackAnimation, usePlaylistData } from '../WaveformPlaylistContext';\n\nconst ChannelWrapper = styled.div`\n  position: relative;\n`;\n\ninterface BackgroundProps {\n  readonly $color: string;\n  readonly $height: number;\n  readonly $top: number;\n  readonly $width: number;\n}\n\nconst Background = styled.div.attrs<BackgroundProps>((props) => ({\n  style: {\n    top: `${props.$top}px`,\n    width: `${props.$width}px`,\n    height: `${props.$height}px`,\n    background: props.$color,\n  },\n}))<BackgroundProps>`\n  position: absolute;\n  left: 0;\n  z-index: 0;\n  /* Force GPU compositing layer to prevent gradient flickering during scroll */\n  transform: translateZ(0);\n  backface-visibility: hidden;\n`;\n\ninterface ProgressOverlayProps {\n  readonly $color: string;\n  readonly $height: number;\n  readonly $top: number;\n  readonly $width: number;\n}\n\nconst ProgressOverlay = styled.div.attrs<ProgressOverlayProps>((props) => ({\n  style: {\n    top: `${props.$top}px`,\n    height: `${props.$height}px`,\n    width: `${props.$width}px`,\n    background: props.$color,\n    transform: 'scaleX(0)',\n  },\n}))<ProgressOverlayProps>`\n  position: absolute;\n  left: 0;\n  pointer-events: none;\n  z-index: 1;\n  transform-origin: left;\n  /* scaleX changes are composite-only (GPU) — no layout reflow per frame */\n  will-change: transform;\n`;\n\nconst ChannelContainer = styled.div`\n  position: relative;\n  z-index: 2;\n`;\n\nexport interface ChannelWithProgressProps extends SmartChannelProps {\n  /** Start sample of the clip containing this channel (for progress calculation) */\n  clipStartSample: number;\n  /** Duration in samples of the clip */\n  clipDurationSamples: number;\n  /** MIDI note data for piano-roll rendering */\n  midiNotes?: MidiNoteData[];\n  /** Sample rate of the clip (for MIDI note positioning) */\n  clipSampleRate?: number;\n  /** Clip offset in seconds (for MIDI note positioning) */\n  clipOffsetSeconds?: number;\n}\n\n/**\n * SmartChannel wrapper that adds an animated progress overlay.\n * The progress overlay shows the \"played\" portion of the waveform.\n * Updates via the shared animation frame registry — no own rAF loop.\n */\nexport const ChannelWithProgress: React.FC<ChannelWithProgressProps> = ({\n  clipStartSample,\n  clipDurationSamples,\n  midiNotes,\n  clipSampleRate,\n  clipOffsetSeconds,\n  ...smartChannelProps\n}) => {\n  const progressRef = useRef<HTMLDivElement>(null);\n  const callbackId = useId();\n  const theme = useTheme() as WaveformPlaylistTheme;\n  const { waveHeight } = usePlaylistInfo();\n\n  const {\n    isPlaying,\n    currentTimeRef,\n    visualTimeRef,\n    registerFrameCallback,\n    unregisterFrameCallback,\n  } = usePlaybackAnimation();\n  const { samplesPerPixel, sampleRate } = usePlaylistData();\n\n  const progressColor = theme?.waveProgressColor || 'rgba(0, 0, 0, 0.1)';\n\n  // Use shared helper to compute clip pixel width (must match Clip.tsx sizing)\n  // peaksData.length may be shorter than the clip when audio is shorter than configured duration\n  const clipPixelWidth = computeClipPixelWidth(\n    clipStartSample,\n    clipDurationSamples,\n    samplesPerPixel\n  );\n\n  // Register per-frame callback during playback — no own rAF loop\n  useEffect(() => {\n    if (isPlaying) {\n      registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {\n        if (progressRef.current) {\n          const currentSample = visualTime * sr;\n          const clipEndSample = clipStartSample + clipDurationSamples;\n\n          let ratio = 0;\n          if (currentSample <= clipStartSample) {\n            ratio = 0;\n          } else if (currentSample >= clipEndSample) {\n            ratio = 1;\n          } else {\n            const playedSamples = currentSample - clipStartSample;\n            ratio = playedSamples / clipDurationSamples;\n          }\n\n          // scaleX is composite-only — no layout reflow, GPU-accelerated\n          progressRef.current.style.transform = `scaleX(${ratio})`;\n        }\n      });\n    }\n    return () => unregisterFrameCallback(callbackId);\n  }, [\n    isPlaying,\n    clipStartSample,\n    clipDurationSamples,\n    callbackId,\n    registerFrameCallback,\n    unregisterFrameCallback,\n  ]);\n\n  // Also update when not playing (for seeks, stops, etc.). Reads visualTimeRef\n  // so the static progress fill matches the playhead's audible position.\n  useEffect(() => {\n    if (!isPlaying && progressRef.current) {\n      const currentTime = visualTimeRef.current ?? currentTimeRef.current ?? 0;\n      const currentSample = currentTime * sampleRate;\n      const clipEndSample = clipStartSample + clipDurationSamples;\n\n      let ratio = 0;\n      if (currentSample <= clipStartSample) {\n        ratio = 0;\n      } else if (currentSample >= clipEndSample) {\n        ratio = 1;\n      } else {\n        const playedSamples = currentSample - clipStartSample;\n        ratio = playedSamples / clipDurationSamples;\n      }\n\n      progressRef.current.style.transform = `scaleX(${ratio})`;\n    }\n  });\n\n  // Get the draw mode from theme (defaults to 'inverted')\n  const drawMode = theme?.waveformDrawMode || 'inverted';\n\n  let backgroundColor;\n  if (drawMode === 'inverted') {\n    backgroundColor =\n      smartChannelProps.isSelected && theme\n        ? theme.selectedWaveFillColor\n        : theme?.waveFillColor || 'white';\n  } else {\n    backgroundColor =\n      smartChannelProps.isSelected && theme\n        ? theme.selectedWaveOutlineColor\n        : theme?.waveOutlineColor || 'grey';\n  }\n\n  // Use black background for spectrogram mode, themed background for piano-roll\n  const isSpectrogramMode =\n    smartChannelProps.renderMode === 'spectrogram' || smartChannelProps.renderMode === 'both';\n  const isPianoRollMode = smartChannelProps.renderMode === 'piano-roll';\n  const isBothMode = smartChannelProps.renderMode === 'both';\n  const backgroundCss = isSpectrogramMode\n    ? '#000'\n    : isPianoRollMode\n      ? theme?.pianoRollBackgroundColor || '#1a1a2e'\n      : waveformColorToCss(backgroundColor);\n\n  // In \"both\" mode each half (spectrogram + waveform) is waveHeight/2 so the track\n  // container stays the same height as a single-mode track.\n  const halfHeight = Math.floor(waveHeight / 2);\n  const effectiveHeight = waveHeight;\n  const effectiveTop = isBothMode\n    ? smartChannelProps.index * waveHeight\n    : smartChannelProps.index * waveHeight;\n\n  // In \"both\" mode, the waveform portion needs its own (non-black) background\n  const waveformBackgroundCss = waveformColorToCss(backgroundColor);\n\n  return (\n    <ChannelWrapper>\n      {/* Background layer - color depends on draw mode */}\n      {isBothMode ? (\n        <>\n          {/* Spectrogram portion: black background */}\n          <Background\n            $color=\"#000\"\n            $height={halfHeight}\n            $top={effectiveTop}\n            $width={smartChannelProps.length}\n          />\n          {/* Waveform portion: themed background */}\n          <Background\n            $color={waveformBackgroundCss}\n            $height={halfHeight}\n            $top={effectiveTop + halfHeight}\n            $width={smartChannelProps.length}\n          />\n        </>\n      ) : (\n        <Background\n          $color={backgroundCss}\n          $height={effectiveHeight}\n          $top={effectiveTop}\n          $width={smartChannelProps.length}\n        />\n      )}\n      {/* Progress overlay - shows played portion with progress color (skip for piano-roll) */}\n      {!isPianoRollMode && (\n        <ProgressOverlay\n          ref={progressRef}\n          $color={progressColor}\n          $height={effectiveHeight}\n          $top={effectiveTop}\n          $width={clipPixelWidth}\n        />\n      )}\n      {/* Waveform canvas with transparent background */}\n      <ChannelContainer>\n        <SmartChannel\n          {...smartChannelProps}\n          transparentBackground\n          midiNotes={midiNotes}\n          sampleRate={clipSampleRate}\n          clipOffsetSeconds={clipOffsetSeconds}\n        />\n      </ChannelContainer>\n    </ChannelWrapper>\n  );\n};\n","import { createContext, useContext } from 'react';\nimport type {\n  SpectrogramConfig,\n  ColorMapValue,\n  RenderMode,\n  TrackSpectrogramOverrides,\n} from '@waveform-playlist/core';\nimport type { TrackMenuItem } from '@waveform-playlist/ui-components';\n\n/**\n * Single-call canvas registration shape. Consolidates the OffscreenCanvas\n * transfer and per-canvas metadata that earlier required two separate\n * registration paths.\n *\n * Provider/orchestrator look up trackId from clipId and derive\n * globalPixelOffset = chunkIndex * MAX_CANVAS_WIDTH.\n */\nexport interface SpectrogramCanvasRegistration {\n  canvasId: string;\n  canvas: OffscreenCanvas;\n  clipId: string;\n  channelIndex: number;\n  chunkIndex: number;\n  widthPx: number;\n  heightPx: number;\n}\n\nexport interface SpectrogramIntegration {\n  trackSpectrogramOverrides: Map<string, TrackSpectrogramOverrides>;\n  spectrogramConfig?: SpectrogramConfig;\n  spectrogramColorMap?: ColorMapValue;\n  setTrackRenderMode: (trackId: string, mode: RenderMode) => void;\n  setTrackSpectrogramConfig: (\n    trackId: string,\n    config: SpectrogramConfig,\n    colorMap?: ColorMapValue\n  ) => void;\n  /** Single-canvas registration. OffscreenCanvas is non-transferable — call once per canvas. */\n  registerSpectrogramCanvas: (reg: SpectrogramCanvasRegistration) => void;\n  unregisterSpectrogramCanvas: (canvasId: string) => void;\n  /** Render spectrogram menu items for a track's context menu */\n  renderMenuItems?: (props: {\n    renderMode: string;\n    onRenderModeChange: (mode: RenderMode) => void;\n    onOpenSettings: () => void;\n    onClose?: () => void;\n  }) => TrackMenuItem[];\n  /** Settings modal component provided by the spectrogram package */\n  SettingsModal?: React.ComponentType<{\n    open: boolean;\n    onClose: () => void;\n    config: SpectrogramConfig;\n    colorMap: ColorMapValue;\n    onApply: (config: SpectrogramConfig, colorMap: ColorMapValue) => void;\n  }>;\n  /** Get color lookup table for a color map name */\n  getColorMap: (name: ColorMapValue) => Uint8Array;\n  /** Get frequency scale function for a scale name */\n  getFrequencyScale: (name: string) => (f: number, minF: number, maxF: number) => number;\n}\n\nexport const SpectrogramIntegrationContext = createContext<SpectrogramIntegration | null>(null);\n\nexport const SpectrogramIntegrationProvider = SpectrogramIntegrationContext.Provider;\n\n/**\n * Hook to access spectrogram integration provided by @waveform-playlist/spectrogram.\n * Throws if used without <SpectrogramProvider> wrapping the component tree.\n *\n * Follows the Kent C. Dodds pattern:\n * https://kentcdodds.com/blog/how-to-use-react-context-effectively\n */\nexport function useSpectrogramIntegration(): SpectrogramIntegration {\n  const context = useContext(SpectrogramIntegrationContext);\n  if (!context) {\n    throw new Error(\n      'useSpectrogramIntegration must be used within <SpectrogramProvider>. ' +\n        'Install @waveform-playlist/spectrogram and wrap your app with <SpectrogramProvider>.'\n    );\n  }\n  return context;\n}\n","import React, { useCallback } from 'react';\nimport type {\n  AnnotationData,\n  AnnotationAction,\n  AnnotationActionOptions,\n  RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\nimport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface PlaylistAnnotationListProps {\n  /** Height in pixels for the annotation text list */\n  height?: number;\n  /**\n   * Custom render function for annotation items in the text list.\n   * When provided, completely replaces the default annotation item rendering.\n   */\n  renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n  /**\n   * Callback when annotations are updated (e.g., text edited).\n   * Called with the full updated annotations array.\n   */\n  onAnnotationUpdate?: OnAnnotationUpdateFn;\n  /**\n   * Action controls to show on each annotation item (e.g., delete, split).\n   * Only rendered when `annotationsEditable` is true in context.\n   */\n  controls?: AnnotationAction[];\n  /**\n   * Override annotation list config. Falls back to context values\n   * `{ linkEndpoints, continuousPlay }` if not provided.\n   */\n  annotationListConfig?: AnnotationActionOptions;\n  /** Where to position the active annotation when auto-scrolling. Defaults to 'center'. */\n  scrollActivePosition?: ScrollLogicalPosition;\n  /** Which scrollable containers to scroll: 'nearest' or 'all'. Defaults to 'nearest'. */\n  scrollActiveContainer?: 'nearest' | 'all';\n}\n\n/**\n * Standalone annotation text list component for WaveformPlaylistProvider (WebAudio).\n *\n * Requires @waveform-playlist/annotations with AnnotationProvider.\n * Throws if used without `<AnnotationProvider>` wrapping the component tree.\n */\nexport const PlaylistAnnotationList: React.FC<PlaylistAnnotationListProps> = ({\n  height,\n  renderAnnotationItem,\n  onAnnotationUpdate,\n  controls,\n  annotationListConfig,\n  scrollActivePosition = 'center',\n  scrollActiveContainer = 'nearest',\n}) => {\n  const { annotations, activeAnnotationId, annotationsEditable, linkEndpoints, continuousPlay } =\n    usePlaylistState();\n  const integration = useAnnotationIntegration();\n  const { setAnnotations } = usePlaylistControls();\n\n  const resolvedConfig = annotationListConfig ?? { linkEndpoints, continuousPlay };\n\n  const handleAnnotationUpdate = useCallback(\n    (updatedAnnotations: AnnotationData[]) => {\n      setAnnotations(updatedAnnotations);\n      onAnnotationUpdate?.(updatedAnnotations);\n    },\n    [setAnnotations, onAnnotationUpdate]\n  );\n\n  const { AnnotationText } = integration;\n\n  return (\n    <AnnotationText\n      annotations={annotations}\n      activeAnnotationId={activeAnnotationId ?? undefined}\n      shouldScrollToActive={true}\n      scrollActivePosition={scrollActivePosition}\n      scrollActiveContainer={scrollActiveContainer}\n      editable={annotationsEditable}\n      controls={annotationsEditable ? controls : undefined}\n      annotationListConfig={resolvedConfig}\n      height={height}\n      onAnnotationUpdate={handleAnnotationUpdate}\n      renderAnnotationItem={renderAnnotationItem}\n    />\n  );\n};\n","import React, { ReactNode } from 'react';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport type {\n  AnnotationAction,\n  AnnotationActionOptions,\n  RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { usePlaylistState } from '../WaveformPlaylistContext';\nimport { useClipInteractionEnabled } from '../contexts/ClipInteractionContext';\nimport type { GetAnnotationBoxLabelFn } from '../types/annotations';\nimport { PlaylistVisualization } from './PlaylistVisualization';\nimport { PlaylistAnnotationList } from './PlaylistAnnotationList';\n\nexport interface WaveformProps {\n  renderTrackControls?: (trackIndex: number) => ReactNode;\n  /** Custom render function for timescale tick labels. `label` is a formatted string\n   * (bar/beat notation like \"2.3\" in beats mode, or \"m:ss\" in temporal mode). */\n  renderTick?: (label: string, pixelPosition: number) => ReactNode;\n  /** @deprecated Use `renderTick` instead. */\n  renderTimestamp?: (timeMs: number, pixelPosition: number) => ReactNode;\n  /** Custom playhead render function. Receives position (pixels) and color from theme. */\n  renderPlayhead?: RenderPlayheadFunction;\n  annotationControls?: AnnotationAction[];\n  annotationListConfig?: AnnotationActionOptions;\n  annotationTextHeight?: number; // Height in pixels for the annotation text list\n  /**\n   * Custom render function for annotation items in the text list.\n   * Use this to completely customize how each annotation is displayed.\n   */\n  renderAnnotationItem?: (props: RenderAnnotationItemProps) => ReactNode;\n  /**\n   * Custom function to generate the label shown on annotation boxes in the waveform.\n   * Receives the annotation data and its index, returns a string label.\n   * Default: annotation.id\n   */\n  getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n  /** Where to position the active annotation when auto-scrolling: 'center', 'start', 'end', or 'nearest'. Defaults to 'center'. */\n  scrollActivePosition?: ScrollLogicalPosition;\n  /** Which scrollable containers to scroll: 'nearest' (only the annotation list) or 'all' (including viewport). Defaults to 'nearest'. */\n  scrollActiveContainer?: 'nearest' | 'all';\n  className?: string;\n  showClipHeaders?: boolean; // Show headers on clips for visual organization\n  interactiveClips?: boolean; // Enable dragging/trimming interactions on clips (auto-enabled by ClipInteractionProvider)\n  showFades?: boolean; // Show fade in/out overlays on clips\n  /**\n   * Enable mobile-optimized touch interactions.\n   * When true, increases touch target sizes for clip boundaries.\n   * Use with useDragSensors({ touchOptimized: true }) for best results.\n   */\n  touchOptimized?: boolean;\n  /** Callback when a track's close button is clicked. Only renders close button when provided. */\n  onRemoveTrack?: (trackIndex: number) => void;\n  /** Enable vertical track reordering: a drag grip + move up/down buttons on\n   *  each default track control panel. Drag requires ClipInteractionProvider\n   *  (the ambient DragDropProvider); the buttons work regardless. Default: false. */\n  trackReordering?: boolean;\n  // Live recording state for real-time waveform preview\n  recordingState?: {\n    isRecording: boolean;\n    trackId: string; // Which track is being recorded into\n    startSample: number; // Where recording started\n    durationSamples: number; // Current recording length\n    peaks: (Int8Array | Int16Array)[]; // Per-channel live peaks data\n    bits: 8 | 16; // Bit depth of peak values\n    /**\n     * Latency offset (seconds) to skip in the live preview. Absolute replacement\n     * for the auto-computed outputLatency + lookAhead value. Pass the same value\n     * given to useIntegratedRecording so preview and finalized clip match.\n     */\n    latencyOffset?: number;\n  };\n}\n\n/**\n * Waveform visualization component that uses the playlist context.\n *\n * Composes PlaylistVisualization (waveform + tracks) and\n * PlaylistAnnotationList (annotation text list below the waveform).\n */\nexport const Waveform: React.FC<WaveformProps> = ({\n  renderTrackControls,\n  renderTick,\n  renderTimestamp,\n  renderPlayhead,\n  annotationControls,\n  annotationListConfig,\n  annotationTextHeight,\n  renderAnnotationItem,\n  getAnnotationBoxLabel,\n  scrollActivePosition = 'center',\n  scrollActiveContainer = 'nearest',\n  className,\n  showClipHeaders = false,\n  interactiveClips = false,\n  showFades = false,\n  touchOptimized = false,\n  onRemoveTrack,\n  trackReordering = false,\n  recordingState,\n}) => {\n  const { annotations } = usePlaylistState();\n  const clipInteractionEnabled = useClipInteractionEnabled();\n  const effectiveInteractiveClips = interactiveClips || clipInteractionEnabled;\n\n  return (\n    <>\n      <PlaylistVisualization\n        renderTrackControls={renderTrackControls}\n        renderTick={\n          renderTick ??\n          (renderTimestamp\n            ? (label, pix) => renderTimestamp(parseFloat(label) || 0, pix)\n            : undefined)\n        }\n        renderPlayhead={renderPlayhead}\n        annotationControls={annotationControls}\n        getAnnotationBoxLabel={getAnnotationBoxLabel}\n        className={className}\n        showClipHeaders={showClipHeaders}\n        interactiveClips={effectiveInteractiveClips}\n        showFades={showFades}\n        touchOptimized={touchOptimized}\n        onRemoveTrack={onRemoveTrack}\n        trackReordering={trackReordering}\n        recordingState={recordingState}\n      />\n      {annotations.length > 0 && (\n        <PlaylistAnnotationList\n          height={annotationTextHeight}\n          renderAnnotationItem={renderAnnotationItem}\n          controls={annotationControls}\n          annotationListConfig={annotationListConfig}\n          scrollActivePosition={scrollActivePosition}\n          scrollActiveContainer={scrollActiveContainer}\n        />\n      )}\n    </>\n  );\n};\n","import React, { useContext, useRef, useState, useCallback } from 'react';\nimport { DragDropProvider } from '@dnd-kit/react';\nimport { RestrictToHorizontalAxis } from '@dnd-kit/abstract/modifiers';\nimport {\n  Playlist,\n  Track as TrackComponent,\n  Clip,\n  Selection,\n  FadeOverlay,\n  PlaylistInfoContext,\n  DevicePixelRatioProvider,\n  SmartScale,\n  useTheme,\n  waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport { AnnotationIntegrationContext } from '../AnnotationIntegrationContext';\nimport type { Peaks } from '@waveform-playlist/core';\nimport {\n  useMediaElementAnimation,\n  useMediaElementState,\n  useMediaElementControls,\n  useMediaElementData,\n} from '../MediaElementPlaylistContext';\nimport { useAnnotationDragHandlers } from '../hooks/useAnnotationDragHandlers';\nimport { noDropAnimationPlugins } from '../plugins/noDropAnimationPlugins';\nimport { AnimatedMediaElementPlayhead } from './AnimatedMediaElementPlayhead';\nimport { ChannelWithMediaElementProgress } from './ChannelWithMediaElementProgress';\nimport type {\n  AnnotationData,\n  GetAnnotationBoxLabelFn,\n  OnAnnotationUpdateFn,\n} from '../types/annotations';\n\n/**\n * Wrapper that isolates the custom playhead's hooks from MediaElementPlaylist.\n * Calling renderPlayhead() directly inside MediaElementPlaylist's render would\n * merge its hooks into MediaElementPlaylist's hook count, causing \"Rendered more\n * hooks\" errors if renderPlayhead is conditionally provided.\n */\n// Stable refs for MediaElement playback (no AudioContext, so these are always 0)\nconst ZERO_REF: React.RefObject<number> = { current: 0 };\n\nconst CustomMediaElementPlayhead: React.FC<{\n  renderPlayhead: RenderPlayheadFunction;\n  color: string;\n  samplesPerPixel: number;\n  sampleRate: number;\n}> = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {\n  const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n\n  return renderPlayhead({\n    position: ((currentTimeRef.current ?? 0) * sampleRate) / samplesPerPixel,\n    color,\n    isPlaying,\n    currentTimeRef,\n    playbackStartTimeRef: ZERO_REF,\n    audioStartPositionRef: ZERO_REF,\n    samplesPerPixel,\n    sampleRate,\n    controlsOffset: 0,\n  }) as React.ReactElement;\n};\n\nexport interface MediaElementPlaylistProps {\n  /** Custom function to generate the label shown on annotation boxes */\n  getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n  /** Whether annotation boundaries can be edited by dragging. Defaults to false. */\n  editable?: boolean;\n  /** Whether dragging one annotation boundary also moves the adjacent annotation's boundary. Defaults to false. */\n  linkEndpoints?: boolean;\n  /**\n   * Callback when annotations are updated (e.g., boundaries dragged).\n   * Called with the full updated annotations array.\n   */\n  onAnnotationUpdate?: OnAnnotationUpdateFn;\n  /** Custom playhead render function. Receives position, color, and animation refs for smooth 60fps animation. */\n  renderPlayhead?: RenderPlayheadFunction;\n  /** Show fade in/out overlays on the waveform. Defaults to false. */\n  showFades?: boolean;\n  className?: string;\n}\n\n/**\n * Standalone waveform + annotation boxes component for MediaElementPlaylistProvider.\n *\n * Renders the waveform visualization, annotation boxes, selection, and playhead.\n * Does NOT render the annotation text list — use `MediaElementAnnotationList` for that.\n *\n * Must be used inside a `MediaElementPlaylistProvider`.\n *\n * This component can be placed independently in consumer layouts, allowing the\n * waveform and annotation list to be positioned separately (e.g., in different\n * panels or with custom elements between them).\n */\nexport const MediaElementPlaylist: React.FC<MediaElementPlaylistProps> = ({\n  getAnnotationBoxLabel,\n  editable = false,\n  linkEndpoints: linkEndpointsProp = false,\n  onAnnotationUpdate,\n  renderPlayhead,\n  showFades = false,\n  className,\n}) => {\n  const theme = useTheme() as import('@waveform-playlist/ui-components').WaveformPlaylistTheme;\n\n  // MediaElement context hooks\n  const { isPlaying } = useMediaElementAnimation();\n  const { annotations, activeAnnotationId } = useMediaElementState();\n  const annotationIntegration = useContext(AnnotationIntegrationContext);\n  const { play, seekTo, setActiveAnnotationId, setAnnotations, setScrollContainer } =\n    useMediaElementControls();\n  const {\n    duration,\n    peaksDataArray,\n    sampleRate,\n    waveHeight,\n    timeScaleHeight,\n    samplesPerPixel,\n    controls,\n    playoutRef,\n    barWidth,\n    barGap,\n    roundedBars,\n    fadeIn,\n    fadeOut,\n  } = useMediaElementData();\n\n  const [selectionStart, setSelectionStart] = useState(0);\n  const [selectionEnd, setSelectionEnd] = useState(0);\n  const [isSelecting, setIsSelecting] = useState(false);\n\n  // Local ref for scroll container - also register with context for auto-scroll\n  const scrollContainerRef = useRef<HTMLDivElement | null>(null);\n\n  // Callback to register scroll container with context\n  const handleScrollContainerRef = useCallback(\n    (el: HTMLDivElement | null) => {\n      scrollContainerRef.current = el;\n      setScrollContainer(el);\n    },\n    [setScrollContainer]\n  );\n\n  // Calculate dimensions\n  const tracksFullWidth = Math.floor((duration * sampleRate) / samplesPerPixel);\n\n  // Annotation click handler\n  const handleAnnotationClick = useCallback(\n    async (annotation: AnnotationData) => {\n      setActiveAnnotationId(annotation.id);\n      try {\n        await play(annotation.start);\n      } catch (err) {\n        console.error(\n          'waveform-playlist: Failed to start playback for annotation',\n          annotation.id,\n          err\n        );\n      }\n    },\n    [setActiveAnnotationId, play]\n  );\n\n  // Handle annotation boundary updates\n  const handleAnnotationUpdate = useCallback(\n    (updatedAnnotations: AnnotationData[]) => {\n      setAnnotations(updatedAnnotations);\n      onAnnotationUpdate?.(updatedAnnotations);\n    },\n    [setAnnotations, onAnnotationUpdate]\n  );\n\n  // Drag handlers for annotation boundary editing\n  const { onDragStart, onDragMove, onDragEnd } = useAnnotationDragHandlers({\n    annotations,\n    onAnnotationsChange: handleAnnotationUpdate,\n    samplesPerPixel,\n    duration,\n    linkEndpoints: linkEndpointsProp,\n  });\n\n  // Mouse handlers for click-to-seek\n  const mouseDownTimeRef = useRef<number>(0);\n\n  const handleMouseDown = useCallback(\n    (e: React.MouseEvent<HTMLDivElement>) => {\n      const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n      const x = e.clientX - rect.left;\n      const clickTime = (x * samplesPerPixel) / sampleRate;\n\n      mouseDownTimeRef.current = clickTime;\n      setIsSelecting(true);\n      setSelectionStart(clickTime);\n      setSelectionEnd(clickTime);\n    },\n    [samplesPerPixel, sampleRate]\n  );\n\n  const handleMouseMove = useCallback(\n    (e: React.MouseEvent<HTMLDivElement>) => {\n      if (!isSelecting) return;\n\n      const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n      const x = e.clientX - rect.left;\n      const moveTime = (x * samplesPerPixel) / sampleRate;\n\n      setSelectionEnd(moveTime);\n    },\n    [isSelecting, samplesPerPixel, sampleRate]\n  );\n\n  const handleMouseUp = useCallback(\n    (e: React.MouseEvent<HTMLDivElement>) => {\n      if (!isSelecting) return;\n\n      setIsSelecting(false);\n\n      // During playback, use the time captured at mouseDown — auto-scroll shifts the\n      // overlay between mouseDown and mouseUp, so recomputing from getBoundingClientRect()\n      // would produce a wrong (shifted) position.\n      if (isPlaying && playoutRef.current) {\n        const clickTime = Math.max(0, mouseDownTimeRef.current);\n        seekTo(clickTime);\n        setSelectionStart(clickTime);\n        setSelectionEnd(clickTime);\n        playoutRef.current.stop();\n        play(clickTime);\n        return;\n      }\n\n      const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n      const x = e.clientX - rect.left;\n      const endTime = (x * samplesPerPixel) / sampleRate;\n\n      const start = Math.min(selectionStart, endTime);\n      const end = Math.max(selectionStart, endTime);\n\n      // If it's just a click (not a drag), seek to that position\n      if (Math.abs(end - start) < 0.1) {\n        seekTo(start);\n        setSelectionStart(start);\n        setSelectionEnd(start);\n      } else {\n        // It was a drag - finalize the selection\n        setSelectionStart(start);\n        setSelectionEnd(end);\n      }\n    },\n    [isSelecting, selectionStart, samplesPerPixel, sampleRate, seekTo, isPlaying, playoutRef, play]\n  );\n\n  // Show loading if peaks not ready\n  if (peaksDataArray.length === 0) {\n    return <div className={className}>Loading waveform...</div>;\n  }\n\n  return (\n    <DevicePixelRatioProvider>\n      <PlaylistInfoContext.Provider\n        value={{\n          samplesPerPixel,\n          sampleRate,\n          zoomLevels: [samplesPerPixel],\n          waveHeight,\n          timeScaleHeight,\n          duration: duration * 1000,\n          controls,\n          barWidth,\n          barGap,\n          roundedBars,\n        }}\n      >\n        <Playlist\n          theme={theme}\n          backgroundColor={waveformColorToCss(theme.waveOutlineColor)}\n          timescaleBackgroundColor={theme.timescaleBackgroundColor}\n          timescaleWidth={tracksFullWidth}\n          tracksWidth={tracksFullWidth}\n          controlsWidth={controls.show ? controls.width : 0}\n          onTracksMouseDown={handleMouseDown}\n          onTracksMouseMove={handleMouseMove}\n          onTracksMouseUp={handleMouseUp}\n          scrollContainerRef={handleScrollContainerRef}\n          isSelecting={isSelecting}\n          timescale={timeScaleHeight > 0 ? <SmartScale /> : undefined}\n        >\n          <>\n            {peaksDataArray.map((trackClipPeaks, trackIndex) => {\n              // For MediaElement, we have a single track with a single clip\n              const maxChannels =\n                trackClipPeaks.length > 0\n                  ? Math.max(...trackClipPeaks.map((clip) => clip.peaks.data.length))\n                  : 1;\n\n              return (\n                <TrackComponent\n                  key={trackIndex}\n                  numChannels={maxChannels}\n                  backgroundColor={waveformColorToCss(theme.waveOutlineColor)}\n                  offset={0}\n                  width={tracksFullWidth}\n                  hasClipHeaders={false}\n                  trackId={`media-element-track-${trackIndex}`}\n                  isSelected={true}\n                >\n                  {trackClipPeaks.map((clip, clipIndex) => {\n                    const peaksData = clip.peaks;\n                    const width = peaksData.length;\n\n                    return (\n                      <Clip\n                        key={`${trackIndex}-${clipIndex}`}\n                        clipId={clip.clipId}\n                        trackIndex={trackIndex}\n                        clipIndex={clipIndex}\n                        trackName={clip.trackName}\n                        startSample={clip.startSample}\n                        durationSamples={clip.durationSamples}\n                        samplesPerPixel={samplesPerPixel}\n                        showHeader={false}\n                        disableHeaderDrag={true}\n                        isSelected={true}\n                        trackId={`media-element-track-${trackIndex}`}\n                      >\n                        {peaksData.data.map((channelPeaks: Peaks, channelIndex: number) => (\n                          <ChannelWithMediaElementProgress\n                            key={`${trackIndex}-${clipIndex}-${channelIndex}`}\n                            index={channelIndex}\n                            data={channelPeaks}\n                            bits={peaksData.bits}\n                            length={width}\n                            clipStartSample={clip.startSample}\n                            clipDurationSamples={clip.durationSamples}\n                          />\n                        ))}\n                        {showFades && fadeIn && fadeIn.duration > 0 && (\n                          <FadeOverlay\n                            left={0}\n                            width={Math.floor((fadeIn.duration * sampleRate) / samplesPerPixel)}\n                            type=\"fadeIn\"\n                            curveType={fadeIn.type}\n                          />\n                        )}\n                        {showFades && fadeOut && fadeOut.duration > 0 && (\n                          <FadeOverlay\n                            left={\n                              width - Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)\n                            }\n                            width={Math.floor((fadeOut.duration * sampleRate) / samplesPerPixel)}\n                            type=\"fadeOut\"\n                            curveType={fadeOut.type}\n                          />\n                        )}\n                      </Clip>\n                    );\n                  })}\n                </TrackComponent>\n              );\n            })}\n            {annotations.length > 0 && annotationIntegration && (\n              <DragDropProvider\n                onDragStart={onDragStart}\n                onDragMove={onDragMove}\n                onDragEnd={onDragEnd}\n                modifiers={editable ? [RestrictToHorizontalAxis] : []}\n                plugins={noDropAnimationPlugins}\n              >\n                <annotationIntegration.AnnotationBoxesWrapper height={30} width={tracksFullWidth}>\n                  {annotations.map((annotation, index) => {\n                    const startPosition = (annotation.start * sampleRate) / samplesPerPixel;\n                    const endPosition = (annotation.end * sampleRate) / samplesPerPixel;\n                    const label = getAnnotationBoxLabel\n                      ? getAnnotationBoxLabel(annotation, index)\n                      : annotation.id;\n                    return (\n                      <annotationIntegration.AnnotationBox\n                        key={annotation.id}\n                        annotationId={annotation.id}\n                        annotationIndex={index}\n                        startPosition={startPosition}\n                        endPosition={endPosition}\n                        label={label}\n                        color=\"#ff9800\"\n                        isActive={annotation.id === activeAnnotationId}\n                        onClick={() => handleAnnotationClick(annotation)}\n                        editable={editable}\n                      />\n                    );\n                  })}\n                </annotationIntegration.AnnotationBoxesWrapper>\n              </DragDropProvider>\n            )}\n            {selectionStart !== selectionEnd && (\n              <Selection\n                startPosition={\n                  (Math.min(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n                }\n                endPosition={\n                  (Math.max(selectionStart, selectionEnd) * sampleRate) / samplesPerPixel\n                }\n                color={theme.selectionColor}\n              />\n            )}\n            {renderPlayhead ? (\n              <CustomMediaElementPlayhead\n                renderPlayhead={renderPlayhead}\n                color={theme.playheadColor}\n                samplesPerPixel={samplesPerPixel}\n                sampleRate={sampleRate}\n              />\n            ) : (\n              <AnimatedMediaElementPlayhead color={theme.playheadColor} />\n            )}\n          </>\n        </Playlist>\n      </PlaylistInfoContext.Provider>\n    </DevicePixelRatioProvider>\n  );\n};\n","import { Feedback } from '@dnd-kit/dom';\nimport type { Plugins } from '@dnd-kit/abstract';\n\n/**\n * DragDropProvider plugins customizer that disables the Feedback plugin's drop animation.\n *\n * Without this, the Feedback plugin animates the dragged element back to its original\n * position on drop, causing a visual snap-back before React re-renders at the new position.\n *\n * Usage:\n * ```tsx\n * <DragDropProvider plugins={noDropAnimationPlugins} ...>\n * ```\n */\nexport const noDropAnimationPlugins = (defaults: Plugins): Plugins => {\n  let feedbackFound = false;\n  const result = defaults.map((p) => {\n    if (p === Feedback) {\n      feedbackFound = true;\n      return Feedback.configure({ dropAnimation: null });\n    }\n    return p;\n  });\n  if (!feedbackFound) {\n    console.warn(\n      '[waveform-playlist] noDropAnimationPlugins: Feedback plugin not found in defaults — ' +\n        'drop animation may not be disabled. Check @dnd-kit/dom version.'\n    );\n  }\n  return result;\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { useMediaElementAnimation, useMediaElementData } from '../MediaElementPlaylistContext';\n\nconst PlayheadLine = styled.div<{ $color: string; $width: number }>`\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: ${(props) => props.$width}px;\n  background: ${(props) => props.$color};\n  height: 100%;\n  z-index: 100;\n  pointer-events: none;\n  will-change: transform;\n`;\n\ninterface AnimatedMediaElementPlayheadProps {\n  color?: string;\n}\n\n/**\n * Animated playhead for MediaElementPlaylistProvider.\n * Uses the MediaElement context for time tracking instead of Tone.js audio context.\n * Updates position via direct DOM manipulation for smooth 60fps animation.\n */\nexport const AnimatedMediaElementPlayhead: React.FC<AnimatedMediaElementPlayheadProps> = ({\n  color = '#ff0000',\n}) => {\n  const playheadRef = useRef<HTMLDivElement>(null);\n  const animationFrameRef = useRef<number | null>(null);\n\n  const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n  const { samplesPerPixel, sampleRate, progressBarWidth } = useMediaElementData();\n\n  useEffect(() => {\n    const updatePosition = () => {\n      if (playheadRef.current) {\n        // Get current time from the ref (updated by animation loop in context)\n        const time = currentTimeRef.current ?? 0;\n        const position = (time * sampleRate) / samplesPerPixel;\n        playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n      }\n\n      if (isPlaying) {\n        animationFrameRef.current = requestAnimationFrame(updatePosition);\n      }\n    };\n\n    if (isPlaying) {\n      // Start animation loop\n      animationFrameRef.current = requestAnimationFrame(updatePosition);\n    } else {\n      // When stopped, update once to show final position\n      updatePosition();\n    }\n\n    return () => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n        animationFrameRef.current = null;\n      }\n    };\n  }, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef]);\n\n  // Also update position when not playing (for seeks, stops, etc.)\n  useEffect(() => {\n    if (!isPlaying && playheadRef.current) {\n      const time = currentTimeRef.current ?? 0;\n      const position = (time * sampleRate) / samplesPerPixel;\n      playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;\n    }\n  });\n\n  return <PlayheadLine ref={playheadRef} $color={color} $width={progressBarWidth} data-playhead />;\n};\n","import React, { useRef, useEffect } from 'react';\nimport styled from 'styled-components';\nimport {\n  SmartChannel,\n  type SmartChannelProps,\n  useTheme,\n  usePlaylistInfo,\n  type WaveformPlaylistTheme,\n  waveformColorToCss,\n} from '@waveform-playlist/ui-components';\nimport { useMediaElementAnimation, useMediaElementData } from '../MediaElementPlaylistContext';\n\nconst ChannelWrapper = styled.div`\n  position: relative;\n`;\n\ninterface BackgroundProps {\n  readonly $color: string;\n  readonly $height: number;\n  readonly $top: number;\n  readonly $width: number;\n}\n\nconst Background = styled.div<BackgroundProps>`\n  position: absolute;\n  top: ${(props) => props.$top}px;\n  left: 0;\n  width: ${(props) => props.$width}px;\n  height: ${(props) => props.$height}px;\n  background: ${(props) => props.$color};\n  z-index: 0;\n  transform: translateZ(0);\n  backface-visibility: hidden;\n`;\n\ninterface ProgressOverlayProps {\n  readonly $color: string;\n  readonly $height: number;\n  readonly $top: number;\n}\n\nconst ProgressOverlay = styled.div<ProgressOverlayProps>`\n  position: absolute;\n  top: ${(props) => props.$top}px;\n  left: 0;\n  height: ${(props) => props.$height}px;\n  background: ${(props) => props.$color};\n  pointer-events: none;\n  z-index: 1;\n  will-change: width;\n`;\n\nconst ChannelContainer = styled.div`\n  position: relative;\n  z-index: 2;\n`;\n\nexport interface ChannelWithMediaElementProgressProps\n  extends Omit<SmartChannelProps, 'isSelected'> {\n  /** Start sample of the clip containing this channel (for progress calculation) */\n  clipStartSample: number;\n  /** Duration in samples of the clip */\n  clipDurationSamples: number;\n}\n\n/**\n * SmartChannel wrapper for MediaElementPlaylistProvider with animated progress overlay.\n * Uses MediaElement context for time tracking instead of Tone.js audio context.\n */\nexport const ChannelWithMediaElementProgress: React.FC<ChannelWithMediaElementProgressProps> = ({\n  clipStartSample,\n  clipDurationSamples,\n  ...smartChannelProps\n}) => {\n  const progressRef = useRef<HTMLDivElement>(null);\n  const animationFrameRef = useRef<number | null>(null);\n  const theme = useTheme() as WaveformPlaylistTheme;\n  const { waveHeight } = usePlaylistInfo();\n\n  const { isPlaying, currentTimeRef } = useMediaElementAnimation();\n  const { samplesPerPixel, sampleRate } = useMediaElementData();\n\n  const progressColor = theme?.waveProgressColor || 'rgba(0, 0, 0, 0.1)';\n\n  useEffect(() => {\n    const updateProgress = () => {\n      if (progressRef.current) {\n        // Get current time from the ref\n        const currentTime = currentTimeRef.current ?? 0;\n\n        // Convert current time to samples\n        const currentSample = currentTime * sampleRate;\n\n        // Calculate clip bounds in samples\n        const clipEndSample = clipStartSample + clipDurationSamples;\n\n        // Calculate how much of this clip has been played\n        let progressWidth = 0;\n\n        if (currentSample <= clipStartSample) {\n          // Playhead is before this clip - no progress\n          progressWidth = 0;\n        } else if (currentSample >= clipEndSample) {\n          // Playhead is past this clip - full progress\n          progressWidth = smartChannelProps.length;\n        } else {\n          // Playhead is within this clip - partial progress\n          const playedSamples = currentSample - clipStartSample;\n          progressWidth = Math.floor(playedSamples / samplesPerPixel);\n        }\n\n        progressRef.current.style.width = `${progressWidth}px`;\n      }\n\n      if (isPlaying) {\n        animationFrameRef.current = requestAnimationFrame(updateProgress);\n      }\n    };\n\n    if (isPlaying) {\n      animationFrameRef.current = requestAnimationFrame(updateProgress);\n    } else {\n      // When stopped, update once to show final position\n      updateProgress();\n    }\n\n    return () => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n        animationFrameRef.current = null;\n      }\n    };\n  }, [\n    isPlaying,\n    sampleRate,\n    samplesPerPixel,\n    clipStartSample,\n    clipDurationSamples,\n    smartChannelProps.length,\n    currentTimeRef,\n  ]);\n\n  // Also update when not playing (for seeks, stops, etc.)\n  useEffect(() => {\n    if (!isPlaying && progressRef.current) {\n      const currentTime = currentTimeRef.current ?? 0;\n      const currentSample = currentTime * sampleRate;\n      const clipEndSample = clipStartSample + clipDurationSamples;\n\n      let progressWidth = 0;\n      if (currentSample <= clipStartSample) {\n        progressWidth = 0;\n      } else if (currentSample >= clipEndSample) {\n        progressWidth = smartChannelProps.length;\n      } else {\n        const playedSamples = currentSample - clipStartSample;\n        progressWidth = Math.floor(playedSamples / samplesPerPixel);\n      }\n\n      progressRef.current.style.width = `${progressWidth}px`;\n    }\n  });\n\n  // Get the draw mode from theme (defaults to 'inverted')\n  const drawMode = theme?.waveformDrawMode || 'inverted';\n\n  let backgroundColor;\n  if (drawMode === 'inverted') {\n    // For MediaElement, always treat as selected (single track)\n    backgroundColor = theme?.selectedWaveFillColor || theme?.waveFillColor || 'white';\n  } else {\n    backgroundColor = theme?.selectedWaveOutlineColor || theme?.waveOutlineColor || 'grey';\n  }\n\n  const backgroundCss = waveformColorToCss(backgroundColor);\n\n  return (\n    <ChannelWrapper>\n      <Background\n        $color={backgroundCss}\n        $height={waveHeight}\n        $top={smartChannelProps.index * waveHeight}\n        $width={smartChannelProps.length}\n      />\n      <ProgressOverlay\n        ref={progressRef}\n        $color={progressColor}\n        $height={waveHeight}\n        $top={smartChannelProps.index * waveHeight}\n      />\n      <ChannelContainer>\n        <SmartChannel {...smartChannelProps} isSelected={true} transparentBackground />\n      </ChannelContainer>\n    </ChannelWrapper>\n  );\n};\n","import React, { useCallback } from 'react';\nimport type {\n  AnnotationData,\n  AnnotationAction,\n  AnnotationActionOptions,\n  RenderAnnotationItemProps,\n} from '@waveform-playlist/core';\nimport { useAnnotationIntegration } from '../AnnotationIntegrationContext';\nimport { useMediaElementState, useMediaElementControls } from '../MediaElementPlaylistContext';\nimport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport type { OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface MediaElementAnnotationListProps {\n  /** Height in pixels for the annotation text list */\n  height?: number;\n  /**\n   * Custom render function for annotation items in the text list.\n   * When provided, completely replaces the default annotation item rendering.\n   */\n  renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n  /**\n   * Callback when annotations are updated (e.g., text edited).\n   * Called with the full updated annotations array.\n   */\n  onAnnotationUpdate?: OnAnnotationUpdateFn;\n  /** Whether annotation text can be edited. Defaults to false. */\n  editable?: boolean;\n  /**\n   * Action controls to show on each annotation item (e.g., delete, split).\n   * Only rendered when `editable` is true.\n   */\n  controls?: AnnotationAction[];\n  /**\n   * Override annotation list config. Falls back to context values\n   * `{ linkEndpoints: false, continuousPlay }` if not provided.\n   */\n  annotationListConfig?: AnnotationActionOptions;\n  /** Where to position the active annotation when auto-scrolling. Defaults to 'center'. */\n  scrollActivePosition?: ScrollLogicalPosition;\n  /** Which scrollable containers to scroll: 'nearest' or 'all'. Defaults to 'nearest'. */\n  scrollActiveContainer?: 'nearest' | 'all';\n}\n\n/**\n * Standalone annotation text list component for MediaElementPlaylistProvider.\n *\n * Requires @waveform-playlist/annotations with AnnotationProvider.\n * Throws if used without `<AnnotationProvider>` wrapping the component tree.\n */\nexport const MediaElementAnnotationList: React.FC<MediaElementAnnotationListProps> = ({\n  height,\n  renderAnnotationItem,\n  onAnnotationUpdate,\n  editable = false,\n  controls,\n  annotationListConfig,\n  scrollActivePosition = 'center',\n  scrollActiveContainer = 'nearest',\n}) => {\n  const { annotations, activeAnnotationId, continuousPlay } = useMediaElementState();\n  const integration = useAnnotationIntegration();\n  const { setAnnotations } = useMediaElementControls();\n\n  const resolvedConfig = annotationListConfig ?? { linkEndpoints: false, continuousPlay };\n\n  const handleAnnotationUpdate = useCallback(\n    (updatedAnnotations: AnnotationData[]) => {\n      setAnnotations(updatedAnnotations);\n      onAnnotationUpdate?.(updatedAnnotations);\n    },\n    [setAnnotations, onAnnotationUpdate]\n  );\n\n  const { AnnotationText } = integration;\n\n  return (\n    <AnnotationText\n      annotations={annotations}\n      activeAnnotationId={activeAnnotationId ?? undefined}\n      shouldScrollToActive={true}\n      scrollActivePosition={scrollActivePosition}\n      scrollActiveContainer={scrollActiveContainer}\n      editable={editable}\n      controls={editable ? controls : undefined}\n      annotationListConfig={resolvedConfig}\n      height={height}\n      onAnnotationUpdate={handleAnnotationUpdate}\n      renderAnnotationItem={renderAnnotationItem}\n    />\n  );\n};\n","import React from 'react';\nimport type { RenderAnnotationItemProps } from '@waveform-playlist/core';\nimport type { RenderPlayheadFunction } from '@waveform-playlist/ui-components';\nimport { useMediaElementState } from '../MediaElementPlaylistContext';\nimport type { GetAnnotationBoxLabelFn, OnAnnotationUpdateFn } from '../types/annotations';\nimport { MediaElementPlaylist } from './MediaElementPlaylist';\nimport { MediaElementAnnotationList } from './MediaElementAnnotationList';\n\n// Re-export annotation types for convenience\nexport type { GetAnnotationBoxLabelFn, OnAnnotationUpdateFn } from '../types/annotations';\n\nexport interface MediaElementWaveformProps {\n  /** Height in pixels for the annotation text list */\n  annotationTextHeight?: number;\n  /** Custom function to generate the label shown on annotation boxes */\n  getAnnotationBoxLabel?: GetAnnotationBoxLabelFn;\n  /**\n   * Custom render function for annotation items in the text list.\n   * When provided, completely replaces the default annotation item rendering.\n   * Use this to customize the appearance of each annotation (e.g., add furigana).\n   */\n  renderAnnotationItem?: (props: RenderAnnotationItemProps) => React.ReactNode;\n  /** Whether annotation boundaries can be edited by dragging. Defaults to false. */\n  editable?: boolean;\n  /** Whether dragging one annotation boundary also moves the adjacent annotation's boundary. Defaults to false. */\n  linkEndpoints?: boolean;\n  /**\n   * Callback when annotations are updated (e.g., boundaries dragged).\n   * Called with the full updated annotations array.\n   */\n  onAnnotationUpdate?: OnAnnotationUpdateFn;\n  /** Where to position the active annotation when auto-scrolling: 'center', 'start', 'end', or 'nearest'. Defaults to 'center'. */\n  scrollActivePosition?: ScrollLogicalPosition;\n  /** Which scrollable containers to scroll: 'nearest' (only the annotation list) or 'all' (including viewport). Defaults to 'nearest'. */\n  scrollActiveContainer?: 'nearest' | 'all';\n  /** Custom playhead render function. Receives position, color, and animation refs for smooth 60fps animation. */\n  renderPlayhead?: RenderPlayheadFunction;\n  /** Show fade in/out overlays on the waveform. Defaults to false. */\n  showFades?: boolean;\n  className?: string;\n}\n\n/**\n * Simplified Waveform component for MediaElementPlaylistProvider\n *\n * This is a stripped-down version of Waveform that works with the\n * MediaElement context. It supports:\n * - Single track visualization\n * - Click to seek\n * - Annotation display and click-to-play\n * - Playhead animation\n *\n * For multi-track editing, use the full Waveform with WaveformPlaylistProvider.\n */\nexport const MediaElementWaveform: React.FC<MediaElementWaveformProps> = ({\n  annotationTextHeight,\n  getAnnotationBoxLabel,\n  renderAnnotationItem,\n  editable = false,\n  linkEndpoints = false,\n  onAnnotationUpdate,\n  scrollActivePosition = 'center',\n  scrollActiveContainer = 'nearest',\n  renderPlayhead,\n  showFades = false,\n  className,\n}) => {\n  const { annotations } = useMediaElementState();\n\n  return (\n    <>\n      <MediaElementPlaylist\n        getAnnotationBoxLabel={getAnnotationBoxLabel}\n        editable={editable}\n        linkEndpoints={linkEndpoints}\n        onAnnotationUpdate={onAnnotationUpdate}\n        renderPlayhead={renderPlayhead}\n        showFades={showFades}\n        className={className}\n      />\n      {annotations.length > 0 && (\n        <MediaElementAnnotationList\n          height={annotationTextHeight}\n          renderAnnotationItem={renderAnnotationItem}\n          onAnnotationUpdate={onAnnotationUpdate}\n          editable={editable}\n          annotationListConfig={{ linkEndpoints, continuousPlay: false }}\n          scrollActivePosition={scrollActivePosition}\n          scrollActiveContainer={scrollActiveContainer}\n        />\n      )}\n    </>\n  );\n};\n","import React from 'react';\nimport type { KeyboardShortcut } from '../hooks/useKeyboardShortcuts';\nimport { usePlaybackShortcuts } from '../hooks/usePlaybackShortcuts';\nimport { useClipSplitting } from '../hooks/useClipSplitting';\nimport { useAnnotationKeyboardControls } from '../hooks/useAnnotationKeyboardControls';\nimport { usePlaylistData, usePlaylistState, usePlaylistControls } from '../WaveformPlaylistContext';\n\nexport interface KeyboardShortcutsProps {\n  /** Enable default playback shortcuts (Space, Escape, 0). Defaults to false. */\n  playback?: boolean;\n  /** Enable clip splitting shortcut ('s' key). Defaults to false. */\n  clipSplitting?: boolean;\n  /** Enable annotation keyboard controls (arrow nav, boundary editing). Defaults to false. */\n  annotations?: boolean;\n  /** Enable undo/redo shortcuts (Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z). Defaults to false. */\n  undo?: boolean;\n  /** Additional shortcuts appended to the defaults. */\n  additionalShortcuts?: KeyboardShortcut[];\n}\n\n/**\n * Self-closing component that sets up keyboard shortcuts for the playlist.\n * Must be rendered inside a WaveformPlaylistProvider.\n *\n * @example\n * ```tsx\n * <WaveformPlaylistProvider tracks={tracks} {...}>\n *   <KeyboardShortcuts playback clipSplitting />\n *   <Waveform />\n * </WaveformPlaylistProvider>\n * ```\n */\nexport const KeyboardShortcuts: React.FC<KeyboardShortcutsProps> = ({\n  playback = false,\n  clipSplitting = false,\n  annotations = false,\n  undo: undoEnabled = false,\n  additionalShortcuts = [],\n}) => {\n  // Clip splitting setup\n  const { tracks, samplesPerPixel, playoutRef, duration } = usePlaylistData();\n  const {\n    annotations: annotationList,\n    linkEndpoints,\n    activeAnnotationId,\n    continuousPlay,\n  } = usePlaylistState();\n  const { setAnnotations, setActiveAnnotationId, scrollContainerRef, play, undo, redo } =\n    usePlaylistControls();\n\n  const { splitClipAtPlayhead } = useClipSplitting({\n    tracks,\n    samplesPerPixel,\n    engineRef: playoutRef,\n  });\n\n  // Build additional shortcuts from enabled features\n  const allAdditional: KeyboardShortcut[] = [];\n\n  if (clipSplitting) {\n    allAdditional.push({\n      key: 's',\n      action: splitClipAtPlayhead,\n      description: 'Split clip at playhead',\n      preventDefault: true,\n    });\n  }\n\n  if (undoEnabled) {\n    allAdditional.push(\n      { key: 'z', ctrlKey: true, shiftKey: false, action: undo, description: 'Undo' },\n      { key: 'z', metaKey: true, shiftKey: false, action: undo, description: 'Undo' },\n      { key: 'z', ctrlKey: true, shiftKey: true, action: redo, description: 'Redo' },\n      { key: 'z', metaKey: true, shiftKey: true, action: redo, description: 'Redo' }\n    );\n  }\n\n  if (additionalShortcuts.length > 0) {\n    allAdditional.push(...additionalShortcuts);\n  }\n\n  // Playback shortcuts (or just additional if playback is off)\n  usePlaybackShortcuts({\n    enabled: playback || allAdditional.length > 0,\n    ...(playback ? { additionalShortcuts: allAdditional } : { shortcuts: allAdditional }),\n  });\n\n  // Annotation keyboard controls\n  useAnnotationKeyboardControls({\n    annotations: annotationList,\n    activeAnnotationId,\n    onAnnotationsChange: setAnnotations,\n    onActiveAnnotationChange: setActiveAnnotationId,\n    duration,\n    linkEndpoints,\n    continuousPlay,\n    scrollContainerRef,\n    onPlay: play,\n    enabled: annotations && annotationList.length > 0,\n  });\n\n  return null;\n};\n","import React, { useEffect, useMemo } from 'react';\nimport { DragDropProvider } from '@dnd-kit/react';\nimport { RestrictToHorizontalAxis } from '@dnd-kit/abstract/modifiers';\nimport type { Modifiers } from '@dnd-kit/abstract';\nimport {\n  samplesToTicks,\n  ticksToSamples,\n  snapToGrid,\n  ticksPerBeat,\n  ticksPerBar,\n} from '@waveform-playlist/core';\nimport { useBeatsAndBars, getScaleInfo } from '@waveform-playlist/ui-components';\n\nimport { usePlaylistData, usePlaylistControls } from '../WaveformPlaylistContext';\nimport { useClipDragHandlers } from '../hooks/useClipDragHandlers';\nimport { useDragSensors } from '../hooks/useDragSensors';\nimport { ClipCollisionModifier } from '../modifiers/ClipCollisionModifier';\nimport { SnapToGridModifier } from '../modifiers/SnapToGridModifier';\nimport { noDropAnimationPlugins } from '../plugins/noDropAnimationPlugins';\nimport { ClipInteractionContextProvider } from '../contexts/ClipInteractionContext';\n\n// Stable noop to avoid creating a new function reference on every render\nconst NOOP_TRACKS_CHANGE = () => {};\n\nexport interface ClipInteractionProviderProps {\n  /** Enable snap-to-grid for clip moves and boundary trims. When true,\n   *  auto-detects beats snapping from BeatsAndBarsProvider context\n   *  (if present with scaleMode=\"beats\" and snapTo!=\"off\"), otherwise\n   *  falls back to timescale-based snapping. Default: false. */\n  snap?: boolean;\n  touchOptimized?: boolean;\n  children: React.ReactNode;\n}\n\nexport const ClipInteractionProvider: React.FC<ClipInteractionProviderProps> = ({\n  snap = false,\n  touchOptimized = false,\n  children,\n}) => {\n  const { tracks, samplesPerPixel, sampleRate, playoutRef, isDraggingRef, onTracksChange } =\n    usePlaylistData();\n  const { setSelectedTrackId, reorderTrack, bumpTrackReorderEpoch, setTrackDragPreview } =\n    usePlaylistControls();\n  const beatsAndBars = useBeatsAndBars();\n\n  // Derive snap mode from context: beats if provider is in beats mode with snap enabled,\n  // timescale otherwise, off when snap prop is false.\n  const useBeatsSnap =\n    snap &&\n    beatsAndBars != null &&\n    beatsAndBars.scaleMode === 'beats' &&\n    beatsAndBars.snapTo !== 'off';\n  const useTimescaleSnap = snap && !useBeatsSnap;\n\n  // Warn once if onTracksChange is missing — drag/trim edits will be lost\n  useEffect(() => {\n    if (onTracksChange == null) {\n      console.warn(\n        '[waveform-playlist] ClipInteractionProvider: onTracksChange is not set on ' +\n          'WaveformPlaylistProvider. Drag and trim edits will not be persisted.'\n      );\n    }\n  }, [onTracksChange]);\n\n  // Build snapSamplePosition for boundary trim snapping\n  const snapSamplePosition = useMemo(() => {\n    if (useBeatsSnap && beatsAndBars) {\n      const { bpm, timeSignature, snapTo } = beatsAndBars;\n      const gridTicks = snapTo === 'bar' ? ticksPerBar(timeSignature) : ticksPerBeat(timeSignature);\n      return (samplePos: number) => {\n        const ticks = samplesToTicks(samplePos, bpm, sampleRate);\n        const snapped = snapToGrid(ticks, gridTicks);\n        return ticksToSamples(snapped, bpm, sampleRate);\n      };\n    }\n    if (useTimescaleSnap) {\n      const gridSamples = Math.round((getScaleInfo(samplesPerPixel).smallStep / 1000) * sampleRate);\n      return (samplePos: number) => Math.round(samplePos / gridSamples) * gridSamples;\n    }\n    return undefined;\n  }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, sampleRate, samplesPerPixel]);\n\n  // Sensors\n  const sensors = useDragSensors({ touchOptimized });\n\n  // Drag handlers\n  const {\n    onDragStart: handleDragStart,\n    onDragMove,\n    onDragEnd,\n  } = useClipDragHandlers({\n    tracks,\n    onTracksChange: onTracksChange ?? NOOP_TRACKS_CHANGE,\n    samplesPerPixel,\n    engineRef: playoutRef,\n    isDraggingRef,\n    snapSamplePosition,\n  });\n\n  // Wrap onDragStart to auto-select track\n  const onDragStart = React.useCallback(\n    (event: Parameters<typeof handleDragStart>[0]) => {\n      const data = event.operation?.source?.data as\n        | { kind?: string; trackId?: string; trackIndex?: number }\n        | undefined;\n      if (data?.kind === 'track-reorder') {\n        if (data.trackId) setSelectedTrackId(data.trackId);\n        return; // clip handler guards also skip this kind\n      }\n      const trackIndex = data?.trackIndex;\n      if (trackIndex !== undefined && tracks[trackIndex]) {\n        setSelectedTrackId(tracks[trackIndex].id);\n      }\n      handleDragStart(event);\n    },\n    [handleDragStart, tracks, setSelectedTrackId]\n  );\n\n  // Publish the live track-reorder preview. dnd-kit fires dragover on every\n  // collision change; OptimisticSortingPlugin has already updated\n  // source.sortable.index by then. Functional set with an equality guard so\n  // repeated dragovers at the same index don't re-render the playlist.\n  const handleDragOver = React.useCallback(\n    (event: { operation: { source?: { data?: unknown } | null } }) => {\n      const data = event.operation.source?.data as { kind?: string; trackId?: string } | undefined;\n      if (data?.kind !== 'track-reorder' || !data.trackId) return;\n      const sortable = (\n        event.operation.source as unknown as {\n          sortable?: { index: number; initialIndex: number };\n        }\n      ).sortable;\n      if (!sortable) return;\n      const trackId = data.trackId;\n      const toIndex = sortable.index;\n      setTrackDragPreview((prev) =>\n        prev && prev.trackId === trackId && prev.toIndex === toIndex ? prev : { trackId, toIndex }\n      );\n    },\n    [setTrackDragPreview]\n  );\n\n  // Wrap onDragEnd: track-reorder sources commit via reorderTrack, clip sources\n  // flow through to the existing move/trim handler unchanged.\n  const handleDragEnd = React.useCallback(\n    (event: Parameters<typeof onDragEnd>[0]) => {\n      const data = event.operation.source?.data as { kind?: string; trackId?: string } | undefined;\n      if (data?.kind === 'track-reorder') {\n        // Unconditional, regardless of canceled/committed: @dnd-kit/react's\n        // sortable plugins can splice React-owned DOM nodes (Feedback's\n        // placeholder, OptimisticSortingPlugin's reorder()) during the drag\n        // that just ended, desyncing React's fiber tree from the true DOM.\n        // Bumping this forces PlaylistVisualization to remount the\n        // track-controls slots, healing the corruption before the next\n        // reorder (drag or button) needs to touch the DOM. See\n        // bumpTrackReorderEpoch's doc comment in WaveformPlaylistContext.\n        bumpTrackReorderEpoch();\n        // Clear the preview in the same handler that commits: React batches this\n        // with reorderTrack's state updates, so the columns go straight from\n        // previewed layout to committed layout with no intermediate frame.\n        setTrackDragPreview(null);\n        if (event.canceled || !data.trackId) return;\n        const sortable = (\n          event.operation.source as unknown as {\n            sortable?: { index: number; initialIndex: number };\n          }\n        ).sortable;\n        if (sortable && sortable.index !== sortable.initialIndex) {\n          reorderTrack(data.trackId, sortable.index);\n        }\n        return;\n      }\n      onDragEnd(event);\n    },\n    [onDragEnd, reorderTrack, bumpTrackReorderEpoch, setTrackDragPreview]\n  );\n\n  // Build modifiers array\n  const modifiers = useMemo(() => {\n    const mods: Modifiers = [RestrictToHorizontalAxis];\n\n    if (useBeatsSnap && beatsAndBars) {\n      mods.push(\n        SnapToGridModifier.configure({\n          mode: 'beats',\n          snapTo: beatsAndBars.snapTo,\n          bpm: beatsAndBars.bpm,\n          timeSignature: beatsAndBars.timeSignature,\n          samplesPerPixel,\n          sampleRate,\n        })\n      );\n    } else if (useTimescaleSnap) {\n      mods.push(\n        SnapToGridModifier.configure({\n          mode: 'timescale',\n          gridSamples: Math.round((getScaleInfo(samplesPerPixel).smallStep / 1000) * sampleRate),\n          samplesPerPixel,\n        })\n      );\n    }\n\n    mods.push(ClipCollisionModifier.configure({ tracks, samplesPerPixel }));\n    return mods;\n  }, [useBeatsSnap, useTimescaleSnap, beatsAndBars, tracks, samplesPerPixel, sampleRate]);\n\n  return (\n    <ClipInteractionContextProvider value={true}>\n      <DragDropProvider\n        sensors={sensors}\n        onDragStart={onDragStart}\n        onDragMove={onDragMove}\n        onDragOver={handleDragOver}\n        onDragEnd={handleDragEnd}\n        modifiers={modifiers}\n        plugins={noDropAnimationPlugins}\n      >\n        {children}\n      </DragDropProvider>\n    </ClipInteractionContextProvider>\n  );\n};\n","import {\n  Modifier,\n  configurator,\n  type DragDropManager,\n  type DragOperation,\n} from '@dnd-kit/abstract';\nimport { constrainClipDrag } from '@waveform-playlist/engine';\nimport type { ClipTrack, AudioClip } from '@waveform-playlist/core';\n\ninterface ClipCollisionOptions {\n  tracks: ClipTrack[];\n  samplesPerPixel: number;\n}\n\n/**\n * Modifier that constrains clip drag movement to prevent overlaps.\n *\n * For clip move operations: constrains horizontal transform to valid positions\n * using the engine's collision detection.\n *\n * For boundary trim operations: returns zero transform because visual feedback\n * comes from React state updates resizing the clip, not from CSS translate.\n */\nexport class ClipCollisionModifier extends Modifier<\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  DragDropManager<any, any>,\n  ClipCollisionOptions\n> {\n  apply(operation: DragOperation): { x: number; y: number } {\n    const { transform, source } = operation;\n\n    if (!source?.data || !this.options) return transform;\n\n    const { boundary, trackIndex, clipIndex } = source.data as {\n      boundary?: 'left' | 'right';\n      trackIndex: number;\n      clipIndex: number;\n    };\n\n    // Boundary trims: zero transform (visual feedback from React state updates)\n    if (boundary) return { x: 0, y: 0 };\n\n    const { tracks, samplesPerPixel } = this.options;\n    const track = tracks[trackIndex];\n    if (!track) return transform;\n\n    const clip = track.clips[clipIndex];\n    if (!clip) return transform;\n\n    // Convert pixel delta to samples, constrain, convert back\n    const deltaSamples = transform.x * samplesPerPixel;\n    const sortedClips = [...track.clips].sort((a, b) => a.startSample - b.startSample);\n    const sortedIndex = sortedClips.findIndex((c: AudioClip) => c.id === clip.id);\n    const constrainedDelta = constrainClipDrag(clip, deltaSamples, sortedClips, sortedIndex);\n\n    return { x: constrainedDelta / samplesPerPixel, y: 0 };\n  }\n\n  static configure = configurator(ClipCollisionModifier);\n}\n","import {\n  Modifier,\n  configurator,\n  type DragDropManager,\n  type DragOperation,\n} from '@dnd-kit/abstract';\nimport {\n  ticksPerBeat,\n  ticksPerBar,\n  ticksToSamples,\n  samplesToTicks,\n  snapToGrid,\n} from '@waveform-playlist/core';\nimport type { SnapTo } from '@waveform-playlist/ui-components';\n\ninterface SnapToGridBeatsOptions {\n  mode: 'beats';\n  snapTo: SnapTo;\n  bpm: number;\n  timeSignature: [number, number];\n  samplesPerPixel: number;\n  sampleRate: number;\n}\n\ninterface SnapToGridTimescaleOptions {\n  mode: 'timescale';\n  gridSamples: number;\n  samplesPerPixel: number;\n}\n\ntype SnapToGridOptions = SnapToGridBeatsOptions | SnapToGridTimescaleOptions;\n\n/**\n * dnd-kit modifier that quantizes clip drag movement to a grid.\n *\n * Two modes:\n * - \"beats\": Snaps to beat/bar grid using PPQN tick space for exact musical timing.\n * - \"timescale\": Snaps to a sample-based grid derived from timescale markers.\n *\n * Designed to compose with ClipCollisionModifier — snap first,\n * then collision constrains the snapped position.\n */\nexport class SnapToGridModifier extends Modifier<\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  DragDropManager<any, any>,\n  SnapToGridOptions\n> {\n  apply(operation: DragOperation): { x: number; y: number } {\n    const { transform, source } = operation;\n\n    if (!this.options || !source?.data) return transform;\n\n    // Don't snap boundary trims — snapping for trims is handled in the drag handler\n    const { boundary, startSample } = source.data as {\n      boundary?: 'left' | 'right';\n      startSample?: number;\n    };\n    if (boundary) return transform;\n\n    const { samplesPerPixel } = this.options;\n\n    // Snap the absolute position (not just the delta) so clips land exactly\n    // on grid lines even if they started off-grid.\n    if (this.options.mode === 'timescale') {\n      const { gridSamples } = this.options;\n      if (startSample !== undefined) {\n        const proposedPosition = startSample + transform.x * samplesPerPixel;\n        const snappedPosition = Math.round(proposedPosition / gridSamples) * gridSamples;\n        return { x: (snappedPosition - startSample) / samplesPerPixel, y: 0 };\n      }\n      // Fallback: snap delta (no startSample available)\n      const deltaSamples = transform.x * samplesPerPixel;\n      const snappedSamples = Math.round(deltaSamples / gridSamples) * gridSamples;\n      return { x: snappedSamples / samplesPerPixel, y: 0 };\n    }\n\n    // Beats mode\n    const { snapTo, bpm, timeSignature, sampleRate } = this.options;\n\n    if (snapTo === 'off') return transform;\n\n    const gridTicks = snapTo === 'bar' ? ticksPerBar(timeSignature) : ticksPerBeat(timeSignature);\n\n    if (startSample !== undefined) {\n      // Snap absolute position in tick space\n      const proposedSamples = startSample + transform.x * samplesPerPixel;\n      const proposedTicks = samplesToTicks(proposedSamples, bpm, sampleRate);\n      const snappedTicks = snapToGrid(proposedTicks, gridTicks);\n      const snappedSamples = ticksToSamples(snappedTicks, bpm, sampleRate);\n      return { x: (snappedSamples - startSample) / samplesPerPixel, y: 0 };\n    }\n\n    // Fallback: snap delta (no startSample available)\n    const deltaSamples = transform.x * samplesPerPixel;\n    const deltaTicks = samplesToTicks(deltaSamples, bpm, sampleRate);\n    const snappedTicks = snapToGrid(deltaTicks, gridTicks);\n    const snappedSamples = ticksToSamples(snappedTicks, bpm, sampleRate);\n\n    return { x: snappedSamples / samplesPerPixel, y: 0 };\n  }\n\n  static configure = configurator(SnapToGridModifier);\n}\n"],"mappings":"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","names":["useState","useEffect","useRef","useCallback","useMemo","bits","formatTime","useState","useState","useCallback","useRef","useState","useCallback","useRef","useState","useCallback","useRef","useState","useCallback","useRef","useState","useCallback","useRef","trackIndex","React","React","useCallback","useCallback","track","useCallback","handleKeyboardEvent","useCallback","useCallback","useCallback","useMemo","useEffect","LINK_THRESHOLD","useMemo","useCallback","useEffect","useCallback","useEffect","useRef","useState","useEffect","useRef","useCallback","WaveformData","useState","useRef","useCallback","useEffect","useMemo","useRef","useState","useCallback","_a","_b","_c","formatTime","useEffect","_d","sampleRate","volume","createContext","useContext","useState","useEffect","useRef","useCallback","useMemo","ThemeProvider","INSTALL_HINT","defaultTheme","jsx","createContext","useState","useMemo","useRef","useEffect","useCallback","_a","defaultTheme","ThemeProvider","useContext","useCallback","jsx","useCallback","BaseControlButton","jsx","BaseControlButton","useRef","useEffect","jsx","useRef","useEffect","_a","createContext","useContext","jsx","createContext","useContext","useContext","useEffect","useRef","useState","useMemo","useCallback","styled","useTheme","waveformColorToCss","useCallback","useEffect","useRef","jsx","useRef","useCallback","useEffect","useEffect","useState","useRef","useEffect","styled","jsx","styled","useRef","useEffect","useRef","useEffect","styled","Fragment","jsx","styled","useRef","useEffect","_a","_b","createContext","useContext","Fragment","jsx","jsxs","styled","useTheme","useContext","useEffect","useMemo","_a","_b","_c","_d","_e","_f","_g","_h","_i","useState","useRef","useCallback","waveformColorToCss","useCallback","jsx","useCallback","Fragment","jsx","jsxs","useContext","useRef","useState","useCallback","Playlist","TrackComponent","Clip","Selection","PlaylistInfoContext","DevicePixelRatioProvider","SmartScale","useTheme","waveformColorToCss","useRef","useEffect","styled","jsx","PlayheadLine","styled","useRef","useEffect","useRef","useEffect","styled","SmartChannel","useTheme","usePlaylistInfo","waveformColorToCss","jsx","jsxs","ChannelWrapper","styled","Background","ProgressOverlay","ChannelContainer","useRef","useTheme","usePlaylistInfo","useEffect","_a","waveformColorToCss","SmartChannel","Fragment","jsx","jsxs","useTheme","useContext","useState","useRef","useCallback","DevicePixelRatioProvider","PlaylistInfoContext","Playlist","waveformColorToCss","SmartScale","TrackComponent","Clip","Selection","useCallback","jsx","useCallback","Fragment","jsx","jsxs","React","useEffect","useMemo","DragDropProvider","RestrictToHorizontalAxis","samplesToTicks","ticksToSamples","snapToGrid","ticksPerBeat","ticksPerBar","Modifier","configurator","deltaSamples","snappedSamples","snappedTicks","jsx","useEffect","useMemo","ticksPerBar","ticksPerBeat","samplesToTicks","snapToGrid","ticksToSamples","React","RestrictToHorizontalAxis","DragDropProvider"]}