================ 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.
---
# School Letter
## 1. Project
**School Letter** is a translator and digester for the steady drip of
paper that comes home from a child's primary school. The parent
photographs a folded note, a printed newsletter, a stapled trip slip,
or a fridge-magnet calendar — anything the kid handed over crumpled
at the bottom of a school bag — and the app produces a structured
bilingual digest in the parent's first language. Each note keeps its
original photograph, a verbatim transcript in the school's language,
a faithful translation that does not editorialise, and an extracted
**Action Card**: what the school is asking, by when, what to bring,
what the cost is, and the one sentence the parent can say to the
child tonight.
This is the kind of app a Cantonese-speaking mother in Manchester
builds on a Sunday evening, after her seven-year-old empties his
bookbag onto the kitchen counter and out come five folded notes from
the school — bring £5 in cash for the Y3 farm trip on Friday, PE kit
on Monday and Wednesday, a permission slip for swimming that needs
signing, a reminder that there is no school on the May bank holiday,
and a "well done" sticker stuck to the head-teacher's newsletter. It
is also the kind of app an Amharic-speaking mother in Washington DC
builds when her daughter brings home four pages of small-print PDFs
about an upcoming standardised assessment, a field trip to the
National Zoo, a flu-shot consent form, and a request for parent
volunteers — and the mother, who is two years into a new country and
a new alphabet, needs to know which of these has a deadline this
week. Same shape of moment, different first language, different
school system.
The single demo that proves the magic: photograph one folded note →
in under 20 seconds the parent sees a side-by-side bilingual page.
On the left, the photograph of the note exactly as it came home from
school. In the middle, the verbatim English transcript with any
handwritten amendments preserved ("Friday changed to Thursday — sorry
for the late notice!"). On the right, a translation in the parent's
first language, in the school's own voice — not stiffer, not
warmer. Above all three columns, an **Action Card**: a calendar pin
("Friday 30 May, leave home by 08:15"), a cost line ("Bring £5 in
cash, exact change preferred"), a packing list ("packed lunch, water
bottle, waterproof jacket, wellies"), and one suggested sentence for
the parent to say to the child tonight in their first language ("on
Friday you are going to the farm; let's pack your wellies tonight").
And in the harder cases — a long PDF newsletter with seven items
buried in three columns, a hand-amended trip slip where the date was
crossed out in pen, a multilingual note from a school that already
provides Arabic but not the family's actual first language Dari, an
asylum-seeking family that just changed schools and is receiving
the first month of catch-up paperwork all at once — the app holds
the same line. It surfaces every deadline; it never inflates
urgency; it never tells the parent that something is "very important"
unless the school itself used that word.
**Tagline:** _Turn a school bag of paper notes into a calendar, a packing list, and one sentence to say to your child — in any language, any school, any week._
## 2. Target audience
- Non-English-speaking parents of children in English-medium primary schools in the UK, Ireland, Canada, Australia, New Zealand, the US — Cantonese, Mandarin, Vietnamese, Tagalog, Bengali, Punjabi, Urdu, Hindi, Tamil, Arabic, Somali, Amharic, Tigrinya, Swahili, Farsi, Pashto, Dari, Polish, Romanian, Portuguese, Spanish, Russian, Ukrainian
- Parents new to a country whose school system has different conventions: a Polish mother in Glasgow learning that "P1" means Reception, an Eritrean father in Stockholm learning that "fritids" is the after-school programme, a Mexican mother in Houston learning the language of report-card grades
- Diaspora grandparents who pick up grandchildren after school and need to know what the kid is supposed to bring tomorrow
- Refugee and asylum-seeking families in temporary housing, often moved between school catchments, receiving compressed catch-up paperwork from a new school they just joined
- Working parents whose English is excellent but whose evening time is not — they want a single Action Card, not seven items to decipher
- Single-parent households where the only parent works night shifts and the kid hands over the paper at 06:30 on a school morning
- Foster carers receiving paperwork for a child whose school they have only known for two weeks
- Bilingual teaching assistants and family-liaison officers helping multiple families in the same school — they want a tool the families can use independently, not one that puts the assistant in the middle
- Parents whose first language is a script the school's own translation tools do not support — Cantonese in traditional Chinese, Punjabi in Gurmukhi or Shahmukhi, Amharic in Ge'ez, Tamil in Tamil script, Khmer
## 3. Core value propositions
Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app.
- **Reads any school paper, any layout** — a folded photocopy, a glossy newsletter, a hand-written supply teacher note, a stapled trip slip with carbon copies, a PDF emailed home that was actually a photographed paper note, a fridge-magnet annual calendar. Gemini 3.5 Flash does the multimodal OCR and the layout reading in one call. Handwritten amendments (a crossed-out date, a teacher's pencilled "please bring £2 more"), folded creases, sticker overlays, and one-corner-torn-off pages all parse correctly.
- **Translates without editorialising** — the school's "we'd love it if you could send a snack" stays "we'd love it if you could send a snack", not "the school requests that you send a snack". A "bring £5 cash" is rendered as a bring-£5 cash. The translation never makes a casual newsletter sound bureaucratic, and never makes a bureaucratic notice sound casual.
- **Extracts the actions, not the prose** — the Action Card lists exactly the dates, the things to bring, the costs, the consent forms to sign, and (only when explicitly stated) the deadlines. A digest of seven notes becomes a single weekly view with five calendar pins and three packing list items.
- **Never overstates urgency** — if the school did not call it "urgent" the app does not call it urgent. A reminder that picture day is in three weeks does not get the same red flag as a permission slip due tomorrow. Deadlines are surfaced literally as the school wrote them; the app never amplifies them.
- **One sentence to say to your child tonight** — for each note, the app offers a short, optional sentence the parent can say to the child in the parent's first language, naming the thing and pinning it to a moment ("after dinner we'll pack your gym kit for tomorrow"). The parent can read it, edit it, or ignore it.
- **The original is sacred** — the photograph of the actual paper is always one tap away. The transcript and translation sit beside it, never replace it. When the parent later cannot remember whether the note said "Thursday or Friday", the original is there to settle it.
- **Multilingual school cases handled** — a school that already sends an Arabic version of a notice but not the family's actual first language Dari does not confuse the app. The app reads every language present on the page, picks the most authoritative version (usually the original English), and translates to the family's preference.
- **Weekly view, not just a paper list** — every Action Card lands on a single weekly calendar. The parent can see in one glance: Monday gym kit, Tuesday library book return, Wednesday gym kit, Thursday trip-slip deadline, Friday farm trip with packed lunch.
- **Family sharing** — invite the other parent, an older sibling, a grandparent, or a co-carer. Notes captured by one family member appear on everyone's weekly view; the child's other parent does not have to be told everything verbally.
## 4. Features to build
- Camera capture for a single note (mobile-first), with auto-edge-detect and de-skew for folded paper
- Multi-page capture for stapled trip slips or newsletters — the app detects the staple and asks for the next page
- Upload from photo library, screenshot folder, school-app PDF download, or a forwarded email attachment
- Automatic note-vs-newsletter-vs-form detection — single-action note, multi-item newsletter, consent form requiring signature, calendar handout
- Multimodal parse — handwriting + print OCR + multilingual translation + Action extraction, in a single Gemini 3.5 Flash call
- Hand-amended date detection — a crossed-out "Friday" with "Thursday" written above is parsed as two `Date` candidates with the latter marked as the most recent amendment
- Verbatim transcript preserved separately from translation — the school's exact phrasing is kept so the parent can quote it back at the gate
- Translation that preserves voice and register — a chatty headteacher's newsletter, a stiff academy-trust form, a class-teacher's hand-written "please can you send a labelled water bottle?" each translate at their own register
- Action extraction — every actionable item across the page becomes an `Action` with type, deadline, cost, packing list, consent-needed flag, and one suggested sentence for the parent to say to the child
- Action types — single closed enum covering trip slip, money request, packing list, calendar event, school closure, absence, parent meeting, consent form, change of routine, lost-property notice, well-done note, fundraising request, vote/poll, photo permission, medical form, "no action required" newsletter item
- Cost detection — currency symbol, amount, exact-change requirement, deadline for payment, payment method ("cash in an envelope", "ParentPay", "bank transfer")
- Consent-form detection — flags whether the form requires the parent's signature; if it does, the app generates a printable PDF with the parent's translated reading on one side and the original signature page intact on the other
- Suggested sentence for the child — one short sentence in the parent's first language, naming the thing and pinning it to a routine moment ("after homework we'll pack your gym kit"). The parent can edit, regenerate, or remove it.
- Weekly view — every Action's deadline pinned on a Sunday-to-Saturday or Monday-to-Sunday week strip, locale-aware, with one card per day for the items that day requires
- Calendar export — `.ics` per Action with the school's location pre-filled and the parent's first-language note as the event body
- Multi-child support — one family, one to four children, each with their own school, year group, and class teacher. Notes are tagged to a child either by the child's name on the page or by which folder the parent shoots from.
- Sibling distinguisher — if a note is addressed "Dear Parents of Y3" and the family has a Y3 child and a Y5 child, the note is filed to Y3 only
- Family sharing — invite the other parent, a grandparent, or a co-carer; their captures arrive on the same weekly view
- Honest confidence cues — where the OCR was uncertain, the word is faintly underlined; tapping shows the alternates the model considered. The Action Card itself surfaces any field whose extraction confidence is below 0.7.
- Quiet mode for sensitive notes — a "your child has been struggling at lunch" note is filed quietly, not pinned to the weekly view, with a small badge instead of a calendar pin
- Print mode — one A4 sheet per week, the Action Cards laid out for a parent who would rather pin a paper to the fridge than open the app
- Offline draft — captures taken in a kitchen with no signal queue locally and process when the device is online; the parent is never told "no signal, try again"
## 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)
- **Multimodal image input** (Gemini 3.5 Flash) — reads printed school text, hand-written amendments, photocopied newsletters with faded toner, folded creases, sticker overlays, and the partial pages the kid managed to keep intact. One API call per note; multipage newsletters are submitted as a single multi-image call with explicit page numbering.
- **Structured output / JSON Schema** — the response matches the `Note` and `Action` schemas below. Every field is typed; the schema is included verbatim in the system instruction and as `responseSchema`.
- **Multilingual translation** (built into Gemini 3.5 Flash) — handles English, Welsh, Irish (Gaeilge), Scottish Gaelic, French (Canadian and metropolitan), Spanish (Latin American and metropolitan), Portuguese (Brazilian and European), and translates *to* the family's first language — Cantonese (traditional Chinese characters), Mandarin (simplified or traditional per family preference), Vietnamese, Tagalog/Filipino, Bengali, Punjabi (Gurmukhi or Shahmukhi per preference), Urdu, Hindi, Tamil, Arabic (with Modern Standard for written, regional dialect on request), Somali, Amharic (Ge'ez script), Tigrinya, Swahili, Farsi (Nastaliq), Pashto, Dari, Polish, Romanian, Russian, Ukrainian, Korean, Japanese, Khmer.
- **Action extraction (structured output)** — parses a single page or multi-item newsletter into discrete Actions with `type`, `due_date_verbatim`, `due_date_iso`, `cost`, `packing_list`, `consent_required`, and `suggested_sentence_to_child` in the parent's first language. Each Action carries the verbatim quote it was extracted from.
- **Long context (1M tokens)** — for parents who shoot a whole term's worth of paper in a Sunday-night session. A 40-page newsletter PDF + a stack of twenty notes still fits in one weekly-digest call when grouped. **Guardrail**: a parsed Note averages ~600 tokens; a typical week's twelve notes ≈ ~7k tokens (comfortable). A whole term's eighty notes ≈ ~50k tokens (still comfortable). Beyond ~1,200 notes (rare), chunk by half-term before the weekly-digest call.
- **Search grounding** — for school-specific term-date and bank-holiday resolution. "We have a teacher training day on the first Friday of half term" must resolve to a specific date for the parent's local-authority calendar. Grounded search keeps the model from inventing dates.
- **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — reads each Action aloud in the parent's first language at a parent's-evening reading pace, with a one-sentence style directive prepended to the input text. Useful for a parent who can listen while cooking but cannot stop to read the screen.
- **Nano Banana 2** (`gemini-3.1-flash-image`) — generates a simple, readable weekly-fridge-sheet illustration: a hand-drawn one-page calendar with five day columns and the Action Cards pinned in place. Used only for the print/PDF export path; the in-app weekly view uses native typography, not generated imagery.
- **Thinking levels** — `medium` for the primary parse-and-extract call (handwriting + multi-item layout + Action structure). `low` for translation, calendar pinning, and the suggested-sentence-to-child generation. Surface `thoughtSummary` only when the user clicks the small "(i) show how the AI read this" icon next to a low-confidence field.
### 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 family invitations) also requires the sender domain to be authorised in Firebase Auth. Family digests are private to the owner and explicitly-invited family members. No public-by-default.
- **Database — Required.** Firestore for `users`, `families`, `children`, `notes`, `actions`, `weeks`, `family_members`.
- **File storage — Required.** Firebase Storage for original note photographs (preserved at upload resolution, until the parent deletes the note). **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 first photograph upload. Pre-signed URLs only; the photographs are never publicly addressable.
- **Email — Required (transactional).** Family invitations via email link (Firebase Auth magic links). Optional weekly digest email to a co-carer who prefers email to the app.
- **Calendar — Optional integration.** `.ics` export is the default; deeper integrations (Google Calendar, Apple Calendar, Outlook) are user-configured.
- **Payments — Not needed for v1.** Free for personal use. The app does not handle school-fee payments and never proxies money between the parent and the school. If a school says "pay via ParentPay" the app says "pay via ParentPay" and links out; it does not collect the £5.
- **External APIs:** Gemini API for all intelligence; optional school-term-date APIs (UK local-authority calendars, US district calendars) for the grounded date resolution; otherwise the search grounding handles it.
**Environment variables:** every secret (Gemini API key, Firebase service-account JSON, optional calendar-integration tokens) lives in environment variables — never in client bundle. Include a `.env.example`.
**Auth + data privacy reminders:** never log secrets · never store passwords in plain text · use HTTPS everywhere · honour 'delete my account' inside the UI · explicit opt-in for any analytics · school correspondence about a specific child is private to the family and never used to train 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) · medical, safeguarding, and special-educational-needs notes are filed in a quiet view and never surface in shared digests without the capturing parent's explicit confirmation.
**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 |
|------|-------|---------------|---------------|
| Read note, parse → `Note` + `Action[]` schema | `gemini-3.5-flash` | medium | (none) |
| Translate transcript + Actions to family's first language | `gemini-3.5-flash` | low | (none) |
| Generate suggested-sentence-to-child per Action | `gemini-3.5-flash` | low | (none) |
| Resolve school term dates, bank holidays, INSET days | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) |
| Weekly digest across many notes (term-end roll-up) | `gemini-3.5-flash` | medium | (none) — long-context over the week's notes |
| TTS read the Action Card in the family's first language | `gemini-3.1-flash-tts-preview` | n/a | n/a |
| Generate weekly-fridge-sheet illustration for print | `gemini-3.1-flash-image` | n/a | n/a |
*Note for builders:* on TTS and image-generation calls, omit `thinkingConfig` entirely — the field is not supported on those models. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body.
### Primary structured-output schema (seed this verbatim in the prompt)
```typescript
import { z } from "zod";
const HandAmendment = z.object({
field_amended: z.enum([
"date", "time", "cost", "location", "packing_list_item",
"deadline", "child_name", "teacher_name", "other",
]),
original_text: z.string(), // verbatim what was struck through
amended_text: z.string(), // verbatim what was written in
ink_or_pencil: z.string().nullable(), // "blue ballpoint", "pencil", "thick black marker"
appears_to_be_teacher_hand: z.boolean(), // best-effort; affects authority
});
const Cost = z.object({
currency: z.string(), // ISO 4217 if confident: "GBP", "USD", "EUR", "AUD", "CAD"
amount_verbatim: z.string(), // "£5", "5 pounds", "$10 USD"
amount_decimal: z.number().nullable(), // 5.00
exact_change_required: z.boolean(),
payment_method_verbatim: z.string().nullable(), // "cash in a labelled envelope", "ParentPay", "bank transfer"
payment_deadline_verbatim: z.string().nullable(),
payment_deadline_iso: z.string().nullable(),
});
const PackingItem = z.object({
item_verbatim: z.string(), // "wellies", "labelled water bottle"
quantity_or_note: z.string().nullable(), // "one", "named"
optional_or_required: z.enum(["required", "recommended", "optional"]),
});
const Action = z.object({
action_id: z.string(),
action_type: z.enum([
"trip_slip",
"money_request",
"packing_list",
"calendar_event",
"school_closure",
"absence_notice",
"parent_meeting",
"consent_form",
"change_of_routine",
"lost_property",
"well_done_note",
"fundraising_request",
"vote_or_poll",
"photo_permission",
"medical_form",
"safeguarding_note",
"newsletter_item_no_action",
]),
child_name_verbatim: z.string().nullable(), // "Maya", "Year 3"
child_id: z.string().nullable(), // resolved server-side to the family's children
source_quote: z.string(), // verbatim from the page
event_date_verbatim: z.string().nullable(), // "Friday 30 May", "next Wednesday"
event_date_iso: z.string().nullable(),
event_time_verbatim: z.string().nullable(),
event_time_iso: z.string().nullable(), // ISO 8601 with offset where extractable
event_location_verbatim: z.string().nullable(),
deadline_verbatim: z.string().nullable(), // "by Friday", "before half term"
deadline_iso: z.string().nullable(),
cost: Cost.nullable(),
packing_list: z.array(PackingItem),
consent_required: z.boolean(),
consent_form_signed_already: z.boolean().nullable(),
suggested_sentence_to_child: z.string().nullable(), // in the parent's first language; filled in a later call
suggested_sentence_target_lang: z.string().nullable(),
is_urgent_per_school: z.boolean(), // ONLY true if the school itself used an urgency marker
urgency_marker_verbatim: z.string().nullable(), // "URGENT", "PLEASE NOTE", "**Important**"
extraction_confidence: z.number().min(0).max(1),
flagged_for_user_review: z.array(z.object({
field_path: z.string(),
reason: z.string(),
})),
});
const Note = z.object({
note_id: z.string(),
note_image_uris: z.array(z.string()), // one per page captured
note_layout: z.enum([
"single_note", "trip_slip", "newsletter_one_page",
"newsletter_multi_page", "consent_form", "calendar_handout",
"report_card", "headteacher_letter", "class_teacher_note",
"supply_teacher_note", "photo_with_handwritten_caption", "other",
]),
school_name_verbatim: z.string().nullable(),
child_year_group_verbatim: z.string().nullable(), // "Year 3", "P4", "Kindergarten", "CE1"
child_year_group_normalised: z.string().nullable(),
class_teacher_name_verbatim: z.string().nullable(),
date_on_note_verbatim: z.string().nullable(),
date_on_note_iso: z.string().nullable(),
source_language: z.string(), // BCP-47, "en-GB"
source_register: z.enum([
"chatty-headteacher",
"warm-class-teacher",
"formal-academy-trust",
"neutral-administrative",
"supply-teacher-handwritten",
"urgent-safeguarding",
"celebratory",
"other",
]),
multilingual_inserts_present: z.boolean(), // school already translated part of the page
pre_translated_languages_present: z.array(z.string()),
transcript_original: z.string(), // verbatim, all text on the page
hand_amendments: z.array(HandAmendment),
translation_target_lang: z.string().nullable(), // BCP-47, "zh-Hant-HK" for Cantonese
translation: z.string().nullable(),
translation_voice_notes: z.array(z.string()),
actions: z.array(Action), // every actionable item on the page
reading_confidence: z.number().min(0).max(1),
flagged_for_user_review: z.array(z.object({
field_path: z.string(),
reason: z.string(),
})),
});
type Note = z.infer;
type Action = z.infer;
```
### Common failure modes (and how to avoid them)
- Agent silently downgrades `thinkingLevel` on the the handwriting + Action parse call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash misses hand-amended dates and silently collapses multi-item newsletters into a single Action.
- Amended date silently overwrites original — when "Friday" is crossed out and "Thursday" written above, the model returns only "Thursday" with no audit trail. Hard rule: every amendment is stored in `hand_amendments[]` with `original_text` AND `amended_text`. The Action's `event_date_iso` uses the latest amendment; the original is still in the record.
- Cost rounded or normalised — "£5" returned as "5" with no currency. Always populate `Cost.currency` and `Cost.amount_verbatim` exactly.
- Urgency invented — the model adds "URGENT" to a calmly-worded trip slip because of a tight deadline. Hard rule: `is_urgent_per_school` is true ONLY if the school used an urgency marker verbatim. The model must populate `urgency_marker_verbatim` with the exact marker, or set `is_urgent_per_school` to false.
- Translation editorialises — "we'd love it if you could send a snack" becomes "the school requests that you send a snack". Hard rule: match the register named in `source_register`. A chatty headteacher's note stays chatty in translation.
- Suggested-sentence-to-child becomes a lecture — the model returns three sentences with a moral. Hard rule: one short sentence, naming the thing, pinning it to a routine moment. No "remember", no "don't forget", no "make sure".
- Multi-child newsletter mis-tagged — a "Year 3 trip" note tagged to the family's Year 5 child because the model couldn't see the year header. Hard rule: extract `child_year_group_verbatim` first; if not found, the Action is tagged "ambiguous — please confirm" rather than assigned to a child.
- "No action required" items get an Action card anyway — a "well done" newsletter generates a fake calendar pin. Hard rule: items with no actionable content use `action_type: newsletter_item_no_action` and never produce a calendar pin or packing list.
- Multipage newsletters submitted as separate calls — submit as one multi-image call with explicit `page 1 of 3, page 2 of 3` headers so the model can sequence them and detect a stapled boundary.
- TTS speaks the translation in English-accented Cantonese — pin the TTS voice to a native Cantonese voice (the Gemini TTS voice catalogue lists native-language voices); use the family's preferred regional voice where multiple are available.
- Medical or safeguarding notes filed to the shared weekly view by default — a "your child has been struggling at lunch" note appears in the grandparent's digest before the parent has read it. Hard rule: `safeguarding_note` and any item with `urgency-safeguarding` register are filed to the capturing parent's quiet view and require explicit confirmation before sharing.
### Negative constraints (hard rules)
- Do NOT invent urgency. If the school did not call a note urgent, the app does not call it urgent. `is_urgent_per_school` is faithful to the page.
- Do NOT round, soften, or normalise cost amounts. £5 is £5, not £5.00; $20 USD is $20 USD, not "around twenty dollars". Preserve the verbatim string.
- Do NOT amplify or downgrade deadlines. "By Friday" stays "by Friday"; do not infer "by 09:00 Friday morning". Surface only what the school wrote.
- Do NOT translate first names of children, teachers, schools, or place names. "Mrs Henderson", "Maya", "Whitefield Primary" stay verbatim. Add a parenthetical reading guide in the parent's first language on first occurrence only.
- Do NOT silently rewrite hand amendments. Every crossing-out and overwrite is preserved in `hand_amendments[]`. The Action shows the most recent amendment as the operative value; the original is one tap away.
- Do NOT extrapolate from a note to a school policy. If a note says "PE on Monday and Wednesday this term", do not infer "this is the school's permanent PE schedule". Surface the quote; the parent decides.
- Do NOT use the user's school correspondence 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-publish or auto-share. The family digest is private by default. Sharing is explicit, per-family, per-member.
- Do NOT proxy payment. The app never collects money on behalf of the school. It surfaces the cost, the deadline, and the method the school requested; it links out where the school used a payment URL.
- Do NOT label any note as "concerning" or "worrying" unless the school's own register named it so. Safeguarding and SEN notes are filed in a quiet view; the language used is the language the school used.
- Do NOT auto-translate handwritten signatures of teachers. The signature is left as image content with a description ("hand-signed by Mrs Henderson, blue ballpoint"); it is not transliterated.
- Do NOT add cultural commentary. "PE kit" is translated as a school-specific term and explained as a school-specific term, not as "a Western practice".
### 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: Read note, parse → `Note` + `Action[]` schema
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You are reading a piece of paper that came home from a child's
primary school. The reader is a parent whose first language is NOT
the language of the page. The reader needs to know exactly what
the school is asking, when, and what to bring — and nothing more.
Schools encountered include UK primary, US K-5, Canadian elementary,
Australian primary, Irish national school, New Zealand primary, and
international schools in dozens of countries. The language on the
page is most often English (British, American, Canadian, Australian,
New Zealand variants), but you also encounter Welsh, Irish (Gaeilge),
Scottish Gaelic, French (Canadian and metropolitan), Spanish (Latin
American and metropolitan), Portuguese (Brazilian and European). Some
pages are bilingual already (e.g. English + Arabic, English +
simplified Chinese) — read every language present and pick the most
authoritative version (usually the original English) for the
transcript_original.
The page can be:
- a single hand-written class-teacher note
- a printed trip slip with carbon copies
- a one-page newsletter
- a multi-page newsletter (read as a single multi-image call)
- a consent form requiring a signature
- a calendar handout (term dates, INSET days, holidays)
- a report card
- a head-teacher letter
- a supply-teacher note in different handwriting
- a photograph the child brought home with a teacher's caption on the
back
- a stapled stack the kid handed over folded into quarters
Multipage notes are submitted as a SINGLE call with multiple images,
in order. Upload each page via the Gemini Files API (`files/*` resource name) or
send as `inlineData` (base64). Do NOT pass Firebase Storage public
URLs directly to `generateContent` — the API does not fetch them
server-side. Include an explicit "page 1 of 3 / page 2 of 3" header
at the start of each image's accompanying text so the model can
sequence reliably.
Read every visible mark carefully. Distinguish:
- printed text from the school (transcript_original)
- hand-written amendments by a teacher (hand_amendments[])
- the teacher's signature (described, not transliterated)
- pre-translated text where the school already provided a second
language (record in pre_translated_languages_present[])
- annotations a parent may have added in pen at home (ignore for
transcript_original; do not include in Actions)
Output ONLY the Note JSON matching the provided schema, including
the actions[] array.
Hard rules:
- Preserve every word of the original verbatim in transcript_original.
Do not summarise, do not normalise, do not "clean up" line breaks.
- Identify every actionable item. Each item becomes one Action.
A newsletter with seven bullet points becomes (typically) seven
Actions, including newsletter_item_no_action where appropriate.
- For dates: capture event_date_verbatim AND event_date_iso. The
verbatim string is what the school wrote ("Friday 30 May", "next
Weds", "the first day back after half term"). Resolve to ISO only
if a calendar resolution is unambiguous from the page. If the
page says "next Friday" and the date the note was sent home is
not on the page, leave event_date_iso null and flag for review.
- For costs: capture currency, amount_verbatim, and amount_decimal.
Never normalise £5 to £5.00; preserve the verbatim string.
- For hand amendments: every crossed-out + overwritten word becomes
a HandAmendment. The Action uses the amended value as the operative
field; the original is preserved in hand_amendments[].
- is_urgent_per_school is TRUE only if the page contains an urgency
marker — "URGENT", "PLEASE NOTE", "**Important**", "Action needed",
a school-styled red banner, or equivalent. Populate
urgency_marker_verbatim with the exact marker.
- For child name / year group: extract verbatim. If a note is
addressed "Dear Parents of Year 3" and a family has more than one
child, the Action is tagged with the year group only; child
resolution happens server-side.
- For consent forms: consent_required = true. If the parent has
already signed a returned tear-off (visible signature in
hand_amendments), consent_form_signed_already = true.
- For safeguarding-style notes (any note discussing a child's
behaviour, mental health, or safety): set action_type to
safeguarding_note and source_register to urgent-safeguarding. These
are filed in a quiet view; the capturing parent confirms before
sharing.
- Pre-translated language detection: if the page already has Arabic
next to English, list "ar" in pre_translated_languages_present[].
Still read the English as the transcript_original — schools'
pre-translations sometimes summarise; the English is authoritative.
- flagged_for_user_review names any field where confidence is below
0.7 with a one-sentence reason.
No commentary. JSON only.
```
---
### Call: Translate transcript + Actions to family's first language
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You translate a school note from its source language to the family's
first language. The family's first language is specified as a BCP-47
tag in translation_target_lang. Common targets include:
zh-Hant-HK (Cantonese, traditional Chinese characters)
zh-Hans (Mandarin, simplified)
zh-Hant-TW (Mandarin, traditional)
vi (Vietnamese)
tl (Tagalog / Filipino)
bn (Bengali)
pa-Guru (Punjabi, Gurmukhi)
pa-Arab (Punjabi, Shahmukhi)
ur (Urdu)
hi (Hindi)
ta (Tamil)
ar (Arabic, Modern Standard for written)
so (Somali)
am (Amharic)
ti (Tigrinya)
sw (Swahili)
fa (Farsi)
ps (Pashto)
fa-AF (Dari)
pl (Polish)
ro (Romanian)
pt-BR (Brazilian Portuguese)
pt-PT (European Portuguese)
es-419 (Latin American Spanish)
es-ES (European Spanish)
ru (Russian)
uk (Ukrainian)
ko (Korean)
ja (Japanese)
km (Khmer)
You translate to be read by a parent who is helping their child go
to school tomorrow. The priority is faithful information. Do not
embellish; do not editorialise; do not warn the parent that
something is important unless the school called it important.
Hard rules:
- Match the register named in source_register. A chatty
headteacher's note translates chatty; a formal academy-trust
notice translates formal; a supply-teacher's handwritten note
translates as casually as it was written.
- Do NOT translate proper nouns. First names of children and
teachers, school names, place names stay verbatim. Add a
parenthetical reading guide in the target language script on
first occurrence only (e.g. "Mrs Henderson (亨德森老師)") and
never thereafter.
- Do NOT translate school-specific terminology that the family
needs to recognise on the door of the building. "PE kit", "Year
3", "P4", "Kindergarten", "Pre-K", "Reception", "fritids",
"garderie", "academy trust" stay verbatim with a one-line
parenthetical explanation in the target language on first
occurrence. The parent needs to walk into the school office
using the school's words, not yours.
- Do NOT translate measurements or costs. £5 stays £5; bring 2
litres stays 2 litres. Currency symbols and units are universal
in school-paperwork context.
- Preserve hedges, conditionals, and softeners. "We'd love it if
you could…" stays "we'd love it if you could…", not "the school
requests that you…".
- Do NOT amplify deadlines. "By Friday" stays "by Friday"; do not
infer "by 09:00 Friday morning". Do not add "please don't be
late".
- Preserve hand amendments in the translation. If the original had
"Friday" crossed out and "Thursday" written above, the translated
text shows "[crossed out: Friday] Thursday" using the target
language's typographic convention for strikethrough where
available.
- For pre-translated bilingual pages: if the school already provided
a translation in another language (e.g. Arabic next to English),
translate from the English (the authoritative version) and note
in translation_voice_notes that a pre-translated copy was on the
page.
- The translated transcript appears alongside the original, never
replacing it. The original is sacred.
Output: the translation as a single string, with line breaks
preserved exactly as in the original, and an array of
translation_voice_notes (each note one sentence) explaining
non-obvious choices.
Also translate the verbatim text of each Action: event_date_verbatim,
event_time_verbatim, event_location_verbatim, deadline_verbatim,
cost.amount_verbatim, cost.payment_method_verbatim,
cost.payment_deadline_verbatim, and the source_quote. Leave the
*_iso fields untouched; they are machine-readable.
No commentary outside the structured output.
```
---
### Call: Generate suggested-sentence-to-child per Action
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You write ONE short sentence that the parent will say to the child
in the parent's first language. The sentence names the thing the
child needs to know and pins it to a routine moment ("after dinner",
"tomorrow morning when you get dressed", "before bed tonight",
"this Friday").
This is for the parent to read aloud or paraphrase to their child
in their own home, in the parent's own voice. It is not a school
notice. It is not a reminder. It is the kind of thing a parent
says at the kitchen table.
You receive: the Action object, the family's preferred target
language (BCP-47), and the child's first name.
Hard rules:
- ONE sentence. Maximum twenty words in the target language.
- Name the thing concretely. "Your gym kit" not "what you need for
PE". "The £5 for the farm" not "the money for the trip".
- Pin it to a routine moment. "After homework", "tonight before
bed", "tomorrow morning". A child needs a when.
- Use the child's first name only if it feels natural in the
target language and culture; otherwise omit it.
- Use the parent's voice, not the school's voice. Schools say
"please ensure"; parents say "let's".
- Do NOT add a moral. No "remember", no "don't forget", no "make
sure". The parent decides the tone of correction; you supply
the information.
- Do NOT add an emoji.
- Do NOT add a question that the child has to answer ("OK?").
- If the Action is action_type = newsletter_item_no_action,
well_done_note, or safeguarding_note, return null. These do not
need a sentence to the child.
- If the Action is well_done_note specifically, OPTIONALLY return
one short sentence celebrating the achievement in the parent's
voice — but only if it reads natural in the target language;
otherwise null.
- The sentence is shown to the parent as a suggestion. They will
edit or discard it. Write the one they would most plausibly
accept.
Output: a single string in the target language, or null. No
commentary.
```
---
### Call: Resolve school term dates, bank holidays, INSET days
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: search grounding
```
You resolve a school-paper date phrase to an ISO date, using
grounded search for term dates, bank holidays, and teacher-training
(INSET / professional-development) days specific to the school's
local authority, district, or country.
Given: the school's name and town, the country, and the verbatim
phrase from the page (e.g. "first Friday after half term", "INSET
day on the 17th", "back to school Monday after the bank holiday",
"Memorial Day weekend").
Return:
- iso_date (YYYY-MM-DD) for the resolved date
- confidence (0.0 to 1.0)
- one citation URL from grounded search (the school's calendar, the
local authority's term-date page, or the official government
holidays page)
Hard rules:
- Use `google_search` grounding for any phrase that depends on a
specific calendar (school-specific half-term dates, INSET days,
US district professional-development days, public-holiday
shifts).
- If the school has a published online calendar (UK academy trusts,
US districts, Australian state DoE), prefer it as the source.
- If you cannot find an authoritative date with confidence > 0.7,
return iso_date = null and a one-sentence reason. Do NOT
guess.
- Preserve the verbatim phrase the school used. Do not silently
substitute the resolved date into the user-facing text.
Output the response as JSON in the text body (NOT via
`responseSchema` — `responseSchema` and `google_search` cannot be
combined in the same Gemini call today). Server-side: parse the
JSON, then read the citation URL from the response's
`groundingMetadata.groundingChunks[].web.uri` — do NOT ask the
model to include URLs in the JSON body; it will hallucinate them.
No commentary outside the JSON.
```
---
### Call: Weekly digest across many notes (term-end roll-up)
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none — long-context)
```
You receive every Note captured in a single week (or longer span,
up to a half-term). Your task: produce a unified weekly digest
that the parent can read in two minutes on a Sunday evening, in the
family's first language.
Hard rules:
- Group by child (if the family has more than one) and then by day.
- Within a day, order by event_time_iso ascending, then by
deadline_iso ascending.
- Do NOT merge Actions across notes silently. If two notes from
two different teachers both mention Friday's farm trip, surface
both with their respective source_quotes; the parent decides if
they are the same event.
- Do NOT amplify urgency. Apply the same rule as the parse call:
flag urgent only when the school used an urgency marker.
- Do NOT repeat a packing list across days unless the school
itself repeated it. If a note says "PE on Monday and Wednesday,
same kit", produce one packing-list block visible on both days
with a small "(same kit as Wednesday)" note in the target
language.
- Surface conflicts. If one note says "trip on Friday" and a later
amendment changes it to Thursday, the digest shows the Thursday
date with a small "(was Friday, changed)" annotation in the
target language.
- Surface money totals per child per week. If three notes ask
for £5, £2, and £10, the digest sums them only as a separate
"money to send this week" footer; it never collapses three
Actions into one money-Action.
- The digest is offered, never imposed. The parent can dismiss it
and use the individual Notes view.
Output as JSON: a WeeklyDigest object with children[].days[].items[]
matching the existing Action structure, plus a money_total per
child and a one-line family-language summary at the top
("This week: 2 trips, 1 form to sign, £17 to send.").
No commentary outside the structured output.
```
---
### Call: TTS read the Action Card in the family's first language
Model: `gemini-3.1-flash-tts-preview` · n/a · n/a
```
Voice: warm, unhurried, the voice a parent uses to tell another
parent at the school gate. Pick the Gemini 2.5 Flash TTS voice
whose `languageCode` matches the family's `translation_target_lang`
— pronunciation will follow that locale automatically. Prefer the
gender the parent has set in preferences; fall back to whichever is
available rather than blocking.
Pre-process the text before sending it to TTS:
- Compose the spoken text from the Action Card in the parent's
first language: child's name (optional), event date and time, the
thing to bring or do, the cost if any, the deadline if any.
- At each Action boundary, insert a single ellipsis (`…`) so the
TTS model produces a natural pause. Between children (in a
multi-child digest), insert a blank line plus an em-dash (`—`).
Gemini 2.5 TTS does not support SSML `` — these
textual cues are how you signal pace.
- Skip the source_quote and the verbatim original; the parent has
read them already.
- Mid-call voice switching is not supported. If the Action contains
a proper noun in the school's language (e.g. "Whitefield
Primary") inside an otherwise Cantonese sentence, render it in
the target-language voice — pronunciation will approximate; that
is acceptable for the parent's-evening use case. Optional: stitch
a second TTS call client-side for the proper noun if the parent
asks for it.
- Target rate: ~120 words per minute — parent's-evening pace.
Style direction: prepend ONE short directive sentence to the
text input, exactly like: "Read warmly and unhurriedly, as a parent
telling another parent what's on this week. …". 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 (Cantonese tone, Arabic emphatic consonants,
Punjabi retroflex) are NOT exposed by Gemini 2.5 TTS — no SSML
`` tag. Pronunciation comes from the chosen voice's
native locale.
```
---
### Call: Generate weekly-fridge-sheet illustration for print
Model: `gemini-3.1-flash-image` · n/a · n/a
```
Generate a simple, readable one-page weekly-calendar illustration
for the parent to print and pin to the fridge.
The image is a background only — the app overlays the actual Action
text on top in vector type, in the parent's first language. Generate
a clean, friendly week-strip layout: five day columns (Monday to
Friday) or seven (Monday to Sunday), each column a soft pastel,
hand-drawn aesthetic, with a small school-bag motif in the top-left
and the family's school name placeholder in the top centre.
Hard rules:
- No text in the image. The columns are visually distinct but
contain no rendered words; the app overlays day names and Action
text in vector type afterwards.
- No clip-art kids. No teachers. No school buildings. The aesthetic
is paper and ink, not stock illustration.
- No flags. No religious symbols. No anything that would land
differently for one of the dozens of cultures the family might
be from.
- A4 aspect ratio (1:1.414) or US Letter (1:1.294) depending on
the family's locale preference.
- Output: a single image at the resolution the printing pipeline
will downscale from.
This image is generated once per family, cached, and reused for
every weekly print. It is not regenerated per week.
```
## 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 Sunday-evening shoot.** A Cantonese-speaking mother in Manchester sits at the kitchen counter on Sunday evening with five folded notes her son brought home that week. She photographs each one. The app's empty-state Sunday-evening flow runs the captures sequentially with a single weekly-digest call at the end: by the time her tea is cold she has Monday-to-Friday on one screen in Cantonese, with the gym kit, the £5 cash for the farm trip, the swimming permission slip to sign, and the bank holiday.
- **The school bag at 06:30.** A Polish mother in Glasgow finds out at 06:30 on a Monday that there is a trip TODAY. She photographs the wrinkled slip the kid just produced. The app surfaces the Action Card in Polish in fifteen seconds: bring £3, packed lunch, wellies, back by 15:00. No editorialising; no "you should have known earlier".
- **The hand-amended date.** A trip slip says Friday 30 May. A teacher has crossed out "Friday" in pen and written "Thursday — sorry for the late notice". The Note records both; the Action's `event_date_iso` is the Thursday; the original "Friday" is preserved one tap away.
- **The multi-item newsletter.** An Amharic-speaking mother in Washington DC photographs the school's monthly newsletter, four PDF pages, seven items on each. The parse call returns twenty-eight Actions, of which fourteen are `newsletter_item_no_action`. The weekly digest pins the seven actual events and packs them by week across the month.
- **The consent form.** A Tagalog-speaking father in Auckland gets a sex-education consent form. The translation preserves the school's careful wording; the Action is flagged `consent_required: true`; the printable PDF shows the original signature page intact with his translated reading on the back.
- **The supply-teacher handwritten note.** A Vietnamese mother in Calgary gets a one-line note from a supply teacher in spiky cursive: "Please send Anh's library book back tomorrow!" The parse handles the cursive; the Action is filed as `change_of_routine`; the suggested sentence to Anh in Vietnamese is one line: "tonight let's put your library book in your bag for tomorrow".
- **The pre-translated bilingual page.** A Punjabi mother in Birmingham gets a notice the school sent in English and Urdu (the school's standard second language). Her first language is Punjabi, not Urdu. The app reads both, picks the English as authoritative, and translates to Punjabi in Gurmukhi.
- **The safeguarding note.** A Somali-speaking father in Minneapolis gets a one-page note that his daughter has been struggling at recess this week and the counsellor would like to chat. The Note is filed `urgent-safeguarding`; it lands in his quiet view, not the shared family digest; before he shares it with his wife, the app asks once.
- **The well-done note.** A Bengali grandmother who picks up her grandson three days a week gets a sticker-and-note from the class teacher about how well he is reading. The parse files it `well_done_note`; the suggested sentence is one line in Bengali for her to say at home tonight.
- **The diaspora grandparent.** A Cantonese-speaking grandmother who picks up her grandkids three days a week gets the family's invitation to join the digest. She opens the app on Tuesdays and Thursdays at 14:30 before walking to the school; the day's pickup-time pin tells her in Cantonese what each child needs.
- **The asylum-seeker family in temporary housing.** A Dari-speaking family that has just moved into temporary accommodation receives the first month of catch-up paperwork from their new school all at once — eight items, four with deadlines, two requiring signatures. The weekly-digest call groups them by deadline and surfaces a "this month" view alongside the "this week" view; nothing is amplified, nothing is panicked.
- **The other parent.** A Mandarin-speaking mother shares the family digest with the English-speaking father. He sees the same items in English; she sees them in Mandarin; the underlying Notes are one source of truth.
## 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 child's school bag on a kitchen counter at evening, two folded notes half-out, a banana on the edge of the frame. One paragraph: "School Letter turns the paper that comes home from school into a week you can read in your first language — with every date, every cost, and one sentence to say to your child tonight." Single Google sign-in button; Apple sign-in next to it. Below: "Try with a sample week" → loads the demo family in section 8a.
2. **Family setup.** First-launch wizard, three short steps. (1) "What language do you read most easily at home?" — a long scrollable list of language + script options, including Cantonese (traditional), Mandarin (simplified or traditional), Punjabi (Gurmukhi or Shahmukhi), Arabic (with regional voice options), Amharic, Tigrinya, Khmer. (2) "Tell us about your child" — name, year group (locale-aware label: "Year 3" UK, "Grade 3" US, "P4" Scotland), school name, optional class teacher. Repeat for up to four children. (3) "Want anyone else to see the digest?" — invite by email, magic link.
3. **Empty state — "Catch up the week".** Three big input methods: 📷 Photograph notes · 🖼 Upload screenshots · 🔗 Forward email/PDF. A short explainer below each ("Best for paper notes the kid brought home", "Best for screenshots of the school app or website", "Best for newsletters and PDFs sent home by email").
4. **Capture flow** (mobile-first). Live viewfinder with paper-shaped crop guides; auto-shutter when the page is in focus and the kid's banana isn't covering a corner. After capture: "Another page of this note?" — if yes, the camera reopens with a "page 2 of 2" badge. "Whose note is this?" — child picker, defaulting to the most recent child. Each note clusters as a single artefact in the queue.
5. **Processing queue.** A vertical list of the day's captures. Each item shows the thumbnail, the child it is filed to (when known), and a step-by-step honest progress bar in the parent's first language: "Reading the note…" → "Translating to Cantonese…" → "Pulling out the dates and the £5…" → "Writing what to say to Aiden tonight…". Each step takes 3-10 seconds. The user can close the app and come back.
6. **Note detail view.** A three-column layout on desktop, stacked on mobile. **Top:** the Action Card — calendar pin, cost (with currency symbol and exact-change badge), packing list as checkboxes, the suggested sentence to the child with an "edit" link. **Left column:** the photograph of the original (zoomable, with the hand-amended date and any urgency markers annotated). **Middle column:** the verbatim transcript in the source language, with hand-amendments rendered as strikethrough + overwrite. **Right column:** the translation in the family's first language, with translator's-voice notes accessible behind a small "(i)" icon. Sticky header: child name → school name → date on the note → source register chip → "(i) show how the AI read this".
7. **Weekly view.** A Monday-to-Sunday (or Sunday-to-Saturday, locale-aware) strip. Each day is a column; each Action is a card pinned to its day. Visual hierarchy: deadline-today items at the top of each day, packing-list items at the bottom. A footer per child: "Money to send this week: £17". A toggle: "show me only the items that need cash" / "show me only the items that need signing" / "show me only urgent items (per the school)".
8. **Term view.** A scrollable month-by-month view of the whole term. Pins coloured by child. The bank-holiday and INSET-day strip across the top is grounded via the term-date resolver call. Selecting a pin slides up the Note detail.
9. **Family view.** The list of family members with access. Each member is a node; their captures and digests are pinned to them. Co-carers can be limited to specific children if the family is blended. The other parent / grandparent / family-liaison-officer roles are all here.
10. **Print-the-week.** A one-A4-page printable version of the weekly view, with the generated fridge-sheet background (from the Nano Banana 2 call) and the Action Cards laid out on top in the family's first language. The parent prints and pins to the fridge for the household.
11. **Quiet view.** Safeguarding, SEN, behaviour, and counselling notes live here. Notes filed in this view do NOT appear on the shared family digest until the capturing parent explicitly confirms sharing. A small badge on the home screen shows there are unread quiet notes; the badge never shows their content.
12. **Settings.** Languages spoken at home (primary + optional secondary), TTS voice preference per language (gender, region), notification preferences (none by default; opt-in for next-morning summary at the time the parent sets), data export (every Note, every Action, every original photograph, in a single zip), delete-this-family-forever (gone in 60 seconds).
13. **Footer.** "Made for the parents holding paper they cannot read." Privacy: "Your family's school paperwork is yours. We never train on it." 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 School Letter."
- Subhead: "Turn a school bag of paper notes into a calendar, a packing list, and one sentence to say to your child — in any language, any school, any week."
- One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic translation app: it reads handwritten teacher amendments, it never amplifies urgency, and it gives the parent one short sentence to say to the child in their first language at the end of every Action.
- Visual: a small annotated illustration of a folded school note with the relevant marks labelled (hand-amended date, cost line, packing list, deadline) — not a generic document icon.
**Slide 2 — Try it now.**
- One short prompt: "Try with a sample week".
- A live demo input pre-loaded with five notes from the seed content in section 8a.
- 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the hand-amended Friday→Thursday on the trip slip, the multilingual school newsletter, the suggested sentence to the child in Cantonese).
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Swap the sample family in `/data/seed-family/` for your own children's schools."
- "Adjust the prompts in `/server/prompts/` to fit your family's languages."
- "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 (multimodal)** — reads printed school text, hand-written teacher amendments, photocopied newsletters with faded toner, folded creases, sticker overlays. One call per note; multipage newsletters are submitted as a single multi-image call.
- **Gemini 3.5 Flash (multilingual)** — translates between English (British, American, Canadian, Australian, New Zealand variants), French, Spanish, Portuguese on the source side, and Cantonese, Mandarin, Vietnamese, Tagalog, Bengali, Punjabi, Urdu, Hindi, Tamil, Arabic, Somali, Amharic, Tigrinya, Swahili, Farsi, Pashto, Dari, Polish, Romanian, Russian, Ukrainian, Korean, Japanese, Khmer on the target side. Preserves voice and register.
- **Gemini 3.5 Flash (structured output)** — extracts every actionable item on the page into a typed Action with type, date, cost, packing list, and the verbatim quote it came from.
- **Gemini 3.5 Flash (long context)** — for parents who shoot a whole term's worth of notes in one Sunday session, the weekly-digest call sees the whole batch at once.
- **Gemini 3.5 Flash + grounded search** — resolves school term dates, bank holidays, and INSET days for the family's local authority or district without inventing them.
- **Gemini 3.5 Flash (suggested-sentence-to-child)** — writes the one short sentence the parent will say to the child tonight in the family's first language.
- **Gemini 2.5 Flash TTS** — reads each Action Card aloud in the family's first language at a parent's-evening reading pace.
- **Gemini 3.5 Flash Image (Nano Banana 2)** — generates the weekly-fridge-sheet illustration once per family, cached.
- **Firebase Auth** — Google and Apple sign-in, family invitations via magic links.
- **Firestore** — stores your family digest, syncs across devices in real time.
- **Firebase Storage** — keeps the original note photographs at upload resolution.
- **Cost note** — see the detailed breakdown in 6d. A typical school week of 5–10 notes costs about $0.07 of Gemini API spend, total. A school year of ~250 notes ≈ $1.75.
- **Privacy note** — your child's school paperwork is private to you and the family members you invite. 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. Medical, safeguarding, and SEN notes are filed in a quiet view that is private to the capturing parent until they choose to share.
**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; the app never proxies money)
- 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)
- `CALENDAR_INTEGRATION_TOKEN` — optional, only if you want deeper Google/Apple/Outlook calendar integration than the default `.ics` export
**Cost + privacy notes:**
- One short paragraph per cost-sensitive capability: the weekly-digest call is billed per token of input — a 50-note half-term digest costs about $0.04 each time it runs (default: once weekly, on Sunday evening, configurable).
- One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this family forever" — gone in 60 seconds), what is never sent for training, and the quiet-view rule for safeguarding notes.
**Documentation links:**
- AI Studio Build docs
- Gemini API multimodal, multilingual, structured-output, long-context, TTS, Nano Banana 2 docs
- Firebase Auth, Firestore, Firebase Storage docs
- A short note on `.ics` calendar export and the optional school-calendar APIs
**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)
- **Read note + Actions (Gemini 3.5 Flash, medium thinking)** — typical single-page note ≈ 1 image, ~500 output tokens. ~$0.005/note. A four-page newsletter ≈ 4 images, ~1,200 output tokens ≈ ~$0.010/note.
- **Translate transcript + Actions (Gemini 3.5 Flash, low thinking)** — typical 150-word note, both directions counted ≈ ~$0.002/note.
- **Suggested-sentence-to-child (Gemini 3.5 Flash, low thinking)** — one short sentence per Action ≈ ~$0.0003/Action. A note with three Actions ≈ ~$0.001/note.
- **Term-date resolution (Gemini 3.5 Flash + grounded search)** — ~$0.001/place-or-date phrase. A typical family has 10-20 distinct resolution targets per term; resolved once, cached.
- **Weekly digest (Gemini 3.5 Flash, medium thinking, long-context)** — input is the week's parsed Notes (~5-15k tokens) plus the family setup. ~$0.02 per weekly digest. Runs once per week by default.
- **TTS reading of an Action Card (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), effectively ~$0.000003/character. A 60-word Action Card ≈ $0.001 per reading. Cached per Action; charged once.
- **Weekly-fridge-sheet illustration (Nano Banana 2)** — ~$0.03 per image, generated ONCE per family, cached forever.
- **Expected per-note cost on first ingest:** ~$0.008. **Typical school week of 8 notes:** ~$0.07. **Typical school year (~250 notes + 40 weekly digests):** ~$1.75 + $0.80 weekly-digest spend = ~$2.55.
- **Image storage:** Firebase Storage standard tier, ~$0.026/GB/month. A high-resolution phone capture of a school note is ~1.5 MB; a year's 250 notes ≈ ~0.4 GB ≈ ~$0.01/month.
## 7. Design language
- **Mood:** A kitchen counter on Sunday evening, the tea cold, the school bag emptied, the parent sitting down for the first time today. Not a school district admin tool. Not a productivity app. Warm, calm, unhurried — the opposite of the noisy school-app inbox.
- **Typography:** Display sans-serif for headings (Inter Tight or DM Sans, weight 600). Body in a humanist sans (Inter or Source Sans 3, weight 400-500) — must render correctly across every script the app supports, including Amharic Ge'ez, Tamil, Khmer, Punjabi Gurmukhi, Bengali, Arabic Nastaliq. Hand-written accent (sparingly) for the parent's own annotations and edits to the suggested-sentence-to-child — never for the parsed transcript itself. System font fallbacks for every non-Latin script.
- **Palette:** Bone-paper background `#F6F2EB` for note views, deep ink `#1B1714` for body text, a soft sage `#5A7A66` for primary actions (calendar pin, "save", "next"), a muted clay `#B65A3A` only for hand-amended overwrites in the transcript view, a slate blue `#3A5773` for the parent's own annotations so they cannot be mistaken for the school's. Borrowed from a kitchen-pinboard aesthetic, not from school district websites.
- **Imagery:** The photographs of the notes are the hero. Never replace them; never crop them tighter than the parent did. The folded creases, the staple shadows, the kid's banana smear in the corner are honoured. The print-mode fridge-sheet illustration is a soft, hand-drawn pastel — not a stock-illustration smiling-family.
- **Hand-feel touches:** A barely-visible paper grain on the note-detail background. The "show original" expandable panel slides the photograph in with a thin shadow — like lifting the paper off the kitchen counter. Hover on a hand-amendment reveals the original text underneath; never aggressively glow.
- **Spacing:** consistent 4-px base. Generous whitespace — the parent reads at the end of a long day.
- **Radius:** consistent token set (e.g. 6 / 12 / 20 px). Note cards use 6; the print-mode fridge-sheet uses 12; the welcome card uses 20.
- **Shadows:** subtle, layered, warm-tinted. Avoid heavy drop-shadows.
- **Motion:** purposeful — entrance fades, hover lifts, page transitions. Respect `prefers-reduced-motion`. No bouncing splash animations. No theatrical hero animations. The week-view's day-card hover-lift is the one place where motion carries meaning — 120 ms, ease-out; respect reduced-motion by skipping.
- **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons not spinners where possible. Empty states have helpful next-action guidance ("Photograph one note your child brought home this week to start").
## 8. Content generation rules
- Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'.
- Invent plausible school names, child names, teacher names, dates, costs, and notes that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on contemporary primary-school patterns — UK academy trusts and local-authority schools, US public elementary districts, Australian state schools, Canadian boards. Names of children should reflect the diaspora families the app serves (Cantonese, Vietnamese, Punjabi, Bengali, Amharic, Polish, Arabic, Filipino) — but never claim a fictional family is a real one.
- Tone: warm, direct, free of corporate language. This template is for a parent, not a school district.
- 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 parent already speaks the jargon (the parent of a Y6 child wants to see "SATs" written as the school writes it; the parent of a K-5 child wants to see "PTO" written as the school writes it).
- Where the app outputs AI-generated content, never label it as "AI says" — let it speak naturally. Use small uncertainty cues only where epistemic honesty requires them (a low-confidence reading shows as a faintly underlined word; tapping it reveals the alternates the model considered).
## 8a. Seed content (use these specific examples)
Anchor every generated copy + sample data point in the concrete content below. Use these names, numbers, dates, and snippets verbatim where helpful, or generate close variants that sit in the same world.
**Sample families (sidebar):**
- "The Wong family, Manchester" (Aiden Y3 at Whitefield Primary, Mei-Lin in Reception) — first language Cantonese (traditional Chinese); contributors: me, my husband, my mother-in-law who picks up Tuesdays and Thursdays.
- "The Tesfaye family, Washington DC" (Selam in Grade 3 at Brent Elementary, Yonas in Pre-K) — first language Amharic; contributors: me, my sister who lives nearby.
- "The Nowak family, Glasgow" (Zofia in P4 at Hyndland Primary) — first language Polish; just me.
- "The Khalil family, Sydney" (Layla in Y2 at Marrickville West Public School, Omar in Y4) — first language Arabic (with Levantine TTS voice preference); contributors: me, my husband, my mother who lives in Auburn.
**Sample notes in detail view (this is what the demo should show):**
**Note 1 — UK trip slip with hand-amended date (Aiden Wong, Y3):**
- **Source language:** en-GB
- **Note layout:** `trip_slip`
- **Source register:** "warm-class-teacher"
- **Transcript original (excerpt):** "Dear Parents of Year 3, On ~~Friday 30th May~~ Thursday 29th May we will be visiting Heaton Park Farm. Please send your child with: a packed lunch, a labelled water bottle, a waterproof jacket, and wellies. The cost of the trip is £5 per child, in cash, exact change please, in a labelled envelope marked Y3 FARM. Tear off and return the slip below by Wednesday 28th May. Many thanks, Mrs Henderson"
- **Hand amendments (1):** field_amended `date`, original_text "Friday 30th May", amended_text "Thursday 29th May", ink_or_pencil "blue ballpoint", appears_to_be_teacher_hand `true`
- **Translation target language:** zh-Hant-HK (Cantonese, traditional Chinese characters)
- **Translation (excerpt):** "親愛嘅Year 3家長 — [刪除:5月30日(星期五)] 5月29日(星期四)我哋會去Heaton Park Farm。請俾你個小朋友帶:包好嘅午餐、寫咗名嘅水樽、防水外套、同雨靴。費用每個小朋友£5現金,請俾啱數,放入寫咗「Y3 FARM」嘅信封。撕下面嘅回條,5月28日(星期三)之前交返。多謝晒,Henderson 老師"
- **Translation voice notes:** "preserved 'Heaton Park Farm', 'Year 3', and 'Mrs Henderson' verbatim with a one-line reading guide in Cantonese on first occurrence; preserved 'wellies' as 雨靴 with the school-paperwork shorthand; preserved the hand-amendment with strikethrough notation."
- **Actions (1):** action_type `trip_slip`, child_name_verbatim "Year 3", event_date_verbatim "Thursday 29th May" (the amended value), event_date_iso "2026-05-28" (Thursday 28 May 2026), event_location_verbatim "Heaton Park Farm", deadline_verbatim "Wednesday 28th May" (the slip return deadline), cost { currency "GBP", amount_verbatim "£5", amount_decimal 5.00, exact_change_required true, payment_method_verbatim "cash in a labelled envelope marked Y3 FARM" }, packing_list [ "packed lunch (required)", "labelled water bottle (required)", "waterproof jacket (required)", "wellies (required)" ], consent_required true, is_urgent_per_school false (no urgency marker on the page), suggested_sentence_to_child "今晚我哋幫你執書包,星期四你會去農場。" ("tonight we'll pack your bag — on Thursday you're going to the farm.")
**Note 2 — US multi-page newsletter (Selam Tesfaye, Grade 3):**
- **Source language:** en-US
- **Note layout:** `newsletter_multi_page` (3 pages)
- **Source register:** "chatty-headteacher"
- **Transcript original (excerpt):** "Brent Elementary, Week of May 26. Hi families! A few things this week: 1) The Grade 3 field trip to the National Zoo is THIS FRIDAY May 29. Drop-off 8:30, pick-up 2:30. Lunch provided. 2) PTO bake sale next Tuesday — donations of homemade goods welcome at the front office before 8:15. 3) Picture day is Wednesday June 3. School uniform please. 4) No school Monday May 25 (Memorial Day)…"
- **Translation target language:** am (Amharic)
- **Actions (4 visible, more across the full newsletter):** field trip Friday May 29 (no cost, lunch provided), PTO bake sale donations Tuesday (no cost, optional), picture day Wednesday June 3 (uniform required, no cost), school closure Monday May 25 (no action)
- **Per-Action suggested-sentence-to-child examples in Amharic:** for the field trip — "ዓርብ ወደ መካነ አራዊት ትሄጃለሽ፣ ምሳ ትምህርት ቤት ይሰጠናል።" ("on Friday you're going to the zoo, lunch is provided by school.")
**Note 3 — UK supply-teacher handwritten note (Zofia Nowak, P4):**
- **Source language:** en-GB
- **Note layout:** `supply_teacher_note`
- **Source register:** "supply-teacher-handwritten"
- **Transcript original:** "Hi Mum/Dad — Zofia did beautifully today. Could you send her library book back tomorrow please? Many thanks, Miss Bell (covering for Mrs Mackenzie)"
- **Translation target language:** pl (Polish)
- **Translation:** "Dzień dobry, Mamo/Tato — Zofia pięknie pracowała dzisiaj. Czy mogłabyś jutro odesłać jej książkę z biblioteki? Bardzo dziękuję, Miss Bell (zastępuje Mrs Mackenzie)"
- **Actions (2):** well_done_note (no calendar pin), change_of_routine — return library book tomorrow. Suggested sentence in Polish: "Wieczorem włóżmy twoją książkę z biblioteki do plecaka na jutro." ("tonight let's put your library book in your bag for tomorrow.")
**Note 4 — Australian consent form (Layla Khalil, Y2):**
- **Source language:** en-AU
- **Note layout:** `consent_form`
- **Source register:** "formal-academy-trust"
- **Transcript original (excerpt):** "Marrickville West Public School. CONSENT FORM — Year 2 Swimming Programme. The Year 2 Swimming Programme will run for four weeks beginning Monday 1 June 2026. Lessons will be held at Annette Kellerman Aquatic Centre…"
- **Translation target language:** ar (Arabic, with Levantine TTS voice preference but Modern Standard for written)
- **Actions (1):** consent_form, deadline_verbatim "Friday 22 May 2026", consent_required true, cost { currency "AUD", amount_verbatim "$48 AUD", amount_decimal 48.00, exact_change_required false, payment_method_verbatim "via Compass School Manager" }
- **Print mode:** a PDF with the parent's Arabic reading of the form on the front page and the original English signature page on the back — so the parent can sign the same paper the teacher receives.
**Note 5 — UK safeguarding-style note (filed quietly):**
- **Source language:** en-GB
- **Note layout:** `class_teacher_note`
- **Source register:** "urgent-safeguarding"
- **Transcript original:** "Dear Mrs Wong, I wanted to give you a quiet heads-up that Aiden has seemed a little quiet at lunchtimes this week. He's eating fine and his work is on track, but he hasn't joined his usual group at break. I've had a gentle chat with him and Mr Patel in the wellbeing team is aware. Nothing to worry about — I just wanted you to know. Best wishes, Mrs Henderson"
- **Translation target language:** zh-Hant-HK
- **Actions (1):** action_type `safeguarding_note`, is_urgent_per_school false (the school explicitly said "nothing to worry about"), suggested_sentence_to_child null (the parent decides if and how to mention this)
- **Filing:** lands in quiet view; capturing parent sees a small badge on the home screen; before sharing with the husband, the app asks once.
**Sample voice copy:**
- Onboarding: "Photograph one of the notes your child brought home this week. We'll read it — in any language."
- Processing: "Reading the note…" / "Translating to Cantonese…" / "Pulling out the dates and the £5…" / "Writing what to say to Aiden tonight…"
- Empty family digest: "This week is waiting for its first note. Photograph one from your child's bag to start."
- Error (couldn't read): "We couldn't make out the handwriting here. Want to try a clearer photo, or type what you can read into the side panel?"
- Save confirmation: "Added to Aiden's week — trip slip, Thursday 29 May, £5 cash."
- Hand-amendment detected: "A date was amended on this note: Friday → Thursday. We've kept both visible."
- Urgency flag (rare): "The school marked this note 'PLEASE NOTE'. We've surfaced it at the top of your week."
- Low confidence note: "Some handwriting was hard to read. Tap any underlined word to see what the model considered."
- Quiet view badge: "1 quiet note for you to read. Only you can see this."
**Sample family invitation email subject + body:**
- Subject: "Mum — I'm setting up School Letter for Aiden and Mei-Lin. Want to join?"
- Body: "Hi Mum — I'm using a new app to read the notes the school sends home in Cantonese. Since you pick up the kids Tuesdays and Thursdays, I thought it might be useful for you too. Tap to join and you'll see Aiden's and Mei-Lin's notes for the week." [Open Family]
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking shot of a child's school bag on a wooden kitchen counter at evening, two folded notes half-out, a half-eaten banana on the edge of the frame, soft warm pendant light from above. Generate via Nano Banana 2 with a prompt emphasising "wooden kitchen counter, warm pendant light, soft evening shadows, real worn school bag, folded paper notes with creases visible, no people in frame, no school logos".
- **App icon / wordmark:** Set in the display sans. Slightly worn paper texture behind it. No icon — just type. A small icon variant is a folded note with a soft fold-line.
- **Empty-state illustration:** A simple line drawing of a single folded note with a corner curl. Hand-drawn aesthetic, not a flat icon.
- **Demo note photographs:** Generated per the prompts in section 8a — Nano Banana 2 prompts that specifically request "photocopied school paper, faded toner, soft afternoon classroom light, a hand-amended date in blue ballpoint, no logos, no faces". Each demo note should look photographed, not rendered.
- **Weekly-fridge-sheet illustration:** Generated per the spec in section 4b — a clean week-strip with no text, pastel day columns, a small school-bag motif in the top-left corner. Generated once per family, cached, reused.
- **Stock fallbacks:** If image generation fails, fall back to the photographed sample note from `/public/samples/sample-note.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, `loading="lazy"`, explicit `width`/`height` to prevent layout shift.
- **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 layout, not spinners.
- Empty states explain the next action with a button whose label fits THIS app's domain: "Photograph the first note", "Drop a school newsletter PDF", "Invite a co-carer" — never a generic "Add your first item".
- Smooth scroll for in-page anchors.
- All AI-generated content streams in token-by-token where supported, with a clear "reading…" / "translating…" / "writing what to say tonight…" indicator before content starts arriving — in the family's first language.
- If an AI call fails, show a calm, specific error ("We couldn't read this handwriting — try a clearer photo, or type what you can read into the side panel?") and offer retry. Errors are surfaced in the family's first language by default.
- Low-confidence words in the transcript are faintly underlined; tapping reveals the alternates the model considered.
- Hand-amendments in the transcript view render as strikethrough + overwrite; hovering shows the ink description ("blue ballpoint, teacher's hand").
- The Action Card surfaces any field below 0.7 confidence with a soft "please confirm" tag; the parent can confirm with one tap.
- The weekly view's day-card hover-lift is 120 ms ease-out; respect reduced-motion by skipping.
- Print mode previews live, updating as the parent toggles which children to include and which days to show.
## 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.
- **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 parse / translate / weekly-digest, `gemini-3.5-flash` for suggested-sentence-to-child and term-date resolution, `gemini-3.1-flash-tts-preview` for TTS, `gemini-3.1-flash-image` (Nano Banana 2) for the weekly-fridge-sheet illustration. Set `thinkingLevel` explicitly per call where supported; omit `thinkingConfig` on TTS and image calls.
- **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed family on first launch.
- **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it (user-configured); magic-link email as fallback.
- **Storage:** Firebase Storage for original photographs. Pre-signed URLs only.
- **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 weekly view so a co-carer's capture appears live.
- Optimistic UI on writes; reconcile on response.
- Capture flow uses the Web Camera API with fixed focus/exposure where supported; falls back to native camera otherwise.
- **iOS Safari gotchas (graceful degradation):** camera permission does NOT persist across page reloads on iOS — re-request on every letter capture; backgrounded Safari tabs pause `getUserMedia` — re-acquire the stream on `visibilitychange`; on Low Power Mode iOS may degrade resolution — always offer `` as a fallback so a letter photo still uploads when WebRTC is denied; rotation drops the camera track on iOS — re-bind on `orientationchange`.
- Web-font subsetting: load only the script ranges the user has configured in family setup (a Cantonese family does not pay the cost of Amharic webfont bytes).
## 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. Verify in every supported script — Amharic Ge'ez and Tamil glyphs at small sizes need careful weight selection.
- All images have meaningful `alt` text. The original note photographs have `alt` describing the artefact ("photograph of a folded Year 3 trip slip with a hand-amended date in blue ballpoint, top-left corner shows a small banana stain") generated automatically and editable by the parent.
- Form fields have associated `