What it is
A study workspace: 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
Every AI route resolves 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 inverts the usual failure mode: the demo path is the default path, so it cannot rot silently while the real path is the only one exercised.
Provider failover before rate limiting, not after
What I did, and why
Requests try OpenRouter, then Groq, with retries and timeouts; a sliding-window limiter and a response cache sit in front of both.
Free model tiers fail in two different ways, and they need different answers. The provider being down is handled by failover; being over quota is stopped by the limiter and cache before a request ever reaches a provider.
AI explains, math computes
What I did, and why
The five AI routes handle language: summarising, generating cards, tutoring. Every dashboard statistic — streaks, retention, due counts — is computed in code and never passed through a model. That half is the half with 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 is not an AI problem, and drawing that line early is cheaper than discovering 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
- The 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 is no CI, so they run when I run them.
- Custom components are thin on aria attributes; the Radix primitives carry accessibility, my own do not yet.
- The AI-study-tool genre is crowded. What is 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.