Tech Stack
Framework & Build
| Technology | Version | Role |
|---|---|---|
| SvelteKit | ^2.50 | App framework (routing, SSR, adapters) |
| Svelte 5 | ^5.51 | UI framework (runes mode for reactivity) |
| Vite | ^7.3 | Build tool and dev server |
| TypeScript | ^5.9 | Type safety (strict mode) |
| Tailwind CSS | ^4.2 | Utility-first styling via @tailwindcss/vite |
Audio Libraries
| Library | Version | Role |
|---|---|---|
| Tone.js | ^15.1 | Transport scheduling, audio graph, synths (metronome) |
| smplr | ^0.19 | SoundFont instrument playback (GM samples) |
| Pitchy | ^4.1 | McLeod Pitch Method — real-time pitch detection |
Music Notation
| Library | Version | Role |
|---|---|---|
| abcjs | ^6.6 | Renders ABC notation to SVG in the browser |
Testing
| Tool | Version | Role |
|---|---|---|
| Vitest | ^4.1 | Unit testing (node environment) |
| Playwright | ^1.58 | End-to-end browser testing |
| @testing-library/svelte | ^5.3 | Component testing utilities |
PWA
| Tool | Version | Role |
|---|---|---|
| @vite-pwa/sveltekit | ^1.1 | PWA integration with auto-update service worker |
The PWA is configured in vite.config.ts with:
registerType: 'autoUpdate'— service worker updates automatically- Workbox pre-caches JS, CSS, HTML, SVG, and WOFF2 files
- SoundFont (
.sf2) files useCacheFirstruntime caching (30-day expiry) - Standalone display mode with dark theme color (
#0f172a)
Styling Approach
Mankunku uses Tailwind CSS v4 with CSS custom properties for theming:
/* src/app.css */
:root {
--color-bg: #0f172a;
--color-bg-secondary: #1e293b;
--color-bg-tertiary: #334155;
/* Ear Training (default) — Blue Note peacock teal */
--color-accent: #2e8b9e;
--color-accent-hover: #1f6b7a;
/* Blue Note brass — decorative chrome accent */
--color-brass: #c8923d;
--color-paper: #1a1410;
/* Vintage recording-booth red for the active/stop state */
--color-onair: #a8463a;
/* ... */
}
[data-domain='lick-practice'] {
/* Warm terracotta for lick-practice routes */
--color-accent: #c96a3e;
--color-accent-hover: #a64f27;
}
[data-domain='neutral'] {
--color-accent: #94a3b8;
--color-accent-hover: #cbd5e1;
}
:root.light { /* light-mode equivalents */ }
Components reference these variables inline: bg-[var(--color-bg-secondary)]. Theme switching toggles the .light class on . Route domain (ear-training / lick-practice / neutral) is derived in +layout.svelte and applied as data-domain on the layout root — flipping --color-accent re-colors every interactive surface. See documentation/architecture/design-system.md.
The display serif Fraunces (variable font, weight 300–800, Latin subset, self-hosted for offline PWA support) is used for the wordmark, page titles, key/grade readouts, and the primary "Ear Training / Lick Practice" nav labels via the .font-display utility.
Configuration Files
svelte.config.js— Enables runes mode for all non-node_modules files viadynamicCompileOptions. Usesadapter-nodeso the server can run authentication hooks and session middleware.tsconfig.json— Extends SvelteKit's generated config. Strict mode enabled with bundler module resolution.vite.config.ts— Registers Tailwind, SvelteKit, and PWA plugins. Test config points totests/unit/**/*.test.tswithnodeenvironment.
Architecture Summary
Mankunku is a local-first PWA with optional cloud sync:
- State persistence — All user progress, settings, and session history are stored in
localStoragefirst. An optional Supabase backend (src/lib/supabase/,src/routes/api/account/) provides authenticated cloud sync so the same data follows a user across devices. - Audio pipeline — Built entirely on Web Audio APIs. An
AudioWorklethandles onset detection, anAnalyserNodefeeds the pitch detector, and Tone.js manages transport scheduling for metronome and phrase playback. - Music theory — Scales, intervals, transposition, key signatures, and scoring algorithms are implemented in pure TypeScript with no external music theory libraries. The 35-scale catalog and ~250 lick library are defined as typed data structures.
- Deployment —
adapter-nodeproduces a Node.js server bundle (deployed via rsync + PM2 to a Digital Ocean VM). The PWA service worker still enables full offline functionality after initial load.
Why These Choices
- Svelte 5 runes over stores: Fine-grained reactivity without boilerplate.
$stateand$derivedreplace writable/derived stores with simpler semantics. - Tone.js for transport: Provides sample-accurate scheduling via a centralized Transport, essential for synchronizing metronome clicks with phrase playback.
- smplr over Tone.js sampler: Smaller bundle for GM SoundFont playback. Shares the same AudioContext.
- Pitchy over Web Audio
AnalyserNodealone: Implements the McLeod Pitch Method which is more accurate for monophonic instruments than simple FFT peak detection. - ABC notation over MusicXML: Text-based format is trivial to generate from MIDI data. abcjs renders it to SVG with no server required.
- Local-first with optional Supabase: All writes hit
localStoragefirst so the app works fully offline; an authenticated user's Supabase sync is background fire-and-forget, not a request path.