================ ONE-SHOT BUILD CONTRACT (read first) ================
Build this in Google AI Studio "Build" in ONE shot — a complete, working app,
no follow-up turns. These are hard rules, not suggestions:
1. TARGET = Full-Stack Web (Node server runtime, secrets, Firebase allowed).
If you target Native Android instead, you MUST drop all server/DB/Workspace/
secrets and persist locally (Room / SharedPreferences) only.
2. PIN EVERY MODEL CALL — never let the agent auto-select (it downgrades on quota):
- Reasoning / text -> gemini-3.5-flash (thinkingLevel: minimal|low|medium|high)
- 4K image + legible text -> gemini-3-pro-image (image_size "4K", up to 14 refs)
- High-volume image -> gemini-3.1-flash-image
- Expressive TTS -> gemini-3.1-flash-tts-preview (inline tags e.g. [whispers])
- Realtime audio/video (WebSocket) -> gemini-3.1-flash-live-preview
- Sandboxed agent -> antigravity-preview-05-2026
3. DIVISION OF LABOR — the model ONLY parses/extracts to a strict responseSchema.
ALL math, money (store currency as integer minor units / cents), sorting,
balancing and graph logic run in deterministic TypeScript/Python. The model
must never compute totals, splits or balances itself.
4. responseSchema sanitation — no regex patterns, no fixed-length tuples, no
format validators in the schema (they crash the OpenAPI engine). Enforce those
in server-side code AFTER parsing the JSON.
5. responseSchema and google_search grounding are MUTUALLY EXCLUSIVE in one call.
6. CODEGEN — split large output into modular, single-responsibility files so no
file is truncated by the output-token cap.
7. Every external call gets a graceful fallback (e.g. manual paste if a Workspace
read fails). Never a silent dead end.
8. ROBUST STORAGE & CANVAS — Wrap all `localStorage`/`sessionStorage` operations (especially JSON parsing and writes) in `try-catch` blocks to prevent crashes in private windows or quota overflows. Canvas drawing elements must dynamically handle window resize and scale pixel density (`window.devicePixelRatio`) to avoid blurry graphics on retina displays.
=====================================================================
# MUST OBEY — Mobile-first build requirements
This app's PRIMARY surface is a mobile phone. Build it impeccably on mobile FIRST, then verify on tablet and desktop. Treat the rules below as non-negotiable hard constraints, not suggestions.
## Viewports to verify (every screen, every state)
- 320 px, 360 px, 375 px, 390 px, 414 px, 480 px
- 768 px, 834 px (iPad portrait / Pro 11)
- 1024 px, 1280 px, 1440 px, 1920 px, 2560 px
- Plus: 200% browser zoom, landscape orientation on every mobile width, iPhone with safe-area insets visible
## Hard layout rules
- Mobile-first CSS. Default styles target mobile; `@media (min-width: ...)` for larger viewports.
- Use `dvh` and `svh` instead of `vh` for full-height surfaces (iOS Safari URL-bar bug).
- Use `clamp()` for fluid typography across all viewports.
- Prefer container queries (`@container`) over media queries for component-level responsiveness.
- Use `min(100%, ...)` widths so content never overflows. Zero horizontal overflow at any viewport.
- Add `` to every page.
- Apply `padding: max(safe-area-inset-X, fallback)` on every edge-bleeding container so notched iPhones in landscape never clip content.
- Wide tables and code blocks scroll INSIDE their container (`overflow-x: auto`), never push the body.
- Use `background-attachment: scroll` on mobile, not `fixed` (iOS Safari repaint bug).
- Avoid `backdrop-filter` on animated elements. Use it sparingly on static surfaces only.
- **Canvas Scaling**: Canvases must dynamically scale with window resize events and properly handle high-DPI screens (`window.devicePixelRatio`). Set physical dimensions (`canvas.width`/`canvas.height`) using pixel ratio and render relative to this grid, using CSS to control responsive viewport scaling.
- **Robust Storage**: Every access to `localStorage`/`sessionStorage` (especially `JSON.parse` of loaded state or writes) MUST be wrapped in a `try-catch` block to handle disabled storage, private browsing mode, quota limits, or corrupted JSON gracefully. Fall back to a robust in-memory object store.
## Touch & accessibility
- Tap targets ≥ 44 × 44 px on touch (Apple HIG). Increase to 48 px under `@media (hover: none) and (pointer: coarse)`.
- All interactive controls reachable by keyboard with a visible focus ring; respect `:focus-visible`.
- Color contrast ≥ 4.5:1 for body text, 3:1 for UI components.
- All images have meaningful `alt`. Decorative images use `alt=""`.
- Respect `prefers-reduced-motion: reduce` — zero animation durations under that query.
- Forms validate inline; error messages are specific, not "Invalid input".
- Modals: focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, focus restored on close.
## Performance bar (Lighthouse mobile, throttled 3G/4G)
- LCP < 2.5 s · INP < 200 ms · CLS < 0.1
- JS bundle gzip < 200 KB mobile-first; lazy-load non-critical screens via `React.lazy` / dynamic imports.
- No render-blocking resources above the fold.
- Images: WebP/AVIF preferred, `loading="lazy"`, explicit `width`/`height` attributes (zero CLS), `srcset` for retina.
- Videos: `preload="metadata"`, low-resolution poster, max 720p mobile fallback. Never autoplay with audio.
- Fonts: `font-display: swap`; preload only the one used above the fold.
- Smooth scroll honoured via CSS `scroll-behavior: smooth` with reduced-motion fallback.
## Pre-ship mobile checklist (the deployer MUST verify before declaring done)
1. Open at 375 px in DevTools — every screen scrolls vertically only; zero horizontal scroll.
2. Browser zoom 200% — layout reflows without overlap.
3. iPhone Safari with the URL bar visible AND landscape — no content under the home indicator; no notch clipping.
4. iPad portrait (768 px) and landscape (1024 px) — no awkward gaps; tablet-specific breakpoints land cleanly.
5. Tap every interactive element with a thumb at real-device size — every target is easy to hit.
6. `prefers-reduced-motion: reduce` — every transition / animation skips cleanly, scroll-behavior becomes instant.
7. Lighthouse mobile score ≥ 90 across all 4 categories.
8. Zero `console.error` and zero CLS shift in real-device testing on a mid-tier Android (e.g. Pixel 6a) and an iPhone SE.
---
The original template starts below. All rules above apply on TOP of whatever this template specifies.
---
# Bach Coach
## 1. Project
**Bach Coach** is a private practice companion for the late-starting
musician. The user props a phone on the piano lid, plays thirty seconds
of whatever they are working on, and the app returns three specific,
score-grounded observations — what the right hand is doing well, what
the left hand could try, the one bar that will reward a careful second
pass tonight. It listens to the recording the same way a patient teacher
would: against the actual notated score, against the user's own playing
from yesterday, against the difficulty of the passage. It never says the
playing is bad. It surfaces what is working and what to try.
This is the kind of app a retired control-systems engineer named Roland
opens in his sunroom at 6:40 on a Tuesday morning, because he turned
sixty-seven last spring and decided he was finally going to learn the
Goldberg Variations from the beginning, one variation at a time, taking
all the years he needs. It is also the kind of app a sixty-two-year-old
former nurse in Quezon City opens after her rondalla group rehearsal,
holding a borrowed bandurria, learning the inner voice on a kundiman
arrangement her teacher wrote out by hand. Same shape of moment: an
adult learning an instrument they could not afford when they were
twenty, recording themselves quietly, wanting one honest, specific
thing to take into tomorrow's practice. Different repertoire, different
hands, different room.
The single demo that proves the magic: the user props the phone, plays
thirty seconds of Variation 1 from the Goldberg Variations, and taps
stop. In under twenty seconds the app shows three observations grounded
in the score. The hands appear as two coloured staves, the bar lines
numbered, and the one bar the app recommends as tonight's micro-exercise
glows. The observations are concrete: "your right hand's voicing of the
top line through bars 9-12 is clear and even — the trill on the
downbeat of bar 11 lands cleanly on the principal note." "Your left
hand in bars 14-16 is steady but the second beat is consistently the
loudest — try grouping bars 14 and 15 as one breath, letting the second
beat support rather than announce." "The bar to repeat tonight is bar
18 — there is a small lift between the eighth and ninth note in the
right hand that breaks the line; play it ten times very slowly,
listening for the join."
In the universal case, a Filipino-American retiree in Daly City is
learning her grandmother's rondalla repertoire on a borrowed bandurria,
working through a kundiman arrangement her teacher emailed her as a PDF
on Sunday. She records ninety seconds of "Bayan Ko" in her kitchen
after dinner and the app returns three observations on her tremolo
evenness and on a bar where her right wrist is locking. In the harder
case — a Korean-American widower learning gayageum for the first time
at seventy-one because his late wife played, working from a jeongganbo
score his daughter found at the Korean Cultural Center, recording in
the morning while the apartment is quiet — the app handles the
gayageum's pitch-bending notation and the jeongganbo's column-based
layout as carefully as it handles staff notation. And in the classical
piano case — Roland and his Goldbergs, his old upright in tune, his
metronome on the bookshelf, the printed score open to Variation 1 on
the music stand — the app does what a good teacher does on a Saturday
afternoon when you've been practising alone all week.
**Tagline:** _Practise alone, get specific. In any repertoire, on any instrument, with the score in the room and a teacher's ear on the recording._
## 2. Target audience
Surface a multi-cultural, multi-instrument audience clearly in copy, sample archives, and onboarding examples — this app is for late-starting and returning learners across the world, not for conservatory students.
- Adults learning an instrument seriously for the first time in their fifties, sixties, seventies — the late starters who finally have time, money, and the courage to be a beginner in public.
- Returning musicians who put their instrument down at university and picked it up again twenty years later — usually rusty, often embarrassed, always grateful for honest specific feedback.
- Diaspora learners studying their family's traditional instrument: Filipino-Americans on bandurria, laud, or octavina; Korean-Americans on gayageum, geomungo, or haegeum; Vietnamese-Americans on đàn tranh or đàn nguyệt; Chinese-Americans on guzheng, pipa, or erhu; Persian-Americans on santur or tar; Indian diaspora on sitar, sarod, mridangam, or tabla; African diaspora on kora, mbira, or djembe; Latin American diaspora on cuatro, charango, or cavaquinho.
- Choir members and amateur soloists preparing for a community recital, a wedding, a parent's funeral, a Christmas service — practising at home between weekly lessons or rehearsals.
- Music teachers using the app between lessons to give their adult students something concrete to work on for the week, with the teacher's own notes layered in.
- Parents of young learners who want to help their child practise without becoming the bad cop — the app gives the specific bar to repeat so the parent can support without policing.
- Folk musicians and trad players working from tab, lead sheets, or by ear — the app handles tablature, fakebook chord notation, lead-sheet melody plus chord symbols, and chord-only practice.
- People recovering motor function on an instrument after stroke, hand surgery, or long illness — calibrated to celebrate small wins and never to compare to a healthy baseline.
- Working musicians using the app as a private second ear in the practice room — not because they need a teacher, but because they want a quiet, score-grounded check on a passage before tomorrow's session.
## 3. Core value propositions
Surface these clearly through copy, visual emphasis, and the order of the onboarding slides — they are the reasons users pick this app.
- **Listens against the score, not against a pop-music model** — most "music coach" apps grade pitch and tempo as if every song were a karaoke track. Bach Coach reads the actual notated score the user is working from (PDF, MusicXML, jeongganbo, tablature, fakebook lead sheet, hand-written page photograph) and listens to the recording in that context. Voicing in counterpoint, pedalling under a sustained chord, tremolo evenness on a bandurria, slide timing on a gayageum — these are not noticed by pitch-comparison apps.
- **The next thing to try, not a grade** — every session produces three observations and one micro-exercise (the bar to repeat tonight). There is no score, no streak, no leaderboard, no green checkmark, no "you nailed it." There is the next thing to try, and a kind sentence about something working.
- **Never says the playing is bad** — hard product rule, not a soft tone setting. Every observation names something specific that is working and names the next thing to try. "The trill is uneven" is never how it's phrased. "The trill in bar 11 lands cleanly on the principal note; try slowing the approach by half so the upper auxiliary has time to breathe" is.
- **Voice-aware feedback in counterpoint** — for keyboard music with two or more independent voices (Bach, jazz piano, hymns, gayageum left-hand thumb voicing), the app separates the voices in its analysis and gives feedback per voice. "Your soprano line is clear; the alto in bars 5-8 is being covered by your right thumb."
- **Score-grounded, not vibes-grounded** — every observation cites the bar number, the beat, the voice, and (where applicable) the exact notes. The user can tap any observation and the score scrolls to the cited bar with the relevant notes highlighted. No "felt a bit rushed there" without a bar number.
- **Works for any instrument the user can record** — keyboard, plucked string, bowed string, wind, voice, percussion. The app uses Gemini's audio reasoning for pitch, timing, articulation, and voicing; it knows the difference between a piano pedal sustaining and a guitar let-ring, between a violin slide and a gayageum pitch bend.
- **Practice continuity across days** — yesterday's "bar to repeat" is remembered. Tomorrow's session checks whether bar 18 improved, and if it did, says so specifically. If not, the app suggests a different angle on the same bar before moving on.
- **Hand-written and traditional scores welcome** — the user photographs a hand-written page from their teacher, a jeongganbo column from a Korean cultural-center binder, a Filipino kundiman lead sheet written on staff paper with chord symbols — the app reads it, normalises it internally, and grounds the feedback in the user's actual notation.
- **Private by default** — recordings live in the user's own Firebase project. Nothing is sent for model training. Recordings can be deleted from the app, including from cold storage, in under a minute.
## 4. Features to build
- Record from the device microphone — 10-second to 5-minute clips, with a visible audio waveform during recording and a clear "stop" button reachable with one hand.
- Pre-record warning: a one-sentence room-acoustics check on the first recording per device ("your microphone is a little far from the instrument; move closer for clearer voicing detection") that fades in calibrated to a one-time setup.
- Score input: upload PDF, MusicXML, MIDI, photograph of a printed page, photograph of a hand-written page, photograph of jeongganbo or other non-staff notation, paste a tablature snippet, or paste an ABC notation snippet.
- Score parser: Gemini 3.5 Flash multimodal reads the score into a normalised internal representation — bars, beats, voices, notes with pitch + duration + articulation, expression markings, fingering when present.
- Repertoire library: each piece the user is working on has its own page — score, recording history, observations over time, the current "bar to repeat tonight" with a checkbox to mark it cleared.
- Listen call: Gemini 3.5 Flash audio reasoning over the recording plus the parsed score returns the structured observation set (right hand / left hand / pedal / voice / articulation / dynamics / tempo) with bar+beat citations.
- Three-observation rule: every session returns exactly three observations — one celebrating something specific, one offering a try, one identifying the bar to repeat. Never two, never five. Discipline forces specificity.
- The bar to repeat: a single bar (occasionally two adjacent bars) the user is asked to repeat slowly tonight. Includes a written exercise ("play right hand only at half tempo, listening for the lift between notes 8 and 9") and a target ("when the lift is gone, you're done for tonight").
- Score view with scrolling playback: the score auto-scrolls to the bar mentioned in any observation when tapped. A coloured indicator follows the user's playback when they tap "play my recording".
- Hand-separation visual: for keyboard music, the right-hand observations sit in one column, the left-hand observations in another; for two-handed instruments with three voices (organ, gayageum with thumb voicing), three columns.
- Practice journal: an honest, sparse log — date, piece, what was tried, what was cleared. The user can read back across weeks. There is no streak counter and no shaming for missed days.
- Compare with yesterday: when the user records the same passage on two consecutive days, the app surfaces the change in voicing, in tempo steadiness, in pedalling — not as a score, as specific sentences ("bar 18's lift is gone; the trill in bar 11 is now clearer than yesterday").
- Teacher mode: a teacher invites a student into a shared piece; the teacher's observations are layered on top of the app's, distinguishable by the teacher's avatar. The teacher can override or hide an AI observation.
- Score photography flow: a camera mode optimised for music — guides for keystone correction, autocrop, multi-page sequencing, OCR feedback per page.
- Annotation overlay: the user can pencil their own marks on the score (fingerings, dynamics, breath marks). The annotations are remembered across sessions and visible during playback.
- Voice memo on top of the observation: the user can record a 30-second voice note on any observation ("my teacher says to play this with arm weight, not finger weight"). The note re-surfaces when the same bar comes up again.
- Listening voice (TTS): when the user opens the app in the practice room, they can tap "read me the observations" and the app reads them aloud in a warm, unhurried voice, at the elder's reading pace, so the user can keep their eyes on the score.
- Repertoire across instruments and traditions: a single user can have a Bach Goldberg variation, a Filipino kundiman, a Mongolian morin khuur transcription, and a hymn for Sunday all in their library, each with the correct notation and score-grounded feedback.
- Privacy and export: every recording is the user's own. The user can export the journal as a typeset PDF for their teacher, or delete every recording with one confirmation tap.
## 4b. Required Gemini capabilities + backend services
**This template's intelligence comes from the Gemini capabilities below. Wire them up explicitly — don't substitute generic LLM calls.**
### Gemini capabilities (the load-bearing intelligence)
- **Audio reasoning** (Gemini 3.5 Flash) — listens to the recording in the context of the parsed score. Distinguishes voicing, articulation, pedal use, tremolo evenness, slide timing, breath placement, tempo steadiness, ensemble of left and right hand. This is the hero capability. One audio input per session, paired with the parsed score and (optionally) the previous session's observations for the same passage.
- **Multimodal score reading** (Gemini 3.5 Flash) — reads PDFs, photographs of printed scores, photographs of hand-written scores, photographs of non-staff notation (jeongganbo for Korean court music, cipher notation, tablature, lead sheets, Indian sargam tables, Iranian dastgah notation). Outputs a normalised internal representation. Submitted as multipage `inlineData` (base64) or as `files/*` Files API resource names — not as Firebase Storage public URLs.
- **Structured output / JSON Schema** — the listen call returns the `Session` schema below. Every observation is typed: which voice, which bar, which beat, what the observation says, what to try. Pass the schema via `responseSchema` after converting the Zod schema to Gemini's `Schema` type via the SDK helper.
- **Long context (1M tokens)** — the practice-continuity feature reads every previous session for the same passage when generating today's feedback. A typical session is ~3-6 KB of structured observation text; 100 sessions over six months on one passage is comfortably under 1M tokens. Guardrail: chunk by month if the passage's history exceeds 500 sessions (rare; flag and chunk by quarter).
- **Search grounding** — only for the "what is this notation system?" call. When the user uploads a score in a notation system the parser doesn't immediately recognise (a specific jeongganbo dialect, a regional Filipino lead-sheet convention, a Carnatic notation table), Gemini 3.5 Flash with grounded search retrieves a one-paragraph explanation of the notation conventions before the parser runs. The grounded call returns JSON in the text body — `responseSchema` and `google_search` are mutually exclusive in one call.
- **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — reads the three observations aloud in the practice room. Voice is warm, unhurried — the elder's reading pace, not a podcast pace. Voice locale is matched to the user's preferred reading language via `languageCode`; pronunciation of musical terms (rubato, ritardando, tasto, sul ponticello, jangdan, kkeut-eum) follows the chosen voice's native locale.
- **Thinking levels** — `high` for the listen call (audio reasoning + score grounding + previous-session continuity is the hardest reasoning the app does). `medium` for the multimodal score parse. `low` for the journal-summary call and the notation-grounding call.
### Backend services
- **Auth — Required.** Firebase Auth with Google sign-in (auto-provisioned by AI Studio Build). **Apple sign-in is optional but user-configured**: it requires an Apple Developer account, Service ID, Key ID, and private key wired into the Firebase Auth console. **Magic-link email** (used for teacher-student invitations) requires the sender domain to be authorised in Firebase Auth.
- **Database — Required.** Firestore for `users`, `pieces`, `scores`, `sessions`, `observations`, `bar_repeats`, `teacher_links`. Real-time listeners on the active piece view so the teacher and student see each other's annotations live during a coached session.
- **File storage — Required.** Firebase Storage for the user's recordings and score photographs. **Storage is NOT auto-provisioned by AI Studio Build today** — enable it in the Firebase console and wire the bucket name into the AIS Build project before the first recording is uploaded. Pre-signed URLs only; recordings are never publicly addressable.
- **Cold-archive storage — Required for older recordings.** Recordings older than 90 days move to a colder tier (Firebase Storage's coldline class or the user-configured equivalent). The "delete everything" confirmation removes both hot and cold copies — surface this explicitly in the privacy panel.
- **Email — Required (transactional).** Teacher-student invitations via Firebase Auth magic links. Practice-journal exports emailed as PDF attachments when requested.
- **Payments — Not needed for v1.** Free for personal use. A future "shared studio" tier for music schools could meter teacher-student seats.
- **External APIs:** Gemini API for all intelligence. Optional: VexFlow or OpenSheetMusicDisplay for client-side score rendering once the score is parsed into MusicXML.
**Environment variables:** every secret (Gemini API key, Firebase service-account JSON, optional Stripe key for the future studio tier) lives in environment variables — never in the client bundle. Include a `.env.example`.
**Auth + data privacy reminders:** never log secrets · never store passwords in plain text · use HTTPS everywhere · honour 'delete every recording' inside the UI · explicit opt-in for any analytics · the user's recordings and scores are never sent to Gemini for model training. This app uses the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. Hand-written scores from the user's teacher are treated as the teacher's intellectual property and are not exported or shared beyond the user's account without explicit permission.
**Read this first — prompt-craft rules that apply to every call in this template:**
1. **Name the model variant explicitly** in every Gemini API call. Do not let the agent pick the model. See the per-call matrix below.
2. **Pin `thinkingLevel` explicitly** per call. See the matrix.
3. **Seed the JSON Schema as a fenced TypeScript / Zod block** in the system instruction or `responseSchema` field. The literal schema is below. **Convert the Zod schema to Gemini's `Schema` type via the SDK helper** before passing to `responseSchema` — do NOT pass raw Zod. **Numeric `min`/`max` constraints are documentation only inside `responseSchema`; clamp on the server after the response arrives.**
4. **Pin the system instruction separately** from user input. Use the `systemInstruction` field for persona + behavioural rules; use `contents` for user input. Never concatenate.
5. **Pre-declare tools as an enable/disable list** per call. The matrix below names which tools are enabled per call. Tools NOT listed for a call should be disabled.
6. **State negative constraints explicitly** — they are listed below. They are NOT "be careful" suggestions; they are hard rules the model must follow.
7. **Grounded responses can wrap JSON in ```json fences or add prose preamble.** Server-side, strip fences and brace-extract:
```typescript
function safeExtractJSON(raw: string): T {
const clean = raw.replace(/```json\s*|```/gi, '').trim();
const s = clean.indexOf('{'); const e = clean.lastIndexOf('}');
if (s === -1 || e === -1) throw new Error('No JSON boundaries in grounded response');
return JSON.parse(clean.slice(s, e + 1)) as T;
}
```
8. **Strip unsupported Zod modifiers before passing to `responseSchema`** — Gemini's OpenAPI subset rejects `.regex()` / `pattern`, fixed-length `z.tuple()`, and other custom validators. Use a sanitizer that flattens tuples to length-2 arrays and removes regex patterns before serializing. Validate those constraints in middleware AFTER parsing.
### Per-call model + tools matrix
| Call | Model | thinkingLevel | Tools enabled |
|------|-------|---------------|---------------|
| Parse score (PDF / image / MusicXML / hand-written) → `Score` schema | `gemini-3.5-flash` | medium | (none) |
| Listen to recording + score → `Session` schema with three observations | `gemini-3.5-flash` | high | (none) |
| Compare today's session with yesterday's for the same passage | `gemini-3.5-flash` | medium | (none) — long-context |
| Identify unfamiliar notation system before parse | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) |
| Generate practice-journal weekly summary | `gemini-3.5-flash` | low | (none) |
| Read observations aloud in the practice room | `gemini-3.1-flash-tts-preview` | n/a | n/a |
*Note for builders:* on the TTS call, omit `thinkingConfig` entirely — the field is not supported on that model. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body. On the grounded notation-identification call, do NOT pass `responseSchema` — `responseSchema` and `google_search` are mutually exclusive in one Gemini call today. Instruct the model to emit JSON in the text body and parse it server-side; read citation URLs from `response.groundingMetadata.groundingChunks[].web.uri`.
### Primary structured-output schema (seed this verbatim in the prompt)
```typescript
import { z } from "zod";
const BarRange = z.object({
bar_start: z.number().int().min(1),
bar_end: z.number().int().min(1),
beat_start: z.number().nullable(), // e.g. 1, 2.5
beat_end: z.number().nullable(),
});
const Voice = z.enum([
"right_hand",
"left_hand",
"pedal",
"soprano",
"alto",
"tenor",
"bass",
"melody",
"accompaniment",
"thumb_voicing", // gayageum / pipa style
"drone", // sitar / tanpura context
"ornament", // trills, slides, mordents in isolation
"ensemble", // both hands together / full texture
"other",
]);
const ObservationKind = z.enum([
"celebrate", // something specific is working
"try", // a try, never a correction
"bar_to_repeat", // the one bar tonight
]);
const Observation = z.object({
kind: ObservationKind,
voice: Voice,
bar_range: BarRange,
what_is_happening: z.string(), // verbatim description, no judgment
what_to_try: z.string().nullable(), // null for celebrate; populated for try / bar_to_repeat
why_this_helps: z.string().nullable(), // short, one sentence; null when obvious
cites_notes: z.array(z.object({ // exact notes referenced, when applicable
note_name: z.string(), // "F#5", "Bb3", or "the third gayageum string"
bar: z.number().int().min(1),
beat: z.number(),
})),
confidence: z.number().min(0).max(1),
});
const PreviousSessionLink = z.object({
previous_session_id: z.string(),
delta_summary: z.string().nullable(), // "the lift in bar 18 is gone since yesterday"
});
const Session = z.object({
session_id: z.string(),
piece_id: z.string(),
recording_uri: z.string(),
recording_duration_seconds: z.number(),
recorded_at_iso: z.string(),
passage_attempted: BarRange, // which bars the user was working on
tempo_observed_bpm: z.number().nullable(),
tempo_target_bpm: z.number().nullable(),
// exactly three observations: one celebrate, one try, one bar_to_repeat
observations: z.array(Observation).length(3),
// continuity
previous_session_link: PreviousSessionLink.nullable(),
// safety
flagged_for_user_review: z.array(z.object({
field_path: z.string(),
reason: z.string(),
})),
// the app never grades; this is internal-only confidence on the whole listen
internal_listen_confidence: z.number().min(0).max(1),
});
type Session = z.infer;
```
The `Score` schema (parsed from the user's uploaded score):
```typescript
const ScoreNote = z.object({
voice: Voice,
bar: z.number().int().min(1),
beat: z.number(),
duration_beats: z.number(),
pitch: z.string(), // "C4", or instrument-specific "string 3 fret 5"
articulation: z.array(z.string()), // ["staccato", "slur_start"]
fingering: z.string().nullable(), // "3", "RH thumb", "LH pinky"
});
const ScoreBar = z.object({
bar: z.number().int().min(1),
time_signature: z.string().nullable(), // "3/4" if it changes
key_signature: z.string().nullable(),
tempo_marking: z.string().nullable(),
expression_markings: z.array(z.string()), // ["dolce", "rit.", "p"]
notes: z.array(ScoreNote),
});
const Score = z.object({
score_id: z.string(),
piece_title: z.string(),
composer_or_tradition: z.string(), // "J.S. Bach" / "Filipino kundiman" / "Korean court music"
notation_system: z.enum([
"staff_western",
"tablature_guitar",
"tablature_ukulele",
"lead_sheet",
"jeongganbo",
"cipher_notation",
"sargam_table",
"abc_notation",
"hand_written",
"other",
]),
bars: z.array(ScoreBar),
source_pages_uris: z.array(z.string()),
parser_confidence: z.number().min(0).max(1),
});
type Score = z.infer;
```
### Common failure modes (and how to avoid them)
- Agent silently downgrades `thinkingLevel` for the listen call to save quota — pin `gemini-3.5-flash` explicitly with `thinkingLevel: high`. Flash misses voicing differences in counterpoint and drops the score grounding.
- App gives a "score" or a "grade" anywhere — every observation must be qualitative and specific. There is no overall rating. Test by trying to find a numeric grade in the UI; if you find one, remove it.
- App says "you played wrong" or any equivalent phrasing — hard rule: never say bad, wrong, sloppy, off, weak, poor, struggling. Phrase observations as "what is happening" + "what to try". A unit test asserts no observation string in the wild contains those banned words.
- Three observations collapses into two or expands into seven — the schema requires exactly three, with one of each kind (celebrate, try, bar_to_repeat). Clamp this server-side after the model returns.
- Bar numbers off by one — model uses one-indexed bars, the score sometimes starts with a pickup (anacrusis) which the user may or may not count as bar 1. Pin the convention explicitly: anacrusis is bar 0; the first full bar after the pickup is bar 1. State this in the system instruction and in the UI key.
- Voicing observation given on a single-voice instrument — `voice` enum allows non-applicable values; the model should pick `melody` for monophonic instruments and never claim a counterpoint observation on a monophonic recording.
- Score parser invents notes that aren't on the page — pin in the system instruction: "transcribe only what is visibly written; if a note is ambiguous, set parser_confidence below 0.7 and add an entry to flagged_for_user_review." Add a unit test on the seed Bach score: the model must not introduce notes that aren't in the published edition.
- Pedalling feedback given on instruments that have no pedal — guard server-side: only run pedal-targeted observation prompts when the score's instrument is piano, organ, harp, or similar.
- Hand-written score parsed as if it were engraved — model should detect hand-writing and set `notation_system: "hand_written"`. When detected, raise `thinkingLevel` to high for the score parse call, lower parser_confidence baseline, and prompt the user to confirm the parsed bars before listening.
- Non-staff notation (jeongganbo, sargam) parsed as staff — model should detect the system, set the enum correctly, and parse into the same `Score` schema with appropriate `pitch` strings ("the third gayageum string", "Sa lower octave"). The notation-grounding call runs first when the system is unfamiliar.
- TTS reads "Bach" as English /bæk/ instead of German /bax/ — set TTS `languageCode` to match the source-language directive, or accept English pronunciation as the cost. Mid-call voice switching is not supported on Gemini 2.5 TTS; the directive sentence at the top of the input is how style is shaped.
- Practice-continuity call hits 1M token ceiling — guard server-side: chunk by month when the session count exceeds 500 for a single passage. Most users won't reach this; flag and warn.
### Negative constraints (hard rules)
- Do NOT use the words "bad", "wrong", "sloppy", "off", "weak", "poor", "struggling", "failed", "incorrect", "mistake", "error" anywhere in observation strings. Phrase every observation as "what is happening" + "what to try".
- Do NOT give a numeric score, a star rating, a letter grade, a percent, or any single-number summary of the playing. The app's intelligence is qualitative and specific.
- Do NOT compare the user's playing to a "correct" recording of the piece (no Gould reference, no Hewitt reference, no etalon). The reference is the score; recordings are not the standard.
- Do NOT invent notes that are not on the score. If a note is ambiguous on a hand-written page, set parser_confidence low and ask the user to confirm.
- Do NOT count practice streaks. There is no "you've practised 7 days in a row" badge. Some weeks are bad weeks; the app respects that.
- Do NOT use the user's recordings to train or fine-tune any model. Use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. The capabilities-info panel says this in plain English.
- Do NOT auto-share recordings with the user's teacher. Sharing is explicit, per-piece, per-session.
- Do NOT give pedalling, voicing, or counterpoint observations on instruments that don't support them. Gate server-side by the score's instrument.
- Do NOT extrapolate to "you're getting better" or "you're plateauing". State the specific delta against yesterday's session, or say nothing about trend.
- Do NOT translate musical terminology that the user knows in its original language. "Rubato", "tasto", "jangdan", "kundiman", "raga", "dastgah" stay verbatim, with a parenthetical English gloss only on first use per session if the user has the gloss preference turned on.
### Per-call `systemInstruction` strings
Use these as the literal `systemInstruction` field for each Gemini API call the built app makes. They complement the series-wide rules already uploaded as the global instructions file (`00-series-instructions.txt`).
### Call: Parse score (PDF / image / MusicXML / hand-written) → `Score` schema
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You are reading a score uploaded by an adult learner working on a
specific piece of music. The score may be:
- a clean engraved PDF (printed edition, e.g. Henle, Bärenreiter,
Schirmer, Filipino conservatory editions, Korean cultural center
print binders)
- a photograph of one or more printed pages
- a photograph of a hand-written page, possibly written by the
user's teacher
- a non-staff notation page: jeongganbo (Korean court music, in
vertical columns), cipher notation (numerals, common in many
Asian traditions), Indian sargam tables, Iranian dastgah
notation, guitar tablature, ukulele tablature, lead sheets with
chord symbols, ABC notation pasted as text
- a MusicXML or MIDI file (already structured; parse straight in)
Detect the notation system first. Set notation_system on the
output Score object. If the system is hand-written or non-staff,
slow down: hand-written scores routinely have ambiguous
articulation marks and small inconsistencies that an engraved
score wouldn't have. Set parser_confidence accordingly.
Output ONLY the Score JSON matching the provided schema.
Hard rules:
- Transcribe only what is visibly written. Do not infer missing
notes from your memory of the piece. The user may be working
from an unusual edition, a teacher's transcription, an
arrangement, or a deliberately simplified version. Do not
"correct" the score against a canonical version.
- Anacrusis convention: a pickup bar is bar 0; the first full bar
after the pickup is bar 1. Surface this in the parser when an
anacrusis is detected so the UI can show "bar 0" labelled as
"pickup".
- Preserve every articulation marking present: staccato dots, slurs,
ties, accents, fermatas, breath marks, ornaments (trills,
mordents, turns, gruppetti, slides, bends).
- Preserve every expression marking present: tempo markings
(Allegro, Adagio, jangdan markings like jinyangjo or jungmori,
kundiman tempo cues), dynamics (p, f, cresc., dim.), character
markings (dolce, espressivo, cantabile).
- For non-staff systems, transcribe each note with the instrument-
specific pitch string in the `pitch` field — for gayageum: "string
3, hwang plucked open"; for guitar tablature: "string 3 fret 5";
for sargam: "Sa", "Re komal".
- For hand-written scores, transcribe what the writer wrote even
if it includes obvious typos. Do not silently fix them. Flag any
uncertainty in flagged_for_user_review.
- If you cannot read a bar at all, set parser_confidence on that
bar to 0 and leave it empty rather than guessing.
- Submit each page via the Files API (`files/*` resource name) or as
`inlineData` (base64). Do NOT pass Firebase Storage public URLs
to `generateContent`; the API does not fetch them.
No commentary. JSON only.
```
---
### Call: Listen to recording + score → `Session` schema with three observations
Model: `gemini-3.5-flash` · thinkingLevel: high · Tools: (none)
```
You are listening to an adult learner play a passage on their
instrument, in their home, recorded on a phone microphone. The user
is at any level: a beginner, a returning musician, a hobbyist of
thirty years, a working musician using the app as a private second
ear. Calibrate your specificity to the passage and the recording;
do not calibrate to a conservatory standard.
You receive:
- the parsed Score for the piece, including bar numbers, voices,
notes, articulations, expression markings, instrument
- the recording (audio), the user's stated passage (bar range),
and optionally the previous session's observations for the same
passage
You return exactly three observations:
1. one observation of kind "celebrate" — something specific that is
working, named by bar, voice, and note where applicable
2. one observation of kind "try" — a try, not a correction. Phrase
as "the X is doing Y; try Z" — never as "the X is wrong; fix it"
3. one observation of kind "bar_to_repeat" — the one bar (or one
pair of adjacent bars) to repeat tonight, with a concrete written
exercise ("play right hand only at half tempo") and a target
("when the lift between notes 8 and 9 disappears, you're done
for tonight")
Hard rules — never use these words in any observation string:
"bad", "wrong", "sloppy", "off", "weak", "poor", "struggling",
"failed", "incorrect", "mistake", "error", "messy", "rough", "ugly".
Phrase every observation as "what is happening" + "what to try".
Hard rules — specificity:
- Every observation cites a bar range (bar_range) and a voice
(voice).
- Every "try" observation cites a what_to_try string.
- Every "celebrate" observation names the specific thing that is
working — not "your playing is musical" but "your right hand's
voicing of the top line through bars 9-12 is clear and even".
- Every "bar_to_repeat" observation cites a single bar (or two
adjacent bars), names the specific phenomenon (a small lift, a
smudged trill, an unbalanced chord), and gives a measurable
target the user can know they hit.
Hard rules — instrument-aware:
- Pedal observations only when the score's instrument has a pedal
(piano, organ, harp). If the score is for an instrument with no
pedal, do not return a pedal observation.
- Counterpoint / voicing observations only when the score has two
or more independent voices. For monophonic instruments, voice =
"melody" and observations are about articulation, intonation,
rhythm, ornament timing.
- Tremolo / repeated-pluck observations apply to bandurria, laud,
mandolin, balalaika, pipa, oud, and similar — assess evenness
and tempo within the tremolo.
- Slide / bend observations apply to gayageum (left-hand pitch
bending), erhu, sarangi, slide guitar — assess timing and depth
of the bend against the notated target.
- Breath / phrasing observations apply to wind instruments and
voice — assess where breaths are taken against the marked
phrasing.
Hard rules — continuity:
- If a previous_session_link is provided, compare today's recording
to yesterday's observations for the same passage. Surface real
deltas in delta_summary ("the lift in bar 18 is gone since
yesterday"). Do not invent improvement. Do not say "you're
getting better"; say what specifically changed.
- If yesterday's bar_to_repeat is now clean, today's bar_to_repeat
must move to a new bar — do not keep the user on the same bar
once it's resolved.
Hard rules — calibration:
- The user is not auditioning. Match the level of feedback to the
user's apparent stage. A beginner does not need a comment on
rubato; an advanced player does not need a comment on note
accuracy.
- If the recording is too short or too noisy to make a specific
observation, return three observations that are honest about the
audio quality and ask for a longer or clearer recording — but
still keep them specific and kind.
Hard rules — language:
- Preserve musical terminology in its original language. "Rubato",
"tasto", "jangdan", "kundiman", "raga", "dastgah", "jeongganbo"
stay verbatim. Add an English gloss in parentheses only on first
use per session and only if the user has the gloss preference on.
Output the Session JSON matching the provided schema. No
commentary outside the structured output.
```
---
### Call: Compare today's session with yesterday's for the same passage
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none, long-context)
```
You receive every session the user has logged for the same passage
across the past 90 days, in chronological order. Your task: write
a single delta_summary string for today's session against
yesterday's, naming the specific change in voicing, pedalling,
tempo steadiness, tremolo evenness, slide timing, breath placement,
or articulation.
Hard rules:
- Name the change, do not rate the change. "The lift in bar 18 is
gone" is correct. "You played much better today" is not.
- If the user did not record yesterday, compare to the most recent
session for the same passage. Say in the summary: "compared to
your session on Sunday".
- If the user's last session for this passage was more than 14
days ago, the comparison may not be meaningful. Say so honestly:
"It's been three weeks since you last worked on this passage —
hard to compare directly. Today's recording stands on its own."
- Do NOT extrapolate to trend. Do NOT say "you're getting better"
or "you're plateauing". State the specific delta or say nothing.
- Output: a single delta_summary string, one to three sentences,
no commentary. Plain text.
No commentary outside the delta_summary string.
```
---
### Call: Identify unfamiliar notation system before parse
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: search grounding
```
You receive a photograph of a notated score. Your task: identify
the notation system and return a one-paragraph explanation of its
conventions, grounded in current published sources.
Cover at minimum:
- the name of the notation system in its tradition's language
(jeongganbo, cipher notation / jianpu, sargam, dastgah notation,
ABC notation, tabbed lead sheet, kundiman lead sheet on staff
with chord symbols, etc.)
- the orientation (vertical columns, horizontal staves, table
rows)
- how pitch is denoted (note names, numerals, position on string,
finger position)
- how duration / rhythm is denoted (column subdivision, beam
groupings, ornament marks)
- how ornaments and expression are denoted
- any conventions the parser needs to know to read it correctly
Output as JSON in the text body (NOT via `responseSchema` —
`responseSchema` and `google_search` cannot be combined in the same
Gemini call today). Fields: { notation_system: string,
explanation: string }. Server-side: parse the JSON, then read
citation URLs from the response's
`groundingMetadata.groundingChunks[].web.uri`. Do NOT ask the
model to include URLs in the JSON body; it will hallucinate them.
If the notation system is staff Western or guitar tablature or any
extremely common modern system, return notation_system and a one-
line explanation; no grounded search needed for those — set
notation_system and an empty explanation, and the server-side will
skip the grounding hop.
No commentary outside the JSON.
```
---
### Call: Generate practice-journal weekly summary
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive every session from the past seven days across every
piece in the user's library. Your task: write a short journal
entry — three to five sentences — that the user reads on Sunday
evening before next week's practice.
Hard rules:
- Sparse and honest. If the user practised twice this week, say so
factually. Do NOT shame, do NOT congratulate, do NOT use the
word "consistent" or "discipline".
- Name the specific bars cleared this week, the specific bars
still being worked on, and the piece that has had the most
attention. Name them in the user's words from the session
history when possible.
- Suggest at most one thing to consider for the coming week. Phrase
it as a question or a quiet observation, not as a prescription.
- Do NOT count streaks. Do NOT compute days-practised. Do NOT show
a progress bar.
Output: plain text. No headings, no bullets. Three to five
sentences. No commentary.
```
---
### Call: Read observations aloud in the practice room
Model: `gemini-3.1-flash-tts-preview` · n/a · n/a
```
Voice: warm, unhurried. Pick the Gemini 2.5 Flash TTS voice whose
`languageCode` matches the user's preferred reading language —
pronunciation follows that locale automatically. Prefer a voice
that sounds like a kind teacher at a kitchen table, not a podcast
host.
Pre-process the text before sending it to TTS:
- Read the three observations in order: celebrate, then try, then
bar_to_repeat.
- Insert a single ellipsis (`…`) at the end of each sentence to
produce a natural pause. Between observations, insert a blank
line plus an em-dash (`—`) so the TTS model produces a longer
pause. Gemini 2.5 TTS does not support SSML ``;
these textual cues are how you signal pace.
- Read bar numbers as words: "bar eleven" not "bar 11".
- Read note names in the user's preferred convention: do-re-mi for
Romance-language users if set, A-B-C otherwise. Preserve
ornament names verbatim ("trill", "mordent", "slide").
- Read musical terms in their original language: "rubato",
"tasto", "jangdan", "kundiman" — pronunciation follows the
voice's native locale and accept the approximation.
- Mid-call voice switching is not supported. If observations
contain a single foreign-language term, render it inline and
accept the locale's approximation; do not split the call.
- Target rate: ~110 words per minute — practice-room pace, not
podcast pace.
Style direction: prepend ONE short directive sentence to the text
input, exactly: "Read warmly and unhurriedly, as a kind teacher
reading three short observations to a student at the piano. …"
There is no separate `style` API field on Gemini 2.5 TTS; the
directive sentence inside the input is how style is conveyed.
Phoneme overrides (Polish ł, German ü, Hebrew final letters,
Arabic emphatic consonants, Korean ㅓ, Tagalog ng-) are NOT
exposed by Gemini 2.5 TTS — no SSML `` tag.
Pronunciation comes from the chosen voice's native locale.
```
## 5. Use cases & content to include
Build dedicated UI sections or flows for each of these — they tell you what content the app must support.
- **The kundiman after rondalla.** Imelda, sixty-two, in Daly City, came home from rondalla rehearsal at the parish hall holding a borrowed bandurria. Her teacher emailed her a kundiman arrangement of a folk song on Sunday — a PDF on staff paper with chord symbols. She uploads the PDF, records ninety seconds of the first sixteen bars in her kitchen. The app comes back: her tremolo through bars 5-8 is even and warm; her wrist is locking on the second beat of bar 11 (try resting the picking-hand weight on the lower edge of the soundboard, letting the wrist hinge from the elbow); the bar to repeat tonight is bar 13, where the slide between the third and fourth notes is rushing. She nods, plays bar 13 ten times very slowly, and goes to bed.
- **The gayageum at seven in the morning.** Mr Park, seventy-one, in Fort Lee, New Jersey, lost his wife three years ago. She played gayageum. After the first year he started taking lessons at the Korean Cultural Center on Saturdays, and he practises in their old apartment before the neighbours are up. His teacher gives him photocopied jeongganbo pages. He photographs Saturday's page, records the first column at 7:02 am. The app identifies the notation system, parses the columns, listens. His left-hand pitch bending (nonghyeon) on the third string in column 2 is reaching the marked pitch, but the bend is arriving early; the bar to repeat is the third measure of column 2, with a written exercise to listen for the bend's peak against the right-hand pluck.
- **The Goldberg Variations at sixty-seven.** Roland in Walnut Creek opens the app on his upright at 6:40 a.m. He has been working on Variation 1 for four months. He records thirty-two bars, the full first half. The app shows: his right-hand voicing through 9-12 is clear; his left hand in 14-16 is a touch too active on the second beat; bar 18 has a small lift between the eighth and ninth note in the right hand that breaks the line — play right hand only at half tempo, listening for the join. Three months ago bar 18 was a different problem. He laughs.
- **The bossa guitar.** Lúcia, fifty-eight, in Belo Horizonte, learning bossa nova guitar from a teacher who lives two states away. The teacher sends hand-written lead sheets — melody on the top staff, chord symbols above, the bossa pattern she's working on sketched in the margin. The app reads the hand-written page, listens to Lúcia play "Manhã de Carnaval" for two minutes, observes that her thumb is keeping steady time but her index-finger pattern is slipping into a samba feel by bar 24; the bar to repeat is 24, with a target ("when the index finger lands on the and-of-three two times in a row, you're set").
- **The cantor before the high holy days.** A volunteer cantor at a synagogue in Mexico City rehearses Hashkiveinu in his living room two weeks before Rosh Hashanah. His shaliach tzibbur (prayer leader) sent him a hand-written page in modified ashkenazic notation. The app reads the hand-written nusach, listens, names the bar where the phrasing is collapsing the descent. No grade, no comparison to a "famous" cantor — just the bar to repeat.
- **The stroke recovery.** A retired pediatrician in Lagos, seventy-three, returning to piano three years after a left-sided stroke. Her left hand is slowly coming back. She uploads a hymnal arrangement of "Be Thou My Vision" and plays it once through. The app celebrates a specific clean voicing in her right hand bars 5-8 — she did not know it was clean — and gives her a try on bar 13 that respects her current left-hand reach. The "bar to repeat" is short, gentle, and ends with "when you can play this bar twice in a row without the left thumb stiffening, stop for the night." She practises it three times and stops. The app says nothing about the stop.
- **The teacher giving homework.** A teacher in Seoul links one of her adult students into a shared piece — a Schumann Album for the Young miniature. After the lesson, she records herself playing the passage cleanly into the shared session and adds her own annotations on top of the AI's. When her student practises tomorrow, the teacher's voice memo on bar 17 ("arm weight, not finger weight — like dropping a coin") plays alongside the app's observation about voicing.
- **The choir member learning her solo.** A 47-year-old in Birmingham who sings in her church choir is preparing a Handel aria for a Christmas concert. She records the run on "all we like sheep" four mornings in a row. The app's continuity feature shows the specific change: by Thursday morning the descent into the lower octave is no longer being clipped on the breath. She didn't notice; the app did.
- **The cuatro at the diaspora festival.** A Venezuelan-American in Houston is preparing his cuatro for a community festival. His grandfather wrote out the arrangement decades ago in a cipher notation system common to certain folk traditions. The notation-grounding call identifies the system and pulls a one-paragraph reference; the parser handles it; the listen call gives feedback on his strumming pattern's evenness without ever pretending to know the tradition better than the player does.
## 6. Page structure
Build the following screens / sections in this order. Adjust copy to fit the voice, but keep the structural intent.
1. **Welcome / sign-in.** A photographed-looking image of a phone propped against a metronome on the lid of an upright piano, the printed score open on the music stand, late-afternoon light from a window. One paragraph: "Bach Coach listens to your practice against the score you're actually working from. Three specific observations, the bar to repeat tonight, and nothing else — no grades, no streaks, no comparing you to anyone." Google sign-in button; Apple sign-in next to it. Below: "Try with the sample piece" → loads Variation 1 of the Goldbergs from the seed content.
2. **Empty state — "Add your first piece".** Three big input methods: 📄 Upload a score (PDF / MusicXML / MIDI) · 📷 Photograph the score · ✍️ Paste tablature or ABC. A short explainer below each ("Best for a printed edition or a digital file", "Best for your teacher's hand-written page or a binder photocopy", "Best for guitar tab or folk tunes you have as text"). Beneath: a small "What instruments are supported?" link expands to the list (piano, organ, guitar, ukulele, bandurria/laud/octavina, gayageum, erhu, sitar, sarod, tabla, mridangam, kora, cuatro, charango, cavaquinho, voice, recorder, violin, viola, cello, double bass, woodwinds, brass — "and others; the app does its best with any instrument it can hear, even if the notation is unfamiliar").
3. **Piece detail view.** The score occupies the top half of the desktop view (the full width on mobile, scrollable horizontally if the piece is wider than the viewport). The bottom half has three columns on desktop, stacked on mobile: left column "What's working" (celebrate observation), middle column "Try this" (try observation), right column "Tonight's bar to repeat" (the highlighted bar with the written exercise). The bar to repeat is shown both in the bottom card and as a glowing highlight on the score above. A "Read it to me" button at the top of the right column reads the three observations aloud with Gemini TTS.
4. **Record view.** A clean recording screen — large waveform, one large red record button, one large stop button. The currently-selected passage (a bar range) is shown at the top: "Recording bars 1 to 32 of Variation 1." A small toggle: "I'll tell you the bars" / "Whole passage". The piece's score is visible behind the recording controls at low opacity so the user can glance.
5. **Score view with playback.** The score scrolls automatically as the user plays back their recording. A subtle indicator follows the user's actual playback against the printed bars. The user can tap any bar to scroll the score there.
6. **Session history.** A chronological list of every session for the current piece. Each entry shows the date, the passage attempted, the bar that was the "bar to repeat" that night, and a small checkmark if it was cleared the next session. Tapping an entry opens the full Session detail with all three observations and a playback of that recording.
7. **Compare view.** When the user has played the same passage on two consecutive days (or more), the compare view stacks the recordings on top of one another, with the score above. The delta_summary string from the comparison call sits at the top in one to three sentences. No grade. No graph.
8. **Repertoire library.** A grid of every piece the user is working on, each card showing the title, the composer or tradition, the date of the last session, and the current "bar to repeat". Filter by instrument, by tradition, by piece-being-worked-on / piece-set-aside. Adding a new piece is a single big card at the start of the grid.
9. **Teacher view (when shared).** A toggle in the piece detail view: "Show teacher's annotations". The teacher's avatar appears next to their annotations. The teacher can override any AI observation by writing their own; the override shows the AI's original as a strikethrough beneath the teacher's note.
10. **Weekly journal.** A quiet page the user opens on Sunday evening. Three to five sentences generated from the week's sessions: which pieces had the most attention, which bars were cleared, one quiet question for next week. No metrics, no charts. A small section at the bottom: pieces not touched in 14+ days, listed factually.
11. **Settings & privacy.** Account, instrument preferences (default reading language, note-naming convention, ornament terminology), gloss preference (translate musical terms inline or leave verbatim), recording quality, microphone calibration, teacher link management, and a single button: "Delete every recording, every score, every observation." A second button: "Export everything as a typeset PDF."
12. **Onboarding entry point.** Persistent `?` icon in the header reopens the first-visit onboarding modal.
13. **Footer.** "Made for the late-starting practice room." Privacy: "Your recordings are yours. We never train on them." Capabilities `(i)` icon in header.
## 6b. First-visit onboarding
Show a **first-visit onboarding** the first time a visitor lands on the app (detect via `localStorage` flag; do not show on return visits). Three slides, dismissible at any time. Persistent re-entry: a `?` icon in the header reopens it.
**Slide 1 — What this is.**
- Headline: "Welcome to Bach Coach."
- Subhead: "Three specific observations, the bar to repeat tonight, the score in the room."
- One paragraph (≤ 60 words) explaining who this is for: adult learners, returning musicians, late starters, hobbyists at any level. The app listens against the actual score you're working from. It never grades. It never says the playing is bad. It surfaces what's working and what to try.
- Visual: a small illustration of a phone propped against a metronome on a piano lid, the score open, the recording waveform visible.
**Slide 2 — Try it now.**
- One short prompt: "Try with the sample piece."
- A live demo input pre-loaded with Variation 1 of the Goldberg Variations (parsed score + a pre-recorded sample audio file from the seed content in section 8a). Tap "Listen" and the app generates the three observations in real time.
- 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the voicing observation in the right column citing bars 9-12; the bar-to-repeat highlight glowing on the score above).
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Swap the sample piece in `/data/seed-pieces/` for your own scores and recordings."
- "Adjust the prompts in `/server/prompts/` for the instruments and traditions your users play."
- "Wire up your Gemini API key and Firebase project via the env-var list in the capabilities panel."
- Primary CTA: "Use this template" → links to AI Studio Build remix entry point.
- Secondary: "Just exploring — close" (sets localStorage flag, never auto-shows again).
**Accessibility:** focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, `aria-labelledby`, focus restored to trigger on close. Respect `prefers-reduced-motion`.
**Don't:**
- Don't gate content behind the modal. The page beneath must be fully usable.
- Don't auto-reshow on return visits. Use `localStorage['onboarding-seen-v1']`.
- Don't include unrelated CTAs (newsletter signup, social follow). Keep it about the template only.
## 6c. Capabilities info button (persistent in header)
Add a persistent `(i)` icon in the top-right of the header (next to the primary nav). Click → opens a modal/panel titled **"What powers this app"**.
**Panel contents (in this order):**
**Gemini capabilities used (the hero list):**
- **Gemini 3.5 Flash (audio reasoning + score grounding)** — the hero capability. Listens to your recording against the parsed score: voicing in counterpoint, pedalling, tremolo evenness, slide timing, breath placement. One call per session, thinkingLevel high.
- **Gemini 3.5 Flash (multimodal score parse)** — reads engraved PDFs, photographs of printed pages, hand-written pages, jeongganbo, cipher notation, sargam tables, tablature, lead sheets, ABC notation, MusicXML, MIDI. One call per upload, thinkingLevel medium.
- **Gemini 3.5 Flash (long context)** — for practice continuity. Reads every previous session on the same passage when generating today's feedback. Surfaces real deltas, never trend extrapolation.
- **Gemini 3.5 Flash + grounded search** — only when an unfamiliar notation system needs a one-paragraph reference before the parser runs. Most uploads skip this hop.
- **Gemini TTS (`gemini-3.1-flash-tts-preview`)** — reads the three observations aloud in the practice room, at the elder's reading pace, so you can keep your eyes on the score.
- **Firebase Auth** — Google and Apple sign-in. Teacher-student invitations via magic-link email.
- **Firestore** — stores your pieces, sessions, observations, and the current "bar to repeat". Syncs across devices in real time.
- **Firebase Storage** — keeps your recordings and score photographs. Recordings older than 90 days move to a colder tier; both tiers are deleted in under a minute by the "Delete everything" button.
- **Cost note** — see the detailed breakdown in 6d. A typical week of practice — five sessions, ~3 minutes each — costs about $0.40 of Gemini API spend.
- **Privacy note** — your recordings and scores are private to you and the teacher you choose to link with. This app uses the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. The "Delete everything" button removes recordings, scores, and observations from hot and cold storage within sixty seconds.
**Backend services this app depends on:**
- Auth: see section 4b
- Database: see section 4b
- Storage: see section 4b
- Email: see section 4b
- Payments: see section 4b (not used in v1)
- External APIs: see section 4b
**Environment variables you'll need to configure:**
- `GEMINI_API_KEY` — your Google AI Studio API key
- `FIREBASE_PROJECT_ID` — your Firebase project id
- `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only)
- `FIREBASE_STORAGE_BUCKET` — your storage bucket name (Storage must be enabled in the Firebase console — not auto-provisioned by AIS Build)
**Cost + privacy notes:**
- One short paragraph per cost-sensitive capability: the listen call uses thinkingLevel high on Gemini 3.5 Flash and is the most expensive call per session (~$0.07 for a typical three-minute recording, including the score grounding). Score parsing is a one-time cost per piece (~$0.03 for a standard engraved PDF; ~$0.06 for a hand-written page).
- One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete everything" — gone in 60 seconds, including cold-archive copies), what is never sent for training.
**Documentation links:**
- AI Studio Build docs
- Gemini API audio reasoning, multimodal, long-context, TTS docs
- Firebase Auth, Firestore, Firebase Storage docs
- A short note on MusicXML and on the supported non-staff notation systems
**Accessibility:** same standards as the onboarding modal — focus trap, `Esc`, ARIA, restored focus.
**Behaviour:**
- Always available — single click from anywhere in the app.
- Tooltip on the `(i)` icon: "How this app is built".
- Mobile: opens as a full-screen sheet that slides up.
- Should be the most honest part of the app — never hand-wave service requirements; never say "AI" without naming the specific Gemini model and capability.
## 6d. Detailed cost breakdown (deployer reads this BEFORE shipping)
- **Score parse — engraved PDF (Gemini 3.5 Flash, medium thinking)** — ~2-6 pages of multimodal input, ~3,000 output tokens of structured notes. ~$0.03 per piece, one-time.
- **Score parse — hand-written photograph (Gemini 3.5 Flash, medium thinking)** — same shape but with `thinkingLevel` bumped because the parse is harder. ~$0.06 per piece, one-time.
- **Notation-system identification (Gemini 3.5 Flash + grounded search)** — only fires on unfamiliar systems. ~$0.001 per call. Most uploads skip this hop entirely.
- **Listen call (Gemini 3.5 Flash, high thinking)** — typical three-minute recording, ~30s of audio input billing equivalent + ~2,000 output tokens (three observations with citations). ~$0.07 per session.
- **Compare-with-yesterday (Gemini 3.5 Flash, medium thinking, long-context)** — only fires when the same passage has been played on two recent days. ~$0.04 per comparison.
- **Weekly journal (Gemini 3.5 Flash, low thinking)** — input is the week's sessions (~1-5 KB of structured text); output is 3-5 sentences. ~$0.002 per week.
- **TTS — read observations aloud (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens, effectively ~$0.000003/character). Three observations average ~600 characters → ~$0.002 per read-aloud. Cached per session.
- **Expected per-session cost (typical):** listen $0.07 + occasional compare $0.04 = ~$0.07-$0.11 per session. **A typical week of five sessions:** ~$0.40. **Score parse:** ~$0.03 per piece, one-time.
- **Recording storage:** Firebase Storage standard tier hot for 90 days, then coldline. A 3-minute mono 48 kHz WAV is ~17 MB; an AAC encoding at 192 kbps is ~4 MB. A year of daily three-minute sessions ≈ 1.5 GB ≈ ~$0.04/month on standard, less in coldline.
## 7. Design language
- **Mood:** A practice room at 6:40 a.m. or 9:30 p.m. — domestic, warm, lit by a single lamp on the piano lid. Not a tech product. Not a music school's marketing site. The phone is propped where a metronome would be. The score is open. The user is alone but not lonely.
- **Typography:** A clean classical serif for body and score-adjacent content (Adobe Caslon Pro, Source Serif Pro, or a similar text-grade serif). The observations are typeset in serif — they read like a teacher's note rather than a notification. A clean grotesque (Inter or Geist) for app chrome. The score itself renders in a music-notation font (Bravura via VexFlow / OpenSheetMusicDisplay) and is never decorated.
- **Palette:** A warm bone background (`#F4EFE6`) for the score and observation cards. A deep ink (`#1B1714`) for body text. A muted sepia (`#7B4F2A`) for the score's barlines and accidentals. A quiet evergreen (`#2F5D4F`) for "what's working" celebrate observations — never bright green. A warm amber (`#B07A2C`) for "try this" observations — never alarm orange. A muted blue (`#3A5773`) for the user's own annotations and for the teacher's annotations (distinguishable by avatar, not by colour). A faint highlight wash (`rgba(176, 122, 44, 0.18)`) for the bar to repeat. No red anywhere — red implies wrong, and the app does not.
- **Imagery:** The score is the hero. The recording waveform is the second hero. Photographs of the user's own score (when uploaded) are honoured at upload resolution and never cropped. The hero image on the welcome screen is a real-feeling photograph of a phone propped on a piano lid in late-afternoon light — generated via Nano Banana 2 with a prompt emphasising "wooden upright piano, warm desk-lamp light, printed score open on the music stand, phone propped against a metronome, no people in frame, soft shadow on the score, real worn pages".
- **Hand-feel touches:** The bar-to-repeat highlight pulses very slowly, like a metronome at 60 bpm — only on the score, never on the observation card. The score-scrolling during recording playback is smooth and unhurried. The "Read it to me" button has a subtle audio-wave animation only while playing — never as a decorative resting state.
- **Spacing:** consistent 4-px base. Generous whitespace — observations need room to breathe.
- **Radius:** consistent token set (e.g. 6 / 12 / 20 px). Observation cards use 12; score view uses 6; welcome card uses 20.
- **Shadows:** subtle, layered, warm-tinted. Avoid heavy drop-shadows. The score-card shadow is barely-there — the score is paper, paper does not float.
- **Motion:** purposeful — observation cards fade in as the listen call completes (one at a time, in order: celebrate → try → bar to repeat). Score scrolling during playback is smooth. The bar-to-repeat highlight pulse is the canonical reduced-motion check; verify it stops pulsing under `prefers-reduced-motion`. No bounce animations. No theatrical reveal.
- **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons that match the eventual observation card layout. Empty states have helpful next-action guidance ("Upload your first score — PDF, photograph, or paste tab").
## 8. Content generation rules
- Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'.
- Invent plausible names, dates, pieces, and bar-level observations that fit the domain. When inventing, lean on real repertoire and real traditions — the Goldbergs, the Filipino kundiman, the Korean court music tradition, the Brazilian bossa nova lineage — but never claim that a fictional observation is the canonical one for a passage. Every observation in seed content is the app's reading of a fictional recording, not a critical-edition truth.
- Tone: warm, direct, free of corporate language. This template is for an adult practising alone, not for a music school.
- Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'.
- Body copy: short paragraphs (2-4 sentences). Use lists where appropriate.
- Plain language. Avoid jargon — except where the user already speaks the jargon (the gayageum learner wants to see "nonghyeon" in their app; the bossa guitarist wants to see "Manhã de Carnaval" written correctly).
- Where the app outputs AI-generated content, never label it as "AI says" — let it speak naturally. The observations read like a teacher's note. Uncertainty cues appear only where epistemic honesty requires them (a low-confidence bar number shows the score with a softer highlight; tapping reveals "the model is 0.62 confident this is bar 18 — does that match your score?").
## 8a. Seed content (use these specific examples)
Anchor every generated copy + sample data point in the concrete content below. Use these names, dates, pieces, and observation snippets verbatim where helpful, or generate close variants that sit in the same world.
**Sample repertoire library (sidebar):**
- "Goldberg Variations — Variation 1" — J.S. Bach. Roland's piece. Engraved PDF. Eighteen sessions over four months. Current bar to repeat: bar 18, right-hand lift between notes 8 and 9.
- "Bayan Ko (kundiman arrangement)" — Filipino traditional. Imelda's piece. Hand-written lead sheet, staff with chord symbols. Six sessions over three weeks. Current bar to repeat: bar 13, tremolo evenness on the slide between beats 3 and 4.
- "Cheongseong Jajinhanip" — Korean court music. Mr Park's piece. Jeongganbo photocopy. Eleven sessions over two months. Current bar to repeat: column 2 measure 3, left-hand nonghyeon arriving early.
- "Manhã de Carnaval" — Luiz Bonfá, bossa nova arrangement. Lúcia's piece. Hand-written lead sheet (teacher's hand). Twenty-two sessions over six weeks. Current bar to repeat: bar 24, index-finger pattern slipping to samba feel.
- "Hashkiveinu" — traditional ashkenazic, hand-written by the user's shaliach tzibbur. Four sessions in the past week.
- "Be Thou My Vision" — hymnal arrangement, simplified by the user's piano teacher post-stroke. Three sessions this week, all on the same first eight bars.
**Sample session in detail view (this is what the demo should show):**
- **Piece:** Goldberg Variations, Variation 1
- **Session date:** 24 May 2026
- **Passage attempted:** bars 1 to 32
- **Recording duration:** 1:48
- **Tempo observed:** ~68 bpm
- **Tempo target (user-set):** 60 bpm
- **Observation 1 — celebrate:**
- **Voice:** right_hand
- **Bar range:** bars 9-12
- **What is happening:** "Your right hand's voicing of the top line through bars 9-12 is clear and even. The trill on the downbeat of bar 11 lands cleanly on the principal note, and the answering phrase in bar 12 comes out at the same dynamic level as the opening of the phrase."
- **What to try:** null
- **Cites notes:** F#5 at bar 11 beat 1, D5 at bar 12 beat 1
- **Observation 2 — try:**
- **Voice:** left_hand
- **Bar range:** bars 14-16
- **What is happening:** "Your left hand through bars 14-16 is steady, and the second beat of each bar is consistently the loudest in the line."
- **What to try:** "Try grouping bars 14 and 15 as one long breath — let the second beat support rather than announce. One way: play the left hand alone, very softly, with your right hand resting on your lap, and listen for the second beat being part of the line rather than the head of it."
- **Why this helps:** "Bach's left hand here is one continuous voice; treating the second beat as a downbeat fragments the line."
- **Cites notes:** G3 at bar 14 beat 2, F#3 at bar 15 beat 2
- **Observation 3 — bar_to_repeat:**
- **Voice:** right_hand
- **Bar range:** bar 18
- **What is happening:** "In bar 18 of the right hand there is a small lift between the eighth and ninth note — a tiny gap that breaks the line between the descending figure and the cadence."
- **What to try:** "Play right hand only at half tempo (around 30 bpm). Listen for the lift between the eighth and ninth notes. The exercise is done when the lift is gone — when those two notes join into one phrase."
- **Why this helps:** "Variation 1's line wants to arrive at the cadence in bar 19 without breath; the lift at this exact join is the most common place for that arrival to be lost."
- **Cites notes:** A4 at bar 18 (note 8 of the bar), G4 at bar 18 (note 9 of the bar)
- **Previous-session link:** session from 23 May 2026; delta_summary: "Bar 18's lift was the same yesterday. The right-hand voicing through bars 9-12 is clearer today than yesterday — the trill on the downbeat of bar 11 was uneven yesterday and is clean today."
- **Internal listen confidence:** 0.88
**Sample input artefacts (for the build to demonstrate):**
- An engraved PDF of Variation 1 from the Goldberg Variations (use any public-domain critical-edition scan; cite source in the seed-data README).
- A hand-written page of a kundiman lead sheet, melody on staff with chord symbols above and a bossa-style rhythm sketch in the margin.
- A photocopy of one column of jeongganbo from a Korean court-music binder.
- A photograph of a hand-written hymnal arrangement of "Be Thou My Vision", with the left-hand part simplified by the user's teacher.
- A 90-second audio recording of a piano playing the first 16 bars of Variation 1 at ~68 bpm in a small, mostly dry room.
- A 60-second audio recording of a bandurria playing the first 8 bars of a kundiman arrangement in a kitchen.
**Sample voice copy:**
- Onboarding: "Prop your phone on the piano. Play 30 seconds. We'll listen, then point at the one bar to repeat tonight."
- Processing: "Reading the score…" / "Listening to your recording…" / "Comparing to yesterday's session…" / "Writing tonight's three observations…"
- Empty repertoire: "This library is waiting for your first piece. Upload a PDF, photograph a page, or paste a tablature snippet."
- Recording prompt: "Press record when you're ready. The app will listen against bars 1 to 32 of Variation 1."
- Error (couldn't read the score): "We couldn't read this page clearly — want to try a clearer photograph, or tell us the bars by hand?"
- Error (couldn't hear): "The recording was a little quiet for a specific reading. Want to move the phone closer to the instrument and try again?"
- Save confirmation: "Added to Variation 1 — session 18, 24 May."
- Bar-to-repeat reminder: "Tonight's bar to repeat is bar 18. When the lift between notes 8 and 9 is gone, you're done."
- Weekly journal opening: "Five sessions on Variation 1 this week. Bar 18 cleared on Thursday. Bayan Ko had two sessions — the slide in bar 13 is still being worked on."
- Privacy reminder: "Your recordings are yours. We never train on them."
**Sample teacher-invitation email subject + body:**
- Subject: "Variation 1 — I'd like to share my practice sessions with you."
- Body: "Hi Ms Patel — I've been working on Variation 1 in Bach Coach. I'd love your eye on my last week of sessions. Tap to join and you'll see my recordings and the app's notes. You can leave your own notes on top." [Open Piece]
**Sample weekly journal entry (generated):**
- "Five sessions on Variation 1 this week. Bar 18's lift cleared on Thursday morning, so tonight the bar to repeat moves to bar 24. The right-hand voicing through bars 9-12 has been clear all week. Bayan Ko had two sessions; the slide in bar 13 is still being worked. The Hashkiveinu hasn't been touched since Sunday — high holy days are still nine days away, so no hurry. Quiet question for next week: would it help to record only bars 17-22 of Variation 1 separately, to give bar 24 more focus?"
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking shot of a phone propped against a metronome on the lid of an upright piano, the printed score open on the music stand, late-afternoon light from a window. Generate via Nano Banana 2 with a prompt emphasising "warm-toned upright piano, printed score open to a Bach variation, phone in landscape orientation propped against a small mechanical metronome, soft window light from the left, no people in frame, real worn paper texture on the score, single desk lamp on the piano lid".
- **App icon / wordmark:** Set in the display serif. Slightly worn paper texture behind it. No icon glyph — just the type. The wordmark should look at home on an upright piano's music stand.
- **Empty-state illustration:** A simple line drawing of an open score on a music stand. Hand-drawn aesthetic, not a flat icon.
- **Score rendering:** When the user uploads MusicXML or a clean PDF, render the score using OpenSheetMusicDisplay or VexFlow with Bravura font. When the user uploads a photograph or hand-written page, render the user's own image as the primary, with the parsed structure as an overlay only when the user taps "show the parse".
- **Demo audio files:** A small library of seed audio files in `/public/samples/` — one piano recording of Variation 1, one bandurria recording of a kundiman, one gayageum recording of a court-music excerpt. Each ~60-90 seconds, mono, 48 kHz, AAC at 192 kbps. The demo audio is the app's own studio recording for demonstration purposes — never a real recording of an identifiable musician.
- **Period imagery and decoration:** Restraint. The app does not decorate observation cards with photographs of composers, with concert-hall imagery, or with stock practice-room shots. The score and the user's own image are the only imagery.
- **Stock fallbacks:** If image generation fails, fall back to the photographed sample piano scene from `/public/samples/sample-piano.jpg`. Never to a "🎹" emoji.
- **Generated imagery:** prefer Nano Banana 2 over stock photography. Prompt for warmth, asymmetry, and slight imperfection — avoid the glossy 'AI render' look.
- **Optimisation:** WebP/AVIF for static images, `loading="lazy"`, explicit `width`/`height` to prevent layout shift. Audio files preloaded with `preload="metadata"` only.
- **Icons:** `lucide-react` for UI. Use sparingly — never decorative-only.
### Build-time asset manifest (explicit specs)
Every image, illustration, and visual reference mentioned above must resolve to ONE of the three buckets below — runtime-generated, seed-shipped, or user-supplied. Do NOT ship `` tags whose `src` is not listed here. Do NOT depend on bare "section 8a prompts" without binding them to explicit paths and model IDs.
**Bucket 1 — Runtime-generated (Nano Banana Pro `gemini-3-pro-image` for hero/demo photographs; Nano Banana 2 `gemini-3.1-flash-image` for in-app illustrations and reference-conditioned variants).** Cached to Firebase Storage; served via signed URL. Every reference above to "Nano Banana 2" or "Nano Banana Pro" MUST be wired to one of these specific calls with an explicit model id:
- `/public/generated/hero.webp` (2400×1500, WebP) — model `gemini-3-pro-image` — uses the literal prompt described as "Hero image (landing screen)" above. Run once at build; commit a `/public/samples/hero-fallback.webp` (1600×1000) generated from the same prompt with `gemini-3.1-flash-image` so the page renders if quota is exhausted.
- `/public/generated/demo/{demo-slug}-{NN}.webp` (1600×1200, WebP) — model `gemini-3.1-flash-image` (reference-conditioned where the prior frame is passed as input) — one path per "Demo X" image referenced above. The slug derives from the seed example in section 8a; the NN index covers each frame in the demo sequence.
- `/public/generated/illustrations/{name}.webp` (1024×1024, WebP) — model `gemini-3.1-flash-image` — one path per named illustration above ("Empty-state illustration", "Recipe-card hero illustrations", "Curriculum picker imagery", "Period-style frames", etc.). Each illustration's prompt is the literal description above; ship a deterministic seed in the request so re-runs are reproducible.
**Bucket 2 — Seed assets shipped with the deliverable.** Every "Stock fallback" path referenced above (e.g. `/public/samples/sample-X.jpg`) is generated once via Nano Banana 2 (`gemini-3.1-flash-image`) at 1024×1024 WebP using the same prompt as its Bucket-1 counterpart, then committed to the repo so the page renders identically if Gemini quota is exhausted or the user is offline. Replace any `.jpg` extension above with `.webp` to match the optimisation rule. Also commit these empty-state seeds (1024×1024 WebP, single-stroke hand-drawn line, no colour fill):
- `/public/samples/empty-state-primary.webp` — line drawing of the app's primary empty surface (the named "Empty-state illustration" above), generated from that exact prompt.
- `/public/samples/empty-state-archive.webp` — line drawing of an empty saved/archive view, single-stroke outline.
- `/public/samples/empty-state-error.webp` — line drawing of a hand placing a single object aside with care, used when an AI call fails.
**Bucket 3 — User-supplied.** Uploads from the user's camera / file picker land at the Firebase Storage path conventional for this template (named in section 4b). The build ships with Bucket-1 + Bucket-2 only; no user-supplied images at first paint.
**Hard rules**
- Every `` tag MUST have a `src` that resolves to a path listed in Bucket 1, Bucket 2, or a Bucket 3 upload path. Anything else is a build error.
- No bare `image.jpg` / `hero.jpg` / `placeholder.png` references anywhere in the code.
- Model IDs: `gemini-3-pro-image` for hero-quality photographic generation; `gemini-3.1-flash-image` for in-app illustrations, reference-conditioned variants, empty-state seeds, and stock fallbacks. Never use a legacy model id (no `imagen-*`, no `gemini-1.5-*-image`).
- File format: WebP everywhere (AVIF acceptable where the target browsers support it). No `.jpg` / `.jpeg` / `.png` in `/public/samples/`.
## 10. Interactivity & states
- Every interactive element has hover, focus, active, and disabled states.
- Forms validate inline and show specific error messages (not "Invalid input").
- Loading states use skeletons that match the eventual observation card layout, not spinners.
- Empty states explain the next action with a button whose label fits THIS app's domain: "Upload your first score", "Photograph the page", "Press record when you're ready" — never a generic "Add your first item".
- Smooth scroll for in-page anchors.
- All AI-generated content streams in observation-by-observation where supported (celebrate first, then try, then bar to repeat — each card fading in as it arrives) with a clear "listening…" indicator before content starts arriving.
- If an AI call fails, show a calm, specific error ("We couldn't get a clear read on this recording — want to try a longer take?") and offer retry.
- Low-confidence bar numbers are shown with a softer highlight on the score; tapping reveals "the model is 0.62 confident this is bar 18 — does that match your score?".
- The bar-to-repeat highlight pulses slowly at 60 bpm; `prefers-reduced-motion` falls back to a static highlight.
- The score auto-scrolls during recording playback; manual override at any time pauses the auto-scroll.
- Audio recording uses the Web Audio API for waveform display; fall back to native recorder UI if the user denies microphone permission, with a clear explanation of what the permission is for.
## 11. Tech & responsive requirements
- **TTS markdown-stripping preprocessor:** before sending any user-authored markdown to `gemini-3.1-flash-tts-preview`, strip non-spoken markdown: `#`/`##`/`###` headings (keep the title text), `**bold**` (keep the inner text), `[label](url)` (keep `label`, drop URL), `` ``` `` fenced code blocks (skip entirely), `>` block-quote markers (keep the text), and `|` table pipes (read row-by-row as sentences). Insert `…` between sentences for a short pause and a blank line plus `—` between paragraphs for a long pause. The model does not understand markdown; raw markdown will be read aloud as literal characters ("asterisk asterisk").
- **File downloads on Safari / Firefox:** when offering local-disk save of any export (PDF, CSV, MP3, ZIP, JSON, image), fall back to `` with a blob URL — the File System Access API (`showSaveFilePicker()`) is Chromium-only. Detect with `'showSaveFilePicker' in window`; otherwise use the anchor-download path.
- **Stack:** React + TypeScript + Tailwind CSS. Functional components + hooks. Use Shadcn UI primitives where appropriate. Score rendering via OpenSheetMusicDisplay (preferred) or VexFlow.
- **Build runtime:** AI Studio Build — full-stack with Cloud Run server-side functions. All Gemini API calls happen server-side; API key lives in Secrets Manager, never in client bundle.
- **Model selection:** explicitly pin `gemini-3.5-flash` for score parse, listen, compare, and `gemini-3.5-flash` for notation grounding and the weekly journal. `gemini-3.1-flash-tts-preview` for read-aloud. Set `thinkingLevel` explicitly per call (omit on TTS).
- **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed repertoire on first launch.
- **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it (Apple requires user-configured developer account); magic-link email for teacher invitations.
- **Storage:** Firebase Storage for recordings and score photographs. Storage must be enabled in the Firebase console — not auto-provisioned. Pre-signed URLs only. Recordings older than 90 days move to coldline. The "Delete everything" button removes both tiers.
- **Audio handling:** record at 48 kHz mono, encode to AAC at 192 kbps for storage and for upload to Gemini. Long recordings (>5 min) prompt the user to confirm; the listen call is calibrated to 10-second to 5-minute clips.
- **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+.
- Use `clamp()` for fluid typography. Prefer container queries over media queries for component-level responsiveness.
- Use `dvh` / `svh` instead of `vh`. Respect safe-area insets on iOS.
- Zero horizontal overflow at any width. Zero layout shift on load.
- Persist user data in Firestore. Use real-time listeners on the piece view (for teacher-student shared sessions).
- Optimistic UI on writes; reconcile on response.
- Recording flow uses the MediaRecorder API; falls back to native camera/microphone where the API isn't supported.
- **iOS Safari gotchas (graceful degradation):** Safari `MediaRecorder` only supports `audio/mp4` (AAC) — feature-detect `MediaRecorder.isTypeSupported('audio/webm;codecs=opus')` and persist as AAC at 48 kHz mono / 192 kbps; microphone permission does NOT persist across page reloads on iOS — re-request on every practice session; an incoming call interrupts the audio session (`MediaStreamTrack.onmute` fires) — auto-pause, discard the partial chunk if shorter than 3s, and prompt the student to restart; backgrounded Safari tabs throttle — combine a screen Wake Lock with the existing safe-area + dvh handling so a long practice take isn't cut off.
## 12. Accessibility (WCAG 2.2 AA)
- Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`.
- All interactive controls reachable by keyboard with a visible focus ring.
- Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. The evergreen "celebrate" and amber "try" colours are tuned to meet 4.5:1 against the bone background.
- All images have meaningful `alt` text. The user's own score photographs have `alt` describing the page ("photograph of a hand-written kundiman lead sheet, melody on staff with chord symbols and bossa rhythm sketch in the margin").
- The score rendered in the app exposes its structural content to screen readers (bar number, voice, notes) via ARIA labels — not just as an image. The "Read it to me" button reads the three observations aloud and is fully keyboard-accessible.
- Form fields have associated `