Fig. 05 · Personal project

Cadence

An exam-preparation operating system built on spaced repetition, per-user data isolation and push reminders.

Project
Cadence
Role
Designer and developer
Period
05/2026 - 06/2026
Context
Personal project
Stack
Next.js · TypeScript · Supabase · Postgres RLS · Web Push · MCP
Outcomes
  1. FSRS spaced repetition with mastery, readiness and pace engines
  2. Per-learner data isolation through Postgres row-level security
  3. One set of pure engines shared by the web app and a local MCP server

Problem

Preparing for an exam means deciding what to study, remembering it, and actually showing up each day. Cadence brings planning, spaced-repetition review and reminders into one place.

Constraints

  • Each learner’s data is private. Isolation must hold even though the browser talks to the database directly.
  • Schedules must be trustworthy. Review timing should follow a proven algorithm, not ad-hoc rules.
  • No duplicated logic. Every surface - the app and an AI assistant - must compute the same answers.

Architecture

The figure follows a review through the engines, into storage, and back as a reminder.

  1. 01

    A plan that adapts to memory

    Each review feeds FSRS spaced repetition and engines for mastery, readiness, pace and weak topics, which reshape what to study next.

  2. 02

    Every learner's data is theirs alone

    The browser talks to Postgres with an anonymous key, and row-level security restricts every query to the signed-in learner's own rows.

  3. 03

    Reminders arrive when reviews are due

    Scheduled jobs find what is due and send reminders through the browsers' Web Push services.

  4. 04

    AI drafts, the learner decides

    Gemini turns study notes into draft flashcards that the learner can edit before they join the review schedule.

  5. 05

    One engine, two front doors

    A local MCP server lets a desktop AI assistant drive Cadence by calling the same pure engines as the web app - no duplicated logic. Its privileged key stays on the local machine and never reaches a browser.

Fig. 05 - CadenceScale model
LearnerWeb app(Next.js)DomainenginesPostgreswith RLSScheduledjobsWeb PushservicesGeminiAPIMCPserver(local)AIassistant
Fig. 05 · Plan - components, connections and data flows
Read the diagram as text

Components

Learner
Someone preparing for an exam, studying and reviewing on any device.
Web app (Next.js)
The study app - plans, reviews, mock exams, streaks and progress.
Domain engines
Pure TypeScript engines for FSRS spaced repetition, mastery, readiness, pace and weak topics.
Postgres with RLS
Supabase Postgres; row-level security limits every query to the signed-in learner's own rows.
Scheduled jobs
Scheduled jobs that find what is due and trigger reminders.
Web Push services (external, not owned)
The browsers' push services, which deliver reminders to the learner's devices.
Gemini API (external, not owned)
A hosted model used for AI-assisted features such as turning notes into flashcards.
MCP server (local)
A local-only MCP server that lets an AI assistant drive Cadence through the same engines the web app uses.
AI assistant (external, not owned)
A desktop AI assistant (such as Claude Desktop) that calls the MCP server's tools.

Flows

  • A review updates the plan: Learner → Web app (Next.js) → Domain engines.
  • Progress saved under row-level security: Web app (Next.js) → Postgres with RLS.
  • A reminder for what is due: Postgres with RLS → Scheduled jobs → Web Push services.
  • Notes become flashcards: Web app (Next.js) → Gemini API.
  • An assistant uses the same engines: AI assistant → MCP server (local) → Domain engines.

Key decisions & trade-offs

Pure engines shared by every front door

Scheduling, mastery, readiness, pace and weak-topic logic are pure functions. The web app and the local MCP server both call the same engines, so there is no second source of truth.

The alternative was to keep the logic inside API routes and components, which is faster at first but would have forced the MCP server to re-implement it - and two implementations of a schedule eventually disagree.

Row-level security instead of a custom API layer

The browser uses the anonymous key and Postgres row-level security restricts every query to the signed-in learner. The privileged service-role key is used only by the local MCP server and never reaches a browser.

A custom API layer would have centralised authorisation in application code. Row-level security puts it in the database itself, so no new endpoint can forget a check; the cost is that every policy must be written and tested in SQL.

Results

  • FSRS-based review scheduling with mastery, readiness, pace and weak-topic engines.
  • 14 database migrations and 19 test files covering the engines.
  • A local MCP server that drives the app through the same engines.

What I’d do next

  • Calibrate the readiness score against real mock-exam results.
  • Bring reminders to native mobile notifications in addition to Web Push.