An offline flashcard app with spaced repetition, built with React Native and Expo. Author your cards as a plain JSON file (by hand or with an LLM), import them into the app, and study whatever is due each day. The app schedules reviews using an SM-2 style algorithm, entirely on your device. No server, no accounts, no sync.
Built as a portfolio project to showcase React Native and Expo development: file based routing, Reanimated animations, a pure TypeScript domain layer with full unit test coverage, and an import pipeline that never destroys study progress.
| Home | Question | Answer | Import and settings |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
- 📥 JSON import as source of truth: pick a
.jsonfile withexpo-document-picker; cards are validated row by row, and errors are reported per row instead of failing the whole file. - 🔀 Merge safe imports: cards are identified by a 64 bit hash of
deck + question, so importing an updated file upserts content without ever destroying study progress. Imports never delete; a summary for each deck reports added / updated / unchanged / duplicates / errors. - 🧠 Spaced repetition (SM-2 lite): three feedback buttons (Again / Good / Easy) drive ease and interval updates, with a configurable daily cap on new cards and a 4am rollover hour so a late night session does not count as the next day.
- 🎴 Animated card flip: the question shows first, then a tap flips the card with a Reanimated 4 animation. Lapsed cards requeue within the session, and a summary at the end shows the reviewed count, accuracy, and the Again / Good / Easy breakdown.
- 📝 Markdown everywhere: questions and answers render Markdown, including fenced code blocks, so the app works well for technical topics (JavaScript, TypeScript, React Native, system design…).
- 💾 Local persistence and backup export: all state lives in AsyncStorage; one tap exports a versioned JSON backup of your progress via
expo-sharing. - 🌗 Light and dark theme: follows the system appearance.
| Area | Choice |
|---|---|
| Framework | Expo SDK 57, React Native 0.86, React 19.2 |
| Language | TypeScript (strict) |
| Navigation | Expo Router (file based, with typed routes) |
| Animation | React Native Reanimated 4 |
| State | React Context + reducer (src/state/StoreProvider.tsx), persisted to AsyncStorage |
| Content rendering | react-native-markdown-display |
| File I/O | expo-document-picker, expo-file-system, expo-sharing |
| Testing | Jest + jest-expo + React Native Testing Library |
The core design principle: the JSON file owns content; the app owns scheduling. All spaced repetition logic is isolated in a domain layer with no React or React Native imports, which keeps it trivially testable.
src/
├── app/ # Expo Router screens: Home (deck list), Study, Summary
├── components/ # FlipCard, FeedbackButtons, MarkdownText, themed primitives
├── domain/ # Pure TypeScript, no React/RN imports:
│ ├── identity.ts # card identity hashing (64 bit FNV-1a)
│ ├── validate.ts # import file validation with per row errors
│ ├── merge.ts # upsert merge of imported cards into stored state
│ ├── schedule.ts # SM-2 lite ease/interval/due date math
│ ├── session.ts # study session queue building and requeueing
│ └── stats.ts # session summary stats
├── state/ # StoreProvider (Context + reducer)
├── storage/ # AsyncStorage persistence + backup export
└── lib/ # dashboard/derived stats
Every domain module has a matching test suite under __tests__/ (13 suites covering identity hashing, merge semantics, scheduling math, session building, validation, and persistence).
Prerequisites: Node.js 20+, and for simulators: Xcode (iOS) or Android Studio (Android).
-
Install dependencies:
npm install
-
Start the dev server:
npx expo start
From the terminal UI you can open the app in an iOS simulator (
i), Android emulator (a), or the web (w). -
Or build and run natively:
npm run ios
npm run android
The repo includes sample-deck.json. In the app, tap Import / Update, pick the file, and start studying. To make it available on a simulator, drag the file onto the simulator window (iOS) or push it with adb (Android).
A flat JSON array with no IDs to manage, so it is easy to generate by hand or with an LLM:
[
{
"deck": "JavaScript",
"question": "What is a closure?",
"answer": "A function bundled together with references to its surrounding (lexical) scope."
},
{
"deck": "JavaScript",
"question": "What does this log?\n```js\nconsole.log(typeof null)\n```",
"answer": "`\"object\"` — a long-standing bug kept for backwards compatibility."
}
]All three fields are required strings; question and answer support Markdown, including fenced code blocks.
npm testnpm run test:watchThe design spec and implementation plan that guided the build live in docs/superpowers, including the reasoning behind the card identity hashing, the merge semantics on import, and the scheduling constants.
See LICENSE.



