diff --git a/apps/ui/src/components/AnalysisResults.tsx b/apps/ui/src/components/AnalysisResults.tsx
index c7990ecb..a34e6e9c 100644
--- a/apps/ui/src/components/AnalysisResults.tsx
+++ b/apps/ui/src/components/AnalysisResults.tsx
@@ -40,11 +40,7 @@ import { TranscriptionPianorollBlock } from './TranscriptionPianorollBlock';
import { Mt3TranscriptionPanel } from './Mt3TranscriptionPanel';
import { StemListeningNotesPanel } from './StemListeningNotesPanel';
import { hasStemListeningNotesContent } from '../services/sessionMusician';
-import {
- StatusBadge,
- TokenBadgeList,
-} from './MeasurementPrimitives';
-import { Button, DeviceRack, MetricBar, MetricTile, Pill, SectionHeader } from './ui';
+import { Button, DeviceRack, MetricBar, MetricTile, Pill, SectionHeader, TokenBadgeList } from './ui';
import { PhaseSourceBadge } from './PhaseSourceBadge';
import { StickyNav, type StickyNavSection } from './StickyNav';
import { CitationBlock, CitationHeadline } from './CitationBlock';
@@ -1048,7 +1044,7 @@ export function AnalysisResults({
icon={}
label="METER"
value={phase1.timeSignature}
- footer={}
+ footer={{meterStatusLabel(phase1)}}
/>
{/* CHARACTER — genre primary, characteristic pills secondary */}
@@ -1066,7 +1062,7 @@ export function AnalysisResults({
items={[
{ label: phase1.genreDetail.genreFamily, tone: 'accent' },
...(phase1.genreDetail.secondaryGenre
- ? [{ label: phase1.genreDetail.secondaryGenre, tone: 'muted' as const }]
+ ? [{ label: phase1.genreDetail.secondaryGenre, tone: 'neutral' as const }]
: []),
]}
/>
@@ -1418,7 +1414,7 @@ export function AnalysisResults({
Instruments
({ label: item, tone: 'muted' as const }))}
+ items={styleProfile.instruments.map((item) => ({ label: item, tone: 'neutral' as const }))}
/>
)}
@@ -1428,7 +1424,7 @@ export function AnalysisResults({
Production Techniques
({ label: item, tone: 'violet' as const }))}
+ items={styleProfile.productionTechniques.map((item) => ({ label: item, tone: 'neutral' as const }))}
/>
)}
diff --git a/apps/ui/src/components/MixDoctorPanel.tsx b/apps/ui/src/components/MixDoctorPanel.tsx
index e5d55fd3..c7c492db 100644
--- a/apps/ui/src/components/MixDoctorPanel.tsx
+++ b/apps/ui/src/components/MixDoctorPanel.tsx
@@ -1,10 +1,6 @@
import React from 'react';
import type { MixDoctorReport, MixDynamicsIssue, MixIssue } from '../services/mixDoctor';
-import {
- DeltaBadge,
- StatusBadge,
-} from './MeasurementPrimitives';
-import { DataTable, MetricTile, Panel } from './ui';
+import { DataTable, DeltaBadge, MetricTile, Panel, Pill } from './ui';
import { getTextRoleClassName } from '../utils/displayText';
interface MixDoctorPanelProps {
@@ -59,17 +55,16 @@ export function MixDoctorPanel({ report }: MixDoctorPanelProps) {
accent="accent"
label="Target Genre"
value={report.genreName}
- footer={}
+ footer={{report.genreId}}
/>
+
+ {`${report.overallScore}/100`}
+
}
/>
(
-
+ {item.label}
{item.message}
))}
@@ -176,7 +171,7 @@ export function MixDoctorPanel({ report }: MixDoctorPanelProps) {
label: 'Issue',
render: (row) => (
),
},
diff --git a/apps/ui/src/components/ui/DeltaBadge.tsx b/apps/ui/src/components/ui/DeltaBadge.tsx
new file mode 100644
index 00000000..39c17250
--- /dev/null
+++ b/apps/ui/src/components/ui/DeltaBadge.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+
+import { Pill } from './Pill';
+import type { Tone } from './variants';
+
+export interface DeltaBadgeProps {
+ value: number | null | undefined;
+ unit?: string;
+ decimals?: number;
+ okThreshold: number;
+ warnThreshold: number;
+ invert?: boolean;
+ showSign?: boolean;
+ className?: string;
+}
+
+function formatSignedNumber(value: number, decimals: number, showSign: boolean): string {
+ const abs = Math.abs(value).toFixed(decimals);
+ if (!showSign) return value.toFixed(decimals);
+ return `${value >= 0 ? '+' : '-'}${abs}`;
+}
+
+/**
+ * Threshold-driven delta chip. Renders a {@link Pill} whose tone reflects how
+ * far `value` sits from zero relative to the ok/warn thresholds (or the inverse
+ * when `invert`). Non-finite values render an `n/a` neutral pill. Migrated from
+ * the retired MeasurementPrimitives layer onto the canonical token palette.
+ */
+export function DeltaBadge({
+ value,
+ unit,
+ decimals = 1,
+ okThreshold,
+ warnThreshold,
+ invert = false,
+ showSign = true,
+ className,
+}: DeltaBadgeProps) {
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
+ return (
+
+ n/a
+
+ );
+ }
+
+ const magnitude = Math.abs(value);
+ const tone: Tone =
+ magnitude <= okThreshold
+ ? invert
+ ? 'error'
+ : 'success'
+ : magnitude <= warnThreshold
+ ? 'warning'
+ : invert
+ ? 'success'
+ : 'error';
+ const label = `${formatSignedNumber(value, decimals, showSign)}${unit ? ` ${unit}` : ''}`;
+
+ return (
+
+ {label}
+
+ );
+}
diff --git a/apps/ui/src/components/ui/TokenBadgeList.tsx b/apps/ui/src/components/ui/TokenBadgeList.tsx
new file mode 100644
index 00000000..5ad2fddb
--- /dev/null
+++ b/apps/ui/src/components/ui/TokenBadgeList.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+import { cn } from './cn';
+import { Pill } from './Pill';
+import type { Tone } from './variants';
+
+export interface TokenBadgeListProps {
+ items: Array<{ label: string; tone?: Tone }>;
+ className?: string;
+}
+
+/**
+ * A wrapped row of {@link Pill} chips for tag/token lists (genres, instruments,
+ * production techniques, …). Migrated from the retired MeasurementPrimitives
+ * layer; tones are restricted to the token palette (off-palette tones collapse
+ * to `neutral` at the call site).
+ */
+export function TokenBadgeList({ items, className }: TokenBadgeListProps) {
+ return (
+
+ {items.map((item, index) => (
+
+ {item.label}
+
+ ))}
+
+ );
+}
diff --git a/apps/ui/src/components/ui/index.ts b/apps/ui/src/components/ui/index.ts
index 05553333..7f18a0f5 100644
--- a/apps/ui/src/components/ui/index.ts
+++ b/apps/ui/src/components/ui/index.ts
@@ -9,6 +9,7 @@ export {
type DataTableColumn,
type DataTableProps,
} from './DataTable';
+export { DeltaBadge, type DeltaBadgeProps } from './DeltaBadge';
export {
DeviceRack,
deviceRackVariants,
@@ -41,4 +42,5 @@ export {
type SignalStageStatus,
} from './SignalChain';
export { TimeReadout, type TimeReadoutProps } from './TimeReadout';
+export { TokenBadgeList, type TokenBadgeListProps } from './TokenBadgeList';
export { Tooltip, TooltipProvider, type TooltipProps } from './Tooltip';
diff --git a/apps/ui/tests/services/mixDoctorPanel.test.ts b/apps/ui/tests/services/mixDoctorPanel.test.ts
index 296fa3ae..2c68669d 100644
--- a/apps/ui/tests/services/mixDoctorPanel.test.ts
+++ b/apps/ui/tests/services/mixDoctorPanel.test.ts
@@ -127,9 +127,9 @@ describe('MixDoctorPanel', () => {
);
expect(html).toContain('86/100');
- expect(html).toContain('border-success/30 bg-success/10');
- expect(html).toContain('border-warning/30 bg-warning/10');
- expect(html).toContain('border-error/30 bg-error/10');
+ expect(html).toContain('bg-success/20');
+ expect(html).toContain('bg-warning/20');
+ expect(html).toContain('bg-error/20');
expect(html).toContain('-1.8');
});