diff --git a/apps/ui/src/components/SamplePlayback.tsx b/apps/ui/src/components/SamplePlayback.tsx index ffe57133..a47bd671 100644 --- a/apps/ui/src/components/SamplePlayback.tsx +++ b/apps/ui/src/components/SamplePlayback.tsx @@ -7,6 +7,14 @@ import { generateSamples, } from '../services/sampleGenerationClient'; import { BackendClientError } from '../services/backendPhase1Client'; +import { + Button, + DeviceRack, + Panel, + Pill, + SectionHeader, +} from './ui'; +import type { Tone } from './ui'; interface SamplePlaybackProps { runId: string | null | undefined; @@ -31,6 +39,18 @@ const CATEGORY_LABELS: Record = { melody: 'Melody / lead phrase', }; +type RackStatus = 'idle' | 'active' | 'success' | 'warning' | 'error'; + +function rackStatusFor( + panelStatus: PanelStatus, + hasManifest: boolean, +): RackStatus { + if (panelStatus.kind === 'error') return 'error'; + if (panelStatus.kind === 'generating') return 'active'; + if (hasManifest) return 'success'; + return 'idle'; +} + /** * Audition panel that renders generated audio clips for the current run. * @@ -98,79 +118,84 @@ export function SamplePlayback({ if (!runId) return null; + const rackStatus = rackStatusFor(status, Boolean(manifest)); + return ( -
-
-
-

- Audition samples (Phase 3 — heuristic) -

-

- Short clips derived from Phase 1 measurements (and Phase 2 context when - available) so you can ear-check the measurement chain. These are not - Ableton-accurate reconstructions — follow Phase 2 in Live for the - production character. -

-
- {manifest && ( - - )} -
- - {!measurementCompleted && ( -

- Measurements still running — audition samples become available once Phase 1 - completes. + + ) + } + > +

+

+ Short clips derived from Phase 1 measurements (and Phase 2 context when + available) so you can ear-check the measurement chain. These are not + Ableton-accurate reconstructions — follow Phase 2 in Live for the + production character.

- )} - {measurementCompleted && !manifest && status.kind !== 'generating' && ( - - )} + {!measurementCompleted && ( +

+ Measurements still running — audition samples become available once + Phase 1 completes. +

+ )} - {status.kind === 'generating' && ( -

Rendering audition clips…

- )} + {measurementCompleted && !manifest && status.kind !== 'generating' && ( + + )} - {status.kind === 'error' && ( -

- {status.message} -

- )} + {status.kind === 'generating' && ( +

+ Rendering audition clips… +

+ )} - {manifest && groupedSamples.length > 0 && ( -
- - {groupedSamples.map(([category, samples]) => ( - - - - ))} -
- )} -
+ {status.kind === 'error' && ( +

+ {status.message} +

+ )} + + {manifest && groupedSamples.length > 0 && ( +
+ + {groupedSamples.map(([category, samples]) => ( + + + + ))} +
+ )} + + ); } @@ -184,13 +209,9 @@ function ManifestMeta({ manifest }: { manifest: SamplesManifest }) { ? 'PyTheory' : 'Pure-Python theory fallback'; return ( -
- - Music theory: {theoryLabel} - - - Synthesis: {synthesisLabel} - +
+ Music theory: {theoryLabel} + Synthesis: {synthesisLabel}
); } @@ -204,15 +225,18 @@ interface SampleGroupProps { function SampleGroup({ category, samples, runId, apiBaseUrl }: SampleGroupProps) { return ( -
-

- {CATEGORY_LABELS[category]} -

-