From 9951039b7b7e875a9919323c9fb668be2c3afdd7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 03:34:05 +0000 Subject: [PATCH] UI overhaul Phase 1: unify typography on the --text-* scale tokens Replace 294 arbitrary text-[Npx] utilities across App.tsx and 24 feature components with the sanctioned --text-* scale tokens, eliminating the ad-hoc third typography mechanism (the .text-role-* roles and the size tokens stay; the arbitrary values are gone). Add --text-pico (7px) so the 13 finest dense-label usages keep their exact size; only two singletons snap minimally (6px->7px grid label, 22px->24px hero stat). Widen the style-discipline guard to per-rule enforced roots: the arbitrary-text-size rule now covers all of src/ (so App.tsx is held to it too); raw-hex stays scoped to components/ui until the hex-cleanup phases. Update the one unit test that asserted the old text-[9px] class hook. Verified: lint:style, tsc --noEmit, 830 unit tests, and production build all green. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_016to3B8Hf9eAGxAbzpQNvku --- apps/ui/scripts/check-style-discipline.mjs | 30 ++-- apps/ui/src/App.tsx | 36 ++-- apps/ui/src/components/AnalysisResults.tsx | 164 +++++++++--------- .../ui/src/components/AnalysisStatusPanel.tsx | 6 +- .../src/components/BrowserLoudnessPanel.tsx | 14 +- apps/ui/src/components/CitationBlock.tsx | 10 +- apps/ui/src/components/DiagnosticLog.tsx | 8 +- apps/ui/src/components/FileUpload.tsx | 8 +- apps/ui/src/components/HarmonyLanes.tsx | 16 +- apps/ui/src/components/IdleValuePropPanel.tsx | 10 +- .../src/components/MeasurementDashboard.tsx | 160 ++++++++--------- .../src/components/MeasurementPrimitives.tsx | 18 +- apps/ui/src/components/MiniHeatmap.tsx | 6 +- .../src/components/Mt3TranscriptionPanel.tsx | 2 +- apps/ui/src/components/PatchSmithPanel.tsx | 16 +- .../components/Phase2ConsistencyReport.tsx | 6 +- apps/ui/src/components/PhaseSourceBadge.tsx | 2 +- apps/ui/src/components/SamplePlayback.tsx | 18 +- .../components/StemListeningNotesPanel.tsx | 16 +- apps/ui/src/components/StickyNav.tsx | 4 +- apps/ui/src/components/StructureLanes.tsx | 12 +- .../TranscriptionPianorollBlock.tsx | 4 +- .../sessionMusician/ConfidenceBandBadge.tsx | 4 +- .../sessionMusician/MelodyContourBlock.tsx | 10 +- .../sessionMusician/NoteDraftBlock.tsx | 28 +-- .../sessionMusician/QuantizeControls.tsx | 8 +- apps/ui/src/index.css | 3 +- .../tests/services/citationHeadline.test.ts | 4 +- 28 files changed, 314 insertions(+), 309 deletions(-) diff --git a/apps/ui/scripts/check-style-discipline.mjs b/apps/ui/scripts/check-style-discipline.mjs index e914b9fc..5da12f3f 100644 --- a/apps/ui/scripts/check-style-discipline.mjs +++ b/apps/ui/scripts/check-style-discipline.mjs @@ -12,8 +12,9 @@ * * Why this exists: a 2026-05-13 pass removed hardcoded hex, but it silently * returned because nothing enforced the rule. This guard is that enforcement. - * ENFORCED_DIRS is widened directory-by-directory as the UI overhaul clears - * each area, so the guard can never regress what it has already cleaned. + * Each rule's enforced roots (rule.dirs) are widened phase-by-phase as the UI + * overhaul clears each area, so the guard can never regress what it cleaned. + * Phase 1 widened arbitrary-text-size to all of src/; raw-hex stays at ui/. * * Out of scope (allowlisted): * - *.stories.tsx — dev-only Storybook artifacts, not shipped UI. @@ -29,9 +30,6 @@ const here = dirname(fileURLToPath(import.meta.url)); const uiRoot = join(here, '..'); const srcRoot = join(uiRoot, 'src'); -// Directories currently held to the discipline. Widen as overhaul phases land. -const ENFORCED_DIRS = [join(srcRoot, 'components', 'ui')]; - // Basenames exempt anywhere within enforced dirs. const ALLOWLIST = new Set([ 'colorScales.ts', @@ -46,15 +44,18 @@ const ALLOWLIST = new Set([ 'PianoRollCanvas.tsx', ]); +// Each rule names the roots it enforces; widen per-rule as cleanup lands. const RULES = [ { id: 'arbitrary-text-size', re: /text-\[[0-9.]+(?:px|rem|em)\]/g, + dirs: [srcRoot], hint: 'use a --text-* scale token (e.g. text-meta, text-eyebrow, text-body-sm)', }, { id: 'raw-hex-color', re: /#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b/g, + dirs: [join(srcRoot, 'components', 'ui')], hint: 'use a semantic color token (var(--color-*) or a Tailwind color utility)', }, ]; @@ -77,12 +78,15 @@ function walk(dir) { } const violations = []; -for (const dir of ENFORCED_DIRS) { - for (const file of walk(dir)) { - if (ALLOWLIST.has(basename(file))) continue; - const lines = readFileSync(file, 'utf8').split('\n'); - lines.forEach((line, i) => { - for (const rule of RULES) { +for (const rule of RULES) { + const seen = new Set(); + for (const dir of rule.dirs) { + for (const file of walk(dir)) { + if (ALLOWLIST.has(basename(file))) continue; + if (seen.has(file)) continue; // overlapping roots → scan each file once + seen.add(file); + const lines = readFileSync(file, 'utf8').split('\n'); + lines.forEach((line, i) => { rule.re.lastIndex = 0; let m; while ((m = rule.re.exec(line)) !== null) { @@ -94,8 +98,8 @@ for (const dir of ENFORCED_DIRS) { hint: rule.hint, }); } - } - }); + }); + } } } diff --git a/apps/ui/src/App.tsx b/apps/ui/src/App.tsx index 19cea301..2ef051d8 100644 --- a/apps/ui/src/App.tsx +++ b/apps/ui/src/App.tsx @@ -1027,7 +1027,7 @@ export default function App() { value={selectedModel} onChange={(e) => setSelectedModel(e.target.value)} disabled={phase2ModelSelectorDisabled} - className="appearance-none bg-transparent border-none text-text-secondary/70 hover:text-text-primary text-[10px] font-mono py-0 pl-0 pr-4 rounded-sm focus:outline-none focus:text-text-primary cursor-pointer disabled:opacity-50 transition-colors" + className="appearance-none bg-transparent border-none text-text-secondary/70 hover:text-text-primary text-meta font-mono py-0 pl-0 pr-4 rounded-sm focus:outline-none focus:text-text-primary cursor-pointer disabled:opacity-50 transition-colors" > {MODELS.map((model) => (