What it is
A study workspace I built: upload or paste your material and get summaries, spaced-repetition flashcards, quizzes, a tutor chat, and a planner with streaks and a heatmap.
Why I built it
An idea, not a problem I was living with. I liked it enough to keep going until it was a finished application.
This one started as an idea, not a problem I was suffering through. Students collect material faster than they can turn it into something studyable, and the tedious bit is the gap between having a PDF and having recall practice. I liked the idea enough to keep building until it was actually finished, which took a lot longer than I thought it would. What I got out of it was the engineering: auth, a data model, a provider layer that survives two free tiers failing in different ways, a demo strategy. All of it decided one at a time, by me.
In the app
Captured from the demo account the demo button opens, seeded with its own material.




By the numbers
- server-side AI routes
- 5
- tests over the spaced-repetition scheduler
- 30
- commits over ~3 months
- 32
- lines of TypeScript
- ~11,500
Counted against the source on 11 August 2026, not estimated.
Decisions that mattered
3 of them. Each opens onto what I did and the reasoning behind it.
Mock-first: the app runs with no environment variables at all
What I did, and why
I routed every AI call through a provider layer that falls back to fixture responses, so a fresh clone with an empty .env boots into a fully working app.
A project nobody can run is a project nobody evaluates. It also flips the usual failure mode: the demo path is the default path, so it can't rot silently while only the real path gets exercised.
Provider failover before rate limiting, not after
What I did, and why
Requests try OpenRouter, then Groq, with retries and timeouts, and I put a sliding-window limiter and a response cache in front of both.
Free model tiers fail in two different ways, and they need different answers. A provider being down is handled by failover. Going over quota gets stopped by the limiter and cache before a request ever reaches a provider.
AI explains, math computes
What I did, and why
I kept the five AI routes to language: summarising, generating cards, tutoring. Every dashboard statistic — streaks, retention, due counts — is computed in code and never goes through a model. That half has tests: 30 assertions pin the SM-2 ease bounds, the leech threshold and overdue-card forecasting.
A model asked to add up study minutes will eventually get it wrong, confidently, and the user has no way to tell. Anything with a correct answer isn't an AI problem, and drawing that line early is cheaper than finding it from a bug report.
Stack
- Next.js 15 (App Router)
- React 19
- TypeScript (strict)
- Supabase (Postgres + Auth)
- OpenRouter + Groq
- Zustand
- Tailwind + Radix
- Vercel
- Next.js App Router9 pages, 5 streaming API routes
- Supabaseauth, Postgres, migrations, repository pattern
- Rate limitingcustom sliding window with free-tier guards
- StreamingReadableStream responses from the chat route
What is honest about it
- My tests cover the scheduler and stop there: 30 assertions over ease, intervals and the forecast, nothing over the five AI routes the mock-first layer was built to make testable. There's no CI, so they run when I run them.
- The components I wrote myself are thin on aria attributes. The Radix primitives carry accessibility; mine don't yet.
- The AI-study-tool genre is crowded. What's distinctive here is the engineering underneath, not the idea.
Want this kind of work on your codebase? Email me and tell me what you’re trying to build.