================ 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.
---
# Driving-Theory Coach
## 1. Project
**Driving-Theory Coach** is a personal study companion for the week
before a national driving-theory test. The learner picks the country
and region whose handbook they will be tested on, and the app becomes
a live-voice tutor grounded in that exact handbook — the FRSC Highway
Code in Nigeria, the RTO learner's manual in their Indian state, the
SEMOVI reglamento in Mexico City, the DVSA Highway Code in the UK, the
Førerkortbeviset materials in Norway. The user is quizzed by voice on
the topics they pick or the topics the app has noticed they keep
missing. Every wrong answer is logged with the page of the handbook it
came from and the smallest reason it tripped them. Sessions are
short, sharp, and sit between bus stops and bedtimes.
This is the kind of app a 17-year-old in Lagos opens at 10:42 pm
the night before her FRSC theory test, after a long day of school
and tutoring her brother, when she wants to be drilled on
right-of-way at roundabouts one more time before she sleeps. It is
also the kind of app a 41-year-old asylum-seeker in Manchester opens
two months after arriving, because she drove a delivery van in Aleppo
for nine years and the British theory test contains thirty-five
specifically British things (mini-roundabouts, smart-motorway red X,
zebra vs pelican vs puffin crossings) that nobody warned her about.
Same shape of moment, different country, different stakes.
The single demo that proves the magic: the learner picks "Nigeria
— FRSC, Lagos State" from the country picker, taps the green
microphone, and says "quiz me on stopping distances and rain." For
the next four minutes the app speaks: "At fifty kilometres an hour
on a wet asphalt road, what is the total stopping distance the
FRSC code says you must allow?" The learner answers; the app says
"close — the code wants thirty-six metres for fifty, not thirty;
the thinking distance stays the same, the braking distance
doubles in rain. Want to try sixty?" When the session ends the
home screen shows two small red dots — *thinking distance vs braking
distance* and *what counts as "wet"* — and an offer to drill those
two again tomorrow morning on the bus.
And in the harder cases — the test the user has already failed
twice, the test in a country whose handbook they cannot fully read,
the test for a category the learner needs for work — the app
holds the line. It never invents a rule that is not in the handbook
("the FRSC code does not specify a flashing-amber rule for
pedestrian crossings; in the absence of a stated rule, treat as a
controlled crossing"). It admits when a question is contested
between regional editions ("the RTO manual for Karnataka lists this;
the RTO manual for Maharashtra does not — your test is in
Karnataka, so we'll use Karnataka"). It quietly tracks every miss
across sessions so the night-before quiz pulls from the user's
actual weak spots, not the textbook's table of contents.
**Tagline:** _Quiz me on my country's driving code — live, in my
language, with the rules from my actual handbook and the misses
remembered between sessions._
## 2. Target audience
- Teenagers a week away from their first national driving-theory
test (Nigeria FRSC, India RTO, Mexico SEMOVI, UK DVSA, US state
DMVs, Brazil DETRAN, Indonesia SIM A, Philippines LTO, South
Africa K53, Kenya NTSA)
- Adults who passed once decades ago, moved country, and now need
to convert or re-sit (the Syrian van driver in Manchester, the
Filipina nurse in Toronto, the Ukrainian engineer in Berlin, the
retired Indian government driver helping his grandson)
- Learners taking the test in a language that is not their strongest
reading language but is their strongest spoken language — voice-led
drill is the unlock
- Repeat-takers — the learner who failed once or twice and now wants
drill targeted at the topics they actually missed, not the whole
syllabus again
- People studying for a category beyond car (motorcycle, taxi,
truck/HGV, autorickshaw, passenger-PSV) — each category has its
own handbook sections and its own weight on certain topics
- Driving-school instructors building a quick-revision tool for the
cohort they will examine on Saturday, in their state or region's
exact curriculum
- Parents whose teenager has the test Tuesday and who want to know
what to ask them at dinner
- Asylum-seekers, refugees, and recently-arrived migrants whose
ability to drive locally affects work, school runs, hospital
appointments
- People with reading-heavy disabilities (dyslexia, low vision) for
whom a voice tutor is the meaningful path through a 250-page
handbook
## 3. Core value propositions
Surface these clearly through copy, visual emphasis, and section
ordering — they are the reasons users pick this app.
- **Grounded in the actual national handbook** — every quiz question
cites the exact section of the user's country/region/category
handbook. The FRSC Highway Code, the DVSA Highway Code, the
Karnataka RTO manual, the SEMOVI reglamento, the Indonesian SIM A
syllabus, the Philippine LTO Land Transportation and Traffic Code.
No generic "international driving rules". When the handbook does
not specify, the app says so out loud.
- **Live voice tutor at study pace** — the user speaks; the app
speaks back. No tapping through screens. The Gemini Live API
carries the whole quiz session, calibrated to the user's pace,
with natural pauses for thinking and an explicit pacing dial
("slow / normal / fast").
- **The misses are remembered** — every wrong answer is logged
with the exact handbook citation, the smallest reason it tripped
the user, and the topic cluster. Sessions four days later pull
from the actual weak-spot list, not the syllabus.
- **Country / region / category aware** — the user picks "Nigeria →
Lagos State → Category B (car)" or "India → Karnataka → LMV +
AR/Auto" once; every question after is from that exact corpus.
Switching regions resets the handbook grounding cleanly.
- **First-language quiz, target-language exam** — a learner whose
test is in English but whose comfort language is Tamil can be
quizzed in Tamil; the official English phrasing of the rule
appears beside the spoken explanation so the user recognises it
when it shows up on the test screen.
- **The night-before mode** — a single tap on the home screen the
evening before the test runs a fifteen-minute mixed-difficulty
drill from the user's misses + the three highest-risk topics in
their region's pass-rate data ("right of way", "stopping
distance", "road markings"). It ends with a one-page printable
cheat sheet of the user's specific weak spots.
- **Handbook citations, never invented** — when the app cannot find
a rule in the configured handbook, it says so. It never paraphrases
a rule from a neighbouring country's handbook to fill the gap.
- **Quiet, no shame** — no streaks, no scores leaderboard, no
emoji-laden encouragement. The progress shown is the topics
mastered and the topics still weak, in plain text.
## 4. Features to build
- Country / region / category picker on first launch — three drop-downs,
with "Nigeria → Lagos → Cat B (car)" as the default if device locale
hints at it
- Handbook ingest — load the configured national/regional handbook (PDF
or HTML) into Gemini 3.5 Flash long-context once per region, summarise
into a topic tree of ~30 subjects, cache the summary in Firestore
- Topic tree home screen — a short list of the topics in the user's
handbook, each with a small status chip (untouched / weak / shaky /
strong / mastered), tappable to start a focused quiz
- Live voice tutor — Gemini Live API session with the configured
handbook in system context; voice-in, voice-out; visual transcript
underneath; "pause", "hint", "skip" buttons always reachable
- Live quiz session UI — large microphone button, clear visual state
("listening" / "thinking" / "speaking"), an always-visible "I don't
know" button that triggers the explanation flow rather than a
judgement
- Mixed-difficulty drill mode — pulls from the user's miss log,
weighted by recency and topic risk; sessions clamped to 5 / 10 /
15 / 25 minutes
- Topic-focused drill mode — the user picks one topic ("stopping
distances"), the session stays there; useful the morning of the
test
- Mock-test mode — runs the official format of the user's region
(FRSC 40 questions in 60 minutes, DVSA 50 multiple-choice + hazard
perception count placeholder, RTO 15 questions in 30 minutes,
Philippines LTO 60 questions); spoken or on-screen, learner's
choice
- Miss log with citation — every wrong answer becomes a row: question,
user's answer, correct answer, handbook section + page, smallest
reason (one sentence), topic cluster
- Weak-spot dashboard — the topics with the highest miss density in
the last 14 days; tap to drill that topic only
- Night-before mode — a single home-screen tap that mixes 60% misses,
30% high-risk regional topics, 10% review-of-recently-strong; ends
with a printable one-pager
- Bilingual mode — pick "comfort language" (Tagalog, Hindi, Yoruba,
Quebec French, Mexican Spanish, Cantonese, Tamil, Tigrinya, Urdu,
Igbo, Marathi, Wolof, Hausa, Vietnamese, Swahili, …) plus "test
language" (the official test language); the tutor speaks the
comfort language and shows the test-language phrasing on screen
- Cheat-sheet exporter — one-page PDF of the user's current weak
spots with handbook citation; for printing on the way to the
test centre
- Parent / instructor share — a read-only weak-spots view the user
can text to a parent or driving instructor; never auto-shared
- Accessibility-first speech — slow pacing dial, repeat-last-thing
shortcut, on-screen large transcript synchronised with audio
- Offline review mode — the topic tree + miss log + cheat sheet are
cached on-device so the user can review on the bus to the test
centre without a connection (live tutor mode is online-only)
- Honest scope — the app does not predict the test, does not claim
endorsement by the issuing authority, and does not collect any
data the user did not explicitly enter or speak
## 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)
- **Gemini Live API** — the primary tutor session. Voice in, voice
out. The configured handbook + the user's miss log live in the
session's system context for the duration of the call. Sessions
cap at twenty minutes by app convention; the user can resume a
new session and the miss-log thread persists in Firestore between
them. The Live API is also where the quiz pace is felt — pauses
are real pauses, hints come at the moment the learner hesitates.
- **Long context (1M tokens)** — once, per region/category, the app
loads the full handbook (often 150-300 pages of PDF, plus regional
amendments and the official sample questions where published) into
a Gemini 3.5 Flash long-context call that produces a structured topic
tree, a per-topic summary, and a per-topic difficulty hint. The
topic tree is cached in Firestore; the long-context call runs again
only when the user changes country/region/category or the handbook
version increments.
**Guardrail**: many national handbooks plus amendments plus sample
question banks comfortably fit in 1M tokens; very large jurisdictions
with multi-language editions (e.g. an Indian state with English +
Hindi + a regional language official editions) can approach the
ceiling. Chunk by official language and run the topic-tree call
once per language; merge in the app layer.
- **Search grounding** — for the topic-tree call ONLY: the model is
permitted to ground "what is the official handbook URL for [country
/ region / category]" via `google_search` so the deployer does not
have to hand-curate every jurisdiction. The handbook content itself
is then ingested as long-context, not summarised from search.
- **Structured output** — every quiz interaction that is logged to
Firestore (the miss log, the topic-mastery state, the night-before
drill plan) goes through a Gemini 3.5 Flash structured-output call with
the `Miss` / `TopicMastery` / `DrillPlan` Zod schemas below.
- **Thinking levels** — `medium` for the topic-tree extraction (the
call has to reason about how a regional handbook organises its
subjects). `low` for the per-quiz-question generation and for the
miss-log structuring (the work is well-bounded). The Live API
session uses its own model configuration; thinking is implicit
in the live model.
- **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — only for the
printable cheat-sheet's "read this aloud once before bed" mode
and for resuming a saved drill in voice-only mode without a live
session. The live quiz uses the Live API's own audio; TTS is the
offline fallback.
### 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.
The whole app is usable by an unauthenticated visitor on the device
(the country picker + a sample quiz of ten questions), with
optional sign-in to sync miss logs across devices.
- **Database — Required.** Firestore for `users`, `regions`,
`handbook_versions`, `topic_trees`, `quiz_sessions`, `misses`,
`topic_mastery`, `drill_plans`. Miss logs are private to the user
and the people they explicitly share a read-only view with (e.g.
the user's driving instructor).
- **File storage — Optional.** Firebase Storage for the cached
handbook PDFs the deployer uploads per region. **Storage is NOT
auto-provisioned by AI Studio Build today** — enable it in the
Firebase console if you intend to serve large handbook PDFs to
long-context. For most deployments, the handbook is fetched from
its official URL at ingest time and the topic tree alone is
persisted in Firestore.
- **Email — Required (transactional).** Magic-link sign-in
(sender-domain authorisation required in Firebase Auth). Share-
miss-log emails to driving instructors. Never marketing.
- **Payments — Not needed for v1.** Free for personal use. A future
driving-school tier (instructor dashboards across a cohort) could
charge per seat.
- **External APIs:** Gemini API for all intelligence. Optional:
none in v1. (The Live API and long-context Gemini calls are the
product.)
**Environment variables:** every secret (Gemini API key, Firebase
service-account JSON) lives in environment variables — never in the
client bundle. Include a `.env.example`.
**Auth + data privacy reminders:** never log secrets · never store
passwords in plain text · use HTTPS everywhere · honour 'delete my
account and all my misses' inside the UI · explicit opt-in for any
analytics · the user's quiz answers, misses, and voice recordings
are never sent to Gemini for model training (use the Gemini API on
the paid tier, where Google does not use your content for model
training, per the Gemini API Additional Terms) · audio recordings
from Live sessions are not retained server-side beyond the session
window unless the user opts in.
**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 schemas
are 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.
### Per-call model + tools matrix
| Call | Model | thinkingLevel | Tools enabled |
|------|-------|---------------|---------------|
| Topic-tree extraction from full handbook | `gemini-3.5-flash` | medium | (none — handbook supplied as long-context input) |
| Find official handbook URL for a region | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) |
| Live tutor session (voice-in, voice-out) | `gemini-3.5-flash` (Live API variant) | n/a (Live API) | (none beyond the session's grounded context) |
| Per-question authoring on demand | `gemini-3.5-flash` | low | (none) |
| Miss-log structuring → `Miss` schema | `gemini-3.5-flash` | low | (none) |
| Night-before drill plan → `DrillPlan` schema | `gemini-3.5-flash` | low | (none) |
| Cheat-sheet TTS narration | `gemini-3.1-flash-tts-preview` | n/a | n/a |
*Note for builders:* on TTS and Live-session 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. The "find official handbook
URL" call uses `google_search` grounding, which is mutually exclusive
with `responseSchema` — the model returns JSON in the text body and
the server parses it; citation URLs come from
`response.groundingMetadata.groundingChunks[].web.uri`.
### Primary structured-output schemas (seed these verbatim in the prompt)
```typescript
import { z } from "zod";
const Region = z.object({
country_code: z.string(), // "NG", "IN", "MX", "GB", "US-CA"
country_name: z.string(), // "Nigeria"
region_code: z.string().nullable(), // "LA" (Lagos State), "KA" (Karnataka)
region_name: z.string().nullable(), // "Lagos State"
category_code: z.string(), // "B" (car), "A" (motorcycle), "C" (truck), "AR" (autorickshaw)
category_name: z.string(), // "Category B — Car"
issuing_authority: z.string(), // "FRSC", "RTO Karnataka", "SEMOVI", "DVSA"
handbook_official_url: z.string().nullable(),
handbook_version_label: z.string().nullable(), // "2024 edition", "v3.1"
official_test_language: z.string(), // BCP-47, "en-NG"
notes: z.string().nullable(),
});
const HandbookTopic = z.object({
topic_id: z.string(), // "stopping-distances"
title: z.string(), // "Stopping distances"
handbook_section_label: z.string(), // "FRSC HC §4.3"
handbook_page_range: z.string().nullable(), // "pp. 47-52"
one_line_summary: z.string(), // for the topic-tree screen
example_questions: z.array(z.string()), // 2-4 verbatim from sample-question bank if published
difficulty_hint: z.enum(["foundational", "common-miss", "edge-case"]),
notes: z.string().nullable(),
});
const TopicTree = z.object({
region: Region,
topics: z.array(HandbookTopic),
generated_from_handbook_version: z.string(),
notes: z.string().nullable(), // e.g. "topic 'flashing-amber' not present in this handbook"
});
const QuizQuestion = z.object({
question_id: z.string(),
topic_id: z.string(),
question_text: z.string(), // spoken aloud by the tutor
question_text_official_language: z.string(), // the wording the user will encounter on the test
expected_answer: z.string(), // canonical answer
acceptable_answer_variants: z.array(z.string()), // "30 metres" / "30 m" / "thirty metres"
handbook_citation: z.object({
section_label: z.string(),
page_range: z.string().nullable(),
quoted_passage: z.string(), // verbatim from handbook
}),
difficulty: z.enum(["easy", "medium", "hard"]),
});
const Miss = z.object({
miss_id: z.string(),
user_id: z.string(),
session_id: z.string(),
region_id: z.string(),
topic_id: z.string(),
question_id: z.string(),
question_text: z.string(),
user_answer_verbatim: z.string(), // exactly what the learner said
correct_answer: z.string(),
smallest_reason: z.string(), // one sentence, plain language
handbook_citation: z.object({
section_label: z.string(),
page_range: z.string().nullable(),
quoted_passage: z.string(),
}),
occurred_at_iso: z.string(),
reviewed_correctly_count: z.number().min(0), // how many subsequent sessions this miss was answered right
status: z.enum(["new", "reviewing", "shaky", "strong", "mastered"]),
});
const TopicMastery = z.object({
user_id: z.string(),
region_id: z.string(),
topic_id: z.string(),
status: z.enum(["untouched", "weak", "shaky", "strong", "mastered"]),
last_reviewed_iso: z.string().nullable(),
consecutive_correct: z.number().min(0),
total_misses: z.number().min(0),
recent_misses_14d: z.number().min(0),
});
const DrillPlan = z.object({
plan_id: z.string(),
user_id: z.string(),
region_id: z.string(),
mode: z.enum([
"topic-focused", "weak-spot-mix", "night-before",
"mock-test", "review-only", "sample-quiz",
]),
duration_minutes: z.number().min(1).max(60),
question_ids_in_order: z.array(z.string()),
miss_ids_to_revisit: z.array(z.string()),
weight_explanation: z.string(), // "60% misses, 30% high-risk topics for this region, 10% recent strong"
generated_at_iso: z.string(),
});
const LiveSessionSummary = z.object({
session_id: z.string(),
user_id: z.string(),
region_id: z.string(),
started_at_iso: z.string(),
duration_seconds: z.number().min(0),
questions_asked_count: z.number().min(0),
correct_count: z.number().min(0),
misses: z.array(Miss),
topics_touched: z.array(z.string()),
pace_used: z.enum(["slow", "normal", "fast"]),
user_notes_verbatim: z.string().nullable(),
});
type Miss = z.infer;
type DrillPlan = z.infer;
```
### Common failure modes (and how to avoid them)
- Agent picks `gemini-3.5-flash` for topic-tree extraction to save quota —
pin `gemini-3.5-flash` explicitly. Flash on a 250-page handbook drops
whole sub-topics silently and merges unrelated rules into one
bullet ("stopping distances and pedestrian crossings" as a single
topic, which makes the drill incoherent).
- Quiz answers invented from neighbouring countries' codes — a Nigerian
user is asked a UK-specific rule about smart-motorway red X. Hard
constraint: every question must include a verbatim handbook
citation for the configured region; if the handbook does not
mention the topic, the model must say so, not paraphrase a foreign
source.
- Live session forgets the region — partway through a quiz the model
starts citing the wrong country's rules. Pin the
`region` object verbatim in the live session's system instruction at
start; refuse to answer if `region` is not present.
- Live session keeps the microphone open during the user's silence
and treats throat-clearing as an answer — configure VAD with a
conservative end-of-speech timeout (≥1200 ms) for the slow pace,
≥800 ms for normal, ≥500 ms for fast.
- TTS reads "30 m" as "thirty em" — pre-process the cheat-sheet text
before TTS: expand units in source language ("30 metres", "30
meters", "30 mètres", "30 metros") so the voice reads naturally.
Gemini 2.5 TTS does not support ``.
- Miss log conflates "user said 30 metres when correct is 36 metres"
with "user said the wrong unit entirely" — the `smallest_reason`
field must capture the difference in one sentence ("close — under-
estimated braking distance in rain by 6 m" vs "wrong unit — answered
in feet, but FRSC uses metres").
- Handbook URL hallucinated — the URL-finder call uses `google_search`
grounding, returns citation URLs from `groundingMetadata.groundingChunks[].web.uri`, and never accepts a model-asserted URL without a grounding match. If no citation is present, the call fails closed and the deployer
hand-curates.
- Long-context call sent the handbook PDF as a public Firebase Storage
URL — Gemini does not fetch arbitrary public URLs. Upload via the
Gemini Files API (`files/*` resource name) or `inlineData` base64. Wire the
Firebase Storage bucket name into the AIS Build project before
attempting handbook ingest.
- Sample-question bank used as the only question source — many
national bodies publish ~100 sample questions and 10× as many real
test questions are kept private. The app must generate fresh
questions grounded in handbook clauses, not just shuffle the public
bank.
- Night-before drill turns into a torture session of every miss
ever — clamp to the durations in the schema; mix in 10% recent-
strong topics so the learner ends on a clean answer; never
exceed 25 minutes.
- TTS played in a Live session pane — they are different calls.
Live API audio is the live tutor; TTS is offline cheat-sheet
narration only. Mixing them produces echo and overlap.
### Negative constraints (hard rules)
- Do NOT invent or paraphrase rules that are not in the configured
handbook. If the user asks "what does the code say about [topic
not in handbook]?" the tutor must answer: "the [authority] handbook
for [region] does not specify that. The closest related rule is
[cite] — but it is not the same thing."
- Do NOT cite a foreign country's handbook for a configured region.
A Karnataka learner is never quizzed on a DVSA rule.
- Do NOT predict the exact questions on the user's actual upcoming
test. The app rehearses topics from the handbook; it does not
claim insider knowledge of an exam paper.
- Do NOT claim endorsement by the FRSC, DVSA, RTO, SEMOVI, LTO,
DETRAN, NTSA, or any other issuing authority. The app is a
third-party study companion; it says so clearly in the footer and
onboarding.
- Do NOT shame the learner for misses. No "you got this wrong
before". The miss log surfaces topics; the language is "topic
to revisit", not "you keep failing".
- Do NOT translate official terms-of-art that have legal weight on
the test. "Smart motorway", "controlled crossing", "right of
way" — keep verbatim with an explanation in the comfort language;
do not coin a translation that the test paper will not recognise.
- Do NOT use the user's voice recordings, misses, or quiz history
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.
- Do NOT auto-share the miss log. The "share with my instructor"
flow is explicit, per-share, with the user typing the recipient.
- Do NOT push notifications without explicit opt-in. The "test is
Tuesday" reminder requires the user to enable notifications and
to type the test date.
- Do NOT log microphone audio outside the active session window.
When the Live session ends, the audio buffer is discarded server-
side unless the user has opted in to keep recordings.
### 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: Topic-tree extraction from full handbook
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none — handbook supplied as long-context input)
```
You receive the full text of one national or regional driving-theory
handbook (or learner's manual) as long-context input, together with
a Region object describing the country, region, category, and
issuing authority. Your task is to produce a TopicTree JSON that
covers EVERY topic the handbook examines, in the handbook's own
order, with citation labels that match the handbook's own section
or chapter numbering.
The handbooks you may encounter include (non-exhaustive):
- Nigeria: FRSC Highway Code (English, with Yoruba, Hausa, Igbo
editions where published) — category B, A, C, taxi.
- India: state RTO manuals (Karnataka, Maharashtra, Tamil Nadu, Delhi,
West Bengal, …) in English, Hindi, and the state language —
category LMV, MCWG, AR (autorickshaw), HMV, TR (transport).
- Mexico: SEMOVI Reglamento de Tránsito (Mexico City) and state
reglamentos (Jalisco, Nuevo León, …) — categories A1, A2, B, C, D, E.
- United Kingdom: DVSA Highway Code + The Official Highway Code for
Northern Ireland — categories B, A, C, D.
- United States: per-state DMV driver's manuals (California, Texas,
New York, Florida, …) — Class C, M, A, B, CDL.
- Philippines: LTO Land Transportation and Traffic Code + Driver's
Manual (Tagalog and English editions) — Categories A, A1, B, B1, B2,
C, etc.
- Brazil: DETRAN Código de Trânsito Brasileiro state amendments —
Categories A, B, C, D, E.
- Indonesia: SIM A (car) and SIM C (motorcycle) syllabi from KORLANTAS.
- Kenya: NTSA driving school curriculum.
- South Africa: K53 manual.
- European jurisdictions: Norway Førerkortbeviset, Germany
Fahrerlaubnis-Verordnung, France Code de la Route, Spain DGT
manual, Italy Patente B materiale ufficiale.
- Other: Egypt, Pakistan, Bangladesh, Vietnam, Thailand, Turkey,
Saudi Arabia, UAE, Argentina, Chile, Colombia, Peru, Ethiopia,
Ghana, Senegal — load whichever the deployer configures.
Hard rules:
- Topic IDs are stable kebab-case slugs derived from the handbook's
own headings ("stopping-distances", "right-of-way-at-roundabouts",
"controlled-pedestrian-crossings", "smart-motorway-signals", "k53-
yard-test"). Do NOT invent thematic groupings that do not exist in
the handbook's structure.
- handbook_section_label is the handbook's OWN reference ("FRSC HC
§4.3", "DVSA HC Rule 126", "RTO Karnataka Ch. 7", "SEMOVI Art. 11"),
never your own renumbering.
- handbook_page_range is the actual page range in the cited handbook
version; if pagination is unstable across editions, set it to null
and rely on section labels.
- one_line_summary is plain language, ≤ 25 words, in the handbook's
official test language. It is what the learner sees on the topic-
tree screen.
- example_questions: include 2-4 example questions ONLY if the
handbook publishes an official sample-question bank. Quote them
verbatim. If no official sample bank exists, return an empty array
for this topic — do not invent.
- difficulty_hint: "foundational" for topics the handbook treats as
universal (e.g. road signs); "common-miss" for topics named in any
published pass-rate or failure-analysis material from the issuing
authority; "edge-case" for topics that appear in the handbook but
rarely on the test.
- If a topic appears only in some editions of the handbook (e.g.
Karnataka mentions autorickshaw-specific clauses absent from
Maharashtra's edition), note that in `notes`.
- Do NOT include topics from other countries' codes. If the deployer
asks for the Mexico handbook, do not blend in any DVSA content.
- Do NOT abridge the topic list because the handbook is repetitive.
If the FRSC Highway Code repeats stopping-distance content in two
chapters, both citations belong in the same topic.
Output the TopicTree JSON only, matching the provided schema.
No commentary.
```
---
### Call: Find official handbook URL for a region
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding (no `responseSchema` — see below)
```
You receive a Region object specifying a country, region, and licence
category. Your task is to find the OFFICIAL handbook URL (PDF or web)
from the issuing authority's own domain — FRSC (frsc.gov.ng), DVSA
(gov.uk), state RTO portals (parivahan.gov.in or state portals),
SEMOVI (semovi.cdmx.gob.mx), LTO (lto.gov.ph), DETRAN state portals,
NTSA (ntsa.go.ke), etc.
Hard rules:
- Use `google_search` grounding. The URL you return MUST appear in
the grounding citations the API returns. If it does not, the
caller will reject the response and ask the deployer to hand-
curate.
- Prefer government-domain results (.gov.ng, .gov.in, .gob.mx,
.gov.uk, state-RTO subdomains). If the only available URL is a
third-party mirror, return null and explain in `notes`.
- If multiple editions are current (an older one for transition,
a newer one for the next test cycle), return the one named in
the issuing authority's current notice; mention the alternative
in `notes`.
- For licence categories where the handbook differs (motorcycle vs
car), return the URL for the requested category, not the generic
one.
- Do NOT return URLs to commercial driving-school PDF mirrors as
the official source.
- Do NOT fabricate a URL. If grounding does not surface a real
one, return null.
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). The JSON has shape:
{ handbook_official_url: string | null, handbook_version_label:
string | null, notes: string }. Server-side: parse the JSON, then
read citation URLs from the response's
`groundingMetadata.groundingChunks[].web.uri`. Do NOT ask the
model to include citation URLs in the JSON body; it will
hallucinate them.
No commentary outside the JSON.
```
---
### Call: Live tutor session (voice-in, voice-out)
Model: `gemini-3.5-flash` (Live API variant) · thinkingLevel: n/a · Tools: (none beyond the session's grounded context)
```
You are a calm, patient driving-theory tutor. The learner has a real
test soon — possibly tomorrow. Your job is to quiz them on the
configured Region's handbook in their chosen pace, listen to their
spoken answer, mark it against the handbook citation, and explain
the smallest reason any miss happened.
At session start you receive in the system context:
- the Region object (country, region, category, authority, official
test language)
- the TopicTree for that region (so you know what is in scope and
what is out of scope)
- the user's current miss log (up to 200 most recent misses, with
topic IDs, citations, and `smallest_reason`)
- the user's comfort language and the official test language
- the requested pace ("slow" / "normal" / "fast")
- the session mode ("topic-focused" / "weak-spot-mix" / "mock-test"
/ "sample-quiz" / "night-before")
- the duration cap in minutes
Hard rules:
- Always quiz in the comfort language for the conversational
framing; speak the official-test-language phrasing of the rule
when you state the canonical answer ("the official wording on
the test will be: 'leave a two-second gap'").
- Pause for the pace requested. Slow: at least 1.2 s of silence
before treating end-of-speech; do not interrupt thinking. Normal:
0.8 s. Fast: 0.5 s.
- Every question must come from a topic the TopicTree contains.
If the user asks "ask me about X" and X is NOT in the TopicTree,
say so honestly: "the [authority] handbook for [region] does
not cover X — we'll skip it. Want to try the closest related
topic, [Y]?"
- When a learner is wrong, state the correct answer once, cite the
handbook section verbatim, and give the smallest reason ("close
— you under-estimated the braking distance in rain by 6 m. FRSC
§4.3 wants 36 m total at 50 km/h on wet asphalt."). Then ask if
they want to try the next, repeat, or move on. Do not stack
three explanations on one miss.
- When a learner says "I don't know" or stays silent past the
pace's timeout, treat it as a non-miss and explain the answer
with the same citation. Do not log "I don't know" as a wrong
answer; log it as `status: new` with a `smallest_reason` of
"did not attempt".
- Never invent a rule. If you cannot find the rule in your grounded
context, say so: "I cannot find that in the handbook I have for
[region]. Want to mark it for the deployer to add?"
- Never cite a foreign country's handbook. If the learner asks
"is this the same as the UK rule?" you may compare in passing
but always anchor in the configured region.
- Never shame. Avoid "you got this wrong before". Use "topic to
revisit" or "want to try this one again?" instead.
- At session end, summarise: questions asked, correct count,
topics touched, the one specific topic to drill next time. Do
NOT say "good job" or "well done"; say "ten questions, seven
right; the two stopping-distance ones are worth one more round."
You may use proactive teaching for high-risk topics. If the learner
gets a question right on a topic the issuing authority publishes as
a common failure point, say so honestly: "FRSC's own pass-rate
material flags this as a common miss — good catch."
End the session cleanly at the duration cap. Save the session
summary and the misses via the `Miss` / `LiveSessionSummary`
schemas in a separate structured call (this Live call only produces
audio; structuring happens client-side from the transcript).
```
---
### Call: Per-question authoring on demand
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive a topic_id, a Region object, and the relevant excerpt
of the handbook for that topic. Your task is to author ONE quiz
question grounded in that excerpt, in the QuizQuestion schema.
Hard rules:
- `question_text` is plain, spoken language in the comfort
language. Short — under 25 words. Conversational, not test-
paper formal.
- `question_text_official_language` is the same question phrased
the way the official test paper will phrase it. Match the
handbook's wording closely; do not paraphrase legal terms-of-
art.
- `expected_answer` is the canonical answer from the handbook
excerpt. `acceptable_answer_variants` contains common spoken
variants ("30 metres", "30 m", "thirty metres"; "give way",
"yield"; "smart motorway", "controlled motorway").
- `handbook_citation.quoted_passage` is verbatim from the excerpt
provided. Do NOT paraphrase. Do NOT trim mid-sentence.
- `handbook_citation.section_label` is the handbook's own label
("FRSC HC §4.3"), not your renumbering.
- difficulty: "easy" if the answer is a single number or a yes/no;
"medium" if it requires combining two clauses; "hard" if it
requires reasoning across a clause and a road-sign meaning.
Hard NO list:
- Do NOT invent rules. If the handbook excerpt does not contain
the answer, return an error in the schema's `notes` field and
produce no question.
- Do NOT use UK, US, or any other foreign country's wording for a
non-foreign region.
- Do NOT generate trick questions whose answer turns on legal
hair-splitting absent from the handbook itself.
Output the QuizQuestion JSON only. No commentary.
```
---
### Call: Miss-log structuring → `Miss` schema
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive a single quiz turn: the QuizQuestion that was asked, the
user's verbatim spoken answer (as transcribed from the Live session),
the expected answer, and the handbook citation. Your task is to
produce a Miss JSON if and only if the user's answer was wrong or
"did not attempt".
Hard rules:
- `user_answer_verbatim` is the exact transcript of what the user
said. Do not normalise units, capitalise, or correct spelling.
- `smallest_reason` is ONE sentence in plain language, in the
user's comfort language, naming the smallest specific gap. Good:
"under-estimated braking distance in rain by 6 m". Bad: "the
user did not understand stopping distances". The bad version is
shame; the good version is a hint.
- `status` starts at "new". Do not set it to "reviewing", "shaky",
"strong", or "mastered" — those transitions happen in a
separate function as the user revisits the miss.
- `handbook_citation.quoted_passage` is verbatim from the
handbook. Never paraphrase.
- If the user said "I don't know" or stayed silent, produce a Miss
with `user_answer_verbatim: ""` and `smallest_reason: "did not
attempt"` — this is logged for revisit, not counted as wrong.
Output the Miss JSON only. No commentary.
```
---
### Call: Night-before drill plan → `DrillPlan` schema
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive: the user's full miss log for the configured region
(grouped by topic and status), the TopicMastery records, the
TopicTree, and the requested duration (default 15 minutes; max 25).
Your task is to produce a DrillPlan that mixes:
- 60% of session time on misses with status "new" or "shaky", with
recency weighting (more recent misses prioritised)
- 30% of session time on topics the TopicTree flags as
"common-miss" difficulty for the configured region, regardless of
whether the user has touched them
- 10% of session time on topics where the user is "strong" — to
end on confident notes
Hard rules:
- duration_minutes never exceeds 25.
- question_ids_in_order is the planned spoken order. Mix difficulty
levels — do not stack three hard questions in a row.
- weight_explanation is one sentence the user will see on the home
screen ("60% from your weak spots, 30% from topics often missed
in Lagos, 10% to end strong").
- If the user has fewer than five misses logged in total, lean on
the TopicTree's "common-miss" topics for the bulk of the plan
and say so in weight_explanation.
- Never include questions from topics outside the configured
Region's TopicTree.
Output the DrillPlan JSON only. No commentary.
```
---
### Call: Cheat-sheet TTS narration
Model: `gemini-3.1-flash-tts-preview` · thinkingLevel: n/a · Tools: n/a
```
Voice: calm, unhurried. Pick the Gemini 2.5 Flash TTS voice whose
`languageCode` matches the user's comfort language. Pronunciation
follows the voice's native locale automatically.
Pre-process the cheat-sheet text before sending it to TTS:
- Expand units to words in the comfort language ("30 metres" not
"30 m"; "km/h" → "kilometres per hour" or the locale equivalent;
"mph" → "miles per hour").
- At each line break in the cheat sheet, insert a single ellipsis
(`…`) so the TTS produces a natural pause.
- At each topic break, 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 handbook citation labels — they are visual only.
- Style direction: prepend ONE short directive sentence to the
text input, exactly like: "Read calmly and unhurriedly, as a
study companion the evening before a driving-theory test. …".
There is no separate `style` API field on Gemini 2.5 TTS; the
directive sentence inside the input is how style is conveyed.
Mid-call voice switching is not supported. If the cheat sheet
contains both the comfort language and the official-test-language
phrasing (e.g. Tamil with English terms-of-art), keep the voice in
the comfort language and let the official phrasing render in the
voice's native pronunciation; the user sees both on the printed
sheet.
Target rate: ~115 words per minute. Letter-reading pace, not
podcast pace.
Phoneme overrides (Hindi retroflexes, Arabic emphatics, Yoruba
tones) are NOT exposed by Gemini 2.5 TTS — no SSML ``
tag. Pronunciation comes from the chosen voice's native locale.
```
## 5. Use cases & content to include
Build dedicated UI sections or flows for each of these — they tell
you what content the app must support.
- **The night before FRSC.** A 17-year-old in Lagos opens the app at
10:42 pm. Test is at 8 am. The home screen shows three weak topics:
"stopping distances in rain", "right of way at uncontrolled
T-junctions", "Class B and AR sign differences". She taps "drill
the weak ones for 15 minutes" and the live tutor begins in English
with Yoruba switch-words for emphasis.
- **The Karnataka RTO conversion.** A 32-year-old who drove for nine
years in Maharashtra needs to retake the LMV test because his licence
expired during a long stay abroad. He picks "India → Karnataka →
LMV". The TopicTree highlights three topics absent from the
Maharashtra manual he last studied: a state-specific clause on
yellow-line lane discipline, a state-specific autorickshaw stop-
marker rule, and a state-specific fine schedule. The app drills
those three first.
- **The SEMOVI re-sit.** A 24-year-old in Mexico City failed her
theoretical exam by two questions. She uploads her score sheet
(PDF screenshot) and the app maps each wrong answer to a SEMOVI
reglamento section, then proposes a five-day drill plan.
- **The asylum-seeker in Manchester.** A 41-year-old Syrian woman
who drove a delivery van in Aleppo wants to convert. She picks "UK
→ England & Wales → Category B". Her comfort language is Arabic;
the test will be in English. The tutor speaks Arabic, shows the
official English phrasing on screen for every rule, and emphasises
the specifically-British items absent from the Syrian code: mini-
roundabouts, the smart-motorway red X, zebra-vs-pelican-vs-puffin
crossings.
- **The Filipina student nurse in Toronto.** A 28-year-old preparing
for the Ontario G1 written test wants drill in Tagalog with the
official English phrasing on screen. She studies on the streetcar
between shifts; sessions are clamped to 12 minutes.
- **The first-time LTO test in Manila.** A 19-year-old has the LTO
Land Transportation written exam Saturday. He picks "Philippines →
LTO → Category A1 (motorcycle)". The tutor quizzes in Tagalog with
English terms-of-art. The app surfaces the Philippine-specific
rules on rider gear, swerving, and overtaking on national highways
that the LTO commonly tests.
- **The cohort instructor in Nairobi.** A driving-school instructor
preparing 14 students for their NTSA test on Saturday. She loads
her cohort, taps "share weak-spot view", and texts each student
their own three topics to drill that night.
- **The retired engineer learning at 64.** A man who has driven for
forty years on an out-of-date paper licence finally has to convert
to a digital one and discovers the handbook has changed in seven
important ways. He picks "slow pace" and the tutor takes the time
he needs.
- **The Tigrinya speaker, K53 in Cape Town.** A 30-year-old whose
comfort language is Tigrinya is preparing for the K53 written test
in English. The tutor speaks Tigrinya for the explanation and
shows the K53 manual's English phrasing for the official wording.
- **The teen with dyslexia in Birmingham.** A 17-year-old whose
reading is slow opens the app on the bus to school. Mock-test
mode runs voice-only: questions are spoken, answers are spoken,
the on-screen transcript follows along in large type with words
highlighted as they are spoken.
## 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 teenager
on a bedroom desk at evening with a phone propped against a stack
of notebooks, a half-drunk mug, the corner of a learner's permit
poking out. One paragraph: "Pick the country and category of
your driving-theory test. We'll quiz you live, in your language,
from the actual handbook — and remember the misses for next
time." Single Google sign-in button; Apple sign-in next to it.
Below: "Try the sample quiz without signing in" → loads the demo
region in section 8a (default: Nigeria FRSC, Lagos State, Cat B).
2. **Region picker.** Three drop-downs (country → region → category)
with a one-line "what authority issues this test" caption per
choice. Comfort-language picker below. The submit button reads
"Load my handbook" — never "Continue".
3. **Handbook-loading screen.** A specific, honest progress sequence:
"Finding the official handbook for FRSC, Lagos State, Category B…"
→ "Reading 184 pages…" → "Building your topic tree (about 30
topics)…" → "Ready." Total ~40 seconds. Show the citation URL
the app found, with a "use a different URL" override.
4. **Topic-tree home.** A short list of the topics in the user's
handbook, each with a small status chip (untouched / weak /
shaky / strong / mastered). At top, three big buttons: "Quiz me
for 5 minutes" · "Quiz me on a specific topic" · "Night-before
drill". A small text caption: "Your handbook: FRSC Highway Code,
2024 edition · Lagos State Cat B".
5. **Live quiz session.** Full-screen on mobile. Large mic button
centred; current question text appears above it as the tutor
speaks (also for accessibility); the tutor's voice is the
primary modality. Three persistent buttons: "I don't know"
(skip to explanation), "Repeat that please", "End session". The
pace dial (slow / normal / fast) lives in a quiet corner.
6. **Topic detail view.** A single topic with: the handbook excerpt
(verbatim), the topic's example questions if any are published
in the official sample bank, the user's miss history for this
topic (date, what they said, the smallest reason), and a "drill
this topic only" button.
7. **Miss log.** Reverse-chronological list of every wrong answer:
question, user's verbatim answer, correct answer, handbook
citation, smallest reason. Filter: by topic, by status, by
recency. The default sort surfaces "shaky" first.
8. **Weak-spot dashboard.** The five topics with the highest miss
density in the last 14 days, with the specific sub-reasons surfaced
under each. Tap any topic to drill that topic only.
9. **Night-before mode.** A single-button screen the evening before
the test. Tapping it generates a 15-minute drill plan
(`DrillPlan`), shows the planned topic mix with a one-sentence
explanation, and starts the live session on confirm. After the
session, generates the printable one-page cheat sheet.
10. **Mock-test mode.** Runs the official format for the configured
region. The format card at top reads "FRSC: 40 questions, 60
minutes, pass mark 80%" (or DVSA: 50 multiple-choice questions,
57 minutes, pass mark 86%, hazard perception separate; or
Philippines LTO: 60 questions). On-screen or spoken; learner
chooses. End-of-test summary names the topics missed, not just
the score.
11. **Cheat-sheet exporter.** One-page PDF preview. Choose: comfort
language only / both languages / official-test-language only.
Includes the handbook section citations beside each rule.
"Print" button is the primary CTA.
12. **Sharing & instructor view.** Modal: "Send your weak-spots view
to your instructor". Magic-link email to a single typed
recipient, read-only. Never auto-shared.
13. **Footer.** "Built to drill, not to predict the exam." Privacy:
"Your misses are yours. We never train on them." Capabilities
`(i)` icon in header.
## 6b. First-visit onboarding
Show a **first-visit onboarding** the first time a visitor lands on
the app (detect via `localStorage` flag; do not show on return
visits). Three slides, dismissible at any time. Persistent re-entry:
a `?` icon in the header reopens it.
**Slide 1 — What this is.**
- Headline: "Welcome to Driving-Theory Coach."
- Subhead: "Quiz me on my country's driving code — live, in my
language, with the rules from my actual handbook and the misses
remembered between sessions."
- One paragraph (≤ 60 words) explaining who this is for and what
makes it different from a generic flash-card app: it grounds every
question in the user's actual national handbook, it tracks misses
across sessions, and it speaks the user's comfort language while
showing the official-test-language phrasing on screen.
- Visual: a small annotated illustration of a phone propped on a
desk with a "live quiz" indicator, a topic-tree silhouette, and
a small "FRSC §4.3" citation label — not a generic study-app icon.
**Slide 2 — Try it now.**
- One short prompt: "Try the sample quiz".
- A live demo session pre-loaded with the seed region in section 8a
(default: Nigeria FRSC, Lagos State, Cat B).
- 1-2 sentences pointing at *the specific page elements* where the
Gemini magic happens (the live voice tutor, the verbatim handbook
citation under each answer, the miss log that opens after one
wrong answer).
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Pick your country, region, and licence category — or add your
own handbook via the URL override in `/server/regions/`."
- "Adjust the prompts in `/server/prompts/` to fit your test
format and pace defaults."
- "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 Live API** — the live voice tutor. The whole quiz
session is a single live call, voice in and voice out, with the
configured handbook and your miss log in the session's grounded
context. Sessions cap at twenty minutes; the miss log persists
between them.
- **Gemini 3.5 Flash (long context, 1M tokens)** — used once per
region/category to read the full national handbook and produce
the topic tree, with citations. Re-runs only when the handbook
version changes.
- **Gemini 3.5 Flash + grounded search** — used once per region to
find the official handbook URL from the issuing authority's own
domain. Never invents a URL.
- **Gemini 3.5 Flash (structured output)** — used to write the night-
before drill plan and to produce each quiz question on demand.
- **Gemini 3.5 Flash (structured output)** — used to log every miss
to the Miss schema with the smallest reason and the handbook
citation.
- **Gemini TTS (2.5 Flash Preview)** — used for the cheat-sheet
read-aloud and for the offline drill-resume mode. Voice locale
follows the comfort language.
- **Firebase Auth** — Google and Apple sign-in. Signed-in is
optional; the sample quiz works without an account.
- **Firestore** — stores your topic tree, miss log, and drill
plans. Syncs across devices in real time.
- **Cost note** — see the detailed breakdown in 6d. A typical week
of revision (six 12-minute live sessions + one night-before
drill) costs about $0.85 in Gemini API spend.
- **Privacy note** — your misses, your quiz answers, and your
voice recordings are private to you. 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. Live-
session audio is not retained server-side beyond the session
window unless you opt in.
**Backend services this app depends on:**
- Auth: see section 4b (Apple sign-in requires Apple Developer
account + Service ID configuration in Firebase)
- Database: see section 4b
- Storage: see section 4b (Firebase Storage is NOT auto-provisioned
by AI Studio Build; enable it in the Firebase console if you
intend to cache handbook PDFs)
- Email: see section 4b (magic-link sign-in requires sender-domain
authorisation in Firebase Auth)
- Payments: see section 4b (not used in v1)
- External APIs: see section 4b
**Environment variables you'll need to configure:**
- `GEMINI_API_KEY` — your Google AI Studio API key
- `FIREBASE_PROJECT_ID` — your Firebase project id
- `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side
only)
- `FIREBASE_STORAGE_BUCKET` — only if you choose to cache handbook
PDFs
**Cost + privacy notes:**
- One short paragraph per cost-sensitive capability: the live
session is billed per audio minute; a 12-minute slow-pace
session costs about $0.06; the topic-tree extraction runs once
per region per handbook version and costs about $0.40 for a
large national handbook.
- One short paragraph on privacy: where the data lives (your
Firebase project), how to delete it (Settings → "Delete my
account and all my misses" — gone in 60 seconds), what is never
sent for training, and how Live session audio is treated.
**Documentation links:**
- AI Studio Build docs
- Gemini Live API docs
- Gemini API long-context, structured-output, grounded-search,
TTS docs
- Firebase Auth, Firestore, Firebase Storage docs
- A short note on adding a new region by uploading a handbook PDF
**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)
- **Topic-tree extraction (Gemini 3.5 Flash, medium thinking, long-
context)** — a typical national handbook is 150-300 pages ≈
~250k-400k input tokens, with ~6k output tokens for the topic
tree. At 3 Pro input $1.50/M, output $9/M: ~$0.34-$0.53 per
region per handbook version. Runs once per region, re-runs only
when the handbook version increments. Cached in Firestore.
- **Find official handbook URL (Gemini 3.5 Flash + grounded search)**
— small input, small output, ~$0.001 per region. Runs once.
- **Live tutor session (Gemini Live API)** — billed per audio
minute. A 12-minute slow-pace session ≈ ~$0.06; a 15-minute
night-before drill ≈ ~$0.075. Six revision sessions in a week
≈ ~$0.36.
- **Per-question authoring (Gemini 3.5 Flash, low thinking)** — only
triggered when the user requests a topic the tutor has not yet
generated questions for. ~$0.002 per question. Typically 20-40
fresh questions across the revision week ≈ ~$0.08.
- **Miss-log structuring (Gemini 3.5 Flash, low thinking)** —
~$0.0005 per miss. A week of revision typically logs 30-60
misses ≈ ~$0.03.
- **Night-before drill plan (Gemini 3.5 Flash, low thinking)** —
~$0.01 per plan. Runs once per "night-before" session.
- **Cheat-sheet TTS (Gemini 2.5 Flash TTS)** — billed per output
token (~$10/M output tokens), ≈ ~$0.000003/character. A one-page
cheat sheet ≈ 1,500 characters ≈ ~$0.005 per narration.
- **Expected total cost for one learner's revision week:** ~$0.85
(six 12-minute sessions + one night-before drill + cheat sheet).
**For a driving school running 14 students through one week
on the same configured region:** ~$10 (the topic tree is
shared; only live sessions multiply).
- **Image storage:** none. Handbook PDFs are fetched at ingest
and only the topic tree is persisted. If the deployer chooses
to cache PDFs in Firebase Storage standard tier ~$0.026/GB/
month, a typical 2 MB handbook costs ~$0.0001/month.
## 7. Design language
- **Mood:** A study companion that respects the learner's time.
Not a flash-card app. Not a gamified study platform. The desk
lamp on at 10 pm, the test is Tuesday, the phone propped against
the textbook. A small, focused tool that disappears into the
rehearsal.
- **Typography:** A quiet humanist sans for the body and chrome
(Inter or Source Sans 3). A monospace accent (JetBrains Mono or
IBM Plex Mono) reserved for handbook section citations —
"FRSC HC §4.3", "DVSA HC Rule 126", "RTO Karnataka Ch. 7" — so
they read as the artefact references they are. The live-quiz
question text is set in slightly larger humanist sans, with a
soft drop-shadow that anchors it on the screen for the learner
glancing up between answers.
- **Palette:** A near-white #FAFAF7 background — the desk light at
night, not a screen at noon. Deep graphite #1C1D22 for body
text. A muted forest green #3A6B4F for correct/confirm states
(never the panicked SaaS-green). A soft brick #B85A4A used only
for the miss log (never for general warnings). A warm sand
#E9DFC9 for the cheat-sheet background, so a printed page reads
like notebook paper. A quiet sky #4B6C8A for the cite-monospace
text. Borrowed from a leather notebook, not from a quiz-app
design system.
- **Imagery:** Almost none. The product is voice and text. The
welcome image is photographed-looking — a desk corner at
evening. The empty-state illustration on the topic-tree home is
a single small line drawing of an open notebook page with a
ruled margin. No road signs as decoration (they belong to the
handbook, not to the chrome).
- **Hand-feel touches:** The mic button has a barely-visible inner
ring that pulses gently when the tutor is listening, jumps to a
steady glow when speaking, and falls quiet when paused. The
miss-log entries have a soft top border like notebook lines.
The night-before mode's home button has a subtle warm glow that
fades on tap.
- **Spacing:** consistent 4-px base. Generous whitespace — voice-
led UI needs breath.
- **Radius:** consistent token set (e.g. 8 / 14 / 22 px). The mic
button is fully round; topic chips are 14; the welcome card is
22.
- **Shadows:** subtle, layered, warm-tinted. Avoid heavy drop-
shadows.
- **Motion:** purposeful — entrance fades, pace-dial transitions,
the soft mic-ring pulse. Respect `prefers-reduced-motion`. No
bouncing splash animations. No theatrical hero animations. The
mic-ring pulse is the canonical reduced-motion check — verify it
stays static (just a colour change) when the preference is set.
- **States:** every interactive element has hover, focus, active,
disabled. Loading uses skeletons not spinners. Empty states have
helpful next-action guidance ("Pick your country to load your
handbook").
## 8. Content generation rules
- Write **realistic, specific copy**. NO Lorem Ipsum. NO generic
placeholders like 'Your tagline here'.
- Invent plausible regions, dates, citation labels, sample
questions, miss reasons that fit the domain (use the seed content
in section 8a as a starting point). When inventing, lean on real
national handbook structures — FRSC chapters and section numbers,
DVSA Highway Code rule numbers, RTO state-manual chapters,
SEMOVI articles — but never claim that a fictional citation is
a real handbook reference.
- Tone: warm, direct, free of corporate language. This template is
for a person studying for a real test, not for a study-app
brand.
- Headlines: punchy and concrete. No 'Empower your X' filler. No
'Revolutionize'. No 'Seamless'. No 'Crush the test'.
- Body copy: short paragraphs (2-4 sentences). Use lists where
appropriate.
- Plain language. Avoid jargon — except where the user already
speaks the jargon (the learner studying for HMV wants to see
"HMV Cat C+E", not "heavy vehicle commercial").
- Where the app outputs AI-generated content (a quiz question, a
miss reason), never label it as "AI says" — let it speak
naturally. Use small uncertainty cues only where epistemic
honesty requires them (a question generated from a handbook
section the long-context call flagged as ambiguous shows a small
"(i) source" link that opens the handbook excerpt).
## 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 regions (sidebar / picker defaults):**
- "Nigeria → Lagos State → Category B (car), FRSC, 2024 edition,
English" — the universal default seed
- "India → Karnataka → LMV, RTO Karnataka, 2024 edition, English
/ Kannada" — covers state-specific clauses on yellow-line
discipline and stop-marker rules
- "Mexico → Mexico City → Cat A (motorcycle), SEMOVI Reglamento
2024" — covers Mexico City's specific reglamento articles
- "Philippines → National → Cat A1 (motorcycle), LTO Land
Transportation and Traffic Code, 2024 edition, English /
Tagalog" — covers LTO's specifically Philippine rules on rider
gear, swerving, and national-highway overtaking
- "United Kingdom → England & Wales → Category B (car), DVSA
Highway Code, 2024 edition, English" — covers smart motorway,
zebra/pelican/puffin/toucan crossings
- "South Africa → National → Category B (car), K53 manual, 2024
edition, English" — covers the K53 yard test and observation
protocol
- "United States → California → Class C (car), CA DMV Driver
Handbook, 2024 edition, English / Spanish" — covers California-
specific right-of-way and freeway entry
- "Brazil → São Paulo → Categoria B (car), DETRAN-SP, 2024 edition,
Portuguese" — covers BR-specific signs and CTB articles
**Sample TopicTree (for the default region: Nigeria FRSC, Lagos
State, Cat B):**
- `stopping-distances` — FRSC HC §4.3, pp. 47-52 — "Total stopping
distance at 50 km/h on dry asphalt: 25 m; on wet asphalt: 36 m."
- `right-of-way-roundabouts` — FRSC HC §6.2, pp. 81-85 — "Give way
to traffic already in the roundabout; signal exit before the
exit before yours."
- `road-signs-mandatory` — FRSC HC Ch. 3, pp. 28-44 — full plate
set covered.
- `road-signs-warning` — FRSC HC Ch. 3, pp. 45-63
- `pedestrian-crossings` — FRSC HC §5.4, pp. 67-70
- `vehicle-documentation` — FRSC HC §2.1, pp. 12-18
- `overtaking-rules` — FRSC HC §7.1, pp. 90-96
- `drink-and-drug-driving` — FRSC HC §9.3, pp. 115-119
- `night-driving` — FRSC HC §8.2, pp. 102-107
- `wet-and-harmattan-conditions` — FRSC HC §8.4, pp. 109-113 —
"Harmattan reduces visibility; reduce speed; use dipped
headlights from 6 pm to 6 am or whenever visibility is below
50 m."
- `seat-belts-and-helmets` — FRSC HC §2.3, pp. 21-24
- `vehicle-loading-and-passenger-limits` — FRSC HC §10.1,
pp. 122-127
- `commercial-vs-private-vehicle-distinctions` — FRSC HC §1.4,
pp. 8-11
- `motorcycle-rider-protective-gear` — FRSC HC §11.2, pp. 134-139
- `accident-response-and-reporting` — FRSC HC §12.1, pp. 144-149
- `licence-categories-overview` — FRSC HC §1.2, pp. 4-7
- (And ~12 more topics drawn from the FRSC HC structure.)
**Sample live-quiz transcript (this is what the demo should show):**
- Tutor: "Ready? First one — at fifty kilometres an hour, on a wet
asphalt road, what total stopping distance does the FRSC code
want you to allow?"
- Learner: "Thirty metres?"
- Tutor: "Close — thirty is what you'd need on dry asphalt at that
speed. On wet asphalt the code wants thirty-six metres. The
thinking distance stays the same, around fifteen metres at fifty;
the braking distance is what doubles in the wet. So thinking
fifteen, braking twenty-one, total thirty-six. FRSC section four
point three, page forty-seven. Want to try sixty?"
**Sample Miss entry generated by that turn:**
- `topic_id`: "stopping-distances"
- `question_text`: "At fifty kilometres an hour on wet asphalt, what
total stopping distance does the FRSC code want?"
- `user_answer_verbatim`: "thirty metres"
- `correct_answer`: "36 metres"
- `smallest_reason`: "under-estimated braking distance in rain by
6 m; thinking distance was right"
- `handbook_citation.section_label`: "FRSC HC §4.3"
- `handbook_citation.page_range`: "pp. 47-52"
- `handbook_citation.quoted_passage`: "Total stopping distance at
50 km/h on dry asphalt: 25 m; on wet asphalt: 36 m."
- `status`: "new"
**Sample night-before DrillPlan (for the default region the night
before the test):**
- `duration_minutes`: 15
- `weight_explanation`: "60% from your weak spots, 30% from topics
often missed on the FRSC Lagos test, 10% to end strong."
- topics planned: stopping-distances (4 min), right-of-way-
roundabouts (4 min), wet-and-harmattan-conditions (2 min),
road-signs-warning (3 min), seat-belts-and-helmets (2 min)
- followed by a one-page cheat sheet keyed to the same five topics
**Sample input artefacts (for the build to demonstrate):**
- A learner's score sheet (PDF) from a previous FRSC attempt, with
six questions wrong; the app maps each wrong answer to a topic
and adds them to the miss log.
- A Karnataka RTO sample-question booklet (PDF), publicly
published, ingested to add example_questions to the topic tree.
- A learner's voice memo "I want to drill stop signs and right of
way before bed" as a free-text intent feeding into a session-
planner call.
**Sample voice copy:**
- Onboarding: "Pick your country and category. We'll quiz you
live, from your handbook, and remember what you missed."
- Processing: "Reading the FRSC Highway Code…" / "Building your
topic tree…" / "Ready when you are."
- Empty topic tree: "No handbook loaded yet. Pick your country and
category to start."
- Error (couldn't find handbook URL): "We couldn't find an official
URL for this region. You can paste a handbook URL or upload a
PDF — we'll take it from there."
- Live tutor (correct): "That's right. FRSC §4.3, page 47-52.
Next: at sixty km/h, what's the wet stopping distance?"
- Live tutor (wrong): "Close — under-estimated the braking distance.
Want me to walk through it, or move on and revisit later?"
- Save confirmation: "Two topics added to your weak-spots list:
stopping distances, right of way at roundabouts."
- Night-before banner: "Test tomorrow at 8 am. Want a fifteen-
minute drill on the topics you've been missing?"
- Low confidence note: "I couldn't find this rule in the FRSC
handbook I have. Want to mark it for the deployer to add?"
**Sample instructor-share email subject + body:**
- Subject: "Folake — your weak spots for Saturday's test"
- Body: "Hi Folake — based on this week's drills, three topics are
worth one more round before Saturday: stopping distances in
rain (FRSC §4.3), right of way at roundabouts (§6.2), and
warning-sign colours (Ch. 3). Tap to open the read-only view."
[Open weak-spots view]
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking shot of
a teenager at a bedroom desk in the evening with a phone propped
against a textbook, a half-drunk mug, the corner of a learner's
permit poking out from under a notebook, warm desk-lamp light.
Generate via Nano Banana 2 with a prompt emphasising "wooden
desk, warm desk-lamp light, hand of a teenager, late evening,
gentle out-of-focus tea mug, real textbook paper, soft shadow
under the phone, no logos visible". Vary the teenager across
Nigerian, Indian, Mexican, and East-London framings in the
rotating hero set; never centre one nationality.
- **App icon / wordmark:** Set in the humanist sans. A small,
filled-circle mic glyph beside the wordmark. No road signs.
- **Empty-state illustration:** A simple line drawing of an open
notebook with a ruled margin and a single mic icon resting on
the page. Hand-drawn aesthetic, not a flat icon.
- **Demo region card art:** Generated per the prompts above —
Nano Banana 2 prompts that specifically request "small desk
scene, photographed feel, soft afternoon window light, no people
in frame". Each region's card art shows a recognisable cultural
detail (a paper LASRRA card corner for Lagos; a Karnataka RTO
appointment-slip corner; a SEMOVI test-day appointment QR; an
LTO appointment slip for Manila).
- **Road-sign assets:** loaded from the configured handbook's own
publicly-published sign plates where the handbook publishes
them; otherwise rendered as a small monochrome SVG with the
handbook's verbatim caption. NEVER decorative — only in topic
detail views and miss-log explanations.
- **Stock fallbacks:** If image generation fails, fall back to
the photographed sample desk scene from
`/public/samples/sample-desk.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"). The region picker validates that a handbook URL
is reachable before allowing "Load my handbook".
- Loading states use skeletons that match the eventual layout, not
spinners. The handbook-loading screen is the one place a multi-
step honest progress sequence is shown (Finding URL → Reading
pages → Building topic tree).
- Empty states explain the next action with a button whose label
fits THIS app's domain: "Pick your country", "Try the sample
quiz", "Drill your first topic" — never a generic "Add your
first item".
- Smooth scroll for in-page anchors.
- Live-session UI streams the tutor's transcript token-by-token as
audio plays, with the spoken word highlighted in the transcript
for accessibility.
- If a Live session call fails (network drop), the session pauses
with a clear "Reconnecting — your progress is saved" message and
resumes from the next question on recovery. The miss log is
flushed to Firestore at every turn so nothing is lost.
- Low-confidence quiz questions (where the topic-tree call flagged
ambiguity in the handbook excerpt) show a small "(i) source"
link beside the question that opens the verbatim handbook
excerpt in a side panel.
- The pace dial transition between slow / normal / fast is
immediate; respect `prefers-reduced-motion` by changing the dial
state without animation.
## 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.
- **Live API session:** opened from the client via a short-lived
server-issued token; the actual session brokering happens via
the server-side Cloud Run function so the API key never reaches
the client.
- **Model selection:** explicitly pin `gemini-3.5-flash` for topic-tree
extraction, per-question authoring, and night-before drill plan;
`gemini-3.5-flash` for grounded URL search and miss-log
structuring; `gemini-3.5-flash` (Live API variant) for the live
tutor; `gemini-3.1-flash-tts-preview` for the cheat-sheet TTS.
Set `thinkingLevel` explicitly per call.
- **Database:** Firestore (auto-provisioned by AI Studio Build).
Show the seed region on first launch.
- **Auth:** Firebase Auth — Google sign-in by default; Apple
sign-in next to it; magic-link email as fallback. Apple sign-in
and magic-link both require deployer configuration (see 4b).
- **Storage:** Firebase Storage (deployer-enabled) for cached
handbook PDFs only if the deployer chooses; otherwise the
handbook is fetched at ingest time and discarded after the
topic-tree call.
- **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px
(iPad), 1024 px, 1440 px+. The live-quiz UI must remain usable
one-handed at 375 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
miss log and topic-mastery views.
- Optimistic UI on writes; reconcile on response.
- Microphone access via the standard Web Audio API with explicit
user-gesture-gated permission; fall back to a "type your answer"
mode if mic permission is denied or the device has no mic.
- **iOS Safari gotchas (graceful degradation):** the Live API tutor
must survive audio-session interruption (incoming call, Siri,
alarm) — listen for `MediaStreamTrack.onmute` and pause; resume on
`onunmute`. Mic permission does NOT persist across reloads on iOS
— re-request on session start. Backgrounded Safari tabs throttle
WebSocket and kill `getUserMedia` — combine `visibilitychange`
with a screen Wake Lock during a session, and silently swap to
"type your answer" mode if the session can't survive the
background pause. PCM streaming via `AudioWorklet` (Safari
`MediaRecorder` is AAC-only).
## 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. Validate the warm sand cheat-sheet background
against the graphite body text; if contrast is borderline at
certain print preview settings, darken the body to #15161A.
- All images have meaningful `alt` text. Road-sign assets carry
the handbook's verbatim caption as `alt` ("warning sign: cattle
crossing — FRSC HC Ch. 3").
- Form fields have associated `