Mnemonic is a spaced-repetition flashcard app that runs entirely in the browser. Scheduling is FSRS-5 through ts-fsrs. Persistence is Dexie over IndexedDB. The UI is React 19 with React Router 7 and Tailwind 4 on the ash lumen design system.
architecture
card rows are updated in place by FSRS. every rating also appends an immutable review log row, which is what the stats read.
logs store a local YYYY-MM-DD string instead of a UTC timestamp, so a late-night review lands in the right heatmap square. there is no separate new-card queue: due <= now is the whole session query.
Three tables. Decks are name and creation time. Cards store the raw FSRS state inline: stability, difficulty, elapsed and scheduled days, reps, lapses, learning steps, state, due time, and last review. Review logs are one immutable row per rating with the card, the deck, the rating, a timestamp, and a local calendar date. Schema version 3 adds the three sync tables. Every version bump has been additive.
One fsrs() instance is configured with a 0.9 retention target, a 365-day maximum interval, and fuzz enabled. The study page runs a single Dexie query per session, cards in the deck with due <= now sorted by due, and snapshots the result into component state. Learning steps are whatever ts-fsrs returns, and re-due cards surface in the next session. All four ratings are previewed before the user commits, so each button shows its resulting interval.
Reads are live queries through dexie-react-hooks, so there is no state store. Sync is opt-in: records deck/, card/ and review/ go to an outbox with tombstones for deletes, drained five seconds after a write burst and on tab focus.
technical decisions
- FSRS-5 over SM-2. FSRS models stability and difficulty separately instead of one per-card multiplier.
- Local-first with no backend. No account, offline after first load, and the app is unchanged when the sync server is down or unconfigured.
- Dexie over raw IndexedDB. Typed tables, versioned migrations, and live queries.
- A review log separate from card state. FSRS mutates the card row in place. Every rating also appends a log row, and the heatmap and totals read the log, so history survives rescheduling.
- Local day boundaries. Logs store a local
YYYY-MM-DDstring rather than a UTC timestamp, so a late-night review lands in the right heatmap square. The heatmap grid is Monday-anchored. - Keyboard flow. Space flips, 1 to 4 rates.
hard problems
Rating.Manualis zero and is excluded from ts-fsrs’s preview type, so indexing the scheduling result needs aGradecast.scheduled_daysis zero for cards in the learning phase, so the displayed interval is computed from the due time minus now.- Deleting a deck under sync tombstones each card individually, so other devices cascade the same way.
numbers
| measure | value |
|---|---|
| source | about 1,700 lines across 19 files |
| tests | none. CI gates on lint and tsc -b |
| heatmap | 52 weeks |
| scheduler | retention 0.9, maximum interval 365 days |