Building Scholaris: A Digital SAT Prep Platform

What Inspired Me

The idea for Scholaris came from a simple observation: test prep is broken. Tools like OnePrep (oneprep.xyz) showed me what a premium platform could look like, but they were expensive and out of reach for most students. I wanted to build something that combined that premium experience with a radical commitment to free access.

The mission was clear from day one:

$$ \text{Scholaris} = \text{Premium UX} + \text{Free access} + \text{AI-powered tutoring} $$

I saw students spending hundreds of dollars on tutoring and prep books, while open-source College Board question banks sat unused. The College Board had released 2,474 SAT questions through their OpenSAT dataset (1,443 English + 1,031 Math), and no one had built a beautiful, free platform around them. That gap became Scholaris.

I was also inspired by the idea of Scho, an AI tutor that would not just give answers but teach like a Socratic mentor. The prompt engineering behind Scho was deliberate: no immediate answers, LaTeX for math, warmth for students who were struggling.


What I Learned

1. Database Architecture Is Everything

The hardest lesson was realizing that a fragmented data layer kills velocity. Scholaris started with parallel systems:

  • practice_sessions + practice_attempts for practice
  • exam_sessions + question_attempts for exams
  • Hardcoded questionBank.ts instead of real database queries
  • Dual diagnostic result tables

Unifying these into a single repository layer (QuestionRepository, SessionRepository, DiagnosticRepository, ProgressRepository) was the single most impactful refactoring decision.

2. Supabase RLS Timeouts Are Real

Our Supabase project (aioyzumxxsepbnfivvdd) has strict statement timeouts:

$$ t_{\text{anon}} = 3\text{s}, \quad t_{\text{authenticated}} = 8\text{s} $$

Any query that tried to scan the full SAT question bank (6,181 Math questions, 4,225 English questions) with ORDER BY id would blow past these limits. I learned to use:

  • Capped practice pools (LIMIT 400 unordered)
  • Cursor pagination instead of sequential OFFSET
  • SECURITY DEFINER RPCs with function-level SET statement_timeout overrides

3. PostgREST Error 202 Is a Silent Killer

When the client called extended RPC signatures that the live database did not have, PostgREST returned PGRST202. The client treated this as "0 questions" and showed a "none passed quality checks" toast. The actual issue was that a migration for question_subtype had never been applied. This taught me to never assume a migration landed without explicit verification.

4. CSS Specificity Wars Are Real

The dark theme global rule:

.dark textarea { background: hsl(var(--background)) !important }  /* (0,1,1) */

Built With

Share this project:

Updates