posted an update

ProjectMath — Build Log A LeetCode-style practice platform for Singapore H2 A-Level Maths. This is the story of how it got built, in public, from the first commit to today.

8 June 2026 → 8 August 2026 · 227 commits · ~500 questions live

June 8–15 · The foundations The first week was about proving the core loop works: pick a topic, answer a question, find out if you're right.

Express + TypeScript + Supabase backend — the first commit. Topics, questions, attempts. React + Vite frontend with KaTeX rendering, so maths looks like maths and not like code. The roadmap — the whole syllabus as a visual tree you pan and zoom around, rather than a boring list. Click a node, a drawer slides out with the concepts and every question in that topic. 24-topic H2 syllabus loaded in, with prerequisite concepts mapped per topic. Starred questions — bookmark anything you want to come back to. A visual maths keyboard (MathLive) — typing \frac{x}{5} on a laptop is miserable, so there's a proper 10-group symbol palette that inserts real LaTeX for you. Progress bars per topic. Multi-part questions — real exam questions have (a), (b)(i), (b)(ii). The app now models them properly: a shared preamble, per-part answer boxes, inline ✓/✗, and the solution only unlocks once every graded part is done. June 15–23 · Real papers, and getting grading right First real paper: ASRJC Prelim 2025 (Papers 1 & 2), followed by DHS Prelim 2025. Answer checking, reworked twice. Students write the same answer ten different ways. The checker learned numeric equivalence, or/and normalisation inside inequality chains, tolerant spacing, and MathLive's compact notation (\frac34 → \frac{3}{4}). All unit-tested. Solution tab + retry button — answers moved out of the main view so you can re-attempt a question cleanly instead of being spoiled the moment you submit. Attempts & Hints tabs alongside the question. Streaks and stats — daily streak tracking with a GitHub-style contribution heatmap. June 22–24 · The AI arrives Two big features landed back to back, and both were designed so the API key never leaves the server.

AI hint chatbot — a Socratic tutor that sits beside the question. It knows the question, the parts, and the confidential model solution, and is explicitly instructed to give one small hint at a time and never the final answer. Off-topic and jailbreak attempts get refused. Photo-based AI grading — the headline feature. Photograph your handwritten working; Gemini grades it against the stored model solution as an examiner, not a solver. It returns a per-part verdict, marks awarded, every error pinned to the step it happened on, and hints. Sketches are checked for labelled intercepts, asymptote equations, stationary points and shape. Blank photos, desk clutter and unrelated pages get filtered out and explained rather than graded. Upload via phone — desktops don't have cameras. Scan a QR code, your phone opens a capture page, and photos stream to your desktop live over WebSockets. Hit Done on the phone and the grade appears on the laptop. June 24–28 · Studying smarter Accuracy by topic — stats page now shows where you're actually weak, not just what you've finished.

Review page with 5 modes:

Corrections on questions you got wrong Practice on your weakest topics Re-attempt the solved questions that took you longest Spaced-repetition review Question randomiser Each mode locks itself if you have nothing in that bucket, rather than dumping you on an empty screen.

SM-2 spaced repetition — the Wozniak algorithm (the SuperMemo/Anki one) replaced a simpler Leitner schedule. Card state updates in the background every time you complete a question.

AI weakness diagnostic — Gemini reads your per-topic accuracy and returns a weak/moderate/strong breakdown with written insight. Locked until you've done 5 questions, so it has something to work from.

AI study plan — a quest-log of up to 10 AI-picked questions from your weakest topics. Rows tick themselves off as you get them right. Lives in a collapsible sidebar available anywhere in the app, and syncs to your account so it follows you between devices.

