The Analysis Report
The saved, source-of-truth analysis every page of this course was written from. Analyzed 2026-07-14 at commit c32119a β 41 commits, ~12,000 lines of TypeScript.
Wera is a personal-but-SaaS-shaped internship application OS: Next.js app + five packages, Postgres via Drizzle, Clerk auth behind a seam, Trigger.dev background jobs, a sandboxed LaTeXβPDF pipeline, and an AI boundary that validates everything and logs every cent. It was built spec-first, riskiest-link-first.
What it is
A hosted web app for one CS student (architected for many) that runs the volume-with-quality loop: discover fresh internship postings fast, tailor a resume grounded in real profile evidence, apply ASAP, and see momentum on a scoreboard. The core object is an application, not a document; tracking exists to motivate volume, not to be a CRM.
Next.js server components read from stores; ~30 server actions write. No REST layer at all.
Status state machine, needs-flags math, evidence resolution. Zero I/O, 100% unit-testable.
Drizzle schema (23 tables) + four store factories. Every query scoped by user_id.
Budget guard β prompt β zod validation β retry once β typed error. Every call logged to ai_runs.
Six job sources behind one typed interface; unparseable items quarantined with raw JSON kept.
Tectonic subprocess with shell-escape blocklist, secretless env, hard timeout, hermetic package cache.
Tech stack & why
| Technology | Role | Why it won |
|---|---|---|
| Next.js 15 + React 19 | App, server components, server actions | Hosted from day one; server actions replace an entire REST layer |
| PostgreSQL (Neon) + Drizzle | Data + ORM | Managed Postgres over an HTTP driver β no connection pool on serverless; schema as TypeScript source of truth |
| Clerk | Auth | Fastest hosted auth, deliberately isolated behind getCurrentUser() so migration stays possible |
| Trigger.dev v4 | Background jobs | Compile, discovery cron, reminders live outside serverless request lifecycles; custom build extension bakes the LaTeX toolchain |
| Tectonic 0.16.9 (pinned) | LaTeXβPDF | Self-contained engine with --untrusted and --only-cached β sandboxable and hermetic |
| Cloudflare R2 | PDF storage | S3-compatible, cheap egress, keys namespaced {userId}/β¦ |
| OpenAI (behind a seam) | Production AI | A deterministic heuristic client serves dev/test; the interface hides which one is running |
| Zod | Validation everywhere | Connector inputs, raw items, AI outputs, task payloads β nothing unvalidated crosses a boundary |
| Vitest + Playwright + PGlite | Tests | PGlite gives a real in-process Postgres, so integration tests need no Docker or network |
The monorepo, annotated
The build order (reconstructed from git)
The product spec went through engineering, design, and cross-model review β findings folded back in β before scaffolding.
The least-proven link (LaTeXβPDF in the target runtime) proven first, with hermetic caching and escaping.
23 tables and the Clerk seam. Everything downstream needs user_id to exist.
Manual import β AI analysis β evidence-gated tailoring β PDF β mark applied β scoreboard. The product was usable here.
Five advisor plans as parallel branches: CSV helpers + tests, ingest caps, fetch timeouts, SSRF guard, PGlite harness.
Connector interface, Greenhouse/Lever/public lists, scheduled runs, dedup + posting lifecycle, then Apify/Instagram bolt-ons.
CRM detail, tasks and reminders with hourly delivery, communication drafts, the approval gate, weekly planning.
Audit findings landed as style(design) commits; the "instrument-alive" motion system came last, driven by user feedback.
Patterns the codebase teaches
Idempotency keys everywhere
Deterministic keys + unique indexes turn retries from hazards into no-ops: sourceRun:scheduled:{key}:{day}, system:{app}:{type}.
Errors as values
AiResult, CompileResult, SourceRunResult are discriminated unions. throw is reserved for programmer errors.
Store factories + injected seams
createApplicationStore(db = createDb()). Process runner, AI client, fetch, clock β all injectable, all testable offline.
Quarantine over rejection
Parse failures become stored artifacts with raw payloads β schema drift is a visible health metric, not a 6 a.m. exception.
Receipts for AI claims
Every generated bullet carries evidenceRefs that must resolve to real profile items β hallucination is a type error.
Deploy-time proofs
The image build re-compiles with --only-cached to prove the LaTeX cache is complete β a broken cache fails the deploy, not a user.
Real bugs preserved in the history
| Bug | Root cause | Where it's taught |
|---|---|---|
| Saved-source provider poisoning | Run ingestion upserted the connector row and overwrote the saved provider with an internal string β the source vanished from scheduled discovery | Module 5 |
| CSV historical rows counted as applied-today | appliedAt omitted vs explicitly null mean different things; naive defaulting faked dates | Module 6 |
| Template not found in deploy bundle | Repo-relative paths don't exist inside the deployed task image (open TODO) | Module 4 |
| Serverless timeout on big lists | Unbounded public-list ingestion; fixed with manual/scheduled caps | Module 5 |
| Week boundary miscounts | Local-timezone getDay() vs UTC-stored appliedAt; fixed with UTC week math | Module 6 |
| One bad timestamp aborts a query | SQL ::timestamptz cast fails the whole statement; fixed by parsing per-row in JS | Module 5 |
The portable markdown version of this report lives at analysis/codebase-analysis.md in the course folder β same content, plus verified primary-source links and per-component deep technical notes.