Auto-generated from all feature plans. Last updated: 2025-11-13
-
JavaScript ES6+, React 18.2.0 + React Testing Library (@testing-library/react ^13.0.0), Jest (^29.0.3), Playwright (@playwright/test) (001-constitution-compliance)
-
Firebase (^9.9.4) for user data, localStorage for client-side state (001-constitution-compliance)
-
JavaScript ES6+, React 18.2.0 + React event system, eslint-plugin-jsx-a11y (already configured) (002-fix-a11y-errors)
-
N/A (no data model changes) (002-fix-a11y-errors)
-
JavaScript ES6+, React 18.2.0 + React event system (onKeyDown), existing menu components (003-menu-arrow-navigation)
-
N/A (no data persistence, pure UI interaction) (003-menu-arrow-navigation)
-
JavaScript ES6+, React 18.2.0 + React Testing Library (@testing-library/react ^13.0.0), jest-axe, Playwright (@playwright/test), @axe-core/playwrigh (001-piano-key-keyboard-navigation)
-
Firebase (^9.9.4) for user data, localStorage for client-side state (scales, progress), existing scale state managemen (001-piano-key-keyboard-navigation)
-
JavaScript ES6+, React 18.2.0 + React 18.2.0 (existing), no new dependencies required (004-fix-relative-notenames)
-
N/A (no data persistence - pure display logic) (004-fix-relative-notenames)
-
JavaScript ES6+, React 18.2.0 + ReactPlayer (react-player ^2.10.1), React Bootstrap (^2.5.0), react-draggable (^4.4.5) (001-custom-video-player)
-
N/A (session state only, no persistence) (001-custom-video-player)
-
JavaScript ES6+, React 18.2.0 (001-constitution-compliance)
backend/
frontend/
tests/
npm test && npm run lint
JavaScript ES6+, React 18.2.0: Follow standard conventions
All interactive components must include:
role="button"for keyboard-accessible elementsaria-labelwith descriptive text (e.g., "Play C4", "Share", "Watch tutorial video")tabIndex={0}to include in natural tab order
Example:
<div
role="button"
aria-label={`Play ${note}`}
tabIndex={0}
onKeyDown={handleKeyDown}>
{/* content */}
</div>Handle Enter and Space keys for button activation:
handleKeyDown = (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault(); // Prevent Space from scrolling page
// Trigger action
}
};- Use browser default focus indicators (no custom outline removal)
- Ensure focus/hover parity (same information shown on both interactions)
- Maintain focus after activation (keyboard events should not move focus)
- 60-70% Integration Tests: Use React Testing Library with jest-axe for accessibility audits
- 20-30% E2E Tests: Use Playwright with @axe-core/playwright for cross-browser validation
- 10-20% Unit Tests: Edge cases in focus management and unusual scenarios
Example integration test:
import { axe, toHaveNoViolations } from 'jest-axe';
const { container } = render(<Component />);
const results = await axe(container);
expect(results).toHaveNoViolations();Icon-only buttons must have descriptive aria-labels:
- ShareButton:
aria-label="Share" - VideoButton:
aria-label="Watch tutorial video" - HelpButton:
aria-label="Help" - DropdownCustomScaleMenu:
aria-label="Customize scale settings"
The relative notation system uses scale-degree-based syllables (movable-do solfege) instead of chromatic-position-based syllables. This ensures syllables remain consistent across all 12 chromatic keys.
Key Principle: DO always represents the tonic (root note) regardless of key. Syllables are determined by scale degree position, not absolute pitch.
Located in src/Model/MusicScale.js as SCALE_DEGREE_TO_SYLLABLE constant:
const SCALE_DEGREE_TO_SYLLABLE = {
// Diatonic degrees (major scale baseline)
"1": "DO", "2": "RE", "3": "MI", "4": "FA",
"5": "SO", "6": "LA", "7": "TE", "△7": "TI",
// Chromatic alterations (lowered degrees)
"b2": "RA", // Phrygian, Locrian
"b3": "ME", // All minor scales
"b5": "SE", // Locrian
"b6": "LE", // Natural Minor, Harmonic Minor, etc.
"b7": "TE", // Same as "7"
// Raised degrees (ascending alterations)
"#1": "DI", "#2": "RI", "#4": "FI", // Lydian
"#5": "SI", "#6": "LI"
};| Scale | Syllables | Key Characteristics |
|---|---|---|
| Natural Minor | DO RE ME FA SO LE TE | b3, b6, b7 |
| Harmonic Minor | DO RE ME FA SO LE TI | b3, b6, △7 (raised 7th) |
| Phrygian | DO RA ME FA SO LE TE | b2, b3, b6, b7 |
| Locrian | DO RA ME FA SE LE TE | b2, b3, b5, b6, b7 |
| Dorian | DO RE ME FA SO LA TE | b3, b7 |
| Lydian | DO RE MI FI SO LA TI | #4 |
| Mixolydian | DO RE MI FA SO LA TE | b7 |
| Melodic Minor | DO RE ME FA SO LA TI | b3 (ascending) |
| Major (Ionian) | DO RE MI FA SO LA TI | No alterations |
Method: MusicScale.makeRelativeScaleSyllables(semiToneSteps, scaleNumbers, extendedSteps)
- Maps each chromatic step to a syllable based on scale degree position
- Uses
Recipe.numbersarray (e.g.,["1", "2", "b3", "4", "5", "b6", "7"]) to determine degrees - Handles chromatic scale as special case (fallback to
MakeChromatic) - O(1) lookup performance via constant mapping table
Integration: Called from BuildExtendedScaleToneNames() case "Relative"
case "Relative":
theScale["Relative"] = this.makeRelativeScaleSyllables(
semiToneSteps,
this.Recipe.numbers,
this.ExtendedScaleSteps
);
break;- 60-70% Integration Tests: Test scale generation with
MusicScaleclass - 20-30% E2E Tests: Test user workflows (scale selection + notation display)
- 10-20% Unit Tests: Edge cases only (chromatic scale, pentatonics, etc.)
Example integration test:
import MusicScale from '../Model/MusicScale';
import scalesObj from '../data/scalesObj';
const naturalMinorRecipe = scalesObj.find(s => s.name === 'Natural Minor/Aeolian');
const scale = new MusicScale(naturalMinorRecipe, 'C', 0, 36);
const syllables = scale.ExtendedScaleToneNames.Relative;
expect(syllables.slice(0, 7)).toEqual(['DO', 'RE', 'ME', 'FA', 'SO', 'LE', 'TE']);CRITICAL: All scales MUST show identical syllables across all 12 chromatic keys.
Test pattern:
const keys = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
const expectedPattern = ['DO', 'RE', 'ME', 'FA', 'SO', 'LE', 'TE'];
keys.forEach(key => {
const scale = new MusicScale(naturalMinorRecipe, key, 0, 36);
const first7 = scale.ExtendedScaleToneNames.Relative.slice(0, 7);
expect(first7).toEqual(expectedPattern); // MUST be identical
});- <50ms syllable update on scale/key change
- <5s per integration test
- <30s per E2E test
- O(1) lookup via constant table (actual: <1ms per scale generation)
The relative notation fix MUST NOT affect other notation modes:
- English: C D Eb F G Ab Bb (unchanged)
- German: C D Eb F G As B (unchanged)
- Romance: Do Re Mib Fa Sol Lab Sib (unchanged)
- Scale Steps: 1 2 b3 4 5 b6 7 (unchanged)
- Chord Extensions: (unchanged)
Always test all notation modes when modifying BuildExtendedScaleToneNames().
- 001-custom-video-player: Added JavaScript ES6+, React 18.2.0 + ReactPlayer (react-player ^2.10.1), React Bootstrap (^2.5.0), react-draggable (^4.4.5)
- 004-fix-relative-notenames: Added JavaScript ES6+, React 18.2.0 + React 18.2.0 (existing), no new dependencies required
- 004-text-size-in-video-overlay: Added JavaScript ES6+, React 18.2.0 + React Bootstrap, ReactPlayer, react-draggable, Firebase (^9.9.4), SCSS