diff --git a/apps/ui/src/components/AnalysisResults.tsx b/apps/ui/src/components/AnalysisResults.tsx index cfd12fd5..4fb91e40 100644 --- a/apps/ui/src/components/AnalysisResults.tsx +++ b/apps/ui/src/components/AnalysisResults.tsx @@ -10,16 +10,12 @@ import { StemSummaryResult, } from '../types'; import { - Activity, AudioWaveform, Check, ChevronDown, ChevronRight, - Clock, - Disc, FileJson, FileText, - Music, Settings2, Sliders, } from 'lucide-react'; @@ -38,8 +34,7 @@ import { TranscriptionPianorollBlock } from './TranscriptionPianorollBlock'; import { Mt3TranscriptionPanel } from './Mt3TranscriptionPanel'; import { StemListeningNotesPanel } from './StemListeningNotesPanel'; import { hasStemListeningNotesContent } from '../services/sessionMusician'; -import { Button, DeviceRack, MetricBar, MetricTile, Pill, SectionHeader, TokenBadgeList } from './ui'; -import { PhaseSourceBadge } from './PhaseSourceBadge'; +import { Button, DeviceRack, Pill, SectionHeader } from './ui'; import { StickyNav, type StickyNavSection } from './StickyNav'; import { CitationBlock, CitationHeadline } from './CitationBlock'; import { ConfidenceBandBadge } from './sessionMusician/ConfidenceBandBadge'; @@ -66,7 +61,8 @@ import { getTextRoleClassName, type TextRole, } from '../utils/displayText'; -import { MetaBadgeList, ResultsSectionHeader, textRoleClassName, type StyleProfileSectionState } from './analysisResults/shared'; +import { lowConfidenceIndicator, MetaBadgeList, ResultsSectionHeader, textRoleClassName, type StyleProfileSectionState } from './analysisResults/shared'; +import { MeasurementSummarySection } from './analysisResults/MeasurementSummarySection'; import { AudioObservationsSection } from './analysisResults/AudioObservationsSection'; import { ProjectSetupSection } from './analysisResults/ProjectSetupSection'; import { TrackLayoutSection } from './analysisResults/TrackLayoutSection'; @@ -349,21 +345,6 @@ function SourcesToggle({ sources, showSources, onToggle }: { sources?: string[]; // three-level Confidence Notes chips. Retired — chips now route through // `ConfidenceBandBadge` with the canonical four-band ladder. -function shortenCharacteristicName(name: string): string { - return name.trim().split(/\s+/).slice(0, 2).join(' '); -} - -function characteristicPillClass(confidence: string): string { - const normalized = String(confidence).trim().toUpperCase(); - if (normalized === 'HIGH') { - return 'bg-success/20 text-success border-success/30'; - } - if (normalized === 'MED' || normalized === 'MODERATE') { - return 'bg-warning/20 text-warning border-warning/30'; - } - return 'bg-error/20 text-error border-error/30'; -} - function groupIcon(groupName: string): React.ReactNode { if (groupName.includes('DRUM PROCESSING')) return '🥁'; // Audit #13: 🫧 (bubbles) is not a bass signifier in any audio @@ -391,21 +372,6 @@ function withAlpha(hexColor: string, alphaHex: string): string { return `${hexColor}${alphaHex}`; } -const LOW_CONFIDENCE_TITLE = "Low confidence — treat this as approximate."; - -function lowConfidenceIndicator(show: boolean) { - if (!show) return null; - return ( - - ⚠ - - ); -} - interface InterpretationWarningMapping { originalValue?: string; coercedValue?: string; @@ -609,14 +575,6 @@ function getChordStrength(phase1: Phase1Result): number | null { return toFiniteNumber((chordDetail as Record).chordStrength); } -function isAssumedMeter(phase1: Phase1Result): boolean { - return phase1.timeSignatureSource === 'assumed_four_four' || (phase1.timeSignatureConfidence ?? 1) <= 0; -} - -function meterStatusLabel(phase1: Phase1Result): string { - return isAssumedMeter(phase1) ? 'ASSUMED' : 'DETECTED'; -} - // Audit Finding #4: `formatBpmScore` retired — the BPM card now renders // the canonical band pill via ConfidenceBandBadge, same vocabulary as // every other confidence surface. @@ -911,131 +869,13 @@ export function AnalysisResults({ - -
- {/* TEMPO */} - } - label="TEMPO" - value={finalBpm} - unit="BPM" - headerRight={} - footer={ -
- {/* Audit Finding #4: `SCORE 0.86` badge retired in favor of the - canonical band pill — same vocabulary as Key, Character, and - every other confidence surface. */} - - {phase1.bpmSource && ( - - {phase1.bpmSource.replace(/_/g, ' ')} - - )} -
- } - /> - - {/* KEY SIG */} - } - label="KEY SIG" - value={{finalKey}} - headerRight={ -
- - {lowConfidenceIndicator(keyIsApproximate)} -
- } - footer={ -
- - {/* Audit Finding #4: `CONF 62%` text replaced with the canonical - band pill so every confidence reads in the same vocabulary. */} - -
- } - /> - - {/* METER */} - } - label="METER" - value={phase1.timeSignature} - footer={{meterStatusLabel(phase1)}} - /> - - {/* CHARACTER — genre primary, characteristic pills secondary */} - {phase1.genreDetail ? ( - } - label="CHARACTER" - value={{phase1.genreDetail.genre}} - headerRight={} - footer={ -
- - - {/* Audit Finding #4: `CONF X%` replaced with the canonical - band pill — same vocabulary across every confidence. */} - -
- } - /> - ) : ( - } - label="CHARACTER" - value={ - - SCANNING... - - } - footer={ - characteristicPills.length > 0 ? ( -
- {characteristicPills.map((item, idx) => ( - - {shortenCharacteristicName(item.name)} - - ))} -
- ) : undefined - } - /> - )} -
-
+ {/* Audit Finding #1: MeasurementDashboard was here at the top of the results scroll, ahead of Style / Sonic Elements / Mix Chain / Patches. diff --git a/apps/ui/src/components/analysisResults/MeasurementSummarySection.tsx b/apps/ui/src/components/analysisResults/MeasurementSummarySection.tsx new file mode 100644 index 00000000..18444072 --- /dev/null +++ b/apps/ui/src/components/analysisResults/MeasurementSummarySection.tsx @@ -0,0 +1,175 @@ +import { Activity, Clock, Disc, Music } from 'lucide-react'; + +import type { Phase1Result, Phase2Result } from '../../types'; +import { ConfidenceBandBadge } from '../sessionMusician/ConfidenceBandBadge'; +import { PhaseSourceBadge } from '../PhaseSourceBadge'; +import { DeviceRack, MetricBar, MetricTile, Pill, TokenBadgeList } from '../ui'; +import { lowConfidenceIndicator } from './shared'; + +// Helpers exclusive to the Measurement Summary tiles — moved verbatim out of the +// AnalysisResults monolith (Phase 5 split) alongside the section they serve. + +function shortenCharacteristicName(name: string): string { + return name.trim().split(/\s+/).slice(0, 2).join(' '); +} + +function characteristicPillClass(confidence: string): string { + const normalized = String(confidence).trim().toUpperCase(); + if (normalized === 'HIGH') { + return 'bg-success/20 text-success border-success/30'; + } + if (normalized === 'MED' || normalized === 'MODERATE') { + return 'bg-warning/20 text-warning border-warning/30'; + } + return 'bg-error/20 text-error border-error/30'; +} + +function isAssumedMeter(phase1: Phase1Result): boolean { + return phase1.timeSignatureSource === 'assumed_four_four' || (phase1.timeSignatureConfidence ?? 1) <= 0; +} + +function meterStatusLabel(phase1: Phase1Result): string { + return isAssumedMeter(phase1) ? 'ASSUMED' : 'DETECTED'; +} + +export function MeasurementSummarySection({ + phase1, + finalBpm, + finalKey, + keyIsApproximate, + characteristicPills, +}: { + phase1: Phase1Result; + finalBpm: number; + finalKey: string; + keyIsApproximate: boolean; + characteristicPills: Phase2Result['detectedCharacteristics']; +}) { + return ( + +
+ {/* TEMPO */} + } + label="TEMPO" + value={finalBpm} + unit="BPM" + headerRight={} + footer={ +
+ {/* Audit Finding #4: `SCORE 0.86` badge retired in favor of the + canonical band pill — same vocabulary as Key, Character, and + every other confidence surface. */} + + {phase1.bpmSource && ( + + {phase1.bpmSource.replace(/_/g, ' ')} + + )} +
+ } + /> + + {/* KEY SIG */} + } + label="KEY SIG" + value={{finalKey}} + headerRight={ +
+ + {lowConfidenceIndicator(keyIsApproximate)} +
+ } + footer={ +
+ + {/* Audit Finding #4: `CONF 62%` text replaced with the canonical + band pill so every confidence reads in the same vocabulary. */} + +
+ } + /> + + {/* METER */} + } + label="METER" + value={phase1.timeSignature} + footer={{meterStatusLabel(phase1)}} + /> + + {/* CHARACTER — genre primary, characteristic pills secondary */} + {phase1.genreDetail ? ( + } + label="CHARACTER" + value={{phase1.genreDetail.genre}} + headerRight={} + footer={ +
+ + + {/* Audit Finding #4: `CONF X%` replaced with the canonical + band pill — same vocabulary across every confidence. */} + +
+ } + /> + ) : ( + } + label="CHARACTER" + value={ + + SCANNING... + + } + footer={ + characteristicPills.length > 0 ? ( +
+ {characteristicPills.map((item, idx) => ( + + {shortenCharacteristicName(item.name)} + + ))} +
+ ) : undefined + } + /> + )} +
+
+ ); +} diff --git a/apps/ui/src/components/analysisResults/shared.tsx b/apps/ui/src/components/analysisResults/shared.tsx index 83c29dae..1fc99dcc 100644 --- a/apps/ui/src/components/analysisResults/shared.tsx +++ b/apps/ui/src/components/analysisResults/shared.tsx @@ -56,6 +56,25 @@ export interface MetaBadgeItem { value?: string | null; } +const LOW_CONFIDENCE_TITLE = 'Low confidence — treat this as approximate.'; + +/** + * Inline ⚠ glyph flagging a low-confidence measurement. Shared between the + * Measurement Summary tiles and the harmonic-content card in AnalysisResults. + */ +export function lowConfidenceIndicator(show: boolean) { + if (!show) return null; + return ( + + ⚠ + + ); +} + export function MetaBadgeList({ items }: { items: MetaBadgeItem[] }) { const visibleItems = items.filter((item) => typeof item.value === 'string' && item.value.trim().length > 0); if (visibleItems.length === 0) return null;