# Classroom Timer Integration ## Purpose A visual countdown timer with sensory backgrounds and a sound library. Sounds come from three places, unified in one picker: hardcoded **built-in** synthesized sounds, **generated** sounds (`recipe` rows), and **uploaded** audio (`file`/`url` rows) from the `audio_files` library. ## Current Behavior - `frontend/src/components/frameworks/ClassroomTimer.tsx` is a thin wrapper that takes `userRole` (from the shell) and calls `useClassroomTimer(userRole)`. - Timer state, fullscreen, custom-time parsing, progress/urgency, particles, and Web Audio orchestration live in `frontend/src/business/classroom-timer/`. `shared/constants/classroomTimer.ts` keeps timing/particle config plus the sound-group labels and the generated/uploaded fallback icons. - View pieces are under `frontend/src/components/classroom-timer/`. The sound picker (`TimerSettingsPanel`) groups sounds by origin — **Built-in / Generated / Uploaded** — for clear structure. - Backgrounds/presets/tips and the built-in sound metadata are backend-owned `content_catalog` records loaded via the shared content-catalog API. The built-in **sounds themselves** are synthesized in-browser (`business/classroom-timer/audio.ts`, `playBuiltInSound`). - Missing content-catalog data renders an explicit backend error (no frontend seed fallback). ## Audio Library The picker merges the built-ins with the `audio_files` library (`business/audio-files/`): - **API/types**: `shared/api/audioFiles.ts`, `shared/types/audioFiles.ts` (incl. the `SoundRecipe` shape). - **Hooks**: `useAudioFiles` (list; `retry: false` so a caller without `READ_AUDIO_FILES` silently falls back to the built-ins), `useGenerateAudioFile`, `useDeleteAudioFile`. `canManageAudioFiles` gates the manage affordances (director/office_manager/teacher). - **Playback branches by kind**: `builtin` → `playBuiltInSound(id)`, `recipe` → `playRecipe(recipe)` (`business/classroom-timer/audio-recipe.ts`, pure Web Audio from JSON params), `file`/`url` → `new Audio(url)`. - **Generate**: managers type a name and click *Generate*; this creates a `recipe` row whose synthesis parameters come from a **local stub** (`business/audio-files/generate.ts`). When an AI key is wired, only that function changes — persistence, playback and the library list are unchanged. An empty name falls back to a generated name. - **Delete**: managers can remove their own (non-default) library rows. ## Tests - `business/classroom-timer/selectors.test.ts` (timer math) - `business/audio-files/selectors.test.ts` (`canManageAudioFiles`), `business/audio-files/generate.test.ts` (local recipe stub shape) ## Verification - `npm run typecheck`, `npm run lint`, `npm run test` pass. ## Remaining Work - Swap the local `generateSoundRecipe` stub for a real model call once an AI key exists. - Binary `file` upload UI — needs a typed upload client and the file-download ownership fix (see `backend/docs/audio-files.md`); `recipe`/`url` rows are unaffected.