June 26–30 · Accounts Firebase Authentication end to end. The old anonymous session ID is gone; every request carries a verified ID token, and your history, streaks, stars, chats and gradings are tied to a real account. Editable transcription for photo grades — Gemini now transcribes your handwriting into LaTeX and shows it back to you in a rendered, per-line editor. If it misread a character, fix it and re-grade instantly without re-photographing anything. Dark theme redesign — a proper dark indigo palette, Bricolage Grotesque headings, status-styled roadmap nodes (completed / in-progress / up-next / locked), SVG icons throughout. Landing page at /, with auth-aware navigation and honest copy. June 30 – July 4 · More typeable answers, and CI Typed submissions for parts that used to be un-typeable, plus multi-value answer boxes — questions like "find a, b and c" now render one labelled box per value, and the part is only correct when every box matches. Accurate roadmap progress bars — fixed per-part correctness accounting so the bars reflect reality. GitHub Actions CI with a vitest suite over the grading logic. Model solution graphs. Sketch questions used to just say "sketch the curve" and then show you nothing. Now every sketch answer renders an actual generated graph — with asymptotes, labelled intercepts, stationary points, open/closed endpoints, parametric curves, Argand diagrams, scatter plots and shaded regions. Verified against the official mark schemes. July 3–9 · Payments Stripe integration — Checkout and Customer Portal, card and PayNow (Singapore's local rail). Pricing settled at $5/month and $25/semester (the annual plan was dropped). PayNow rollover and plan-switching. Buying PayNow while you still have PayNow time stacks on top instead of restarting. Switching from card to PayNow schedules the card to end and starts PayNow the day it does — no gap, no double-billing. Switching PayNow → card sets a trial so the card isn't charged until your PayNow period actually runs out. Transactional emails via Resend — welcome/induction, first-purchase congrats, transaction receipts (including renewals), and a daily cron that reminds PayNow users before their access expires. Each send is claimed atomically so nothing double-fires. Profile page — email, member since, plan, days left to renewal, billing portal link, today's AI usage, streak heatmap and per-topic accuracy, all in one place. Feedback form in the header, straight to email. Correct-answer tick celebration and animated streak UI. Small thing, feels good. July 5–11 · Making the free tier fair (and abuse-proof) AI calls cost real money, so this stretch was about metering them honestly instead of slamming a paywall down.

Metered free tier rather than a hard block: free accounts get 3 AI scans and 3 chat messages per day, resetting at midnight Singapore time. Paid is unlimited. Rejected scans don't count against you. Per-user cooldowns (5s for hints, 60s for grading) plus a shared request queue for all Gemini traffic — if the app is busy, your request waits in line with a friendly message instead of failing. Hint chat history resets each time you open it, by design, so the panel isn't a wall of old text — but nothing is ever deleted server-side, so limits can't be reset by reopening. Rate limits bucketed by account, not IP. Two students behind the same school NAT were sharing one bucket and starving each other. Fixed. Tier is read from the database, never from the auth token. A token caches its claims for up to an hour, which meant a cancelled subscriber could keep paid access for another hour. Now every request derives access from the live subscription row. Stripe webhooks made idempotent with an event ledger, so a replayed payment event can never grant two billing periods for one payment. Typed re-grades paced properly — fixing a transcription typo is instant, but the exemption is spent per scan so the route can't be used as an uncooled free grader. Generic error messages to the client instead of raw internal ones. July 11–14 · The content explosion Ten days, nine more question sets.

Prelim papers added: YIJC · EJC · JPJC · NJC · NYJC · VJC — joining ASRJC, DHS, HCI, ACJC, CJC and RI. Twelve schools, Papers 1 and 2 each.

Tutorial discussion questions (non-exam, provenance stripped) across four strands:

Functions & Graphs — parametrics, systems of equations, graphing techniques, functions, transformations, conics, inequalities Calculus — differentiation techniques and applications, Maclaurin series, integration techniques, definite integrals, differential equations Statistics — permutations & combinations, probability, discrete random variables, binomial, normal, sampling & estimation, hypothesis testing, correlation & regression Vectors (basic / lines / planes), Sequences & Series, and Complex Numbers Given diagrams in prompts. Loads of questions say "the diagram shows…" and then ship no diagram, because the figure lived in a PDF. Those diagrams are now reconstructed and rendered in the question, using the same engine as the solution graphs — including two 3-D vector scenes (a triangular canopy, an inclined roof slab) drawn as the exam's own oblique projection, with the projection basis measured off the original figures so the reproduction matches the paper.

Solutions reformatted so each part label starts on its own line instead of running together in one block.

July 13–19 · Security hardening and going live Row-level security enabled across the database, with all public grants revoked — the backend service is the only client. Auth required on the question and solution endpoints. The ID patterns were predictable enough that the whole question bank was scrapable without an account. Global backstop rate limiter over the entire API surface, plus helmet security headers. Length caps on all user-supplied strings. Deployed to Google Cloud Run as a single container with WebSockets intact, plus a repeatable deploy script and push-to-main continuous deployment. Card-only launch — PayNow put behind a feature flag for the initial release. Landing page copy cleanup and footer disclaimer. August · Polish More questions made typeable — a review of every "find…" part that the checker can grade robustly (± value pairs, inequality chains, symbolic expressions, coordinate and vector components) unlocked 24 more parts for typed answers instead of just revealing the answer. Roadmap viewport fixes — stale height on async layout shifts, and lateral scrolling / zoom on the roadmap. Responsive landing page, replacing a shrink-to-fit hack. Header no longer clips the account dropdown.

Log in or sign up for Devpost to join the conversation.