Skip to content

fix(ui): audit quick-hits bundle (StickyNav, Mix Chain, Mix Doctor, header) - #58

Merged
slittycode merged 6 commits into
mainfrom
fix/quick-hits
May 17, 2026
Merged

fix(ui): audit quick-hits bundle (StickyNav, Mix Chain, Mix Doctor, header)#58
slittycode merged 6 commits into
mainfrom
fix/quick-hits

Conversation

@slittycode

Copy link
Copy Markdown
Owner

Stacked on #56#55#54 → main. PR base is `fix/unified-confidence`. When the stack lands in order, each PR's base auto-rebases to `main`.

Summary

Closes six items from the audit's "Quick hits (low ROI, real)" list. Each is independent and surgical; bundled because they fit cleanly together and all touch the same surfaces (results-page nav, Mix Chain cards, Mix Doctor panel, header).

What changed

  1. QH1 — StickyNav clipped "MEASUREMENTS" at 1440px. Was `overflow-x-auto` + `min-w-max`, which produces an invisible horizontal scroll on macOS (default-hidden scrollbars → last pill looks clipped with no scroll affordance). Switched to `flex-wrap` so pills flow to multiple rows at narrow widths.

  2. QH2 — Mix Chain card-number order badges removed. Cards are grouped by processing stage after ordering, so order numbers appeared out-of-sequence within each group ("1, 6, 8, 9 / 2, 4 / 5, 7 / 3 / 10") and read as a presentation bug. The visual sequence within each group is already meaningful; the badge added confusion without information.

  3. QH4 — Mix Doctor Band Diagnostics now show target range alongside optimal. Previously the column rendered only the optimal dB. Two bands with similar Delta dB could land in different Issue buckets without the producer being able to see why — the Issue is determined by absolute thresholds (`target.minDb` / `maxDb`), not by the diff from optimal. Now: "-12.0 (-16 to -8)" so Sub Bass at -6.4 norm + Delta +5.6 → TOO-LOUD (exceeds -8 by 1.6) is visibly different from Low Mids at -14.5 norm + Delta +5.5 → OPTIMAL (sits at the -14 boundary). Threshold logic itself was correct; this is a display fix.

  4. QH5 — Dense DAW Lab link removed from header. A prior audit pass had de-emphasized it to muted text; the new audit re-flagged it for sitting in the primary flow's header. Route stays accessible via direct URL (`getAppViewHref('daw-concept')` and `main.tsx`'s active-view router).

  5. QH6 — header model selector visually demoted. Dropped the "Interpretation Model" label; shrank from a bordered dropdown to a discreet text-secondary inline select. The audit flagged the prior styling as foregrounding an AI-model choice an intermediate producer has no basis to make. Selector stays accessible (smoke specs guard `phase2-model-desktop` visibility) but now reads as background metadata. Aria-label + title attribute carry the long-form context.

  6. QH7 — Mix Doctor section title renamed. Was "MixDoctor" in source, but `formatWord` in `utils/displayText.ts` case-normalizes single-token CamelCase to sentence case, so it rendered as "Mixdoctor" with a lowercase 'd'. Two-word "Mix Doctor" matches every other section name's pattern (Style Profile, Project Setup, Mix & Master Chain, Patch Framework, etc.) and renders cleanly through the existing pipeline.

Test plan

  • `npm run verify` — lint + ~600 unit + build + 44 smoke. All green.
  • Live UI check drove the upload against saved run `855f1376…` via Playwright POST intercept. All six fixes verified:
    1. StickyNav: `.sticky.top-3 .overflow-x-auto` wrapper gone; pills wrap onto two rows (STYLE/SETUP/.../PATCHES on row 1, MEASUREMENTS on row 2).
    2. Mix Chain order badges: zero `span.w-6.h-6.bg-bg-app` elements inside `#section-mix-chain`.
    3. Mix Doctor table: "TARGET (RANGE)" header present, "Target dB" header gone. Visual confirms the audit's apparent contradiction is now self-explanatory.
    4. "Dense DAW Lab" text absent from page.
    5. "Interpretation Model" label absent from page.
    6. Section title reads "Mix Doctor" (correctly capitalized); "Mixdoctor" absent.

Reviewer notes

  1. Smoke tests still require `phase2-model-desktop` visible at desktop viewport. QH6 demotes but doesn't hide the selector — `responsive-layout.spec.ts:267` and `upload-estimate-phase1.spec.ts:239` continue to pass.
  2. Threshold logic in mixDoctor.ts was not the bug. Verified by reading the source: `issue` is computed correctly via `normalizedDb > target.maxDb` (too-loud) / `normalizedDb < target.minDb` (too-quiet). The audit's apparent contradiction was a display issue — Delta dB and Issue were derived from different bases (diff from optimal vs absolute range bounds). Adding the range to the table makes the verdict legible without changing logic.
  3. `getAppViewHref` import retired but the helper still exists in `utils/appView.ts` for any future re-introduction behind a settings menu. Just no current consumer in `App.tsx`.
  4. Audit quick-hit Preserve signal monitor playback during analysis and harden run flow #3 from the original list (Mix Chain vs Patch Framework overlap) was already addressed in PR fix(ui): stop Phase 2 results surface from leaking engine output #54 (Sub-bug B-overlap of Finding feat: value-first backport — genre profiles, MixDoctor, broken-pipe fixes #1) — buildPatchCards filters out devices that appear in mixAndMasterChain. Not re-included here.

