================ 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.
---
# Code Read-Along
## 1. Project
**Code Read-Along** is a guided tour through an unfamiliar codebase for the
person who just cloned it for the first time. The user opens any file in
the repository — `src/index.ts`, `app/main.py`, `lib/auth/session.go` —
and a live voice walks them through it line by line: what this file is
for, who calls it, what it imports, why the names are the way they are,
which functions matter most, which are dead. The user can interrupt at
any time and ask "what is `ctx.req.locals.user` here?" — the voice
answers from the actual repository, citing the exact files and lines
where that field is defined and where it gets populated. The
explanation is always grounded; the app never invents code that isn't
in the repo, never names a function that doesn't exist, never claims
"the convention here is X" without pointing to the file that
demonstrates it.
This is the kind of app a Filipino bootcamp graduate in Manila opens on
her first morning at her new remote job, when her senior engineer drops
a Slack message that reads "welcome — pull the repo, get it running by
Friday, ping me if you're stuck", and the repo turns out to be 240,000
lines across 1,400 files in a stack she has used for exactly eight
weeks. It is also the kind of app a Lagos-based self-taught engineer
opens when he finally gets contributor access to the Django payments
library he's been depending on for three years, and decides he's
going to read it — really read it, the way a piano student reads a
score — instead of grepping for the one function he needs to monkey-patch.
Same shape of moment, different city, different stack.
The single demo that proves the magic: a bootcamp learner opens
`src/index.ts` in the sample Next.js commerce template the app ships
with. She taps the file. In under three seconds, a live voice begins:
"This is the entry point for the App Router. It re-exports the root
layout from `app/layout.tsx` — that's the file two lines down at the
top of the import block — and it bootstraps the auth middleware at
line 14. We'll come back to that middleware in a minute; for now,
notice that nothing here mounts a React tree directly. That happens in
`app/layout.tsx` instead. Want me to open that next, or do you want to
finish reading this file first?" Every claim — "App Router", "line
14", "nothing here mounts a React tree" — links directly to the
relevant span in the file view. She taps the function `getSession` in
line 14 and the voice continues: "`getSession` is defined in
`lib/auth/session.ts` at line 22, and it's called from four other
places. The most important one is `app/checkout/page.tsx` — that's
where the cart screen checks who you are before it shows your address.
Want me to read `session.ts` next?" The voice never narrates a function
that doesn't exist. The voice never says "this is a classic pattern in
React" without showing the file where this codebase, specifically, does
the pattern.
And in the harder cases — legacy monorepos with twelve years of
accreted abstraction, polyglot repos mixing Rust and Python and
TypeScript across a shared protobuf boundary, security-critical libraries
where one wrong reading of `unsafe_eval_user_input()` could ship a
vulnerability — the app refuses to bluff. If the voice cannot resolve a
symbol with confidence, it says so out loud ("I'm not sure where
`legacy_session_v2` is defined — I see four places it might be
exported from. Want to look at all four, or pick the most likely?")
and surfaces the candidates with citations, never a single confident
hallucination. The voice never says "you can probably ignore this" about
code in a security path. The voice always names the file and the line
it is quoting.
**Tagline:** _Read your first big codebase the way a senior engineer reads it — file by file, with a voice in your ear that never makes up the codebase._
## 2. Target audience
- Bootcamp graduates in their first three months on the job, cloning the company monorepo and trying not to ask the same question twice in the same standup
- Self-taught engineers reading their first large open-source codebase — Django, React, Rails, Postgres, Linux, Postgres extensions, Bevy, Helix, Zed
- Career changers six months into a CS degree, opening their first real repo after a semester of toy assignments
- Interns at their summer internship, given an issue tagged "good-first-issue" in a 400-file service they have not yet read
- Senior engineers joining a new company who refuse to be the one who asks "wait, where is auth handled?" in week six
- Open-source maintainers onboarding new contributors who need to read enough of the repo to send a useful PR
- Graduate CS students reading the reference implementation of a paper they're reproducing
- Reverse engineers and security researchers reading a closed-source binary's decompiled output alongside any leaked or open-source historical version
- Engineering managers returning to code after two years of management who need to re-read their team's main service before code-reviewing again
## 3. Core value propositions
Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app.
- **Grounded, always, no exceptions** — every claim the voice makes is anchored to an exact file path and line range in the repository the user opened. The UI shows the citation as a chip next to each spoken sentence ("`lib/auth/session.ts:22-48`"). Tap the chip; the file view scrolls to that exact line. If the voice can't ground a claim, the voice doesn't make the claim.
- **Reads the whole repository, not just the open file** — Gemini 3.5 Flash's 1M-token context window lets the app load a sampled, structured representation of the entire repo before answering. The voice knows that `getSession` is called from four places because it has seen all four files, not because it guessed from the name.
- **Live, not transcript** — the voice arrives via the Live API. The learner can interrupt mid-sentence: "wait, what's `ctx`?" The voice pauses, answers, and resumes. No "submit message and wait" friction. Reading code is conversational and the tool is conversational.
- **Honest about uncertainty** — if a symbol is dynamically resolved, if a file is generated, if a name shadows another name two scopes up, the voice says so. The voice would rather say "I'm not certain where this is bound at runtime — let me show you the three places it might be" than guess.
- **Knows the naming conventions of this repo, not of "React in general"** — the structured notes file the app generates lists the conventions this codebase uses (the prefix `use*` for hooks, the suffix `*Service` for backend classes, the `_internal` underscore for non-public helpers), each backed by a citation showing the convention in action. No invented conventions, no carry-over from the model's training data.
- **Speaks the user's language** — the voice can narrate the same codebase in English, Tagalog, Vietnamese, Mandarin (simplified and traditional), Cantonese, Korean, Hindi, Tamil, Urdu, Bengali, Punjabi, Spanish, Portuguese (Brazilian and European), French, German, Polish, Amharic, Swahili, Farsi, or Khmer. Function and variable names stay in their original (usually English) form; the explanation around them is the learner's first language.
- **Stays inside the file the user is reading** — no popups, no "let me show you a tutorial". The voice teaches the codebase that is on the screen. If the learner wants to know how React's `useState` works in general, the voice says "that's a question about React, not about this repo — here's the React docs link", and stops.
- **Saves what it reads** — every walkthrough produces a per-file note: a one-paragraph summary, a list of public exports with one-line explanations, a list of internal helpers, the file's role in the larger module. The notes are markdown and live in `.code-read-along/notes/` inside the repo, so they survive a checkout switch and can be checked in (or gitignored).
- **Works on private repos** — the user pastes a local path or connects a private GitHub repo via OAuth. Source never leaves the user's tenant; the Gemini API is called on the paid tier where Google does not use content for model training, per the Gemini API Additional Terms.
## 4. Features to build
- File-tree navigator on the left, file-content viewer in the middle, live voice + transcript panel on the right (desktop); collapsible to single-pane on mobile
- "Read this file" button on every file — the load-bearing primary action, available from the tree, from the file view, and from the keyboard shortcut `R`
- Live voice walkthrough via Gemini Live API — natural speech, interruptible, with citations rendered in real time as the voice speaks
- Transcript panel synced to the voice, with each utterance carrying a citation chip linking to the source file + line range
- "Why does this exist?" — tap any function, class, or symbol; the voice explains what calls it, what it calls, why it's named what it's named — grounded in the repo, not in the model's prior
- "What calls this?" — reverse-lookup of any function or symbol, with the call sites enumerated and a single-tap to read any of them
- "Skip dead code" toggle — if a function has zero call sites in the repo and zero references in tests, the voice optionally elides it ("there are four exported helpers, but two of them are unused; want me to skip those?")
- Structured per-file note generated after every walkthrough — one-paragraph summary, public exports, internal helpers, inferred role in the module, links to the most-related files
- Repo-wide overview generated on first import — module map, conventions inventory, glossary of one-line definitions for the top 100 most-used custom names in the repo
- Conventions inventory (one of the hero artefacts) — every naming convention, every folder-structure convention, every error-handling convention, each with a citation
- Glossary view — the 100 most-mentioned custom names in the repo, each with a one-line definition and a "where defined" link. Generated once on import, refreshed on demand
- "Read me into the codebase" — a guided 20-minute tour the user starts on first open; the voice picks the 5–7 files a senior engineer would read first and walks the user through them in order
- Notes export — `.code-read-along/notes/` directory checked into the repo so a team can collaborate on shared notes; per-user "scratch" notes stay local
- Voice locale picker — 20+ languages with native voices, set globally and per-file overridable
- Search across notes — "where did the voice explain auth?" — semantic + structured across the user's generated notes
- "Refresh notes after this PR" — when the user pulls new commits, the app diffs the changed files, highlights what's new, and offers to re-narrate only the changed sections
- Offline-friendly transcripts — every walkthrough's transcript is saved as plain markdown so the user can read it on a plane
- Keyboard-first navigation: `J`/`K` to move through files, `R` to read the open file, `?` to ask a grounded question, `N` to open the latest note, `G G` to jump to file-tree root
## 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)
- **Long-context code understanding (Gemini 3.5 Flash, 1M tokens)** — the repo-overview call and the per-file walkthrough call both load a structured representation of the entire codebase. For repos under ~600k tokens of trimmed source (typical for a 1,500-file TypeScript or Python project after de-duping, stripping `node_modules`, `dist`, generated code, and large vendored assets), the whole repo fits in one window. For larger repos, chunk per-module and ladder up. **Guardrail: a 1.2M-token repo will exceed the 1M ceiling.** Detect at import time; surface the chunking strategy and ask the user to confirm which subtrees matter.
- **Live API (Gemini 3.5 Flash or Gemini 3.5 Flash)** — bidirectional voice + text streaming for the walkthrough. The voice is interruptible. The session carries the per-file context, the conventions inventory, and the glossary. **Important**: the Live API session must NOT carry the entire repo every turn — only the open file plus the inventory plus the relevant call-sites the app has pre-resolved. Loading the whole repo per turn burns tokens and slows latency.
- **Structured output (JSON Schema)** — every non-voice call (the file-summary call, the convention-inventory call, the glossary call, the call-graph call) returns JSON conforming to the `FileNote`, `Convention`, `GlossaryEntry`, and `CallSite` schemas in the prompt. The schemas are seeded verbatim in the system instruction.
- **Multimodal (Gemini 3.5 Flash)** — for screenshot-based debugging: if the user is staring at a stack trace, a logged-out screenshot, or a `git blame` view in their IDE, they can drag-and-drop the screenshot and ask "what is `LayoutEffectImpl` here?" The voice reads the image and answers grounded in the user's repo, not in screenshot-OCR alone.
- **Multilingual TTS (Gemini 2.5 Flash Preview TTS)** — narrates the walkthrough in the user's chosen locale. Function names, file paths, and code snippets read in their original (usually English) form; the explanatory prose reads in the chosen locale. Pronunciation follows the chosen voice's native locale (Polish ł, Tagalog ng, Vietnamese tones).
- **Thinking levels** — `high` for the repo-overview call on first import (it has to plan how to read 1,400 files). `medium` for per-file deep walkthroughs and for "what calls this?" reverse lookups. `low` for the live voice's quick answers to grounded questions during a session. The Live API call does NOT take a thinkingLevel field; thinking is configured upstream when constructing the live session.
- **Search grounding** — used SPARINGLY, only on the "this question is about the language/framework in general, not about this repo" path. When the user asks "wait, what does Python's `__init_subclass__` even do?", the app routes to a separate Gemini 3.5 Flash call with `google_search` grounding, returns the canonical doc link, and does NOT mix general-knowledge results into the in-repo walkthrough.
### Backend services
- **Auth — Required.** Firebase Auth with Google sign-in (auto-provisioned by AI Studio Build) and GitHub OAuth (user-configured: GitHub OAuth App ID + secret + callback URL set in the Firebase console and the GitHub developer settings). GitHub OAuth is needed so the app can read the user's private repos via the GitHub API. **Apple sign-in is optional but user-configured**: requires an Apple Developer account, Service ID, Key ID, and private key wired into the Firebase Auth console. **Magic-link email** (used to invite teammates to a shared repo workspace) also requires the sender domain to be authorised in Firebase Auth.
- **Database — Required.** Firestore for `users`, `repos`, `file_notes`, `conventions`, `glossary_entries`, `walkthrough_sessions`, `pinned_questions`, `workspace_members`.
- **File storage — Required.** Firebase Storage for: (a) per-repo trimmed-source snapshots (de-noised: no `node_modules`, no `dist`, no minified vendored code) that we re-use as the Gemini context payload across calls; (b) per-user notes archives so they survive across machines. **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 repo import. Pre-signed URLs only; source code is never publicly addressable.
- **Email — Required (transactional).** Workspace invitations via magic-link email; "your repo overview is ready" notification when the first-import long-context call finishes.
- **Server-side code-graph computation — Required.** A Cloud Run worker that, on every repo import, runs a lightweight static analysis (tree-sitter for parsing, ripgrep for fast text indexing) to produce: file list, import graph, exported symbols per file, rough call-site lookup. This index is consulted on every Gemini call so the model gets a structured anchor and is less likely to hallucinate.
- **Payments — Not needed for v1.** Free for personal use up to 3 repos. A "team workspace" tier (unlimited repos, shared notes, SSO, audit log) is a future v2 — pipe to Stripe Checkout when added.
- **External APIs:** Gemini API for all intelligence; GitHub API for private-repo access; optional GitLab API and Bitbucket API for those code-hosts; tree-sitter + ripgrep as in-process libraries on the Cloud Run worker.
**Environment variables:** every secret (Gemini API key, Firebase service-account JSON, GitHub OAuth client secret, Stripe key if added) 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 workspace' inside the UI · explicit opt-in for any analytics · the user's source code is 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) · trimmed-source snapshots stored in Firebase Storage are encrypted at rest and scoped to the workspace owner.
**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. **Every spoken claim cites a file + line range** — citation discipline is enforced by the schema. A `WalkthroughUtterance` with no citation is invalid and dropped.
8. **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;
}
```
9. **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 |
|------|-------|---------------|---------------|
| Repo overview on import (module map, conventions, top-100 glossary) | `gemini-3.5-flash` | high | (none) — long-context over the trimmed repo |
| Per-file walkthrough plan (which spans to read, in what order) | `gemini-3.5-flash` | medium | (none) |
| Live voice walkthrough (interruptible) | `gemini-3.5-flash` (Live API) | n/a (configured on session) | (none) |
| "What calls this?" reverse lookup | `gemini-3.5-flash` | low | (none) — uses pre-computed call-site index |
| Generate per-file note (`FileNote` JSON) | `gemini-3.5-flash` | low | (none) |
| Convention inventory call | `gemini-3.5-flash` | medium | (none) |
| Glossary entry generation (single symbol) | `gemini-3.5-flash` | low | (none) |
| Screenshot debugging (stack trace, error overlay) | `gemini-3.5-flash` | medium | (none) |
| "This is about the language in general" external answer | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) |
*Note for builders:* on TTS, image-generation, and Live API calls, omit `thinkingConfig` entirely — the field is not supported on those models, and Live API thinking is configured at session-creation time. 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 Citation = z.object({
file_path: z.string(), // repo-relative, e.g. "src/lib/auth/session.ts"
line_start: z.number().min(1),
line_end: z.number().min(1),
symbol_name: z.string().nullable(), // "getSession" if the citation is symbol-scoped
});
const CallSite = z.object({
callee_symbol: z.string(), // "getSession"
caller_symbol: z.string().nullable(), // "CheckoutPage" — null if module-level
caller_citation: Citation,
is_test: z.boolean(), // call site lives in a test file
is_likely_dead: z.boolean(), // no inbound calls, no test coverage
});
const Convention = z.object({
name: z.string(), // "hooks prefixed with use*"
rule_description: z.string(), // one sentence
example_citations: z.array(Citation).min(1), // at least one place this convention appears
counter_examples: z.array(Citation), // places that break the convention, if any
confidence: z.number().min(0).max(1),
});
const GlossaryEntry = z.object({
symbol_name: z.string(), // "Workspace", "useSession", "_internal_resolve"
symbol_kind: z.enum([
"type", "interface", "class", "function", "hook",
"constant", "variable", "module", "decorator", "macro", "trait", "other",
]),
one_line_definition: z.string(), // grounded in this repo's usage
defined_at: Citation,
used_in_count: z.number().min(0),
most_important_usage: Citation.nullable(),
});
const FileNote = z.object({
file_path: z.string(),
artefact_type: z.enum([
"entry_point", "module_index", "library_internal",
"test", "config", "migration", "generated",
"type_definitions", "documentation_markdown", "other",
]),
one_paragraph_summary: z.string(), // 60–120 words
public_exports: z.array(z.object({
name: z.string(),
one_line_definition: z.string(),
defined_at: Citation,
})),
internal_helpers: z.array(z.object({
name: z.string(),
one_line_definition: z.string(),
defined_at: Citation,
is_likely_dead: z.boolean(),
})),
inferred_role_in_module: z.string(), // one sentence, grounded in the repo
most_related_files: z.array(z.object({
file_path: z.string(),
relationship: z.enum([
"imports", "imported_by", "tests", "tested_by",
"extends_type", "implements_interface", "configured_by", "other",
]),
})),
reading_difficulty: z.enum(["easy", "moderate", "hard", "expert"]),
flagged_for_user_review: z.array(z.object({
field_path: z.string(), // "public_exports[2].one_line_definition"
reason: z.string(),
})),
});
const WalkthroughUtterance = z.object({
utterance_id: z.string(), // uuid; per spoken sentence
spoken_text: z.string(), // the prose said aloud, in the user's locale
spoken_text_locale: z.string(), // BCP-47, e.g. "tl-PH"
primary_citation: Citation, // every utterance is grounded
supporting_citations: z.array(Citation), // up to 3 additional supporting lines
utterance_kind: z.enum([
"what_this_file_is",
"what_this_function_does",
"who_calls_this",
"naming_convention_note",
"uncertainty_disclosure",
"answering_user_question",
"suggesting_next_file",
"external_knowledge_disclaimer",
]),
pause_after_ms: z.number().min(0).max(3000), // breath before next utterance
});
const WalkthroughSession = z.object({
session_id: z.string(),
repo_id: z.string(),
user_locale: z.string(), // BCP-47
current_file_path: z.string(),
utterances: z.array(WalkthroughUtterance),
user_questions: z.array(z.object({
question_text: z.string(),
asked_at_utterance_id: z.string().nullable(), // null if asked at start
answer_utterance_ids: z.array(z.string()),
})),
session_ended_reason: z.enum([
"user_closed", "file_finished", "user_switched_file",
"live_connection_dropped", "error",
]).nullable(),
});
const RepoOverview = z.object({
repo_id: z.string(),
total_files: z.number().min(0),
primary_languages: z.array(z.string()), // ["TypeScript", "TSX", "MDX"]
entry_points: z.array(Citation), // files a new reader should open first
module_map: z.array(z.object({
module_path: z.string(), // "src/lib/auth"
one_line_purpose: z.string(),
representative_file: Citation,
})),
conventions: z.array(Convention),
glossary_top_100: z.array(GlossaryEntry),
reading_difficulty_overall: z.enum(["easy", "moderate", "hard", "expert"]),
notes_for_first_time_reader: z.string(), // 80–150 words
reading_confidence: z.number().min(0).max(1),
});
type Citation = z.infer;
type FileNote = z.infer;
type WalkthroughUtterance = z.infer;
type RepoOverview = z.infer;
```
### Common failure modes (and how to avoid them)
- Voice claims a function exists when it doesn't — the single most damaging failure. Mitigation: enforce the `Citation` requirement on every `WalkthroughUtterance` server-side. If the cited file/line doesn't actually contain the cited symbol after parse-time verification, drop the utterance and surface "I'm not sure about this — let me re-read" instead.
- Model picks `gemini-3.5-flash` for the repo-overview call to save quota — pin `gemini-3.5-flash` explicitly. Flash hallucinates module purposes on large repos and misses cross-module conventions.
- Live API session loads the entire repo every turn — burns tokens, balloons latency, hits context-window limits. Mitigation: at session start, load the file-summary index + the conventions inventory + the open file. On each turn, load only the additional files the user explicitly navigated to.
- Voice reads code aloud character-by-character (including punctuation: "open paren x comma y close paren") — robotic and useless. Mitigation: in the system instruction, instruct the model to describe what the code does, never to read its literal characters. "This function takes a request and a session" — not "function space get session open paren request comma session close paren".
- Voice carries naming conventions from the model's training data ("this is a classic React pattern") instead of from this codebase. Mitigation: the conventions inventory MUST cite real lines from THIS repo. The model is forbidden from naming a convention that has zero citations in `Convention.example_citations`.
- The voice keeps trying to teach the framework instead of reading the file — "let me tell you about how React Server Components work in general". Mitigation: hard rule in system instruction: "if the user has a question about the language or framework in general, say so and stop. Don't teach the framework."
- Dead code is read aloud with the same weight as live code — wastes time, demoralises the learner. Mitigation: pre-compute the call-site index server-side; pass `is_likely_dead` to the model; offer the user a "skip dead code" toggle that defaults on.
- Tagalog or Vietnamese chosen as locale, but the voice still reads file paths in English with the wrong tone — distracting. Mitigation: pin the TTS voice locale to the user's locale; the model is told to keep code identifiers in their original form but to deliver the surrounding prose in the user's first language. Pronunciation follows the voice's native locale.
- Live API session reconnects mid-walkthrough and the model forgets what file it was on. Mitigation: on every reconnection, replay the current file path + the last 10 utterance summaries as a "where we were" prefix.
- Two functions have the same name in different files (`session.ts` in `lib/auth` and `session.ts` in `lib/test-helpers`) and the voice cites the wrong one. Mitigation: the static-analysis worker produces a fully-qualified symbol table; the model is given `auth/session.ts:getSession` not bare `getSession`.
- Screenshots of paid customer data are submitted to the screenshot-debug call — privacy risk. Mitigation: surface a one-time warning before the screenshot feature is used the first time; offer a local-only OCR fallback for users on strict-privacy plans.
### Negative constraints (hard rules)
- Do NOT name a function, file, class, or symbol that does not exist in the repo. Every spoken name must be cited.
- Do NOT translate code identifiers. `getSession` stays `getSession` in every locale; the explanation around it is translated. File paths stay in their original form too.
- Do NOT teach the language or framework in general. If the user has a general-knowledge question, say so explicitly ("that's a React question, not a question about this repo") and stop, or route to the grounded external-knowledge call. Never blur the two contexts.
- Do NOT carry conventions from the model's training data. "This repo uses the `useFoo` hook convention" is only sayable if `Convention.example_citations` has at least one citation from this repo.
- Do NOT recommend the user "probably ignore" any code in an auth, payments, encryption, or permissions module. Skipping dead code is fine for utility helpers; never for security-critical surfaces.
- Do NOT read code aloud literally. Describe what it does. The user can read the literal characters on screen.
- Do NOT speculate about commit history, author intent, or "why the original author chose this". The voice reads the code that is there, not the imagined narrative around it. If `git blame` data is loaded, cite it; otherwise, don't.
- Do NOT use the user's source code 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, auto-share, or auto-PR notes. Notes are local to the user's workspace; sharing is explicit, per-workspace, per-teammate.
- Do NOT call the live voice an "AI tutor". It's a walkthrough voice. The user is the reader; the voice is the guide.
### 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: Repo overview on import
Model: `gemini-3.5-flash` · thinkingLevel: high · Tools: (none)
```
You are reading a complete software repository for the first time.
The reader who is about to use this overview is, by default, a
bootcamp graduate or self-taught engineer in their first three
months on the job. The repo may be in TypeScript, JavaScript, Python,
Go, Rust, Ruby, PHP, Java, Kotlin, Swift, C, C++, C#, Elixir, Clojure,
Haskell, Scala, Dart, or a polyglot mix. Treat each language with
respect; do not assume the reader knows it.
You receive a trimmed-source snapshot of the entire repo. The snapshot
has already been de-noised: no `node_modules`, no `dist`, no `.git`,
no generated artefacts, no minified vendored code, no lockfiles
longer than 200 lines, no media binaries. File paths are
repo-relative; line numbers are 1-indexed.
Your task: produce a `RepoOverview` JSON object matching the schema
below. Every field is typed; populate every field; leave no nulls
except where the schema explicitly allows.
Hard rules:
- `entry_points` must be real files in the repo. Common candidates:
`index.ts`, `main.py`, `app/main.go`, `cmd//main.go`,
`lib/.rb`, `src/lib.rs`, the top-level binary in
`Cargo.toml` `[bin]`. Cite the file + line.
- `module_map` partitions the source tree into 5–15 coherent
modules. Each module's `one_line_purpose` is grounded in what
the code in that module actually does, not in what its folder
name suggests. If the folder is `src/utils/` but the code is
actually three independent vertical features mis-named "utils",
say so.
- `conventions` lists every naming, structuring, or error-handling
pattern you can detect across at least three independent
citations in the repo. A "convention" requires three independent
examples; one-off uses are not conventions. List the
counter-examples too — patterns are sometimes inconsistent.
- `glossary_top_100` lists the 100 most-frequent custom symbols
(excluding language built-ins and third-party library names).
Rank by occurrence count. Each entry's `one_line_definition` is
grounded in this repo's usage, not in the symbol's general
meaning. `defined_at` must point to the canonical definition;
`most_important_usage` to a representative call site.
- `notes_for_first_time_reader` is 80–150 words and addresses
the specific reader: what to read first, what to defer, where the
surprises live, where the codebase is most opinionated, where it
diverges from defaults the reader might expect.
- `reading_difficulty_overall` is `easy` (≤20 files, single language,
obvious entry point), `moderate` (20–200 files, mostly one
language, some scaffolding), `hard` (200–2000 files, multiple
modules, polyglot), `expert` (>2000 files OR critical security
surface OR significant generated code OR mixed paradigms).
- `reading_confidence` reflects how well the trimmed snapshot
represented the repo. If significant subtrees were dropped (large
vendored monorepos, generated code, binary assets), confidence
drops accordingly.
- `flagged_for_user_review` names any field where confidence is
below 0.7 with a one-sentence reason.
Do NOT invent files, modules, conventions, or symbols. If you
cannot detect a convention with at least three citations, omit it.
Better an empty list than a fabricated entry.
Do NOT carry over conventions from the language or framework's
training-data prior. A pattern is only a convention IN THIS REPO if
it is demonstrated here.
No commentary. JSON only.
```
---
### Call: Per-file walkthrough plan
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You receive: (a) the open file's full source, with 1-indexed line
numbers; (b) the file's note (a `FileNote` object generated in a
prior call); (c) the conventions inventory; (d) the glossary; (e)
the user's locale (BCP-47, e.g. "tl-PH"); (f) the call-site index
for every symbol defined in the file.
Your task: produce a sequence of `WalkthroughUtterance` objects
that, read aloud in order, walk a first-time reader through this
file. Aim for 60–180 utterances; pause sparingly; let the user
breathe.
The utterance order should be: (1) what this file is and where it
fits in the repo; (2) the imports — name each one and where it
comes from; (3) the public exports, in source order; (4) the
private/internal helpers, in source order, optionally collapsed if
they are dead; (5) any meaningful comments by the original author
(quote them, don't paraphrase); (6) a closing utterance pointing
at the next 1–2 files a reader should open.
Hard rules:
- Every utterance MUST carry a primary_citation. No primary
citation, no utterance.
- `spoken_text` is in the user's locale. Code identifiers (function
names, type names, file paths, variable names) STAY in their
original form even inside a locale-translated sentence.
- Never read code character-by-character. "This function takes a
request and a session" is good; "function space get session open
paren request comma session close paren" is forbidden.
- For each call site of a defined function, mention how many other
places call it; if `is_likely_dead`, surface it as an
`uncertainty_disclosure` utterance ("nothing in the repo calls
this; possibly leftover or used by an external integration").
- For conventions, only cite a convention if it is in the
conventions inventory. Do NOT name a convention that isn't there.
- `external_knowledge_disclaimer` is the utterance kind used when
the reader's understanding requires general knowledge of the
language or framework. Emit one of these to signal "this is a
React Server Component pattern; if you want to learn what those
are in general, the React docs cover it; this file just uses one
here."
- Aim for `pause_after_ms` around 400–800 most of the time; longer
(1500–2500) at module-boundary transitions; near zero between
closely-coupled clauses.
- The closing utterance (`suggesting_next_file`) names 1–2 files
to read next, with one short reason per file.
Output the `utterances` array of a `WalkthroughSession` object.
No commentary outside the structured output.
```
---
### Call: Live voice walkthrough (Live API)
Model: `gemini-3.5-flash` (Live API) · thinkingLevel configured on session · Tools: (none)
```
You are the voice that walks a reader through a single file in
their codebase. The reader can interrupt you at any moment. Your
job is to read this file with them, the way a senior engineer
would read it sitting next to them at the desk.
You have, in your session context:
- The full source of the file currently open, with 1-indexed line
numbers.
- The `FileNote` for that file (your structured notes from the
prior call).
- The `conventions` array for this repo.
- The `glossary_top_100` for this repo.
- The call-site index for every symbol defined in the file.
- A short summary of the last 10 utterances if this is a
reconnection.
Voice rules:
- Warm, unhurried, like a senior who is genuinely glad to read
this file with the newcomer. Not lecturing. Not performative.
- Speak in the user's locale. Code identifiers stay in their
original form. File paths stay in their original form.
- Never read code aloud character-by-character. Describe what it
does. The reader can see the code on screen.
- Cite as you speak. Phrases like "at line 42 of
`lib/auth/session.ts`" are good and expected. The UI will
render the citation as a chip the reader can tap.
- If the reader asks a question, answer it grounded in the repo.
If you don't know, say so out loud, name what you'd need to
check, and offer to check it.
- If the reader's question is about the language or framework
in general (not about this repo), name that explicitly:
"That's a question about React in general, not about this
repo. The React docs cover that. Want me to come back to
reading the file?"
- If you cannot resolve a symbol with confidence, say so. Name
the candidates: "I see `getSession` defined in two places.
Probably the one in `lib/auth/session.ts`, but there's also
a test helper in `lib/test-helpers/session.ts`. Want me to
read both?"
- For dead code: "These three exports have no callers in the
repo. Probably leftover; want to skip them?"
- For security-critical files (auth, payments, encryption,
permissions): never offer to skip code. Read every function.
Flag uncertainty explicitly.
- When the reader switches files mid-conversation, end the
current walkthrough with a one-sentence transition ("OK,
switching to `app/checkout/page.tsx` — that's where the
cart screen lives") and begin the new file.
Negative rules:
- Never invent a function, file, class, or import.
- Never carry conventions from the language's general training
data. Only cite conventions present in the `conventions`
array.
- Never read code literally character-by-character.
- Never speculate about the original author's intent, commit
history, or "why they did it this way". Read the code that
is there.
- Never recommend skipping code in a security-critical module.
The session ends when the reader closes the file, switches to a
different file, or explicitly says "stop" / "that's enough" /
"thanks, I've got it". On end, emit a one-sentence wrap that names
what you read and what you didn't.
No on-screen text other than the spoken transcript. JSON only for
the utterance metadata.
```
---
### Call: "What calls this?" reverse lookup
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none, uses pre-computed call-site index)
```
You receive: (a) a target symbol (e.g. `getSession`); (b) the
file it's defined in and its definition's line range; (c) the
pre-computed call-site index (a list of every place in the repo
that references this symbol, with `caller_symbol`, `caller_citation`,
`is_test`, `is_likely_dead`).
Your task: return a short ranked summary, as a JSON array of
`CallSite` objects in usefulness-to-the-reader order. The first
entry should be the call site most important for understanding
the symbol's purpose (typically the canonical user-facing flow that
exercises it). Subsequent entries are secondary call sites; test
call sites and dead-likely sites are de-prioritised but included.
Hard rules:
- Do NOT invent call sites. The list you produce must be a strict
re-ordering of the input index, possibly with rationale strings,
but never additions or omissions.
- If two call sites are equally important, prefer the one in
application code over the one in a library, and the one in a
user-facing flow over the one in a backend cron.
Output JSON only. No commentary.
```
---
### Call: Generate per-file note
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive: (a) the full source of one file; (b) the file's role
in the module-map; (c) the repo's conventions inventory.
Your task: produce a `FileNote` JSON object matching the schema.
Hard rules:
- `one_paragraph_summary` is 60–120 words. Plain language. No
marketing tone. No "leverages", no "robust", no "elegant". State
what the file does, who calls it, and what it depends on.
- `public_exports` lists every named export from this file.
Default-exports also count; name them by inferred role
("default export: the page component"). `defined_at` cites the
line range of the definition.
- `internal_helpers` lists non-exported top-level functions,
classes, and constants that are used somewhere in the file.
`is_likely_dead` is true if a symbol is defined in this file
and not referenced anywhere in the repo per the call-site index.
- `inferred_role_in_module` is one sentence grounded in code, not
speculation.
- `most_related_files` lists at most 5 files. `relationship`
values come from the closed enum.
- `reading_difficulty` is `easy` (under 50 lines, no abstraction),
`moderate` (50–200 lines, standard patterns), `hard` (200+ lines
OR meta-programming OR generics-heavy), `expert` (any of: macros,
reflection, dynamic codegen, manual memory management, security-
critical control flow).
- `flagged_for_user_review` names any field where confidence is
below 0.7 with a one-sentence reason.
Do NOT invent exports, helpers, related files, or roles. Better an
empty array than a fabricated one.
JSON only. No commentary.
```
---
### Call: Convention inventory
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You read the entire trimmed-source repo and produce the
conventions array.
A convention is a naming, structuring, or behavioural pattern that
appears in at least THREE independent places in the repo, in code
that the author plausibly wrote rather than scaffolding boilerplate.
For each convention:
- `name`: short and concrete, e.g. "hooks prefixed with use*",
"services suffixed with *Service", "errors raised via
RaiseError() helper".
- `rule_description`: one sentence stating the rule.
- `example_citations`: at least three citations from independent
files. Cite the file, line range, and (where applicable) symbol.
- `counter_examples`: any places that break the convention,
cited. These help the reader understand the rule isn't absolute.
- `confidence`: how strongly the rule holds. >0.9 = effectively
universal in the repo; 0.7–0.9 = strong with occasional
exceptions; 0.5–0.7 = a tendency, not a rule; below 0.5 =
do not list.
Hard rules:
- Do NOT name a convention with fewer than three citations.
- Do NOT carry over conventions from training data. "React hooks
are prefixed with use*" is only a CONVENTION OF THIS REPO if
THIS REPO demonstrates it three times.
- Counter-examples ARE legitimate findings. List them.
JSON only. No commentary.
```
---
### Call: Glossary entry generation
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive: (a) one custom symbol from the repo (e.g.
`useSession`); (b) every place it is defined or referenced; (c)
the call-site index entry for it.
Your task: produce one `GlossaryEntry`.
Hard rules:
- `one_line_definition` is one sentence, grounded in how THIS
REPO uses the symbol. Not the general meaning; the local
meaning.
- `symbol_kind` from the closed enum.
- `defined_at` cites the canonical definition.
- `most_important_usage` cites the most user-facing or most
central call site (typically the one in application code or
the most-referenced one).
- `used_in_count` is the integer count from the call-site index.
Do NOT invent the definition. If the symbol's purpose is unclear
even after reading every reference, set `one_line_definition` to
"unclear from the repo; appears at :; called from
places" and let the user override.
JSON only. No commentary.
```
---
### Call: Screenshot debugging
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You receive: (a) one screenshot the user pasted (a stack trace,
an error overlay, a `git blame` view, an IDE problems panel);
(b) the user's currently-open file in the repo; (c) the
conventions inventory; (d) the glossary; (e) optionally, the
last 10 utterances if the user is mid-walkthrough.
Your task: ground the screenshot in the user's repo. Identify any
symbols visible in the screenshot that exist in the repo; cite
their definitions; answer the user's question.
Hard rules:
- If a symbol appears in the screenshot but does NOT exist in the
repo, say so. Do not pretend it does.
- If a stack-trace line points to a file/line that doesn't match
the current repo state (the user may be on a different branch
than the screenshot was taken from), say so.
- Stay grounded. If the screenshot contains a generic Python
exception type, name the type but route any general-language
questions to the grounded external-knowledge call. Don't teach
Python in the same breath as reading the user's code.
- Don't OCR-quote the screenshot verbatim. Describe what's visible
and answer the question.
Output a short structured response: { description: string,
relevant_repo_symbols: Citation[], answer: string }. JSON only.
```
---
### Call: External-knowledge answer (grounded search)
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding
```
The reader has asked a question about a language, framework, or
ecosystem (not about THIS repo). Answer the question concisely
and cite an authoritative source.
Examples of in-scope questions:
- "What does Python's `__init_subclass__` do?"
- "What is a React Server Component?"
- "How does Go's `context.Context` cancellation propagate?"
- "What is `async/await` in Rust?"
Hard rules:
- Use `google_search` grounding. Prefer the official docs of the
language/framework as the citation source.
- One short paragraph answer plus one citation URL.
- Do NOT reference the user's repo in this answer. The split is
intentional: in-repo answers come from the walkthrough call;
external answers come from here.
- If the question is ambiguous (could be about the repo, could be
general), default to assuming it's about the repo and route
back to the walkthrough.
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 citation URLs from the response's
`groundingMetadata.groundingChunks[].web.uri` — do NOT ask the
model to include URLs in the JSON body; it will hallucinate them.
No commentary outside the JSON.
```
## 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 Manila Friday.** A Filipino bootcamp grad in Manila is starting at a US remote-first startup on Monday. Her onboarding doc says "read `apps/web/` and `packages/shared/` before standup". She opens the app on a borrowed laptop with her chosen locale set to Tagalog. The voice walks her through the App Router entry point in warm, unhurried Tagalog; the function names stay `getSession`, `useCart`, `CheckoutPage` — the prose around them is hers. By Monday standup she can describe the auth flow well enough that her senior engineer says "ah, you already read it — nice".
- **The Lagos contributor.** A self-taught Nigerian engineer has used django-payments for three years and just got contributor access. He clones the repo and tells the app "I want to read this properly — start me at the top". The app produces a 20-minute guided tour beginning at `setup.py` and ending in `processors/dummy.py`, with the conventions inventory naming the four patterns he's been violating for three years in his own monkey-patches.
- **The CDMX career changer.** A career changer in Mexico City, six months into a CS degree, is interviewing for her first React role. She has been told to "read a real Next.js codebase" in preparation. She loads the Vercel commerce template into the app and asks the voice — in Spanish — to walk her through it from the top. The voice does. When she asks "what is a React Server Component?" the voice routes to the external-knowledge call, returns one short answer with a link to the React docs, and explicitly says "that's a React question, not a question about this repo — want me to come back to the file?"
- **The summer intern.** A CS junior interns at an enterprise B2B company. Their "good first issue" is in a 400-file Java service the team built over five years. The intern opens the repo, taps the issue's ticket id, and the app produces a guided tour of the four files the issue touches plus the three test files that exercise them. The voice flags two pieces of dead code in the path; the intern's first PR cleans them up alongside the bug fix.
- **The senior re-join.** A staff engineer joins a new infrastructure team after eight years at the last company. She opens the team's main service and asks for the conventions inventory before reading any code. The app produces a 22-item list: error-handling pattern (every fallible function returns `Result`, never throws), naming pattern (storage clients suffixed with `*Repo`, application services with `*Service`, REST handlers with `*Handler`), test layout (one test file per source file, sibling, never under a `__tests__` directory). She reads the inventory before reading any code; her first code review the next week is unmistakeable.
- **The Vietnamese open-source maintainer.** A maintainer in Hanoi has been receiving low-quality PRs from new contributors. He generates a contributor-onboarding walkthrough for the four most-touched files, exports it as a markdown tour, and links it in the repo's `CONTRIBUTING.md`. New contributors arrive having already heard the walkthrough; PR quality climbs.
- **The screenshot debug.** A developer is staring at a stack trace in their terminal, ten files from the open file. They drag the terminal screenshot into the app. The voice reads the trace, identifies the three symbols in it that are defined in the repo, cites the lines, and answers "the error is in `lib/auth/session.ts:48` — `getSession()` returns null because the JWT is expired; the test on line 51 doesn't handle the null case".
- **The polyglot monorepo.** A senior engineer joins a team running a Rust crypto core, a Go orchestration service, and a TypeScript dashboard, all in one monorepo with a shared protobuf boundary. The app loads each module separately (chunked by language), produces three module overviews, and lets the user toggle between language-specific voices when reading each subtree.
- **The security-critical read.** A new junior engineer is asked to add a feature to the payments service. The app's reading-difficulty signal flags the module as `expert` and turns OFF the "skip dead code" toggle by default with a one-line note: "this is a security-critical surface; we read every function even if it looks unused." The conventions inventory highlights the strict input-validation pattern at every entry point.
## 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 laptop open on a small kitchen table at evening: a worn copy of *The Elements of Computing Systems* face-down beside it, a half-drunk mug of coffee, the screen showing a file tree. One paragraph: "Code Read-Along walks you through your first big codebase the way a senior engineer reads it — one file at a time, with a voice in your ear that never makes up the codebase." Single Google sign-in button; GitHub sign-in next to it (primary CTA for the developer audience); Apple sign-in below. A small "Try with the sample repository" link → loads the demo repo in section 8a.
2. **Empty state — "Connect a repository".** Three big input methods: 🐙 Connect GitHub · 📁 Local folder · 🔗 Paste a public URL. A short explainer below each ("Best for your work and private repos", "Best if you're reading without uploading anywhere", "Best for any open-source repo on GitHub, GitLab, or Bitbucket").
3. **Repo overview — first-run.** After import, the app runs the repo-overview call (this takes 60–180 seconds depending on size; show an honest progress bar). The overview page shows: total files, primary languages, the module map as a clickable list, the conventions inventory, the top-100 glossary, the entry points (each tappable to start the walkthrough there), and the 80–150-word "notes for first-time reader".
4. **File-tree + reader (the home screen after overview).** A three-column layout on desktop, collapsible to single-pane on mobile. Left column: the file tree, with each file's `reading_difficulty` shown as a small chip and dead-likely files dimmed slightly. Middle column: the file content, with line numbers, syntax highlighting, and the citations-in-flight rendered as glowing chip spans the moment the voice mentions them. Right column: the live voice + transcript panel.
5. **Live walkthrough panel.** The right column. A simple, calm layout: a single "Read this file" CTA when idle; once started, an audio waveform indicating the voice is speaking, a "pause" button (40×40 px tap target), a "skip to next utterance" button, and a scrolling transcript with citation chips. An inline input at the bottom lets the user type a question; voice input is also available via a mic button.
6. **Per-file notes (auto-saved after every walkthrough).** A markdown view of the generated `FileNote`. Editable in place. Saved to Firestore + optionally written to `.code-read-along/notes/.md` in the local repo if the user chose "Local folder" import.
7. **Glossary view.** The top-100 glossary as a searchable table: symbol, kind, one-line definition, defined-at link, used-in count. Filter by kind. Click a symbol to start a walkthrough beginning at its definition.
8. **Conventions view.** The conventions inventory as a list of cards: each card has the name, the rule, three example citations (clickable to jump to the file), counter-examples (if any), and a confidence chip.
9. **Call-graph view (optional, advanced).** A force-directed graph of the top-200 symbols in the repo with edges for "calls", "is called by", "imports", "is imported by". Click a node → jump to its definition + start a walkthrough.
10. **Workspace + sharing.** A workspace contains 1–N repositories. Invite teammates by email (magic link); they land in the same workspace with their own avatar. Shared notes are toggleable per-file.
11. **Settings.** Voice locale (20+ options), reading speed, default thinking level for live sessions, "skip dead code in non-security modules" toggle, repo-import preferences (whether to upload trimmed source to Firebase Storage or keep everything local), data deletion ("delete this workspace forever" — gone in 60 seconds).
12. **Footer.** "For the people reading the codebase nobody helped them onboard onto." Privacy: "Your code 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 Code Read-Along."
- Subhead: "Read your first big codebase the way a senior engineer reads it — file by file, with a voice in your ear that never makes up the codebase."
- One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic "AI tutor": every claim the voice makes is anchored to an exact file path and line range in the repository you opened. No invented function names. No "this is a classic pattern" without showing the line. If the voice doesn't know, it says so.
- Visual: an annotated thumbnail of the file viewer with a citation chip glowing ("`lib/auth/session.ts:22-48`") — not a generic robot or speech-bubble icon.
**Slide 2 — Try it now.**
- One short prompt: "Try with the sample repository".
- A live demo pre-loaded with the sample Next.js commerce repo from section 8a.
- 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the citation chip next to each spoken sentence, the glossary entry for `getSession`, the conventions inventory entry "hooks prefixed with use*").
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Swap the sample repo for any GitHub repo you have access to — public or private."
- "Adjust the per-call system instructions in `/server/prompts/` to suit your team's stack (e.g. add Rust idioms, custom error-handling vocabulary)."
- "Wire up your Gemini API key, your Firebase project, and (if private repos) your GitHub OAuth App 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 (long context, 1M tokens)** — loads a trimmed-source snapshot of your entire repo (typical: 200k–600k tokens after de-noising) so every walkthrough sees the whole codebase. For repos that exceed 1M tokens, we chunk per-module and ladder up.
- **Gemini 3.5 Flash (Live API)** — the live voice walkthrough. Interruptible. The session carries the open file, the conventions inventory, the glossary, and the call-site index for the open file — never the whole repo per turn.
- **Gemini 3.5 Flash (multimodal)** — drag-and-drop a stack-trace screenshot, an error overlay, or a `git blame` panel; the voice reads it and answers grounded in your repo.
- **Gemini 3.5 Flash** — fast, low-cost calls for per-file notes, "what calls this?" reverse lookups, and glossary entries.
- **Gemini 2.5 Flash TTS** — narrates the walkthrough in your chosen locale (20+ languages). Code identifiers stay in their original form; the prose around them is your first language.
- **Gemini 3.5 Flash + grounded search** — answers general language/framework questions ("what is a React Server Component?") with a citation from the official docs, kept strictly separate from in-repo walkthroughs.
- **Firebase Auth** — Google, GitHub (primary for the developer audience), and Apple sign-in; workspace invitations via magic links.
- **Firestore** — stores your notes, conventions, glossary, walkthrough transcripts. Syncs across devices in real time.
- **Firebase Storage** — keeps the trimmed-source snapshot of each repo so we don't re-process it on every call. Encrypted at rest. Pre-signed URLs only.
- **Cost note** — see the detailed breakdown in 6d. A typical 1,500-file repo first-import costs about $1.80 of Gemini API spend, and each subsequent walkthrough is well under $0.05.
- **Privacy note** — your source code is yours. 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. Trimmed-source snapshots in Firebase Storage are scoped to your workspace and encrypted at rest. You can delete a workspace at any time; deletion completes in under 60 seconds.
**Backend services this app depends on:**
- Auth: see section 4b
- Database: see section 4b
- Storage: see section 4b
- Email: see section 4b
- Payments: see section 4b (not used in v1)
- External APIs: see section 4b
**Environment variables you'll need to configure:**
- `GEMINI_API_KEY` — your Google AI Studio API key
- `FIREBASE_PROJECT_ID` — your Firebase project id
- `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only)
- `GITHUB_OAUTH_CLIENT_ID` — required if you want users to read their private GitHub repos
- `GITHUB_OAUTH_CLIENT_SECRET` — paired with the above (server-side only)
- `GITLAB_OAUTH_CLIENT_ID` / `BITBUCKET_OAUTH_CLIENT_ID` — optional, for those code hosts
**Cost + privacy notes:**
- The repo-overview call (long-context, high thinking) is the most expensive single call in the app — about $0.90 per 1,500-file repo on first import. It's cached forever; we only re-run it on explicit user request after a major refactor.
- Live API sessions are billed by audio output tokens; a 12-minute walkthrough costs roughly $0.04. Sessions are interruptible — the user can pause to read on their own without burning tokens.
- Privacy: your source code lives in your Firebase project. Trimmed-source snapshots in Firebase Storage are encrypted at rest. Notes you generate live in Firestore and (optionally) in `.code-read-along/notes/` inside your local repo if you imported via "Local folder". The app does not log full source to your application logs; only file paths and structured metadata.
**Documentation links:**
- AI Studio Build docs
- Gemini API long-context, Live API, multimodal, TTS docs
- Firebase Auth, Firestore, Firebase Storage docs
- GitHub OAuth Apps setup guide
- tree-sitter + ripgrep references (the static-analysis libraries the Cloud Run worker uses)
**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)
- **Repo overview on import (Gemini 3.5 Flash, high thinking, long-context)** — typical trimmed-source snapshot 400k input tokens; ~2,500 output tokens. ~$0.50 input + ~$0.013 output ≈ **~$0.51 per overview**. Cached forever; re-run only on explicit user request.
- **Per-file walkthrough plan (Gemini 3.5 Flash, medium thinking)** — typical file 200 lines ≈ 1.5k input tokens + 12k context (file note + conventions + glossary + call-site index for the file) ≈ ~13.5k input, ~2,000 output. ~$0.017 input + ~$0.010 output ≈ **~$0.027 per file**.
- **Live voice walkthrough (Gemini 3.5 Flash Live API)** — billed by audio-output tokens; a 12-minute walkthrough at ~110 words per minute ≈ ~1,300 words ≈ ~6,000 output tokens audio. ~$0.030 per walkthrough. User-interrupted sessions are pro-rated.
- **"What calls this?" reverse lookup (Gemini 3.5 Flash, low thinking)** — tiny call, ~1k input + ~500 output. ~$0.0003 per lookup.
- **Per-file note generation (Gemini 3.5 Flash, low thinking)** — ~3k input + ~1k output ≈ ~$0.0006 per file.
- **Convention inventory (Gemini 3.5 Flash, medium thinking)** — runs once on import alongside the overview; long-context input ≈ 400k tokens, ~3k output. ~$0.50 input + ~$0.015 output ≈ **~$0.52 per import**. Cached; re-run on demand only.
- **Glossary entry generation (Gemini 3.5 Flash, low thinking)** — runs for the top-100 symbols on import; ~$0.0001 each ≈ **~$0.01 total per import**.
- **Screenshot debugging (Gemini 3.5 Flash, multimodal, medium thinking)** — ~$0.015 per screenshot.
- **External-knowledge answer (Gemini 3.5 Flash + grounded search)** — ~$0.001 per question. Used sparingly.
- **Expected first-import cost for a 1,500-file repo:** repo overview (~$0.51) + conventions (~$0.52) + glossary (~$0.01) + per-file notes on the top 30 files the user opens in the first month (~$0.018 × 30 ≈ ~$0.55) + a handful of live walkthroughs (~$0.04 × 5 ≈ ~$0.20) ≈ **~$1.80 total in the first 30 days**.
- **Ongoing per-walkthrough cost (after first import):** ~$0.04 per live walkthrough + ~$0.0006 per file note refresh. A heavy week (5 walkthroughs, 10 note refreshes) ≈ ~$0.21.
- **Static analysis (Cloud Run worker, tree-sitter + ripgrep):** ~$0.005 per import. Negligible.
- **Storage:** Firebase Storage standard tier, ~$0.026/GB/month. A trimmed-source snapshot of a 1,500-file repo ≈ ~15 MB ≈ ~$0.0004/month. Negligible.
## 7. Design language
- **Mood:** A senior engineer's reading desk at 9 pm. Not a tech product. Not a tutorial site. The pair-programming bench at the back of the office where the new hire reads the codebase out loud to the senior who occasionally interrupts. Warm. Quiet. Unhurried. Confident.
- **Typography:** Display serif for the welcome, the section dividers, and the per-file note prose (Source Serif Pro or iA Writer Duospace). A precise monospace for everything code-shaped (JetBrains Mono, with the bundled ligatures disabled — they obscure what the code actually says). Clean grotesque for app chrome and the right-rail transcript (Inter or Geist).
- **Palette:** Warm bone-paper background `#F6F2EA` for the main reading surface, deep ink `#1B1714` for body text, a single hot accent — desk-lamp amber `#D87B1F` — used only for the live citation chip when the voice is mentioning a span (so the eye knows where to look). Muted slate `#3A5066` for secondary chips (file paths, line numbers, glossary kinds). A near-black charcoal `#0F0F0E` for code blocks; syntax highlighting in restrained pastels (no neon). A faded red `#A33A2C` only for uncertainty markers and dead-code dimming.
- **Imagery:** The codebase is the hero. The interface gets out of the way. The only image in the entire app, after the welcome, is the file content itself. No illustrations of robots, no stock photos of "developers", no "AI" iconography.
- **Hand-feel touches:** A barely-visible paper grain on the reading surface. The citation chip lights up — softly, briefly — the moment the voice begins a sentence that references it; it dims when the voice moves on. The waveform indicating the voice is speaking is a single thin line with a slight tremor, not a multi-bar spectrum analyser. The "pause" button has a small spring back when released.
- **Spacing:** consistent 4-px base. Generous whitespace around the reading column. Code blocks have room to breathe — 12 px of inset padding minimum.
- **Radius:** consistent token set (4 / 8 / 16 px). Citation chips use 4; cards (per-file notes, conventions cards) use 8; the welcome card uses 16.
- **Shadows:** subtle, near-imperceptible. Avoid heavy drop-shadows. The live voice panel sits ~1 px above the reader, no more.
- **Motion:** purposeful — entrance fades, focus rings, the citation-chip lighting cue. Respect `prefers-reduced-motion`. No bouncing splash animations. No theatrical hero animations. The one place motion carries real meaning is the citation-chip lighting cue; with reduced-motion, the chip changes colour instantly with no transition.
- **States:** every interactive element has hover, focus, active, and disabled. Loading uses skeletons that match the eventual layout, not spinners. Empty states have helpful next-action guidance ("Connect a repository to start", "Pick a file to read", "Ask the voice a question").
## 8. Content generation rules
- Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'.
- Invent plausible repo names, file paths, function names, conventions, and quotes that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on real-world stacks — Next.js App Router, Django REST framework, Go cmd/ layouts, Rust workspace crates — but never claim that a fictional file in the demo repo is from a real production product.
- Tone: warm, direct, free of corporate language. This template is for a person, not a company.
- Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'. No 'AI-powered'.
- Body copy: short paragraphs (2-4 sentences). Use lists where appropriate.
- Plain language. Avoid jargon — except where the user already speaks the jargon (the bootcamp grad wants to see "App Router" in the conventions inventory; the senior engineer wants to see "result-type error handling" or "decorator-stacked validation" in the conventions list).
- 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 sentence; the citation chip carries a small "?" if the resolution is ambiguous).
## 8a. Seed content (use these specific examples)
Anchor every generated copy + sample data point in the concrete content below. Use these names, numbers, paths, and snippets verbatim where helpful, or generate close variants that sit in the same world.
**Sample repositories (sidebar):**
- "vercel/commerce (Next.js Commerce)" — 1,420 files, TypeScript + TSX + MDX, App Router. The starter repo a bootcamp grad most often gets handed. Demo locale: Tagalog.
- "django-oscar/django-oscar" — 2,180 files, Python + HTML + JS, a large e-commerce framework. Demo locale: English.
- "tldraw/tldraw" — 1,860 files, TypeScript, complex canvas codebase with non-obvious abstractions. Demo locale: Mandarin (simplified).
- "rails/rails (just `activerecord/`)" — chunked from the full Rails monorepo to focus on one library. Demo locale: Portuguese (Brazilian).
- "neovim/neovim" — 4,200 files, C + Lua + Vim script. Polyglot. Reading difficulty: expert. Demo locale: English.
**Sample file in detail view (this is what the demo should show):**
- **Repo:** vercel/commerce
- **File:** `app/[locale]/(checkout)/checkout/page.tsx`
- **Reading difficulty:** moderate
- **One-paragraph summary (verbatim from the demo):** "This is the checkout page. It runs as a Server Component, pulls the current cart from `lib/shopify/getCart.ts`, and pulls the current user session from `lib/auth/session.ts`. If either is missing it redirects: no cart → back to the catalogue; no user → to the sign-in page. The rendered tree is split between a `CheckoutSummary` (read-only) and a `CheckoutForm` (interactive, marked `'use client'`). Most of the actual checkout logic lives in `CheckoutForm`, not here; this file is the page-level shell that hydrates the data the form needs."
- **Public exports (1):**
- `default export: the page component` — defined at lines 18–62
- **Internal helpers (2):**
- `async function loadCheckoutData(): Promise` — defined at lines 64–86, called once from the default export — `is_likely_dead: false`
- `function isCartHydrated(cart: Cart): boolean` — defined at lines 88–95, called once from `loadCheckoutData` — `is_likely_dead: false`
- **Inferred role in module:** "page-level entry for the checkout flow; orchestrates data fetching, leaves rendering to `CheckoutSummary` and `CheckoutForm`"
- **Most related files (4):**
- `app/components/checkout/checkout-summary.tsx` — relationship: `imports`
- `app/components/checkout/checkout-form.tsx` — relationship: `imports`
- `lib/shopify/getCart.ts` — relationship: `imports`
- `lib/auth/session.ts` — relationship: `imports`
- **Flagged for user review (1):**
- field: `internal_helpers[1].is_likely_dead` — reason: "`isCartHydrated` is only called from one place in this file; if the cart shape changes upstream it may quietly become dead. Worth a re-check next refactor."
**Sample conventions inventory (verbatim items from the demo):**
- "App Router pages live under `app/[locale]/(group)//page.tsx`. The `[locale]` segment carries the i18n; the `(group)` segments are pure organisation and don't appear in the URL." — 14 citations.
- "Server Components are the default; Client Components are marked with `'use client'` at the top of the file and live in `app/components//`." — 38 citations.
- "Data-fetching functions live in `lib//.ts` and are named `get*`, `list*`, `find*`, `create*`, `update*`, `delete*`. They never throw; they return `null` or `undefined` on missing." — 24 citations.
- "Session reads use `getSession()`; session writes use server actions in `app/actions/auth/`. The split is enforced — there is no `setSession()` exported from `lib/auth/`." — 9 citations.
- "Errors surface via thrown `AppError` (from `lib/errors/app-error.ts`) which renders as a styled error page; never via try/catch in the page component itself." — 12 citations, plus 2 counter-examples in older code.
**Sample glossary entries (top of the top-100 for the demo repo):**
- `getCart` (function, defined at `lib/shopify/getCart.ts:14`) — "Fetches the current cart for the active session from Shopify; returns null if no cart exists. Used in 18 places, most importantly in `app/[locale]/(checkout)/checkout/page.tsx`."
- `useCart` (hook, defined at `lib/hooks/use-cart.ts:9`) — "Client-side cart state; reads from a React context populated by `CartProvider`. Used in 12 client components. Don't use in Server Components — it will throw."
- `AppError` (class, defined at `lib/errors/app-error.ts:5`) — "Application's domain error type. Carries an HTTP status, a user-facing message, and an internal code. Thrown anywhere; caught only by the global error boundary in `app/error.tsx`."
- `getSession` (function, defined at `lib/auth/session.ts:22`) — "Reads the current user session from a JWT in cookies; returns null if no valid session. The canonical entry for any auth check. Used in 47 places."
- `CheckoutForm` (component, defined at `app/components/checkout/checkout-form.tsx:24`) — "Interactive client component that owns the checkout form state. Receives initial values as props from the page-level checkout. Submits via the `submitCheckout` server action."
**Sample voice copy (Tagalog demo, with English in brackets for the gallery preview):**
- Walkthrough opener (Tagalog): "Salamat sa pagbukas ng `app/[locale]/(checkout)/checkout/page.tsx`. Ito ang checkout page ng buong app. Server Component, ibig sabihin tumatakbo ito sa server bago dumating ang HTML sa browser. [Thanks for opening `app/[locale]/(checkout)/checkout/page.tsx`. This is the checkout page for the whole app. It's a Server Component, which means it runs on the server before the HTML reaches the browser.]"
- Uncertainty disclosure (English): "I see `getSession` defined in two places — `lib/auth/session.ts:22` and `lib/test-helpers/session.ts:8`. The one being called here is the first one, based on the import on line 7. Want me to confirm by reading the import?"
- External-knowledge disclaimer (English): "That's a question about React Server Components in general, not about this repo. The React docs cover it well. Want me to come back to reading this file?"
- Suggesting next file (Spanish): "Cuando termines aquí, el siguiente archivo natural es `app/components/checkout/checkout-form.tsx`. Es donde vive la lógica interactiva de este checkout. ¿Lo leemos a continuación?"
- Save confirmation (English): "Added a note for `checkout/page.tsx` — six paragraphs, two related files flagged, one helper marked for re-check."
- Processing: "Reading the repo overview…" / "Mapping the modules…" / "Finding the conventions…" / "Picking the entry points for a new reader…"
- Empty workspace: "This workspace is waiting for its first repo. Connect a GitHub repo to start, or open a local folder."
- Error (couldn't ground): "I couldn't find a definition for `legacySessionV2` anywhere in this repo. It may be referenced via a string identifier or come from an external package. Want me to search for the string, or skip it?"
- Low-confidence note: "Some symbols were hard to resolve. Tap any sentence with a faint underline to see the model's candidates."
**Sample workspace invitation email subject + body:**
- Subject: "Lola — I'm reading the payments service. Want to read it together?"
- Body: "Hi Lola — I cloned the payments service today and I'm using Code Read-Along to walk through it. Want to join my workspace so we can share notes? You'll see my notes on `lib/billing/` and add your own. Tap to join." [Open workspace]
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking shot of a small wooden kitchen table at evening: a laptop open, a worn paperback of *The Elements of Computing Systems* face-down beside it, an enamel mug of coffee, the laptop screen showing a file tree. Generate via Nano Banana 2 with a prompt emphasising "warm desk-lamp light, late evening, real worn paper, soft shadow under the laptop, no people in frame, slight asymmetry, no neon glow on the screen".
- **App icon / wordmark:** Set in the display serif. Slightly worn paper texture behind it. A small thin underline accent in desk-lamp amber. No icon — just type.
- **Empty-state illustration:** A simple line drawing of a closed folder with one corner lifting. Hand-drawn aesthetic, not a flat material-design icon.
- **Demo file content:** Real TypeScript / Python / Go source from the open-source repos named in section 8a. Cite the upstream license in the footer of the demo view. Never claim demo source is the user's own.
- **Citation chip styling:** A small rounded rectangle (4 px radius) carrying `:-`, font: precise monospace, 11 px. Background: warm slate `#3A5066` at 10% opacity by default; lights to desk-lamp amber `#D87B1F` at 40% opacity when the voice is currently mentioning the cited span.
- **Stock fallbacks:** If image generation fails, fall back to the photographed sample 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").
- 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: "Connect a GitHub repository", "Open a local folder", "Read this file", "Ask a grounded question" — never a generic "Add your first item".
- Smooth scroll for in-page anchors.
- All AI-generated content streams in token-by-token where supported (the per-file walkthrough plan and the live voice's transcript both stream).
- The Live API session shows a clear "voice listening / voice speaking / voice paused" state. The voice's spoken sentence appears in the transcript as it is uttered; the citation chip lights up at the moment the voice utters the sentence that references it.
- If a Gemini call fails, show a calm, specific error ("The voice lost its connection. Reconnecting in 3… 2… 1…") and recover. On the second consecutive failure, fall back to a non-live walkthrough that streams text.
- Low-confidence utterances in the transcript are faintly underlined; tapping reveals the alternates the voice considered or the symbols it couldn't resolve.
- The citation-chip light-up transition is 220 ms with `prefers-reduced-motion` falling back to instant.
## 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. The code viewer uses Shiki (or a comparable tree-sitter-backed highlighter) for accurate, language-aware syntax highlighting.
- **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. The Live API session is brokered through a server-side WebSocket so the client never sees the Gemini API key.
- **Model selection:** explicitly pin `gemini-3.5-flash` for repo overview, per-file walkthrough plan, conventions inventory, live walkthrough (via Live API), and screenshot debugging; `gemini-3.5-flash` for per-file notes, reverse lookups, glossary entries, and external-knowledge answers; `gemini-3.1-flash-tts-preview` for narration if you want a non-Live fallback. Set `thinkingLevel` explicitly per call.
- **Static-analysis worker:** a Cloud Run service running tree-sitter + ripgrep that ingests a repo and emits the file list, import graph, exported-symbol table, and call-site index. Re-runs on demand (e.g. after the user pulls new commits).
- **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed repo on first launch.
- **Auth:** Firebase Auth — Google + GitHub by default (GitHub is the primary CTA for the developer audience); Apple sign-in optional; magic-link email as fallback.
- **Storage:** Firebase Storage for trimmed-source snapshots and per-user notes archives. Pre-signed URLs only.
- **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. On mobile, the three-column layout collapses to a single column with a bottom tab bar (Files / Read / Notes).
- 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 workspace and notes views.
- Optimistic UI on writes; reconcile on response.
- The Live API client uses the Web Audio API for low-latency voice input/output and gracefully falls back to push-to-talk on browsers without full duplex.
- **iOS Safari gotchas (graceful degradation):** the Live API session must survive audio-session interruption (incoming call, Siri, alarm) — listen for `MediaStreamTrack.onmute` and pause the walkthrough; resume on `onunmute`. Backgrounded Safari tabs throttle WebSocket and kill `getUserMedia` — combine `visibilitychange` with a screen Wake Lock during a session, or fall back to push-to-talk transcript-only mode when audio is unavailable. Microphone permission does NOT persist across reloads on iOS — re-request on every session start. PCM streaming must go 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. Verify the citation-chip lit state against the bone-paper background.
- All images have meaningful `alt` text. The code viewer is rendered as actual text (not as an image), so screen readers can read it directly. Line numbers are decorative (`aria-hidden`).
- Form fields have associated `