Work03LiveApr – Sep 2026
DojoPrep
Interview prep generated from your real résumé: live voice mock interviews, coding interviews where the code actually runs, job-fit scoring, and a Chrome extension that fills applications.

- 3
- clients on one API: web, extension, desktop
- 7
- languages the coding interview can run
- 23
- centralized system prompts
- Free
- fit check, no account needed
The product
call by call
Problem
Interview prep is generic. Candidates rehearse someone else's questions from a list, never hear themselves answer under pressure, and are told what they want to hear. Then they spend the evening re-typing the same résumé into the same application forms. The product's promise on the landing page: walk into your next interview actually prepared.
Discovery
The build started from a one-month launch plan written on 29 April, and the plan changed as the product met users.
- Scope with a kill switch. Version one was the web prep loop, billing on day one, and a desktop live-interview mode. The plan carried a hard rule: at the end of week two, if more than three days behind, cut live mode entirely and ship web-only. Live coding, a Windows build, mobile and job-description-aware questions were deferred on paper before any code existed.
- Cost tiers designed before features. Each plan carried an AI cost ceiling with limits enforced server-side, so no user could cost more than their plan. Prices moved up from the plan as the live voice and coding features turned out to be the product.
- Honesty is the wedge. The demo script's opening line is "Most interview prep tells you what you want to hear. This one doesn't." The two beats that sell are the interviewer pushing back and an unflattering, specific report. The public fit-check lead magnet returns honest scores in the low sixties rather than flattering ones.
- What live usage surfaced. Coding interviews abandoned mid-way left a "Scoring" spinner that never resolved. Slow problem generation made browsers give up before the server finished. Sessions stuck forever on "writing your feedback report" because the server marked them complete and then died generating the report. The first working voice interviewer interrupted itself on laptop speakers.
- Then the diagnosis changed. The next-steps document, written after the product was deep, says the bottleneck is now users and learning, not features: instrument the funnel before building more, get a first run to an "aha" in sixty seconds, post the free fit-check where engineers ask how to prepare, and generate public "how this company interviews for this role" pages from the company-brief engine that already exists.
Decisions
- Never auto-submit an application. The extension scans and fills, attaches the tailored résumé, and stops. Submitting is the candidate's act; automating it violates ATS and LinkedIn terms and risks their account.
- Honest over confident. When the profile can't support a screening answer, the extension says it needs you rather than guessing. Each answer is asked once and remembered forever. The report gives a 6.5, not a 9.
- Distrust the generated grader. The model writes both the coding problem and the harness that judges the candidate, so the harness runs in a real sandbox before the session starts, must print its summary line, and is regenerated once if it doesn't.
- Meter voice by the minute, idempotently. The browser talks to the realtime model directly over WebRTC; the server mints short-lived tokens and counts minutes through a heartbeat that cannot double-charge.
- Credits with per-action costs, and fail-closed budgets on anything public. Anonymous AI endpoints run behind budgets so a demo cannot become a bill.
- Two execution backends, switchable without a deploy. A self-hosted runner for seven languages and Vercel Sandbox as automatic fallback, so the coding interview keeps working when one is paused.
- One API for three clients. Web, Chrome extension and desktop share the same routes, with long-lived tokens exchanged from one-time codes.
- Stop building; go get users. The current plan puts programmatic SEO, onboarding and funnel instrumentation ahead of every deferred feature.
Outcome
Live at dojoprep.com with live billing. Voice interviews run end to end with a scored report. Coding interviews execute real code in a sandbox. The Apply extension, at version 0.2.0, fills real application forms on a third-party site and tracks what was applied to, deduplicated by canonical URL. A scripted public demo plays a full mock interview for anyone who wants to see it without signing up.
Built by hand
The same person made the product calls above and wrote the code below, working daily in Claude Code. Quotes are verbatim from commits and code comments.
Stack
- Next.js 16 · React 19 · TypeScript (Vercel)
- Neon Postgres · Drizzle · pgvector
- Clerk
- Upstash Redis
- Stripe
- Vercel AI SDK v6 (Claude Haiku / Sonnet / Opus, OpenAI Realtime, embeddings)
- Vercel Sandbox (optional self-hosted Piston)
- Electron desktop · Chrome MV3 extension · pnpm monorepo
Three problems
worth telling
A realtime voice interviewer that doesn't fight itself
The live mock interview streams two-way audio over WebRTC. The first working version had a farcical failure mode: on laptop speakers the mic picked up the interviewer's own voice, the server's voice-activity detection read it as the candidate talking, and the interviewer interrupted itself in a loop.
“Echo cancellation is CRITICAL: without it, on speakers the mic picks up the interviewer's own voice, the server VAD reads it as the candidate talking, and the interviewer interrupts itself in a loop. AEC + noise suppression + AGC stop that.”
Audio was half the fight. The stream delivers the same finalized utterance more than once, out of order around turn boundaries, so transcripts came out garbled. Three commits show the iteration, one of them titled "definitive" transcript dedupe, the word betraying that the first attempt wasn't. The final design is one pure function applied at all three boundaries, capture, save and render, instead of patching each symptom.
The deepest bug came last: sessions stuck forever on "writing your feedback report".
“True idempotency: only no-op once a report actually EXISTS. A session can be 'completed' but reportless if the original finalize died mid-generation (Vercel kill / navigation) — those must be able to regenerate, not spin forever.”
Trusting a model to write code that grades code
The coding interview asks a model to generate a problem and the test harness that judges the candidate, and a broken harness silently ruins the interview. The answer wasn't better prompting; it was distrust by design: the generated harness runs in a real sandbox before the interview begins, must run to completion and print its summary line, and is regenerated once on failure. Execution became a two-backend abstraction, and even slow generation got a safety net that recovers the session when the browser gives up before the server finishes.
Automating job applications without a bot
Every applicant-tracking system renders forms differently, and React-based forms ignore naive value writes. The design that emerged: heuristic field classification with per-site adapters, native-setter plus synthetic-event writes so React state updates, résumé PDFs injected through a data transfer, and deduplication on canonical URLs that converge two forms of the same job link onto one key. Chrome fought back: the active-tab permission evaporates on single-page navigation, so the extension moved to per-site opt-in grants, and a stale-manifest state that produced an unexplainable error is now detected and explained to the user.
Screens

The public fit-check: paste a résumé and a job description, get an honest score and your top gaps, no account needed 
Landing page