# 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.
---
# PDF → Plain English
## 1. Project
**PDF → Plain English** turns the documents people are about to
sign — leases, employment contracts, insurance terms, school
enrolment policies, government letters, medical information
sheets, fund prospectuses, terms-of-service updates — into a
short, honest, page-cited brief. Drop the PDF in. Get back a
one-paragraph plain-English summary, the five clauses that
matter most with the page number on each one, the three to five
decisions the reader has to actually make, the four to six
questions to ask the other side before signing, and a candid
"watch out for" red-flag list.
Almost everybody has a PDF on their desk right now that they
are not reading carefully. Skim-reading is how people end up
trapped — by an automatic renewal, a non-compete radius, a
deductible that resets on a date nobody mentioned, an
arbitration clause that quietly removed the right to sue. The
app fixes this by reading the whole thing in one pass and
returning a brief the reader can actually use.
The single demo that proves the magic: a 47-page residential
lease lands on the dropzone. In eighteen seconds the app
returns a one-paragraph summary in plain English (rent, term,
deposit, who pays utilities, what the pet rule actually says),
the five most important clauses each cited with a page number
(deposit return window p. 11, late-rent fee p. 14, sub-let
rules p. 22, repair-and-deduct p. 31, early-termination
penalty p. 39), the three decisions the reader still has to
make ("are you willing to accept the 3 % annual rent
increase?"), the four questions to ask the landlord, and the
honest red-flag list ("p. 39 specifies liquidated damages of
two months' rent on early termination — high end locally";
"p. 22 forbids any short-let, even one night").
In harder cases — an employer's stock-vesting appendix with a
cliff and acceleration triggers, a health-insurance summary
that buries out-of-network coverage on page 31, a fund
prospectus with eight fee categories spread across nine pages
— the same shape of attention applies. Plain-English summary
short. Page citations exact. Decisions concrete. Questions
practical. Red flags honest, never sensational.
**Hero capability:** **Gemini 3.5 Flash** (`gemini-3.5-flash`)
with 1M-token context, multimodal PDF understanding, and
structured output. Post-I/O 2026, Flash beats the old 3.1 Pro
on this task at roughly four times the speed. A 47-page PDF is
~35–50k tokens; a 200-page prospectus ~160–200k tokens. Both
fit in one call.
**Tagline:** _Drop a PDF in. Get back plain English, the page
citations, and the three decisions you have to make before you
sign._
## 2. Target audience
The job-to-be-done is universal — almost every adult has a PDF
they need to read this week. The app does not need to know who
the reader is.
- People about to sign a residential lease (rental, sub-let,
fixed-term, rolling).
- People reviewing an employment offer (base, bonus, equity,
notice, restrictive covenants, IP assignment, garden leave,
repayable training costs).
- People comparing health-insurance plans during open enrolment
or after a job change.
- People reading a school enrolment policy, code of conduct,
or acceptable-use policy on behalf of a child.
- People reading a government letter (tax notice, immigration
decision, benefits adjustment, planning notice).
- People reviewing a medical information sheet or a study
consent form before signing.
- People reading a fund prospectus, KID, or T&Cs update before
investing.
- Small-business owners reviewing a contractor agreement, a
vendor SLA, an NDA, or a partnership agreement.
- People helping a parent or partner read a document that
arrived in a language they no longer read confidently.
- Anyone who has clicked "I have read and agree" without
reading and would like the option to actually read one
once.
## 3. Core value propositions
- **The page number is on every claim.** Every clause the app
surfaces points to the exact page in the source PDF. If the
app cannot cite a page, it does not claim the fact. Hovering
any clause shows the PDF page inline; clicking jumps to it.
- **Dollar amounts are quoted verbatim, never paraphrased.**
The app never says "around two months' rent". It quotes
"liquidated damages equal to two (2) months' rent" with the
page citation. Numbers are too easy to lose in translation.
- **Three decisions, not three hundred bullet points.** The
brief surfaces exactly three to five decisions the reader
actually has to make. "Are you OK with a 3 % annual
increase?" is a decision. "The contract has 47 clauses" is
not.
- **Questions, not advice.** The app drafts the four to six
specific questions to ask the landlord, employer, insurer,
school, or lawyer. The questions are the value. It never
tells the reader what to decide.
- **A candid red-flag list, never sensational.** "p. 22
forbids any short-let, even one night" is a red flag worth
seeing. "This contract is a TRAP" is not how the app talks.
- **Plain English at the reader's level.** The reader can ask
for the summary at 5th-grade, 9th-grade (default), or
legalese-preserving level. The toggle re-runs the brief in
~3 seconds using cached input.
- **Output in the reader's language, faithful to the original.**
Any Gemini-supported language for the commentary; dollar
amounts and party names stay verbatim from the PDF.
- **Hard rule: never substitutes legal or medical advice.** The
app surfaces facts and flags decisions. Whenever a document
touches health, immigration, taxation, criminal liability,
or a decision a qualified professional would normally take,
the brief includes an "ask the lawyer" / "ask the doctor"
question list with the not-a-substitute disclaimer above it.
- **Fast.** A 47-page lease returns a complete brief in under
twenty seconds. A 200-page prospectus under a minute. The
reader watches the brief stream in section by section; they
do not stare at a spinner.
## 4. Features to build
- Single dropzone — the visitor drags a PDF in, or pastes a
public PDF URL, or picks from the device file picker. Max
50 MB; 1,000 pages. Encrypted PDFs prompt for the password
client-side and decrypt before upload.
- Multi-document compare mode — drop two leases / two offers /
two health plans; brief generates per document plus a
side-by-side comparison brief that diffs the relevant
fields per document type.
- Side-by-side viewer — rendered PDF on the left, plain-
English brief on the right. Clicking a cited clause jumps
the PDF to that page and highlights the cited text.
- Document type detection — classifies the PDF into one of
{residential lease, employment contract, insurance terms,
school policy, government letter, medical info, fund
prospectus / KID, terms of service, vendor / contractor
agreement, NDA, other}. The brief template adapts to type.
- One-paragraph plain-English summary, ≤ 120 words, in the
reader's chosen language and reading level.
- The five (up to seven) most important clauses — each a
one-line label, a verbatim quote of the most important
sentence, the page number, and a one-line plain-English
interpretation.
- The three to five decisions the reader has to make —
phrased as yes/no or A/B questions.
- The four to six questions to ask before signing — each one
specific and addressed to the right party.
- The red-flag list — three to seven concrete items with page
citations, severity (informational / watch / high), and a
one-line "why this is a flag".
- "Ask the lawyer / ask the doctor" panel — populated whenever
the document touches a professional domain. The not-a-
substitute disclaimer surfaces above this panel.
- Verbatim-quote validator — every quoted sentence is checked
server-side against the extracted PDF text. Failures
trigger one regeneration; second failures are flagged in
the UI as "approximate — see page X".
- Page-citation jump on hover (thumbnail preview) and click
(jump + highlight in the PDF viewer).
- Plain-English glossary chips — legalese words ("liquidated
damages", "indemnification", "non-compete", "deductible",
"out-of-pocket maximum", "vesting cliff", "garden leave",
"force majeure") appear as small chips; tapping opens a
one-sentence plain-English explanation from a fixed local
glossary (never hallucinated).
- Reading-level toggle — 5th / 9th (default) / legalese-
preserving. Switches re-run in ~3 seconds.
- Language toggle — auto-detects PDF language; any Gemini-
supported language for the brief.
- "Ask a question about this PDF" — text input below the
brief. Answers are grounded in the PDF with a page
citation. Questions that require legal / medical judgement
return a refusal that reframes the question for a
professional.
- "Is this clause normal here?" grounded search — answers a
jurisdiction question with sourced citation URLs from
`groundingMetadata`.
- Save & share — signed-in users can save briefs and share
via per-document magic-link URL (revocable).
- Export — PDF copy of the brief with all citations and
verbatim quotes; or Markdown for the reader's notes.
- Privacy mode — session-only by default for signed-out
users; PDFs and briefs deleted on tab close. Signed-in
users choose "save to library" explicitly.
## 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 PDF understanding** (`gemini-3.5-flash`, 1M
context, multimodal PDF input) — the headline capability.
The raw PDF is sent as `inlineData` (≤ 20 MB) or via the
Files API (`files/*` resource name, e.g. `files/abc123xyz`,
passed via `fileData.fileUri`) for larger files. The model reads
the whole document in one pass, preserves page numbers, and
emits the structured brief. **Guardrail**: a 200-page
prospectus is ~160–200k tokens; a 600-page bundle ~500k.
At >500k tokens of payload switch to chunked extraction
(50 pages per chunk, 2-page overlap, per-chunk summaries,
then a merging call). Never send more than 800k tokens in
one call.
- **Document type classification** (`gemini-3.5-flash`, low
thinking) — a fast first call that classifies the PDF into
one of the ten document types. Used to route to the right
brief template.
- **Structured output / JSON Schema** — the main brief call
returns `DocumentBrief`; comparison returns
`DocumentComparison`; Q&A returns `DocumentAnswer`. Schemas
seeded verbatim in the system instruction and as
`responseSchema`. Numeric `min`/`max` in Zod are
documentation only — clamp server-side. Convert the Zod
schema to Gemini's `Schema` type via the SDK helper before
passing to `responseSchema`; do NOT pass raw Zod.
- **Thinking levels** — `medium` for the main brief (it
weighs which clauses matter across the long document).
`low` for type classification and verbatim-quote re-
extraction. `high` for the 3–4-document comparison call.
- **Grounding via Google Search (separate call)** — used only
when the reader explicitly asks "is this clause normal
here?". `responseSchema` and `google_search` are mutually
exclusive in one call; the model emits JSON in the text
body and the server parses it. Citation URLs come from
`response.groundingMetadata.groundingChunks[].web.uri` —
do NOT include URLs in the JSON body; the model will
hallucinate them.
- **Embeddings** (`gemini-embedding-2`, GA 2026-04-22) — for
the "ask a question about this PDF" feature on documents
> 100 pages. Embed page-by-page once, then retrieve the
top-8 relevant pages on each question and pass them plus
the question to a Gemini 3.5 Flash call.
- **Expressive TTS** (`gemini-3.1-flash-tts-preview`) — reads
the brief aloud for accessibility. No SSML support; encode
pauses as "…" mid-sentence and a blank-line "—" between
paragraphs. Prepend a one-sentence style directive ("Read
clearly and calmly, like reading back a contract summary
to a friend at a kitchen table. …").
- **Nano Banana Pro** (`gemini-3-pro-image`) — generates the
welcome-screen hero with legible in-image typography (the
post-I/O 2026 hero capability). Renders a paper PDF on a
desk with the words "PAGE 11 OF 47" legible in the margin.
### Backend services
- **Auth — Optional for v1.** The dropzone works without
sign-in for one-off briefs (PDF + brief in session memory,
deleted on tab close). Sign-in unlocks saved library,
comparison history, and magic-link sharing. Firebase Auth
with Google sign-in (auto-provisioned by AI Studio Build).
Apple sign-in optional, requires Developer account.
- **Database — Required for signed-in mode.** Firestore for
`users`, `documents`, `briefs`, `comparisons`, `questions`,
`shared_links`, `embedding_index_pointers`.
- **File storage — Required for signed-in mode and for PDFs
> 20 MB.** Firebase Storage. NOT auto-provisioned — enable
in the Firebase console. Pre-signed URLs only; lifecycle
rule auto-deletes session-only PDFs (`session/` prefix)
after 24 hours.
- **Email — Optional.** Magic-link share emails; sender
domain authorised in Firebase Auth.
- **Payments — Not needed for v1.** A future Pro tier (saved
library, 1,000+-page documents, batch comparisons) might
charge via Stripe; not in v1.
- **External APIs:** Gemini API only. Optional server-side
PDF-URL fetcher to avoid client-side CORS.
**Environment variables:** every secret (Gemini API key,
Firebase service-account JSON) 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 · HTTPS everywhere · honour
'delete my account' inside the UI · explicit opt-in for any
analytics · the reader's PDFs and briefs are never sent to
Gemini for model training (use the Gemini API on the paid
tier, where Google does not use your content for model
training, per the Gemini API Additional Terms) · sharing is
per-document and revocable · session-only PDFs deleted within
24 hours.
**Read this first — prompt-craft rules that apply to every call:**
1. **Name the model variant explicitly** in every Gemini API
call. Do not let the agent pick the model. See the matrix.
2. **Pin `thinkingLevel` explicitly** per call. On models
that do not accept `thinkingConfig` (TTS, image-gen),
omit the field entirely — `n/a` in the matrix is
documentation, not a value to serialise.
3. **Seed the JSON Schema as a fenced TypeScript / Zod block**
in the system instruction or `responseSchema` field.
**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.**
4. **Pin the system instruction separately** from user input.
Use `systemInstruction` for persona + behavioural rules;
use `contents` for the PDF + user question. Never
concatenate.
5. **Pre-declare tools as an enable/disable list** per call.
Tools NOT listed for a call should be disabled.
6. **`responseSchema` and `google_search` are mutually
exclusive in one call.** If a call needs grounded search,
the model emits JSON in the text body and the server
parses it; citation URLs come from
`response.groundingMetadata.groundingChunks[].web.uri`.
7. **PDF input over 20 MB uses the Files API** (Developer API
`files/*` resource name, e.g. `files/abc123xyz`, passed via
`fileData.fileUri`). Under 20 MB can use `inlineData`
(base64). Do NOT pass Firebase Storage public URLs to
`generateContent` — the API does not fetch them server-side.
`gs://` URIs belong to Vertex AI / Cloud Storage and are NOT
accepted by the Developer API surface this template runs on.
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 arrays and
removes regex patterns before serializing. Validate those
constraints in middleware AFTER parsing.
### Per-call model + tools matrix
| Call | Model | thinkingLevel | Tools enabled |
|------|-------|---------------|---------------|
| Document type classification | `gemini-3.5-flash` | low | (none) |
| Main brief generation → `DocumentBrief` | `gemini-3.5-flash` | medium | (none) — long-context across the full PDF |
| Verbatim-quote re-extraction | `gemini-3.5-flash` | low | (none) |
| Comparison brief (2 documents) → `DocumentComparison` | `gemini-3.5-flash` | medium | (none) |
| Comparison brief (3-4 documents) → `DocumentComparison` | `gemini-3.5-flash` | high | (none) |
| Embedding pages (documents > 100 pages, for Q&A) | `gemini-embedding-2` | n/a | (none) |
| Q&A retrieval-augmented answer → `DocumentAnswer` | `gemini-3.5-flash` | low | (none) |
| "Is this clause normal here?" grounding | `gemini-3.5-flash` | low | `google_search` (no `responseSchema` on this call) |
| TTS playback of the brief | `gemini-3.1-flash-tts-preview` | n/a | n/a |
| Hero / empty-state illustration | `gemini-3-pro-image` (Nano Banana Pro) | n/a | n/a |
*Note for builders:* on TTS and image-generation calls, omit
`thinkingConfig` entirely. The `n/a` cells are documentation
only; do not serialise them. Grounded search calls emit JSON
in the text body — parse it server-side and read citation
URLs from `groundingMetadata`.
### Primary structured-output schemas (seed verbatim in the prompt)
```typescript
import { z } from "zod";
const DocumentType = z.enum([
"residential_lease", "employment_contract", "insurance_terms",
"school_policy", "government_letter", "medical_info_or_consent",
"fund_prospectus_or_kid", "terms_of_service",
"vendor_or_contractor_agreement", "nda", "other",
]);
const ReadingLevel = z.enum([
"grade_5", "grade_9", "legalese_preserving",
]);
const PageCitation = z.object({
page_number: z.number().int().min(1), // 1-indexed
// The verbatim sentence from the PDF that justifies the claim.
// Server validates this is a substring of the extracted page text.
verbatim_quote: z.string(),
is_quote_verified: z.boolean(), // server-set
});
const Clause = z.object({
clause_label: z.string(), // "Early-termination penalty"
one_line_plain_english: z.string(), // ≤ 25 words
why_it_matters: z.string(), // ≤ 40 words
citation: PageCitation,
});
const Decision = z.object({
decision_question: z.string(), // phrased as yes/no or A/B
what_it_depends_on: z.string(), // ≤ 30 words, neutral
related_citations: z.array(PageCitation),
});
const QuestionForOtherParty = z.object({
question_text: z.string(),
who_to_ask: z.enum([
"landlord", "employer", "insurer", "school",
"doctor", "lawyer", "tax_advisor",
"government_agency", "other_party",
]),
why_ask: z.string(), // ≤ 30 words
related_citations: z.array(PageCitation),
});
const RedFlag = z.object({
flag_label: z.string(),
why_it_is_a_flag: z.string(), // ≤ 40 words, factual
severity: z.enum(["informational", "watch", "high"]),
citation: PageCitation,
});
const VerbatimMoneyMention = z.object({
// Dollar/currency/percentage amounts quoted EXACTLY as the
// PDF wrote them, with the page citation. NEVER paraphrased.
amount_verbatim: z.string(),
context_one_line: z.string(),
citation: PageCitation,
});
const ProfessionalQuestion = z.object({
// The 'ask the lawyer / ask the doctor' panel.
domain: z.enum(["legal", "medical", "tax", "immigration", "other"]),
question_text: z.string(),
why_for_a_professional: z.string(), // ≤ 40 words
citation: PageCitation.nullable(),
});
const DocumentBrief = z.object({
document_id: z.string(),
document_type: DocumentType,
detected_language: z.string(), // BCP-47
brief_language: z.string(), // BCP-47
reading_level: ReadingLevel,
// ≤ 120-word headline summary in brief_language.
plain_english_summary: z.string(),
important_clauses: z.array(Clause).min(3).max(7),
decisions_for_reader: z.array(Decision).min(2).max(6),
questions_for_other_party: z.array(QuestionForOtherParty).min(3).max(8),
red_flags: z.array(RedFlag),
// ALL dollar amounts verbatim, with citations. Never paraphrased.
money_mentions: z.array(VerbatimMoneyMention),
professional_questions: z.array(ProfessionalQuestion),
not_a_substitute_disclaimer_required: z.boolean(),
source_pdf_total_pages: z.number().int().min(1),
brief_confidence: z.number().min(0).max(1),
flagged_for_user_review: z.array(z.object({
field_path: z.string(),
reason: z.string(),
})),
});
const DocumentComparison = z.object({
comparison_id: z.string(),
document_ids: z.array(z.string()).min(2).max(4),
comparison_summary: z.string(),
diff_table: z.array(z.object({
field_label: z.string(), // "Monthly rent"
values_per_document: z.array(z.object({
document_id: z.string(),
value_verbatim: z.string().nullable(),
citation: PageCitation.nullable(),
})),
notable_difference: z.boolean(),
})),
// Per-axis observations only — NEVER picks a winner.
per_axis_observations: z.array(z.object({
axis: z.string(), // "cost", "flexibility"
observation: z.string(), // ≤ 40 words
})),
});
const DocumentAnswer = z.object({
question_id: z.string(),
question_text: z.string(),
answer_kind: z.enum([
"quote_grounded",
"refusal_requires_professional_advice",
"refusal_not_in_document",
]),
answer_text: z.string(),
citations: z.array(PageCitation),
not_a_substitute_disclaimer_required: z.boolean(),
});
type DocumentBrief = z.infer;
type DocumentComparison = z.infer;
type DocumentAnswer = z.infer;
```
### Common failure modes (and how to avoid them)
- **Paraphrased dollar amount** — the model writes "around two
months' rent" instead of quoting "two (2) months' rent". The
system instruction requires `money_mentions` to contain the
verbatim string; the server validates it is a substring of
the extracted text.
- **Fabricated page citation** — the model cites "p. 11" when
the clause is on p. 13, or cites a page exceeding the PDF's
page count. Every `PageCitation` passes two server checks:
`page_number ≤ total_pages` and `verbatim_quote` is a
substring of the extracted text of that page. Failures
trigger one regeneration; second failure flags the clause
as "approximate".
- **Quote from the wrong page** — the model quotes a sentence
that exists in the PDF but on a different page. The server
re-locates the quote and corrects the page number; if not
found anywhere, the clause is flagged.
- **The brief gives legal advice** — the model says "you
should refuse to sign this" or "this is illegal in your
state". The system instruction explicitly forbids
prescriptive advice; the brief surfaces decisions and
questions, never recommendations.
- **The "ask the lawyer" panel is empty when it should not
be** — the model produces an employment-contract brief
without flagging the IP-assignment clause for a lawyer.
The system instruction requires: any clause touching legal
liability, medical decisions, tax positions, or immigration
MUST add an entry to `professional_questions`.
- **Document type misclassified** — a sub-let agreement
classified as a vendor agreement. The classification call
sees the first 3 pages; the main brief call receives the
type as a soft hint but flags the classification if its
content disagrees.
- **Long document exceeds context** — a 600-page bundle. The
server detects > 500k tokens of payload and switches to
the chunked-extraction path: 50 pages per chunk with 2-page
overlap, summarise per chunk, then a merging call.
- **Encrypted PDF** — file is password-protected. Decrypt
client-side using `pdf-lib` or `pdf.js` before upload;
never send the password to the server.
- **OCR needed** — the PDF is a scan with no text layer.
v1: reject with a message asking for a text-layer PDF.
v2: Gemini-multimodal OCR pre-pass that emits page-by-page
text and pins it as the source of truth for the verbatim-
quote validator.
- **Brief language ≠ PDF language and the model translates
the dollar amounts** — the system instruction requires
dollar amounts, party names, and dates stay verbatim from
the PDF; only the plain-English commentary is translated.
- **Grounding call returns URLs in the JSON body** — instruct
the model to OMIT URLs from the JSON body; the server
reads them from `groundingMetadata`. If URLs appear in the
body, log a violation and strip them.
- **The reader asks a question the document does not answer**
— the Q&A call returns `refusal_not_in_document` with a
brief explanation, not a fabricated answer.
- **Compare-mode is asked to rank documents** — the system
instruction forbids ranking. It returns per-axis
observations only ("on cost, A is lower; on flexibility, B
is more permissive") and never picks a winner.
### Negative constraints (hard rules)
- NEVER substitute legal, medical, tax, or immigration
advice. Whenever the document or the reader's question
touches these domains, populate `professional_questions`
and set `not_a_substitute_disclaimer_required: true`. The
UI surfaces the disclaimer non-dismissably above the
professional-questions panel.
- NEVER paraphrase a dollar amount, percentage rate, date,
duration, party name, or page citation. Quote verbatim.
- NEVER claim a fact without a `PageCitation`. If the model
cannot find a citation, it must omit the claim.
- NEVER fabricate a page number. `PageCitation.page_number`
must be ≤ `source_pdf_total_pages`. Server enforces;
violators are rejected and regenerated once.
- NEVER fabricate a quoted sentence. Every `verbatim_quote`
must be a substring of the extracted text of the cited
page. Failures trigger one regeneration, then an
"approximate — see page X" flag.
- NEVER rank documents in compare mode. Per-axis
observations only; the decision stays with the reader.
- NEVER write prescriptive advice ("you should sign", "do
not sign", "refuse this clause"). Surface decisions and
questions; the reader decides.
- NEVER include URLs in a grounded-search JSON body.
Citation URLs come from `groundingMetadata`.
- NEVER train on the reader's documents. Use the Gemini API
on the paid tier, where Google does not use your content
for model training, per the Gemini API Additional Terms.
- NEVER auto-share. Sharing is explicit, per-document, and
revocable; default for signed-out users is session-only
with 24-hour deletion.
- NEVER moralise about the document's content. "This contract
is predatory" is not how the brief talks. "p. 39 specifies
liquidated damages equal to two months' rent" is.
- NEVER auto-translate party names. "Acme Property
Management LLC" stays "Acme Property Management LLC" in a
Spanish brief.
- NEVER summarise a clause without the page citation shown.
If `is_quote_verified` is false after one regeneration, the
clause is rendered with a visible "approximate — verify on
page X" notice in the UI.
### Per-call `systemInstruction` strings
Use these as the literal `systemInstruction` field for each
Gemini API call. They complement the series-wide rules
already uploaded as the global instructions file.
### Call: Document type classification
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive the first three pages of a PDF (multimodal PDF
input) plus the filename. Classify the document into exactly
one of: residential_lease, employment_contract,
insurance_terms, school_policy, government_letter,
medical_info_or_consent, fund_prospectus_or_kid,
terms_of_service, vendor_or_contractor_agreement, nda, other.
Output JSON only:
{
"document_type": "",
"confidence": <0..1>,
"detected_language": "",
"rationale_one_line": "<≤ 20 words, factual, no advice>"
}
Hard rules:
- JSON only, no commentary.
- Use 'other' if the document does not fit cleanly.
- Confidence below 0.7 → flag for user review.
- Do NOT translate party names or the language tag.
```
---
### Call: Main brief generation → `DocumentBrief`
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none, long context across the full PDF)
```
You are a precise, neutral document analyst. You receive the
full PDF of a real-world document the reader is considering
signing or acting on. Your single job is to produce a
DocumentBrief that helps the reader understand the document
and decide. You are not a lawyer. You are not giving advice.
You are surfacing facts and flagging decisions.
Inputs:
- The full PDF (Files API `files/*` resource name via
`fileData.fileUri`, or inlineData) — read every page.
- The document_type from the prior classification (soft hint;
override only if the content disagrees).
- The reader's brief_language (BCP-47) and reading_level
(grade_5 / grade_9 / legalese_preserving).
- The PDF's total_pages (server-supplied).
Produce a DocumentBrief JSON object matching the schema.
Specifically:
1. plain_english_summary — ≤ 120 words, in brief_language at
the chosen reading_level. Cover what the document is, then
the two or three most consequential terms. Numbers stay
verbatim from the PDF.
2. important_clauses — 5 (up to 7) clauses that matter most.
Each: a label, one-line plain English, "why it matters",
and a PageCitation with the verbatim quote and page number.
3. decisions_for_reader — 3 to 5 decisions the reader has to
make, phrased as yes/no or A/B questions. Each points to
the citations it depends on. NEUTRAL framing: "are you OK
with a 3 % annual rent increase?" not "the 3 % annual
increase is unfair".
4. questions_for_other_party — 4 to 6 specific questions to
ask the landlord / employer / insurer / school / doctor /
lawyer / tax_advisor / government_agency. Each names the
addressee in who_to_ask and explains why to ask.
5. red_flags — 3 to 7 concrete items where the document
contains a term a typical reader would want to notice.
Severity: informational / watch / high. Each has a
citation. NEVER sensational ("predatory" is forbidden);
factual only.
6. money_mentions — EVERY dollar / currency / percentage /
date that affects what the reader pays, receives, or has
to do. Quote VERBATIM. Cite the page.
7. professional_questions — if the document touches legal
liability, medical decisions, tax positions, or
immigration, list the specific questions a lawyer /
doctor / tax advisor should weigh in on. Set
not_a_substitute_disclaimer_required: true.
Hard rules:
- Output JSON ONLY matching DocumentBrief. No commentary.
- Every claim has a PageCitation. If you cannot cite, omit.
- Every verbatim_quote MUST be a substring of the actual
page text. Prefer a shorter exact quote over a longer
paraphrased one.
- Every page_number must be ≤ source_pdf_total_pages.
- Quote dollar amounts, rates, dates, party names,
durations, and addresses EXACTLY as they appear. Do NOT
paraphrase numbers.
- Do NOT translate party names. "Acme Property Management
LLC" stays as written.
- Do NOT translate verbatim_quote — quotes stay in the PDF's
original language; only the plain-English commentary is in
brief_language.
- Do NOT give prescriptive advice. No "you should sign", no
"do not sign", no "refuse this clause". Surface decisions
and questions.
- Do NOT moralise. No "predatory", "unfair", "shady". State
the fact; cite the page.
- Reading level grade_5 → simple sentences, common words,
one idea per sentence. grade_9 (default) → clear adult
prose, no jargon without an inline gloss.
legalese_preserving → preserve legal terms with brief
plain-English glosses inline.
- If a clause is ambiguous, say so plainly and put the
ambiguity into questions_for_other_party.
- brief_confidence reflects text-layer quality,
classification confidence, and clause-citation
completeness. Below 0.7 → flag for user review.
The reader will check your citations. Be exact.
```
---
### Call: Verbatim-quote re-extraction
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
The server's validator could not find your quoted sentence in
the extracted text of the cited page. Options:
1. The quote exists elsewhere in the PDF — return the
corrected page_number.
2. The quote is paraphrased — return a SHORTER quote that is
a verbatim substring of the cited page.
3. The clause cannot be cited with confidence — return a
refusal.
Inputs:
- clause_label, one_line_plain_english from the rejected clause.
- The page text the server expected the quote to come from.
- The full PDF text (page-by-page).
Output JSON:
{
"outcome": "corrected" | "shortened" | "refused",
"corrected_page_number": ,
"verbatim_quote": "",
"refusal_reason": ""
}
Hard rules:
- verbatim_quote, if returned, MUST be a substring of the
extracted text of corrected_page_number.
- Prefer shorter exact quotes over longer paraphrased ones.
- If refused, the calling code marks the clause as
"approximate — verify on page X" in the UI.
```
---
### Call: Comparison brief (2 documents) → `DocumentComparison`
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You receive 2 PDFs and their already-generated DocumentBrief
objects. Produce a DocumentComparison JSON object.
Rules:
- comparison_summary — ≤ 120 words, neutral, in brief_language.
- diff_table — for each canonical field of the document_type
(e.g. residential_lease: monthly_rent, deposit, term_length,
notice_period, pet_policy, early_termination_penalty,
utility_responsibility, guest_policy, rent_increase_terms,
renewal_terms), list verbatim value per document with its
PageCitation.
- per_axis_observations — for each meaningful axis (cost,
flexibility, restriction, risk), name which document is
more X on that axis. NEVER pick an overall winner.
Hard rules:
- JSON only.
- Values quoted VERBATIM from each document, with citations.
- Do NOT rank the documents.
- Do NOT give prescriptive advice.
- If a field is present in one document and absent in the
other, the absent document's value_verbatim is null, and
notable_difference is true.
```
---
### Call: Comparison brief (3-4 documents) → `DocumentComparison`
Model: `gemini-3.5-flash` · thinkingLevel: high · Tools: (none)
```
Same as the 2-document comparison, with 3 or 4 documents.
The high thinking level is reserved for multi-document
reasoning because the diff_table grows quickly.
Additional rules:
- Cap at 4 documents per comparison. Reject if more passed.
- Per-axis observations name each document by document_id.
- Do NOT rank overall.
```
---
### Call: Embedding pages (documents > 100 pages, for Q&A)
Model: `gemini-embedding-2` · n/a · n/a
```
Embed each page as one vector. Page text is extracted server-
side from the PDF text layer (or the multimodal OCR pre-pass
if no text layer). Each page becomes one embedding row:
{
"document_id": "",
"page_number": <1-indexed>,
"embedding": ,
"page_text": ""
}
Index keyed by (document_id, page_number); stored alongside
the document for later Q&A retrieval. No system instruction
beyond the embedding model's default; this entry reminds the
builder to embed pages (not the whole document) for retrieval
granularity.
```
---
### Call: Q&A retrieval-augmented answer → `DocumentAnswer`
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
The reader has asked a question about the PDF. Inputs:
- question_text.
- Top-8 retrieved pages (page_number + page_text).
- The full DocumentBrief already generated, for context.
Produce a DocumentAnswer JSON object.
Decision tree:
1. Answer is in the retrieved pages → answer_kind:
"quote_grounded" with citations.
2. Question requires legal/medical/tax/immigration judgement
("should I sign this?", "is this enforceable?") →
answer_kind: "refusal_requires_professional_advice", set
not_a_substitute_disclaimer_required: true. answer_text
reframes the question for the appropriate professional
("ask a lawyer who knows residential lease law in your
jurisdiction whether the liquidated-damages clause on
p. 39 is enforceable").
3. Answer not in the document → answer_kind:
"refusal_not_in_document" with a brief explanation.
Hard rules:
- Every citation MUST be one of the retrieved pages.
- Every quoted sentence MUST be a substring of the cited
page's text.
- Do NOT make up an answer. If unsure, refuse.
- Do NOT give advice. Surface facts and the corresponding
professional question.
Output JSON only.
```
---
### Call: "Is this clause normal here?" grounding
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search`
```
You answer a single grounded question about whether a clause
the reader has highlighted is typical for the document_type
in the reader's stated jurisdiction. Examples:
- "Is a two-month liquidated-damages clause normal for a
residential lease in California?"
- "Is a six-month non-compete normal for a software
engineer in the UK?"
- "Is a $7,500 out-of-pocket maximum normal for a US
individual health plan in 2026?"
Use google_search grounding for every answer. Do NOT answer
from training memory alone for any numeric range.
Output JSON in the text body (NOT via responseSchema —
mutually exclusive with google_search). Server parses the
JSON and reads citation URLs from
response.groundingMetadata.groundingChunks[].web.uri — do NOT
include URLs in the body; the model will hallucinate them.
JSON shape:
{
"answer_one_line": "",
"answer_detail": "<2-4 sentences>",
"typical_range": {
"low_verbatim": "",
"high_verbatim": "",
"unit": ""
},
"jurisdiction": "",
"professional_consultation_recommended":
}
Hard rules:
- Numeric ranges quoted verbatim where sources provide them.
- If sources disagree, surface the disagreement.
- If jurisdiction is unstated, the answer asks for one
rather than guessing.
- professional_consultation_recommended: true for any
question about enforceability, tax treatment, medical
necessity, immigration eligibility, or anything a lay
reader cannot reliably interpret.
- Do NOT include URLs in the JSON body.
```
---
### Call: TTS playback of the brief
Model: `gemini-3.1-flash-tts-preview` · n/a · n/a
```
Voice: clear, calm, unhurried. Pick the Gemini 3.1 Flash TTS
voice whose languageCode matches brief_language.
Pre-process the brief text before sending to TTS:
- Sequence: plain_english_summary → important_clauses →
decisions_for_reader → questions_for_other_party →
red_flags → professional_questions disclaimer (if needed).
- Prepend a one-phrase signpost per section ("The most
important clauses…", "Decisions you have to make…",
"Questions to ask the landlord…", "Red flags…").
- At sentence boundaries, insert "…" for a natural pause.
- Between sections, insert a blank line and an em-dash ("—").
Gemini 3.1 Flash TTS does NOT support SSML ;
these textual cues convey pace.
- Skip metadata that does not belong in spoken playback
(document_id) but DO read page numbers explicitly on red-
flag and important-clause items so the listener can find
them.
- Target rate: ~145 words per minute for clarity.
Style direction: prepend ONE short directive sentence,
exactly like: "Read clearly and calmly, like reading back a
contract summary to a friend at a kitchen table. …". There
is no separate `style` API field; the directive sentence
inside the input is how style is conveyed.
Phoneme overrides for party names and legal Latinisms are
NOT exposed — no SSML . Pronunciation comes from
the chosen voice's locale.
```
---
### Call: Hero / empty-state illustration
Model: `gemini-3-pro-image` (Nano Banana Pro) · n/a · n/a
```
Generate a single photographic-looking image for the welcome
screen / empty state. The key visual element is the rendered
page-number reference in the margin of the paper — Nano
Banana Pro renders legible 4K typography (the I/O 2026 hero
capability).
Prompt anchors:
- "warm afternoon light on a clean light-wood desk, a printed
multi-page contract fanned slightly to the left, the words
PAGE 11 OF 47 printed clearly in the margin in small black
type, a white ceramic coffee mug at the right edge of
frame, a black ballpoint pen resting across the contract,
shallow depth of field"
- "a school-policy PDF printed and laid open on a kitchen
counter at golden hour, the words PAGE 6 OF 14 visible in
the footer, a child's drawing on a corkboard in the soft
background, no people in frame"
- "an employment offer printed and bound with a single
staple, cover page visible with the words OFFER OF
EMPLOYMENT legible in 18-point Garamond, a tablet beside
it, soft window light"
Hard rules:
- Photographic, not cartoon, not illustration-style.
- No people in frame unless explicitly asked.
- No real brand names or party names visible. Use plausible
fictional names ("Acme Property Management LLC",
"Northbridge Health Plan", "Crescent Public Schools").
- The page-number reference in the margin must be LEGIBLE —
this is the post-I/O 2026 Nano Banana Pro trick.
- Warm lighting, slight imperfection, real-paper texture.
Avoid the glossy AI-render look.
- Aspect ratios: 3:2 for hero, 1:1 for empty states.
```
## 5. Use cases & content to include
Build dedicated UI sections or flows for each — they tell you
what content the app must support.
- **The 47-page lease.** Drop a residential lease PDF. In ~18
seconds the brief returns. Summary in one paragraph; five
important clauses (deposit, rent increase, sub-let, repair-
and-deduct, early termination) with verbatim quotes and
page citations. Three decisions (annual rent increase,
notice period, pre-move-in repairs). Four questions for
the landlord (deposit-scheme, boiler servicing, guest cap,
rent-increase date). Red flags: high early-termination
penalty; strict short-let ban.
- **The 28-page employment offer.** A new-grad software
engineer drops an offer PDF. The brief explains base,
bonus, equity (4-year vest, 1-year cliff — verbatim from
p. 14), notice (90 days, p. 21), IP assignment (broad,
p. 24), non-compete (6 months / 50 miles, p. 25),
arbitration (mandatory, p. 27). The "ask the lawyer"
panel surfaces non-compete enforceability, IP-assignment
scope, arbitration carve-outs.
- **The 36-page health-insurance summary.** Brief surfaces
deductible ($1,500 / $3,000), out-of-pocket max ($7,500 /
$15,000), 80/20 in-network vs 60/40 out-of-network,
prescription tier structure, maternity, mental-health, and
the prior-authorisation list. Red flags include the high
out-of-network out-of-pocket and the four-medication-class
prior-authorisation requirement.
- **The 14-page school acceptable-use policy.** Parent drops
the policy. Brief explains what device usage is allowed,
what counts as a violation, what the consequences ladder
looks like, where parental consent is required. Decisions
for the parent: opt-in to the photo-release; sign the
device-loan agreement.
- **The 1-page government letter that is actually 4 pages.**
A tax notice. Brief surfaces amount owed (verbatim),
deadline (verbatim), appeal route, non-payment
consequences. Professional-questions panel lists the
tax-advisor questions.
- **The 38-page fund prospectus.** An investor drops the
prospectus. Brief surfaces the fee schedule (management,
performance, redemption, exit — each verbatim with
citation), lockup period, redemption frequency, risk-
factor list, and the "ask the tax advisor" panel.
- **Compare two leases.** A renter is choosing between two
apartments and drops both. The comparison brief surfaces
a per-field diff (rent, deposit, term, notice, pet,
early-term, guest, rent-increase) with each value quoted
verbatim and cited. Per-axis observations note which
lease is more permissive on each axis without picking a
winner.
- **Ask a question about the PDF.** After the brief renders,
the reader types "is the dog allowed in the courtyard?"
Q&A retrieves the relevant pages, finds the rule on p. 18,
returns a quote-grounded answer. The reader types "should
I sign this?" — Q&A refuses with
`refusal_requires_professional_advice` and reframes the
question for a lawyer.
- **Ask whether a clause is normal here.** The reader taps
the early-termination clause and asks "is two months'
rent normal in California?" The grounded-search call
returns a one-line answer with the typical-range
verbatim, citation URLs from grounding metadata, and
`professional_consultation_recommended: true`.
- **Read aloud.** A blind reader presses play. TTS reads the
brief in their chosen language at ~145 WPM, with section
signposts and natural pauses. Page numbers are read aloud
on red-flag and important-clause items so the listener
can find them.
## 6. Page structure
Build the following screens in this order.
1. **Landing / dropzone.** Hero photograph (printed contract
on a wooden desk with the legible "PAGE 11 OF 47"
reference, golden hour). Headline: "Drop a PDF in. Get
back plain English." Subhead: "And the three decisions
you have to make before you sign." A big dropzone; "or
paste a public PDF URL" link below it; "or pick from
device" link opens the file picker. Below: three small
badges — "We never train on your documents", "Session-
only by default", "Page-cited every claim". Below that:
three example PDFs to try without uploading (sample
lease, sample offer, sample health plan). Quiet "Sign in
to save" link at the bottom.
2. **Processing view.** A progress strip ticks through:
"Reading the PDF…", "Classifying document type…",
"Drafting the plain-English summary…", "Pulling the most
important clauses…", "Drafting the decisions and the
questions…", "Checking every quote against the source…",
"Done." The brief streams in section by section as each
part is ready. PDF renders in a side panel as a
thumbnail strip.
3. **Brief view (side-by-side).** Left: the PDF, scrollable
and zoomable. Right: the brief — summary, important
clauses, decisions, questions, red flags, money mentions,
professional-questions panel. Each citation is a
clickable chip that jumps the PDF to that page and
highlights the cited sentence. Hovering shows a thumbnail
preview.
4. **Reading-level + language toggles.** Top of the brief:
reading level (grade 5 / grade 9 / legalese-preserving)
and brief language (auto-detected from document or any
Gemini-supported language). Toggling re-runs the main
brief call in ~3 seconds.
5. **Ask a question.** Below the brief: "Ask a question
about this PDF" with a text input. Answers stream below,
each card with answer, citation chips, and a kind label
(quote-grounded / needs professional / not in document).
6. **Compare mode.** "Drop another PDF to compare" button.
On a second drop, the comparison brief generates and a
new tab appears. The comparison view shows the diff
table and per-axis observations.
7. **Saved library (signed-in).** Personal library of saved
briefs. Each card shows document type, filename, date,
brief language, summary excerpt. Click → brief view.
Sharing icon on each card.
8. **Shared view (read-only).** Public read-only view of a
shared brief. Same layout as brief view but no edit
controls. Q&A unlocked only if recipient signs in.
9. **Settings & privacy.** Default brief language, default
reading level, default brief retention ("session only" /
"save for 30 days" / "save indefinitely"). Privacy panel
restates the not-trained-on policy in plain English.
"Delete all my briefs and PDFs" button with 60-second
cool-off.
10. **Footer.** "Built for the PDF on your desk you have not
read yet." Privacy: "Your documents and briefs are
yours. We never train on them." Capabilities `(i)` icon
in header.
## 6b. First-visit onboarding
Show a **first-visit onboarding** the first time a visitor
lands (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 PDF → Plain English."
- Subhead: "Drop a PDF in. Get back plain English, the page
citations, and the three decisions you have to make before
you sign."
- One paragraph (≤ 60 words) explaining the job-to-be-done:
the brief surfaces facts, cites the page on every claim,
quotes dollar amounts verbatim, lists the decisions, drafts
questions for the other side, and flags any decision that
needs a lawyer or doctor. It does not give advice.
- Visual: a small annotated illustration of a brief card
with the citation chip, verbatim quote, and decisions
panel labelled.
**Slide 2 — Try it now.**
- One short prompt: "Try with the sample lease".
- A live demo input pre-loaded with the 47-page sample lease.
- 1-2 sentences pointing at *the specific page elements*
where the Gemini magic happens (brief streams in, the
page chip jumps the PDF, the verbatim-quote validator
catches a near-miss in the demo).
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Swap the sample PDFs in `/data/seed-pdfs/` for your own
documents."
- "Adjust the prompts in `/server/prompts/` to fit the
documents you read most often (court filings, IRS
notices, consent forms, etc.)."
- "Wire up your Gemini API key and (optional) Firebase
project via the env-var list in the capabilities panel."
- Primary CTA: "Use this template" → AI Studio Build remix.
- 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.
- Don't include unrelated CTAs (newsletter, social follow).
## 6c. Capabilities info button (persistent in header)
Add a persistent `(i)` icon in the top-right of the header.
Click → opens a modal/panel titled **"What powers this app"**.
**Gemini capabilities used (the hero list):**
- **Gemini 3.5 Flash (long-context PDF understanding)** —
reads the whole document in one pass and emits the
structured brief. 1M-token context, ~4× the speed of the
old 3.1 Pro for this task. Post-I/O 2026 this is the new
default.
- **Gemini 3.5 Flash (structured output)** — every brief is
a typed `DocumentBrief`; every comparison a typed
`DocumentComparison`; every Q&A answer a typed
`DocumentAnswer`. Schemas live in the repo.
- **Gemini 3.5 Flash + grounded search** — resolves "is
this clause normal here?" with sourced citation URLs.
- **Gemini embedding 2** (multimodal) — embeds long
documents page-by-page so Q&A stays cheap on follow-up
questions to 200+ page documents.
- **Gemini 3.1 Flash TTS** — reads the brief aloud in the
reader's chosen language with natural pauses.
- **Nano Banana Pro (`gemini-3-pro-image`)** — generates the
welcome-screen hero with legible in-image typography (the
post-I/O 2026 hero capability).
- **Firebase Auth** — optional Google sign-in for saved
library and shared links.
- **Firestore** — stores saved briefs and comparison history.
- **Firebase Storage** — keeps uploaded PDFs at upload
quality with a 24-hour auto-delete on session-only mode.
**Cost note** — see 6d. A 47-page lease brief is ~$0.09
cold; a 200-page prospectus ~$0.29; a 2-document comparison
~$0.15. Reading-level toggle re-runs use cached input
(~$0.04).
**Privacy note** — your PDFs and briefs are private to you
and anyone you explicitly share with. This app uses the
Gemini API on the paid tier, where Google does not use your
content for model training, per the Gemini API Additional
Terms.
**Not-a-substitute note** — this app surfaces facts and
flags decisions. It is NOT a substitute for legal, medical,
tax, or immigration advice. Whenever a question touches
those domains, the brief surfaces an "ask the lawyer / ask
the doctor / ask the tax advisor" panel with the not-a-
substitute disclaimer above it.
**Backend services this app depends on:**
- Auth: optional Firebase Auth — see section 4b
- Database: Firestore (signed-in mode) — see section 4b
- Storage: Firebase Storage (signed-in mode and large PDFs);
REQUIRES manual enable in Firebase console — AIS Build
does not auto-provision Storage today.
- Email: optional magic-link share emails — see section 4b
- Payments: not used in v1
- External APIs: Gemini API only
**Environment variables you'll need to configure:**
- `GEMINI_API_KEY` — your Google AI Studio API key
- `FIREBASE_PROJECT_ID` — optional (signed-in mode only)
- `FIREBASE_SERVICE_ACCOUNT` — optional (server-side only)
**Documentation links:** AI Studio Build docs; Gemini API
multimodal PDF input, long-context, structured output,
grounded search, embeddings, TTS docs; Firebase Auth /
Firestore / Storage docs.
**Accessibility:** same standards as the onboarding modal.
**Behaviour:**
- Always available — single click from anywhere.
- 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)
Gemini 3.5 Flash pricing (post-I/O 2026): $1.50 per 1M input
tokens, $9.00 per 1M output, $0.15 per 1M cached input.
Nano Banana Pro: ~$2/M input, ~$12/M output (per-image price
is approximate — Google has not pinned an exact public figure;
verify on the Gemini API pricing page before shipping).
Gemini 3.1 Flash TTS: approximate ~$10/M output (≈
~$0.000003/character; the exact TTS character-token price was
not pinned at I/O 2026, treat as a working estimate and verify
before shipping). `gemini-embedding-2`: ~$0.20/M input.
- **Document type classification** — first 3 pages ≈ 2.4k
input + ~150 output → ~$0.005 per call.
- **Main brief generation (47-page lease)** — ~35k input
($0.0525) + ~4k output ($0.036) → **~$0.09 per brief**. A
200-page prospectus at ~160k input + ~5k output → **~$0.29**.
- **Verbatim-quote re-extraction** — only when validator
rejects a quote. ~5k input + ~300 output → ~$0.01 per
rejection. Typical brief has 0–2 rejections.
- **Comparison brief, 2 documents** — ~70k input + ~5k
output → **~$0.15 per comparison**.
- **Comparison brief, 3-4 documents** — ~140k input + ~8k
output → **~$0.28 per comparison**.
- **Embedding pages** — for documents > 100 pages. ~1k
tokens per page × 200 pages = ~200k → ~$0.04 per long
document, one-time.
- **Q&A retrieval-augmented answer** — top-8 pages × ~1k
tokens + question + system instruction = ~10k input +
~400 output → ~$0.02 per question.
- **Grounded "is this clause normal here?"** — small input,
~$0.003 per call.
- **TTS playback** — a 600-word brief readback is ~$0.012
per playback.
- **Hero image (Nano Banana Pro)** — approximate ~$0.04 per
image (Google has not pinned an exact public per-image
figure; verify before shipping). Generated once per app
install (cached client-side) or on
regenerate-empty-state.
- **Cached-input savings on toggle re-runs** — when the
reader switches reading level or brief language, the PDF
is the same; cached input at $0.15/M makes the second
brief call ~$0.04 vs ~$0.09 cold.
- **Storage** — Firebase Storage standard tier ~$0.026/GB/
month. A 5 MB lease PDF ≈ $0.00013/month; a 30 MB
prospectus ≈ $0.00078/month. Session-only documents are
deleted within 24 hours and effectively free.
## 7. Design language
- **Mood:** A quiet desk, the contract laid open, the coffee
mug at the edge of frame. Not a SaaS dashboard. Not a
legal-tech portal. The kitchen counter at 9 pm, the PDF
printed out, a pen across the top page. A reader's
working surface between "I have not read this" and "I
have read this and I know what to ask".
- **Typography:** Clean grotesque for app chrome and data
labels (Inter or Geist). Display serif for section
headings on the brief (Source Serif Pro or Fraunces). A
monospace voice (JetBrains Mono or SF Mono) for verbatim
quotes — to mark the difference between the reader's
working language and the PDF's own words.
- **Palette:** Paper white `#FAFAF7` for the brief surface,
deep ink `#16161A` for body, warm amber `#A37430` for
headings and citation chips, deep navy `#2A3957` for
hyperlinks and active controls, muted green `#577A4B` for
"informational" red-flag severity, muted ochre `#C08A2E`
for "watch", warm red `#A33A2C` for "high" severity and
for the not-a-substitute disclaimer chrome. Borrowed from
a paper notebook, not from SaaS design systems.
- **Imagery:** Photographic. A contract on a desk at golden
hour. A school policy on a kitchen counter. Generated via
Nano Banana Pro with prompts emphasising real surfaces,
warm light, no people, no real brand names, and the
legible page-number reference in the margin as the post-
I/O 2026 hero detail.
- **Hand-feel touches:** Each new section of the brief drops
in with a thin paper-shadow. Citation chips have a tiny
page-corner fold graphic. Hovering shows a thumbnail of
the PDF page with the quoted sentence subtly underlined.
The red-flag list uses a small vertical accent stripe in
the severity colour, never a heavy alert banner.
- **Spacing:** consistent 4-px base. Generous whitespace.
- **Radius:** token set (6 / 12 / 20 px). Citation chips 6;
brief section cards 12; hero card 20.
- **Shadows:** subtle, layered, warm-tinted.
- **Motion:** purposeful — entrance fades, hover lifts.
Respect `prefers-reduced-motion`. The streaming-section
landing animation is the place motion carries meaning;
reduced-motion users see each section jump in instantly.
- **States:** every interactive element has hover, focus,
active, disabled. Loading uses skeletons not spinners.
Empty states have helpful next-action guidance.
## 8. Content generation rules
- Write **realistic, specific copy**. NO Lorem Ipsum. NO
generic placeholders.
- Invent plausible names, party names, page numbers, dollar
amounts, and verbatim quotes that fit the domain. Party
names are fictional ("Acme Property Management LLC",
"Northbridge Health Plan", "Crescent Public Schools").
Dollar amounts and dates are plausible-but-fictional.
- Tone: warm, direct, free of corporate language. The reader
is reading the brief because they did not want to read the
PDF — respect their time and never pad.
- Headlines: punchy and concrete. No 'Empower your X' filler.
No 'Revolutionize'. No 'Seamless'. No 'AI-powered'.
- Body copy: short paragraphs (2-4 sentences). Lists where
appropriate.
- Plain language. Avoid jargon — except in `legalese-
preserving` mode where legal terms appear with an inline
gloss.
- Never label AI output as "AI says" — let it speak
naturally. Use small uncertainty cues only where
epistemic honesty requires them (a low-confidence
citation shows as a faintly outlined chip; tapping
reveals the validator's note).
## 8a. Seed content (use these specific examples)
**Sample documents available without sign-in:**
1. **"47-page residential lease — Acme Property Management"**
(document_type: residential_lease, language: en-US)
- Monthly rent: "$2,450.00 due on or before the first
(1st) day of each calendar month" — p. 3
- Security deposit: "Two thousand four hundred and fifty
dollars ($2,450.00), held in a New York State
licensed deposit account" — p. 11
- Deposit return window: "no later than fourteen (14)
days after the surrender of the premises" — p. 11
- Annual rent increase: "shall increase by three percent
(3.0%) on each anniversary of the Commencement Date"
— p. 14
- Late-rent fee: "five percent (5%) of the monthly rent
if not received by the fifth (5th) day of the month"
— p. 14
- Sub-let / short-let: "No Tenant shall sub-let,
license, or otherwise permit short-term occupancy
(including any rental of one or more nights through
an online platform) of the Premises. Violation
results in liquidated damages equal to one (1)
month's rent." — p. 22
- Repair-and-deduct: "if Landlord fails to make any
repair required by law within thirty (30) days of
written notice, Tenant may make such repair and
deduct the reasonable cost from the next month's
rent, provided such deduction shall not exceed one
(1) month's rent in any month." — p. 31
- Early termination: "liquidated damages equal to two
(2) months' rent" — p. 39
- Decisions: 3% annual increase OK? 60-day move-out
notice OK? Pre-move-in repairs in writing?
- Questions for landlord: which deposit-protection
scheme? boiler servicing? guest cap? rent-increase
date?
- Red flags: HIGH — early-termination two months' rent
(p. 39); WATCH — short-let ban covers single
overnight (p. 22).
2. **"28-page employment offer — Crescent Software Inc."**
(document_type: employment_contract)
- Base salary: "$135,000 per annum" — p. 2
- Annual bonus: "up to twenty percent (20%) of Base,
subject to Company and individual performance" — p. 4
- Equity: "30,000 RSUs vesting over four (4) years with
a one (1) year cliff, with twenty-five percent (25%)
vesting on the first anniversary and the remainder
vesting in equal quarterly instalments thereafter"
— p. 14
- Notice: "ninety (90) days' written notice" — p. 21
- IP assignment: "Employee hereby assigns to Company
all right, title, and interest in any Invention
conceived, developed, or reduced to practice during
the Term, whether or not during working hours and
whether or not on Company premises" — p. 24
- Non-compete: "during the Term and for six (6) months
following the Termination Date, within a fifty (50)
mile radius of any office at which Employee
performed services" — p. 25
- Arbitration: "any dispute arising out of this
Agreement shall be resolved exclusively by binding
arbitration administered by the American Arbitration
Association in San Francisco, California" — p. 27
- Decisions: IP off-hours and off-premises OK?
Non-compete OK? Waive jury trial via arbitration OK?
- Questions for employer: narrow non-compete to direct
competitors? carve-out personal projects off-hours,
off-equipment? severance during notice?
- Professional questions (ask the lawyer): 6-month
non-compete enforceable in your jurisdiction? IP
scope consistent with state law? arbitration carve-
out for statutory claims (discrimination,
whistleblower)?
3. **"36-page health-insurance summary — Northbridge Health"**
(document_type: insurance_terms)
- Monthly premium (individual): "$420.00" — p. 2
- Deductible: "$1,500 / $3,000" individual / family
— p. 4
- Out-of-pocket max: "$7,500 / $15,000" individual /
family — p. 4
- In-network coinsurance: "80% / 20%" — p. 6
- Out-of-network coinsurance: "60% / 40%" — p. 6
- Prescription tiers: "Tier 1 generic $10, Tier 2
preferred brand $40, Tier 3 non-preferred brand $80,
Tier 4 specialty 30% coinsurance to a maximum of
$300 per fill" — p. 12
- Prior-authorisation list: "all Tier 4 specialty
drugs, all inpatient hospitalisations, all
outpatient surgeries exceeding $5,000, and all
advanced imaging (MRI, CT, PET)" — p. 28
- Decisions: $7,500 individual OOP max OK? any current
prescriptions in Tier 4? current providers in-
network?
- Questions for insurer: current PCP in-network?
prescriptions on prior-auth list? prior-auth
turnaround time?
- Professional questions (ask the doctor): generics in
Tier 1/2 for current meds?
**Sample voice copy:**
- Landing dropzone: "Drop a PDF in. Or paste a public PDF
URL. Or pick from device."
- Pre-upload privacy line: "Your document stays in your
browser session and is deleted when you close this tab,
unless you sign in and save it."
- Processing micro-copy: "Reading 47 pages…" / "Pulling the
most important clauses…" / "Checking every quote against
the source…"
- Save confirmation: "Brief generated for Acme lease. 5
clauses cited. 3 decisions for you. 4 questions for the
landlord."
- Validator caught a near-miss: "We adjusted one citation
— the model quoted a sentence that lives on p. 13, not
p. 11. Corrected automatically."
- Reading-level toggle: "Re-reading at 5th-grade level…"
(~3-second toggle).
- Q&A refusal (legal): "This is a question for a lawyer
who knows residential lease law in your jurisdiction.
We have rephrased it as: 'Is the liquidated-damages
clause on p. 39 enforceable in New York?'"
- Q&A refusal (not in document): "This document does not
mention pet deposits. The pet rule on p. 18 covers
presence, not deposit."
- TTS preamble: "Read clearly and calmly, like reading
back a contract summary to a friend at a kitchen
table…"
- Share email subject: "I'd like your read on the Acme
lease — here's the plain-English brief"
- Capabilities tooltip: "How this app is built — what
Gemini capabilities and Firebase services it uses, what
it costs, and what it does with your documents."
- Privacy banner (signed-out): "Session-only by default —
your PDF and brief disappear when you close this tab."
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking
shot of a printed contract on a clean light-wood desk at
golden hour. The legible "PAGE 11 OF 47" reference is in
the margin of the open page (Nano Banana Pro renders this
legibly — the I/O 2026 hero detail). A white ceramic
coffee mug at the right edge; a black ballpoint pen
across the top. Generate via Nano Banana Pro with the
prompt anchors in 4b.
- **App icon / wordmark:** Set in the display serif. Slight
paper texture behind it. No icon — just type.
- **Empty-state illustration:** A simple line drawing of a
rolled paper with a citation chip beside it. Hand-drawn
aesthetic, not a flat icon. Generate once at build time via
Nano Banana 2 (`gemini-3.1-flash-image`), 1:1 WebP at
1024×1024, prompt: "single hand-drawn ink line illustration
of a rolled-up paper scroll on the left and a small
rectangular pill-shaped citation chip on the right with a
tiny page-number glyph inside, off-white paper background,
slight pen imperfection, no shading, no colour fill, no
commercial branding". Ship as a seed asset at
`/public/samples/empty-state-scroll.webp`.
- **Demo PDFs:** The three seed PDFs (47-page lease, 28-page
offer, 36-page health plan) live in `/data/seed-pdfs/`.
Plausible-but-fictional documents authored for this
template; safe to ship.
- **Stock fallbacks:** If image generation fails, fall back
to `/public/samples/desk.jpg` (3:2 WebP, 2048×1365 — ship as
a seed asset; recreate via Nano Banana Pro
(`gemini-3-pro-image`) with the prompt: "photographic
printed contract on a clean light-wood desk at golden hour,
legible 'PAGE 11 OF 47' rendered crisply in the open page's
margin at 4K, a white ceramic coffee mug at the right edge,
a black ballpoint pen across the top of the page, no people,
no commercial branding, real paper texture, soft shadow
under the mug"). Never a "📄" emoji.
- **Generated imagery:** prefer Nano Banana Pro over stock
photography. Use the legible-typography capability for the
page-number reference detail — this is the post-I/O 2026
feature that justifies the model choice.
- **Optimisation:** WebP/AVIF, `loading="lazy"`, explicit
`width`/`height` to prevent layout shift.
- **Icons:** `lucide-react` for UI. Use sparingly.
## 10. Interactivity & states
- Every interactive element has hover, focus, active, and
disabled states.
- Forms validate inline with specific error messages. "This
PDF is encrypted — enter the password to decrypt client-
side" is the right shape.
- Loading states use skeletons matching the eventual layout,
not spinners. The brief streams in section by section;
each section's skeleton is replaced as the section
arrives.
- Empty states explain the next action with a domain-
specific button label: "Drop a PDF in to start", "Try
with the sample lease", "Ask a question about this PDF"
— never a generic "Add your first item".
- Smooth scroll for in-page anchors.
- All AI-generated content streams in section-by-section
with a clear "thinking…" indicator before content
arrives.
- If an AI call fails, show a calm, specific error ("The
brief could not finish because the document is in a
language we couldn't detect. Want to try selecting the
language manually?") and offer retry.
- Low-confidence citations show as faintly outlined chips;
tapping reveals the validator's note.
- The streaming-section landing animation takes ~250 ms
with `prefers-reduced-motion` falling back to instant.
- The not-a-substitute disclaimer surfaces as a non-
dismissable inline banner above the "ask the lawyer /
doctor / tax advisor" panel — not a toast that scrolls
off-screen.
- Citation chips on hover show a thumbnail preview; on click,
the side-by-side PDF viewer jumps to that page and
highlights the cited sentence with a soft underline (not
a yellow highlighter — more readable on printed text).
- Reading-level and brief-language toggles re-run the main
call in ~3 seconds; the brief sections re-stream in the
new shape.
- "Ask a question" answers stream below the question card;
each answer has a kind label (quote-grounded / needs
professional / not in document) with a small coloured
dot for at-a-glance recognition.
- Compare-mode opens as a new tab beside the per-document
tabs; diff table is scrollable horizontally on narrow
viewports with sticky first column.
- TTS playback exposes a transcript that scrolls in sync;
tapping a paragraph jumps the audio to that point.
## 11. Tech & responsive requirements
- **Stack:** React + TypeScript + Tailwind CSS. Functional
components + hooks. Shadcn UI primitives where
appropriate. PDF rendering via `react-pdf` (wraps
`pdf.js`).
- **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. First two apps deploy free to
Cloud Run with no credit card (post-I/O 2026 AI Studio
Build default).
- **Model selection:** explicitly pin `gemini-3.5-flash`
for brief generation, classification, comparison, Q&A;
`gemini-embedding-2` for page embeddings; `gemini-3.1-
flash-tts-preview` for TTS; `gemini-3-pro-image` for the
hero. Set `thinkingLevel` explicitly per call. Do NOT
wire `gemini-3.5-flash` — pre-GA until June 2026.
- **PDF handling:** client-side render via `pdf.js`. Page-
text extraction is done client-side via `pdf.js` and the
extracted text is included in the server payload
alongside the multimodal PDF input, so the verbatim-
quote validator has the source-of-truth text. For PDFs
> 20 MB, upload to Firebase Storage and then server-side
re-upload to the Gemini Developer API Files API; pass the
resulting `files/*` resource name via `fileData.fileUri`
to `generateContent`. Encrypted PDFs decrypted client-side
before upload.
- **Local save fallback (FSA non-Chromium):** the "save
summary as PDF / markdown" affordance uses
`showSaveFilePicker()` on Chromium; on Safari / Firefox,
fall back to an `` element pointed at a blob
URL — FSA `showSaveFilePicker()` is not supported there.
- **Database:** Firestore (auto-provisioned by AI Studio
Build). Signed-in mode only.
- **Auth:** Firebase Auth — Google sign-in by default;
Apple sign-in optional; magic-link email for share
links.
- **Storage:** Firebase Storage for PDFs > 20 MB and
signed-in saved documents. Pre-signed URLs only.
Session-only PDFs stored under a `session/` prefix with
a 24-hour Lifecycle rule.
- **Streaming:** Gemini SDK streaming on the main brief
call so the reader sees the summary land while clauses
are still being drafted.
- **Mobile-first.** Verify at 375 px (iPhone SE), 768 px
(iPad), 1024 px, 1440 px+.
- Use `clamp()` for fluid typography. Prefer container
queries over media queries for component-level
responsiveness.
- Use `dvh` / `svh` instead of `vh`. Respect safe-area
insets on iOS.
- Zero horizontal overflow at any width. Zero layout shift
on load.
- Persist user data in Firestore (signed-in mode). Real-
time listeners on the saved-library view.
- Optimistic UI on saves; reconcile on response.
- Side-by-side PDF + brief view collapses to a stacked
layout below 768 px, with the brief on top and the PDF
pages accessible via a tappable "Open page 11" chip.
## 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. Red-flag severity stripes meet 3:1
against the paper-white background.
- All images have meaningful `alt` text. The hero's `alt`
describes the artefact ("photographed printed contract
on a wooden desk at golden hour, page number 11 of 47
visible in the margin, white coffee mug at the edge of
frame").
- Form fields have associated `