Project notes
Building PRISM: a self-reflection app for iPhone
What PRISM is, why I'm building it the way I am, and where it stands while it's still unreleased.
PRISM is an iPhone app I'm building for reflecting on your day: a short daily check-in, readings with a streamed AI reply, a private journal and a Self Map. It isn't out yet. Here is what it is, the few rules I keep coming back to, and what I still have to prove.
- Open the app and check in
- Choose a question and a spread
- Draw the cards
- Read the streamed reply
- Ask a follow-up
- Save notes to the private journal
What it is
PRISM is meant to be a calm place to check in with how you feel and notice your own patterns. Every day there's a short check-in with a card to think about. A reading starts from a question you pick, a spread and the cards you draw, and ends with a reply that explains each card in its position and suggests one small next step. The journal keeps your notes and past readings, and the Self Map pulls a few ways of looking at yourself into one place.
I use tarot as a mirror, not a crystal ball. PRISM isn't therapy and it doesn't predict anything. That sounds like a product detail, but it shapes the engineering: replies have to stay grounded in the cards and in what the person actually wrote, and the app has to be careful when someone writes something that sounds like distress.
It's Vietnamese first, with English from the start, so every string and every reply exists in both.
- Check-in
- A short daily ritual: name how you feel, choose one small intention and read a card's reflection prompt.
- Reading
- A question, a spread of cards and a streamed reply that explains each card in its position.
- Self Map
- A combined view of a few reflective lenses, such as numerology, birth cards and a personality test.
- Journal
- Private notes, check-ins and saved readings kept over time.
Four rules I keep coming back to
Most decisions trace back to four rules. I try to turn each one into something I can test.
Reflection, not prediction. No dates, no yes or no verdicts, no claims about what other people think. Easy to say, hard to enforce, so I treat it as something to evaluate on real Vietnamese and English cases.
Private by default. A journal is the most personal thing in the app. Private text stays encrypted in storage and out of logs and analytics. I'd rather debug from event types and request ids than from what someone wrote.
The server decides access. What's free, what needs a subscription and what a purchase unlocks is decided on the backend. The app only shows what the account has.
Fail calmly. A streamed reply can get cut off, a phone can go offline, a purchase can arrive late. Each of those should end in a state the person understands, not a spinner.
How it fits together
The app is Flutter with Riverpod. Journal changes queue up offline, so a note written on a train isn't lost. The backend is a FastAPI monolith over PostgreSQL, with Redis for rate limits and a small worker for scheduled jobs.
Replies stream in as text events, so the first words show up while the rest is still being written. I picked a one-way stream because data only flows from the server to the app. The AI provider is a config choice, and there's a stand-in model so I can run every flow locally without keys. Subscriptions go through a third-party service.
I'm keeping this high level on purpose. The app isn't released and I'd rather not publish how access and usage are protected.
What's tested and what isn't
The backend has an automated test suite that runs in CI against a real database and cache, and the app has layout tests from small iPhones up to wide screens. Tests only tell me about the code, though.
The things only real services can answer are still open: sandbox purchases and restores on real devices, a proper evaluation of reply quality in Vietnamese and English, and how the whole thing behaves with real subscribers. Until I've done those, I won't make claims about cost, speed, quality or scale.
Next
Real-device purchase checks first. After that, a bigger evaluation of replies in both languages, including low-mood and prompt-injection cases, and a measurement of how fast the first words appear. When the app is on the App Store I'll link it here and write a follow-up with real numbers.
Takeaways
- Turn product principles into things you can test.
- Keep private text encrypted and out of logs from the start.
- Decide access on the server.
- Design each failure to end in a clear state.
- While an app is unreleased, say plainly what is built, tested and unproven.
References
- Server-sent events (MDN) · how streamed replies work
- Flutter documentation
- FastAPI documentation
- RevenueCat documentation · subscriptions and purchases