🤖 Generated with Claude Code

slittycode and others added 6 commits May 16, 2026 19:08
Closes four orientation gaps in CLAUDE.md: a top-of-Architecture map
that flags advisory/, experiments/, docs/, and tests/ground_truth/ as
off-path; a Scripts-at-a-glance section covering scripts/ and
apps/backend/scripts/; a pointer from the Phase 3 paragraph to
SamplePlayback.tsx + sampleGenerationClient.ts; and a curl example
plus allowlisted field paths on the csv_export.py module entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Oz <oz-agent@warp.dev>
Removes four classes of broken/placeholder content that made the Phase 2
recommendations read as engine output instead of producer-facing advice
(audit finding #1).

1. Mix Chain card role text — `buildRoleSentence` used to fabricate
   "{stage phrase} by {verb}" by lowercasing the first letter of
   Gemini's `reason`. Because `reason` is a present-tense clause, every
   card produced ungrammatical splices like "Controls bass energy by
   ensures the extreme low-end mono…". Now renders the reason verbatim
   with a capitalized first letter and trailing period; HIGH-END cue
   suffix preserved as "(for …)".

2. Patch Framework `patchRole` — was a 7-key category-keyed fallback
   (`mapPatchRole`) that stamped duplicated placeholders ("Primary tone
   generator" on every SYNTHESIS card). Removed the field, the
   fallback, the JSX paragraph, and the contribution to the
   `inferProcessingGroup` text-concat. The category chip + per-card
   `whyThisWorks` already carry the bucket and explanation.

3. Patches/Mix Chain overlap — `buildPatchCards` now case-insensitively
   filters out devices that also appear in `mixAndMasterChain` so the
   Patches section stops re-listing chain devices. Synthetic fallbacks
   (Stereo Width, MIDI Clip Guide) are built from Phase 1 only and
   bypass the filter intentionally.

4. Interpretation Caution raw-JSON dump — the panel rendered
   `originalValue` verbatim, which for dropped recommendations is a
   JSON-dumped `AbletonRecommendation` (see `_stringify_warning_value`
   in `server_phase2.py`). Added `formatDroppedValue` helper that
   parses JSON-shaped values and renders a compact "device: X ·
   parameter: Y · value: Z" summary. Non-JSON strings pass through;
   invalid JSON falls back to a truncated raw string. Also resolves
   the `$Saturator` symptom — Gemini's hallucinated `$`-prefixed
   device name now surfaces as a readable summary line instead of
   leaking through a raw JSON dump.

5. System Diagnostics dev-only leak — `validateNewFieldCoverage`
   emits "Phase 1 field 'X' is present… this warning is benign"
   coverage signals meant for the engine team, not producers. Added
   optional `audience?: 'dev' | 'user'` to `ValidationViolation`,
   marked `NEW_FIELD_UNCITED` as dev, and updated
   `Phase2ConsistencyReport` to filter dev-audience violations from
   the rendered table AND from the header counts (so the header
   doesn't read "5 warnings shown" above an empty table). The
   underlying `ValidationReport` still carries every violation for
   tests and offline analysis.

Tripwire note: the backend grammar fix
`_apply_phase2_grammar_fixes` in `server_phase2.py` only runs on the
legacy `/api/phase2` endpoint, not the analysis-runs path the UI
uses. After this change it becomes a redundant no-op against the new
render shape; left untouched.

Tests: 16 new unit cases across viewModel, UI rendering, validator,
and consistency report. `npm run verify` passes (lint + 565 unit
tests + build + 44 smoke tests).

Live UI verified against saved run 855f1376… — all nine text-leak
probes (`by ensures`, `by ducks`, `by generates`,
`Primary tone generator`, `Texture and movement stage`,
`Stereo placement stage`, raw `\$Saturator`, `this warning is
benign`, "is present in the measurement payload but no Phase 2…")
return false.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Audit Finding #3 — "the chain-of-custody citation, the product's whole
differentiator, renders as a footnote."

Adds a one-line `CitationHeadline` primitive inside the collapsed header
of every Mix Chain / Patch / Sonic Element card so producers see the
measurement evidence at scan-time without expanding. The expanded
CitationBlock stays in the body unchanged — this is the collapsed-state
companion, not a replacement.

Each headline reads `{label} {value} →` with the arrow pointing into
the device h4/h3 that follows in the title row, making the implicit
"measurement justified device" statement visible — exactly the audit's
literal example "Crest factor 8.2 dB → Glue Compressor."

Confidence sibling pills (Solid / Workable / Rough / Unreliable, same
ladder as ConfidenceBandBadge) ride alongside the value when the
primary field has a paired *Confidence sibling. This preserves the
chain-of-custody invariant: low-confidence measurements visibly hedge
in the collapsed view too, not just after expansion.

Implementation:
1. `CitationHeadline` added as a sibling export in
   `apps/ui/src/components/CitationBlock.tsx`. Shares the file's
   imports, the `CONFIDENCE_PILL_CLASSES` map, and the audit lineage.
   Composes existing pure helpers (`pickPhase1Value`,
   `formatCitedValue`, `humanizeFieldPath`,
   `pickPhase1Confidence`, `getConfidenceBand`,
   `formatBandPillLabel`) — no new resolver logic.
2. Mount points in `AnalysisResults.tsx`:
   - Mix Chain card header (~line 2056): between the title row and
     the role paragraph.
   - Patch card header (~line 2215): between the title row and the
     MetaBadgeList.
   - Sonic Element card header (~line 1912): between the title row
     and the summary paragraph.
3. Each mount is guarded by `card.phase1Fields.length > 0`. Cards
   without cited fields fall back to today's exact layout.

Live verification against saved run 855f1376… (the audit reproducer):
all 10 Mix Chain cards, 2 Patch cards, and 7 Sonic Element cards
render the headline correctly with appropriate confidence pills
(PUMPING STRENGTH 47% [ROUGH SKETCH 30%], SUPERSAW DETECTED
[WORKABLE DRAFT 78%], ACID BASS DETECTED [SOLID SCAFFOLD 99%],
NOTE TRANSCRIPTION 61% [WORKABLE DRAFT 61%], KEY C Minor
[SOLID SCAFFOLD 93%], TEMPO 145 BPM [SOLID SCAFFOLD 100%]).

Mobile (375px) verified — headlines fit, pills stay aligned, no
overflow.

Tests: 15 new cases — 10 in `tests/services/citationHeadline.test.ts`
(mirrors `citationBlock.test.ts`) covering label/value/arrow render,
null fallback, confidence-pill bands, value formatter parity,
typography hooks. 5 in `analysisResultsUi.test.ts` covering Mix Chain
/ Patch / Sonic integration, empty-fields fallback, and the
low-confidence Unreliable-band path.

`npm run verify` passes: lint + 575 unit tests + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ladder

Audit Finding #4 — the producer used to see five competing vocabularies
for "how much should I trust this number?" — HIGH/MED/LOW pills on
Detected Characteristics cards, High/Moderate/Low chips on Confidence
Notes, bare CONF X% text on Key/Character metric cards, SCORE X.XX
badges on the BPM card (two render paths), and the canonical four-band
ladder used only by the Session Musician panel. The chain-of-custody
invariant in PURPOSE.md requires low-confidence measurements to produce
visibly hedged advice — five vocabularies fragment that signal so the
producer can't build a single mental model of trust.

This PR promotes the existing four-band ladder
(apps/ui/src/services/sessionMusician/confidenceBand.ts) to the canonical
primitive across every confidence surface. Frontend-only; Gemini still
emits HIGH/MED/LOW and the UI converts locally via a new
`toConfidenceBand` normalizer.

What changed:

1. New normalizer `toConfidenceBand(value)` in `confidenceBand.ts`
   accepts numeric 0-1 floats, 0-100 integers, Gemini string enums
   (HIGH/MED/LOW + High/Moderate/Low variants), and percent strings
   ("62%"). Returns the matching `ConfidenceBand` or null for
   unparseable input. The string enums map to band-mid values (HIGH→0.9,
   MED→0.6, LOW→0.3) so `formatBandPillLabel` reads as honest hedges.

2. `ConfidenceBandBadge` gains:
   - `variant: 'full' | 'compact'` — `compact` omits the copy paragraph
     so the pill can sit inline in card corners and metric-card footers.
     `full` (default) preserves the Session Musician panel behavior
     unchanged.
   - `band?: ConfidenceBand` override prop — when supplied, skips the
     `getConfidenceBand` round-trip. Useful when the caller pre-converts
     a string enum.

3. V1: Detected Characteristics cards (AnalysisResults.tsx ~1650) —
   bespoke HIGH/MED/LOW ternary pill replaced with `<ConfidenceBandBadge
   variant="compact" band={toConfidenceBand(item.confidence)} />`.
   `characteristicPillClass` helper kept alive only for the unrelated
   characteristic-name chips on the Character metric card (line 1000);
   flagged as a follow-up.

4. V2: Confidence Notes chips (AnalysisResults.tsx ~1187) —
   `toConfidenceBadges` viewModel return shape changed from
   `{ label, level }` (3-level legacy enum) to `{ label, band }`
   (canonical four-band ladder). `confidenceClass` helper deleted.

5. V3: Plain `CONF X%` text (3 sites) — replaced with
   `<ConfidenceBandBadge variant="compact" confidence={...} />` in:
   - AnalysisResults.tsx Key card footer
   - AnalysisResults.tsx Character card footer
   - MeasurementDashboard.tsx alternate Key card

6. V4: `SCORE X.XX` badges (2 sites) — replaced with band pills in
   AnalysisResults.tsx Tempo card and MeasurementDashboard.tsx Tempo
   card. `formatBpmScore` deleted from both files. Cross-Check ✓/✗
   StatusBadge in MeasurementDashboard preserved — that's an agreement
   signal (do multiple BPM detectors agree?), not confidence.

7. Dead code retired: `normalizeConfidenceLevel`,
   `parseConfidenceScalar`, `confidenceClass`, two copies of
   `formatBpmScore`. `ConfidenceLevel` type and
   `MelodyInsightsViewModel.confidenceLabel` kept alive — they drive
   non-pill key/value text rows in Sonic Element melody insights
   (flagged for follow-up migration).

3-level → 4-band mismatch documented as an intentional refinement: the
old `normalizeConfidenceLevel` mapped scalar 0.5-0.79 AND "medium" both
to "Moderate"; the new normalizer maps "medium" to 0.6 → workable
(consistent), and scalar 0.25-0.49 to "Rough sketch" (more granular
than the old "Low" bucket).

Tests: 20 new cases across 4 files —
1. `confidenceBand.test.ts` — 9 cases for `toConfidenceBand`
   (null/undefined fallback, 0-1 floats, 0-100 integers, all string
   enum variants, percent strings, NaN/Infinity, negative clamping).
2. `confidenceBandBadge.test.ts` — 4 cases for the compact variant
   and the `band` override prop (label-only when no confidence, tone
   override, Gemini-string routing).
3. `analysisResultsViewModel.test.ts` — rewrote the existing
   `toConfidenceBadges` shape assertion; added a `band: null`
   fallback case.
4. `analysisResultsUi.test.ts` — 5 integration cases proving each
   V1-V4 site renders band pill text and not the old vocabularies.

Live UI verified against saved run 855f1376… — 36 band pills visible
across the page (21 Solid scaffold, 7 Workable draft, 5 Rough sketch,
3 Unreliable). Zero `CONF X%` leakage. Detected Characteristics cards
show the chain-of-custody hedge working as designed (Detuned Supersaw
renders WORKABLE DRAFT in orange while the other 4 detections render
SOLID SCAFFOLD in green). Cross-Check ✓/✗ structurally preserved (the
saved run doesn't populate bpmAgreement, so it doesn't render, but the
code path is untouched).

`npm run verify` passes: lint + ~600 unit + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…eader)

Closes six items from the audit's "Quick hits (low ROI, real)" list.
Each is independent; bundled into one PR because they're all small
surgical fixes.

1. QH1 — StickyNav clipped "MEASUREMENTS" at 1440px. Was
   overflow-x-auto + min-w-max, which produced an invisible horizontal
   scroll (macOS hides scrollbars by default; last pill looked clipped
   with no scroll affordance). Switched to flex-wrap so pills flow onto
   multiple rows at narrow widths; whitespace-nowrap prevents intra-
   label wrap.

2. QH2 — Mix Chain card-number order badges removed. Cards are grouped
   by processing stage AFTER ordering, so the order numbers appeared
   out-of-sequence within each group ("1, 6, 8, 9 / 2, 4 / 5, 7 / 3 /
   10"), which read as a presentation bug. The visual sequence within
   each group is already meaningful; the badge added confusion without
   information. Dropped.

3. QH4 — Mix Doctor Band Diagnostics table now shows the target
   range alongside the optimal. Previously the column rendered only
   the optimal dB (e.g. "-22.0"), so two bands with similar Delta dB
   could land in different Issue buckets without the producer being
   able to see why. The Issue is determined by absolute thresholds
   (target.minDb / maxDb), not by the diff from optimal. Showing the
   range makes the verdict legible at a glance — Sub Bass -6.4 norm,
   range -16 to -8, exceeds the upper bound by 1.6 dB → TOO-LOUD;
   Low Mids -14.5 norm, range -26 to -14, sits at the boundary →
   OPTIMAL. Threshold logic itself was correct; this is a display fix.

4. QH5 — "Dense DAW Lab" link removed from the header. A prior audit
   pass had de-emphasized it to muted text, but the new audit re-
   flagged it for sitting in the primary flow's header without context
   for what it is. Route stays accessible via direct URL (the
   getAppViewHref helper is preserved for future re-introduction
   behind a settings menu, and main.tsx still routes to the
   DenseDawConcept component when activeView === 'daw-concept').

5. QH6 — header model selector visually demoted. Dropped the
   "Interpretation Model" label and shrank the styling from a bordered
   dropdown to a discreet text-secondary inline select. The audit
   flagged the prior styling as foregrounding an AI-model choice an
   intermediate producer has no basis to make. Selector stays
   accessible (smoke spec requires it visible at desktop viewport;
   `responsive-layout.spec.ts` and `upload-estimate-phase1.spec.ts`
   both guard the `phase2-model-desktop` testid) but now reads as
   background metadata. Aria-label + title attribute carry the long-
   form context for assistive tech and hover discoverability.

6. QH7 — Mix Doctor section title renamed from "MixDoctor" to
   "Mix Doctor". The audit flagged that the section header rendered
   as "Mixdoctor" with a lowercase 'd', inconsistent with every other
   section name. Root cause: `formatWord` in `utils/displayText.ts`
   case-normalizes single-token CamelCase to sentence case, so
   "MixDoctor" was rendering as "Mixdoctor". Two words ("Mix Doctor")
   matches every other section name's pattern (Style Profile, Project
   Setup, Track Layout, Mix & Master Chain, Patch Framework, etc.)
   and renders cleanly through the existing pipeline.

`npm run verify` passes: lint + ~600 unit + build + 44 smoke. Live UI
verified against saved run 855f1376… with probes confirming all six
fixes:
- StickyNav.overflow-x-auto wrapper gone (pills wrap to 2 rows)
- Zero Mix Chain order badges in #section-mix-chain
- "Target (range)" header present, "Target dB" header gone
- "Dense DAW Lab" text absent from page
- "Interpretation Model" label absent from page
- "Mix Doctor" section title present, "Mixdoctor" absent

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@slittycode slittycode left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE (posted as COMMENT — can't self-approve)

Summary

Six surgical UI fixes to the results-page nav, Mix Chain cards, Mix Doctor panel, and header. No Phase 1/2/3 logic touched, no contract changes. Node modules absent in the review environment so the test suite couldn't be run independently — see test results section.

Findings

No blocking or should-fix findings.

Worth considering:

  • StickyNav.tsx: the pr-2 trailing padding was dropped when the scroll container was removed. At maximum pill count the last pill now sits edge-flush in the wrapping container. Not broken, just worth eyeballing at a narrow viewport if the nav gains more sections.

Test results

Cannot run independently — node_modules not installed in this environment, no CI configured on the repo. Author reports npm run verify (lint + ~600 unit + build + 44 smoke) all green. Spot-checked the one non-obvious addition: row.targetMinDb / row.targetMaxDb in MixDoctorPanel.tsx:166 — both fields are declared on the MixDoctorBandRow type at mixDoctor.ts:14-15 and populated at lines 223-224. The render is safe.

Phase boundary check

Clean. mixDoctor.ts service logic and threshold computation are untouched. The Band Diagnostics column change is a pure display addition — it exposes targetMinDb/targetMaxDb that were already on the row object (used internally for the issue bucket) but previously not shown. No Phase 1 values mutated or re-derived anywhere in the diff.


Generated by Claude Code

@slittycode slittycode left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE (self-review — GitHub prevents approve on own PRs)

Summary

Six surgical UI fixes: StickyNav pill wrapping, Mix Chain order badge removal, Mix Doctor target-range display, Dense DAW Lab link removal, model selector demotion, and "MixDoctor" → "Mix Doctor" rename. All frontend-only, no Phase 1 reads or writes.

Findings

Worth considering:

  • The getAppViewHref import is removed from App.tsx but the helper remains exported in utils/appView.ts with no current consumer in App.tsx. The comment says "still exported for any future re-introduction" — fine, but if there's no other current consumer, it's dead export. Low priority.
  • Mix Doctor range render: ${formatNumber(row.targetOptimalDb, 1)} (${formatNumber(row.targetMinDb, 0)} to ${formatNumber(row.targetMaxDb, 0)}) — both targetMinDb and targetMaxDb confirmed to exist on BandDiagnosticRow (verified in mixDoctor.ts:14-15). The format is correct.
  • No CI check runs on head commit 3d91a3ff. Local vitest run tests/services: 616 pass, 1 skip, same pre-existing decision_gate.*.live.test.ts failures as the rest of the stack.

Test results

No CI on head commit. No new test cases added (these are pure rendering/layout changes with no new logic branches). The description notes that existing smoke specs guard phase2-model-desktop visibility — the selector is still present with the same data-testid, just restyled.

Phase boundary check

Clean. No Phase 1 reads or writes. The targetMinDb/targetMaxDb values are from the Mix Doctor view model (Phase 1–derived but already projected through mixDoctor.ts), not assigned back to Phase 1.


Generated by Claude Code

slittycode added a commit that referenced this pull request May 17, 2026
…esh (#57), nightly audit doc (#59)

* audits: nightly 2026-05-16 — 5 issues (4 test failures + 1 review item)

3 vitest decision_gate.*.live.test.ts failures (missing /tmp snapshot
gating), 1 Playwright upload-estimate-phase1.spec.ts timeout, and one
mixDoctor.estimatePlr fallback flagged for human boundary review. No
Phase 1 ground-truth mutations found.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(claude): add repo-layout, scripts map, Phase 3 UI entry, CSV recipe

Closes four orientation gaps in CLAUDE.md: a top-of-Architecture map
that flags advisory/, experiments/, docs/, and tests/ground_truth/ as
off-path; a Scripts-at-a-glance section covering scripts/ and
apps/backend/scripts/; a pointer from the Phase 3 paragraph to
SamplePlayback.tsx + sampleGenerationClient.ts; and a curl example
plus allowlisted field paths on the csv_export.py module entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add Layer 2 transcription evaluation guide

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(ui): stop Phase 2 results surface from leaking engine output

Removes four classes of broken/placeholder content that made the Phase 2
recommendations read as engine output instead of producer-facing advice
(audit finding #1).

1. Mix Chain card role text — `buildRoleSentence` used to fabricate
   "{stage phrase} by {verb}" by lowercasing the first letter of
   Gemini's `reason`. Because `reason` is a present-tense clause, every
   card produced ungrammatical splices like "Controls bass energy by
   ensures the extreme low-end mono…". Now renders the reason verbatim
   with a capitalized first letter and trailing period; HIGH-END cue
   suffix preserved as "(for …)".

2. Patch Framework `patchRole` — was a 7-key category-keyed fallback
   (`mapPatchRole`) that stamped duplicated placeholders ("Primary tone
   generator" on every SYNTHESIS card). Removed the field, the
   fallback, the JSX paragraph, and the contribution to the
   `inferProcessingGroup` text-concat. The category chip + per-card
   `whyThisWorks` already carry the bucket and explanation.

3. Patches/Mix Chain overlap — `buildPatchCards` now case-insensitively
   filters out devices that also appear in `mixAndMasterChain` so the
   Patches section stops re-listing chain devices. Synthetic fallbacks
   (Stereo Width, MIDI Clip Guide) are built from Phase 1 only and
   bypass the filter intentionally.

4. Interpretation Caution raw-JSON dump — the panel rendered
   `originalValue` verbatim, which for dropped recommendations is a
   JSON-dumped `AbletonRecommendation` (see `_stringify_warning_value`
   in `server_phase2.py`). Added `formatDroppedValue` helper that
   parses JSON-shaped values and renders a compact "device: X ·
   parameter: Y · value: Z" summary. Non-JSON strings pass through;
   invalid JSON falls back to a truncated raw string. Also resolves
   the `$Saturator` symptom — Gemini's hallucinated `$`-prefixed
   device name now surfaces as a readable summary line instead of
   leaking through a raw JSON dump.

5. System Diagnostics dev-only leak — `validateNewFieldCoverage`
   emits "Phase 1 field 'X' is present… this warning is benign"
   coverage signals meant for the engine team, not producers. Added
   optional `audience?: 'dev' | 'user'` to `ValidationViolation`,
   marked `NEW_FIELD_UNCITED` as dev, and updated
   `Phase2ConsistencyReport` to filter dev-audience violations from
   the rendered table AND from the header counts (so the header
   doesn't read "5 warnings shown" above an empty table). The
   underlying `ValidationReport` still carries every violation for
   tests and offline analysis.

Tripwire note: the backend grammar fix
`_apply_phase2_grammar_fixes` in `server_phase2.py` only runs on the
legacy `/api/phase2` endpoint, not the analysis-runs path the UI
uses. After this change it becomes a redundant no-op against the new
render shape; left untouched.

Tests: 16 new unit cases across viewModel, UI rendering, validator,
and consistency report. `npm run verify` passes (lint + 565 unit
tests + build + 44 smoke tests).

Live UI verified against saved run 855f1376… — all nine text-leak
probes (`by ensures`, `by ducks`, `by generates`,
`Primary tone generator`, `Texture and movement stage`,
`Stereo placement stage`, raw `\$Saturator`, `this warning is
benign`, "is present in the measurement payload but no Phase 2…")
return false.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ui): surface primary citation in collapsed card headers

Audit Finding #3 — "the chain-of-custody citation, the product's whole
differentiator, renders as a footnote."

Adds a one-line `CitationHeadline` primitive inside the collapsed header
of every Mix Chain / Patch / Sonic Element card so producers see the
measurement evidence at scan-time without expanding. The expanded
CitationBlock stays in the body unchanged — this is the collapsed-state
companion, not a replacement.

Each headline reads `{label} {value} →` with the arrow pointing into
the device h4/h3 that follows in the title row, making the implicit
"measurement justified device" statement visible — exactly the audit's
literal example "Crest factor 8.2 dB → Glue Compressor."

Confidence sibling pills (Solid / Workable / Rough / Unreliable, same
ladder as ConfidenceBandBadge) ride alongside the value when the
primary field has a paired *Confidence sibling. This preserves the
chain-of-custody invariant: low-confidence measurements visibly hedge
in the collapsed view too, not just after expansion.

Implementation:
1. `CitationHeadline` added as a sibling export in
   `apps/ui/src/components/CitationBlock.tsx`. Shares the file's
   imports, the `CONFIDENCE_PILL_CLASSES` map, and the audit lineage.
   Composes existing pure helpers (`pickPhase1Value`,
   `formatCitedValue`, `humanizeFieldPath`,
   `pickPhase1Confidence`, `getConfidenceBand`,
   `formatBandPillLabel`) — no new resolver logic.
2. Mount points in `AnalysisResults.tsx`:
   - Mix Chain card header (~line 2056): between the title row and
     the role paragraph.
   - Patch card header (~line 2215): between the title row and the
     MetaBadgeList.
   - Sonic Element card header (~line 1912): between the title row
     and the summary paragraph.
3. Each mount is guarded by `card.phase1Fields.length > 0`. Cards
   without cited fields fall back to today's exact layout.

Live verification against saved run 855f1376… (the audit reproducer):
all 10 Mix Chain cards, 2 Patch cards, and 7 Sonic Element cards
render the headline correctly with appropriate confidence pills
(PUMPING STRENGTH 47% [ROUGH SKETCH 30%], SUPERSAW DETECTED
[WORKABLE DRAFT 78%], ACID BASS DETECTED [SOLID SCAFFOLD 99%],
NOTE TRANSCRIPTION 61% [WORKABLE DRAFT 61%], KEY C Minor
[SOLID SCAFFOLD 93%], TEMPO 145 BPM [SOLID SCAFFOLD 100%]).

Mobile (375px) verified — headlines fit, pills stay aligned, no
overflow.

Tests: 15 new cases — 10 in `tests/services/citationHeadline.test.ts`
(mirrors `citationBlock.test.ts`) covering label/value/arrow render,
null fallback, confidence-pill bands, value formatter parity,
typography hooks. 5 in `analysisResultsUi.test.ts` covering Mix Chain
/ Patch / Sonic integration, empty-fields fallback, and the
low-confidence Unreliable-band path.

`npm run verify` passes: lint + 575 unit tests + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ui): unify five confidence vocabularies onto the canonical band ladder

Audit Finding #4 — the producer used to see five competing vocabularies
for "how much should I trust this number?" — HIGH/MED/LOW pills on
Detected Characteristics cards, High/Moderate/Low chips on Confidence
Notes, bare CONF X% text on Key/Character metric cards, SCORE X.XX
badges on the BPM card (two render paths), and the canonical four-band
ladder used only by the Session Musician panel. The chain-of-custody
invariant in PURPOSE.md requires low-confidence measurements to produce
visibly hedged advice — five vocabularies fragment that signal so the
producer can't build a single mental model of trust.

This PR promotes the existing four-band ladder
(apps/ui/src/services/sessionMusician/confidenceBand.ts) to the canonical
primitive across every confidence surface. Frontend-only; Gemini still
emits HIGH/MED/LOW and the UI converts locally via a new
`toConfidenceBand` normalizer.

What changed:

1. New normalizer `toConfidenceBand(value)` in `confidenceBand.ts`
   accepts numeric 0-1 floats, 0-100 integers, Gemini string enums
   (HIGH/MED/LOW + High/Moderate/Low variants), and percent strings
   ("62%"). Returns the matching `ConfidenceBand` or null for
   unparseable input. The string enums map to band-mid values (HIGH→0.9,
   MED→0.6, LOW→0.3) so `formatBandPillLabel` reads as honest hedges.

2. `ConfidenceBandBadge` gains:
   - `variant: 'full' | 'compact'` — `compact` omits the copy paragraph
     so the pill can sit inline in card corners and metric-card footers.
     `full` (default) preserves the Session Musician panel behavior
     unchanged.
   - `band?: ConfidenceBand` override prop — when supplied, skips the
     `getConfidenceBand` round-trip. Useful when the caller pre-converts
     a string enum.

3. V1: Detected Characteristics cards (AnalysisResults.tsx ~1650) —
   bespoke HIGH/MED/LOW ternary pill replaced with `<ConfidenceBandBadge
   variant="compact" band={toConfidenceBand(item.confidence)} />`.
   `characteristicPillClass` helper kept alive only for the unrelated
   characteristic-name chips on the Character metric card (line 1000);
   flagged as a follow-up.

4. V2: Confidence Notes chips (AnalysisResults.tsx ~1187) —
   `toConfidenceBadges` viewModel return shape changed from
   `{ label, level }` (3-level legacy enum) to `{ label, band }`
   (canonical four-band ladder). `confidenceClass` helper deleted.

5. V3: Plain `CONF X%` text (3 sites) — replaced with
   `<ConfidenceBandBadge variant="compact" confidence={...} />` in:
   - AnalysisResults.tsx Key card footer
   - AnalysisResults.tsx Character card footer
   - MeasurementDashboard.tsx alternate Key card

6. V4: `SCORE X.XX` badges (2 sites) — replaced with band pills in
   AnalysisResults.tsx Tempo card and MeasurementDashboard.tsx Tempo
   card. `formatBpmScore` deleted from both files. Cross-Check ✓/✗
   StatusBadge in MeasurementDashboard preserved — that's an agreement
   signal (do multiple BPM detectors agree?), not confidence.

7. Dead code retired: `normalizeConfidenceLevel`,
   `parseConfidenceScalar`, `confidenceClass`, two copies of
   `formatBpmScore`. `ConfidenceLevel` type and
   `MelodyInsightsViewModel.confidenceLabel` kept alive — they drive
   non-pill key/value text rows in Sonic Element melody insights
   (flagged for follow-up migration).

3-level → 4-band mismatch documented as an intentional refinement: the
old `normalizeConfidenceLevel` mapped scalar 0.5-0.79 AND "medium" both
to "Moderate"; the new normalizer maps "medium" to 0.6 → workable
(consistent), and scalar 0.25-0.49 to "Rough sketch" (more granular
than the old "Low" bucket).

Tests: 20 new cases across 4 files —
1. `confidenceBand.test.ts` — 9 cases for `toConfidenceBand`
   (null/undefined fallback, 0-1 floats, 0-100 integers, all string
   enum variants, percent strings, NaN/Infinity, negative clamping).
2. `confidenceBandBadge.test.ts` — 4 cases for the compact variant
   and the `band` override prop (label-only when no confidence, tone
   override, Gemini-string routing).
3. `analysisResultsViewModel.test.ts` — rewrote the existing
   `toConfidenceBadges` shape assertion; added a `band: null`
   fallback case.
4. `analysisResultsUi.test.ts` — 5 integration cases proving each
   V1-V4 site renders band pill text and not the old vocabularies.

Live UI verified against saved run 855f1376… — 36 band pills visible
across the page (21 Solid scaffold, 7 Workable draft, 5 Rough sketch,
3 Unreliable). Zero `CONF X%` leakage. Detected Characteristics cards
show the chain-of-custody hedge working as designed (Detuned Supersaw
renders WORKABLE DRAFT in orange while the other 4 detections render
SOLID SCAFFOLD in green). Cross-Check ✓/✗ structurally preserved (the
saved run doesn't populate bpmAgreement, so it doesn't render, but the
code path is untouched).

`npm run verify` passes: lint + ~600 unit + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: refresh stale items against current architecture

CHANGELOG.md "Unreleased" was missing nine shipped features (Phase 3
audition samples, URL ingest, admin DELETE bypass, source-audio route,
CSV export, publicStatus, reassigned spectrogram, chain-of-custody audit
overhaul, BatchedBandpass centralization, hosted runtime foundation,
backend monolith split). Added them.

CLAUDE.md's analyze.py CLI example only listed four flags; expanded to
match the full surface (--standard, --pitch-note-only, --stem-dir,
--stem-output-dir, --pitch-note-backend). Expanded the Frontend key
service files list from 9 to 16 entries to cover the services that
landed during the audit overhaul (httpClient, sampleGenerationClient,
appliedRecommendations + userLabels, phase1Picker + phaseLabels,
audioFile, fieldAnalytics + diagnosticLogs, sessionMusician helpers).

apps/ui/AGENTS.md File Map likewise expanded from 4 to 14 service
entries against the same list.

docs/SAMPLE_GENERATION.md's "Snapshot integration" section claimed an
`AnalysisRunSnapshot.stages.sampleGeneration` shape that does not exist
in apps/ui/src/types/backend.ts — the snapshot tracks only measurement,
pitchNoteTranslation, and interpretation. Replaced the stale TS block
with a description that matches the actual on-demand artifact path
(consistent with the doc's own line 47).

apps/backend/AGENTS.md and apps/backend/ARCHITECTURE.md now flag
symbolic_extract.py as orphaned and broken — it imports a removed
BasicPitchBackend symbol from analyze.py, would raise ImportError at
load, and is not referenced from any other module. Slated for removal.

docs/history/README.md had `library-review-torchfx-2026-05-13.md`
listed twice; removed the duplicate.

docs/ARCHITECTURE_STRATEGY.md "Last updated" stamp bumped to May 2026
with a note that no shifts to the three-layer thesis or library
decisions accompanied the recent feature work.

Date stamps in apps/backend/AGENTS.md and apps/ui/AGENTS.md refreshed
to 2026-05-17.

https://claude.ai/code/session_01QEmFAUJSoRgTqPcFTfcnYW

* fix(ui): audit quick-hits bundle (StickyNav, Mix Chain, Mix Doctor, header)

Closes six items from the audit's "Quick hits (low ROI, real)" list.
Each is independent; bundled into one PR because they're all small
surgical fixes.

1. QH1 — StickyNav clipped "MEASUREMENTS" at 1440px. Was
   overflow-x-auto + min-w-max, which produced an invisible horizontal
   scroll (macOS hides scrollbars by default; last pill looked clipped
   with no scroll affordance). Switched to flex-wrap so pills flow onto
   multiple rows at narrow widths; whitespace-nowrap prevents intra-
   label wrap.

2. QH2 — Mix Chain card-number order badges removed. Cards are grouped
   by processing stage AFTER ordering, so the order numbers appeared
   out-of-sequence within each group ("1, 6, 8, 9 / 2, 4 / 5, 7 / 3 /
   10"), which read as a presentation bug. The visual sequence within
   each group is already meaningful; the badge added confusion without
   information. Dropped.

3. QH4 — Mix Doctor Band Diagnostics table now shows the target
   range alongside the optimal. Previously the column rendered only
   the optimal dB (e.g. "-22.0"), so two bands with similar Delta dB
   could land in different Issue buckets without the producer being
   able to see why. The Issue is determined by absolute thresholds
   (target.minDb / maxDb), not by the diff from optimal. Showing the
   range makes the verdict legible at a glance — Sub Bass -6.4 norm,
   range -16 to -8, exceeds the upper bound by 1.6 dB → TOO-LOUD;
   Low Mids -14.5 norm, range -26 to -14, sits at the boundary →
   OPTIMAL. Threshold logic itself was correct; this is a display fix.

4. QH5 — "Dense DAW Lab" link removed from the header. A prior audit
   pass had de-emphasized it to muted text, but the new audit re-
   flagged it for sitting in the primary flow's header without context
   for what it is. Route stays accessible via direct URL (the
   getAppViewHref helper is preserved for future re-introduction
   behind a settings menu, and main.tsx still routes to the
   DenseDawConcept component when activeView === 'daw-concept').

5. QH6 — header model selector visually demoted. Dropped the
   "Interpretation Model" label and shrank the styling from a bordered
   dropdown to a discreet text-secondary inline select. The audit
   flagged the prior styling as foregrounding an AI-model choice an
   intermediate producer has no basis to make. Selector stays
   accessible (smoke spec requires it visible at desktop viewport;
   `responsive-layout.spec.ts` and `upload-estimate-phase1.spec.ts`
   both guard the `phase2-model-desktop` testid) but now reads as
   background metadata. Aria-label + title attribute carry the long-
   form context for assistive tech and hover discoverability.

6. QH7 — Mix Doctor section title renamed from "MixDoctor" to
   "Mix Doctor". The audit flagged that the section header rendered
   as "Mixdoctor" with a lowercase 'd', inconsistent with every other
   section name. Root cause: `formatWord` in `utils/displayText.ts`
   case-normalizes single-token CamelCase to sentence case, so
   "MixDoctor" was rendering as "Mixdoctor". Two words ("Mix Doctor")
   matches every other section name's pattern (Style Profile, Project
   Setup, Track Layout, Mix & Master Chain, Patch Framework, etc.)
   and renders cleanly through the existing pipeline.

`npm run verify` passes: lint + ~600 unit + build + 44 smoke. Live UI
verified against saved run 855f1376… with probes confirming all six
fixes:
- StickyNav.overflow-x-auto wrapper gone (pills wrap to 2 rows)
- Zero Mix Chain order badges in #section-mix-chain
- "Target (range)" header present, "Target dB" header gone
- "Dense DAW Lab" text absent from page
- "Interpretation Model" label absent from page
- "Mix Doctor" section title present, "Mixdoctor" absent

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Oz <oz-agent@warp.dev>
Base automatically changed from fix/unified-confidence to main May 17, 2026 21:29
@slittycode
slittycode merged commit bc79247 into main May 17, 2026
@slittycode
slittycode deleted the fix/quick-hits branch May 17, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant