Repository navigation
fix: minor tetrad symbols render as capital M (m7 -> M7, mMaj7 -> MΔ7) #116
Description
Bug
In the Free Play "Tetrads" section, m7 (minor seventh) and mMaj7 (minor-major seventh) render with a capital M — M7 and MΔ7 — instead of m7 and mΔ7. In jazz notation this reads as "Major" instead of "minor", which is a real ambiguity, not just a style nit.
Confirmed via screenshot of the deployed site (Free Play → Tetrads picker).
Root cause
Not a data bug — CHORD_CATALOG in src/music/data/chords.ts is already correct (m7: { symbol: 'm7' }, mMaj7: { symbol: 'mΔ7' }; the project's existing triangle-glyph convention for major-seventh is Δ, confirmed, no change needed there).
The defect is presentational: three components apply a blind "capitalize first letter" transform to the string returned by formatQualityLabel() (src/music/display.ts), meant to turn word-phrases like major triad / ionian scale into nicely-cased UI text. The transform doesn't distinguish word-phrases (safe to capitalize) from raw notation symbols, where m vs M is semantically load-bearing (unlike e.g. sus2/sus4, where case carries no such meaning). Reproduces in:
FreePlayPicker.vue—.sub-stage-btn { text-transform: capitalize }(the reported screenshot).IdleView.vue—.quality-label { text-transform: capitalize }on the Learn-mode "Quality: …" line. Worse: it interpolates the rawChordQualityunion key directly instead of callingformatQualityLabel, so it currently leaks internal keys to the UI (e.g.Quality: mMaj7) and the capitalize transform corrupts that toQuality: MMaj7.TheoryModal.vue—title = computed(() => capitalize(formatQualityLabel(...))), a JS-level first-letter capitalize on the theory-popup title.
Proposed fix
Add a shared predicate in src/music/display.ts that identifies labels where capitalization must be suppressed (currently only m7 and mMaj7):
export function isMinorSymbolLabel(quality: ChordQuality | ScaleMode | IntervalGroup): boolean {
if (!(quality in CHORD_CATALOG)) return false
const q = quality as ChordQuality
if (TRIAD_LABEL[q]) return false
return CHORD_CATALOG[q].symbol.startsWith('m')
}Deliberately excludes sus2/sus4 (also raw symbols) since their leading letter carries no minor/major ambiguity — their current capitalized look (Sus2, Sus4) stays unchanged.
Each consumer keeps its own capitalization mechanism (CSS text-transform: capitalize for FreePlayPicker/IdleView, JS capitalize() for TheoryModal) but skips it when isMinorSymbolLabel(quality) is true.
Files to touch: src/music/display.ts, src/components/FreePlayPicker.vue, src/components/IdleView.vue (also switch from raw-key interpolation to formatQualityLabel(quality)), src/components/TheoryModal.vue, plus test updates in src/music/__tests__/display.spec.ts, src/components/__tests__/FreePlayPicker.spec.ts, src/components/__tests__/TheoryModal.spec.ts, src/components/__tests__/IdleView.spec.ts.
Activity
privera-git commentedon Jul 12, 2026
Fixed in #125.
privera-git commentedon Jul 17, 2026
For traceability: this bug was fixed on `main` via PR #131 (`FreePlayPicker.vue`, `TheoryModal.vue`) and its follow-up PR #132 (`IdleView.vue`), using `formatQualityLabel`/`isChordSymbolQuality` in `src/music/display.ts`.
PR #125 attempted the same fix independently (`isMinorSymbolLabel`) but was closed without merging since it duplicates already-merged work and would conflict with the chosen implementation.