Skip to content

fix: minor tetrad symbols render as capital M (m7 -> M7, mMaj7 -> MΔ7) #116

Closed

Description

@privera-git

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:

  1. FreePlayPicker.vue — .sub-stage-btn { text-transform: capitalize } (the reported screenshot).
  2. IdleView.vue — .quality-label { text-transform: capitalize } on the Learn-mode "Quality: …" line. Worse: it interpolates the raw ChordQuality union key directly instead of calling formatQualityLabel, so it currently leaks internal keys to the UI (e.g. Quality: mMaj7) and the capitalize transform corrupts that to Quality: MMaj7.
  3. 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

  1. self-assigned this
    on Jul 12, 2026
  2. privera-git commented on Jul 12, 2026

    @privera-git
    OwnerAuthor

    Fixed in #125.

  3. privera-git commented on Jul 17, 2026

    @privera-git
    OwnerAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions