Recall / Independent product / iOS
From a page of notes to a daily review.
Photograph notes or enter a topic, generate flashcards, and return to them with spaced repetition.
Built with
- Application
- TypeScript
- React Native
- Expo
- Expo Router
- Capture & interaction
- Expo Image Picker
- Expo Haptics
- AI generation
- OpenAI API
- Structured Outputs
- Backend & sessions
- Supabase
- PostgreSQL
- Supabase Auth
- Supabase Edge Functions
- Review & local state
- SM-2-style scheduler
- AsyncStorage
- Checks & release
- ESLint
- TypeScript compiler
- EAS Build
- EAS Submit

Original App Store screenshots.
My contribution
I built the flashcard generation flow, editable decks, review scheduler, and guest session experience, and published the iOS app.
Under the surface
The engineering.
Generating a deck and scheduling a review are different problems. I use a model for the draft material, then ordinary application code to decide exactly when each card returns.
Supply material
A photo of notes or a text topic.
Draft a deck
A strict title/cards schema with basic and cloze cards.
Review the draft
Inspect cards, rename the deck, and remove unwanted cards.
Schedule recall
A rating updates interval, ease, and the due date.
Decision / 01
The model returns data the editor can work with.
A paragraph of generated study material is not a usable deck. Each card needs a predictable type, question, and answer before the interface can render or edit it.
The generation endpoint requests strict structured output with title and cards fields. Card type is limited to basic or cloze, with required front and back text. Photo requests use a vision model and instructions to stay within the notes; topic requests use a smaller text model. Refusals and empty decks have explicit error responses.
Schema enforcement makes the output usable by the UI, but does not verify factual accuracy. Learners can inspect and remove generated cards before saving, then edit card content in the saved deck. Photo and topic generation also have different grounding expectations.
Decision / 02
Make the next review a deterministic calculation.
A rating needs a consistent consequence. The interval shown on a button should follow the same calculation used when the learner selects it.
The SM-2-style scheduler is a pure TypeScript function over interval, ease, repetitions, lapses, rating, and time. Again resets repetitions, increments lapses, and sets a due date ten minutes out. Successful reviews advance the interval; ease has a floor of 1.3. Button previews call the same scheduler. In the active session, Again also places the card at the back of the queue for another attempt.
The scheduler is small and explainable, with fixed rules rather than individually fitted memory parameters. The active queue can show a failed card again before its stored ten-minute due date, so session behavior and persisted scheduling are distinct.
Decision / 03
Guest use still needs a durable identity.
Asking for an email before a first deck adds friction, but decks and review history still need a user identity behind them.
Session bootstrap restores an existing Supabase session or signs in anonymously. This lets a learner enter the product without an email form while keeping a session that survives app restarts. Account backup is a separate step in the experience.
An anonymous session is tied to credentials on that device. It should not be presented as a replacement for account recovery or as proof of offline availability.
Edge cases
What happens when…
- A photo produces no usable cards
- Return an explicit error and ask for clearer material instead of saving an empty deck.
- A new card is rated Easy
- The initial interval is four days; Good starts at one day.
- A card is rated Again
- Reset its repetition count, lower ease within the floor, and requeue it for another attempt.
Claro
A personal finance app for importing statements, categorizing transactions, and asking questions about spending.