# 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.
