SkillBridge AI — Test Prep Academy Platform
Multi-tenant AI learning platform for test prep academies. Socratic AI tutor that refuses to give answers, AI quiz generation from uploaded files, async pre-grading, weak-spot detection, and AI parent summaries. Full role-based platform across admin, instructor, student, and parent.
LIVE DEMOExam Engine
The practice exam system runs a shared question bank (SAT and GRE) with multiple question types and live mathematical notation rendering. Two parts of this were genuinely hard: supporting GRE-specific question types, and getting math to render reliably in production.
Question types
Plain multiple-choice is the easy case. The GRE has two types that need real handling:
Quantitative comparison. The student is shown Quantity A and Quantity B and must decide whether A is greater, B is greater, they're equal, or it can't be determined. The four answer choices are fixed and identical across every QC question, but the two quantities are arbitrary expressions. The rendering and answer model both have to treat QC as its own type, not a multiple-choice with custom options.
Multiple-select. Unlike standard multiple-choice (exactly one correct answer), multiple-select questions can have several correct options, and the student must select all of them for credit. Partial selection is wrong. This changes both the input UI (checkboxes, not radio buttons) and the grading logic (set equality, not single-value match).
The question bank stores these with a type discriminator and type-specific JSONB for options/quantities, populated by the import scripts.
Live math rendering with KaTeX
GRE quant questions are full of mathematical notation — fractions, exponents, radicals, Greek letters. These render with KaTeX. Getting it reliable produced the single most instructive bug in the whole project.
The bug
Math rendered perfectly in local development. Deployed to the VPS, the same questions showed raw LaTeX source — \frac, \theta, \right as literal text instead of typeset math.
The root cause
The original implementation used the react-katex InlineMath component. That component throws on certain malformed or edge-case LaTeX, and when it threw, it fell back to rendering the raw string. The reason it only broke in production: the development build tolerated the throw and recovered, while the production build's different error handling let the fallback-to-raw path win. Dev mode masked the bug entirely.
The fix
Replaced react-katex's InlineMath with a direct call to katex.renderToString() configured with { throwOnError: false, strict: false }. Now KaTeX never throws — on a malformed expression it renders what it can and moves on, in both dev and production builds identically. The fix lives in frontend/src/features/exams/MathText.tsx.
The lesson
When something renders fine in local dev but breaks on the VPS, test the local production build first — npm run build plus a preview server — before assuming it's a deployment or environment problem. The dev server and the production bundle handle errors differently, and that difference hid this bug until it shipped. This is now a standing rule for the project.
Why a shared bank
The question bank isn't tenant-scoped (see database-design.md). Standardized test content is identical across academies — every GRE student wants the same GRE questions — so the bank is shared platform content and only student attempts are tenant-scoped. The import scripts (import_question_bank.py, import_gre_question_bank.py, build_gre_quant_bank.py) populate and verify it.