Pistochia
Launching soonAn iPhone app for people with ADHD. You say or type what is on your mind, and it comes back as steps small enough to start, sorted by your energy that day.
- My role
- Design and development, on my own
- Year
- 2026
- Built with
- Expo · React Native · SQLite · Supabase Edge Functions · Claude · Whisper · RevenueCat
Scroll sideways to see the whole diagram.
The problem
For people with ADHD the hard part is often starting, not remembering. A to-do app that stores the whole mountain turns into a list of things to feel guilty about. The unit of work has to shrink until starting feels easy, and the app has to work the second it opens.
How it is built
An offline-first Expo app on local SQLite with versioned migrations. Identity is anonymous Supabase auth, so the first brain dump happens before any sign-up screen. Every AI call goes through an edge function that works by operation: the app sends an operation name and its parameters, never a prompt and never a key. The function checks the operation against an allowlist and enforces rate limits per IP, daily quotas per install and a kill switch. Claude breaks the tasks down, and Whisper, behind the same proxy, handles voice. RevenueCat runs the paywall, and analytics stay off until the user agrees to them.
Decisions that mattered
The app holds no API keys
Operation names go in and structured results come out. The API key lives in the edge function, behind a kill switch, quotas and limits that fail closed.
It works offline first
Tasks live in SQLite on the phone, with real migrations. A tool for ADHD that needs a network to show your next step would fail the people it is for.
No sign-up before the first use
Anonymous auth creates an identity silently. The account screen comes later, once the app has been useful.
Code from the repo
// The legacy free-relay surface: any request still carrying these is either
// an outdated client or an abuse probe — reject outright.
const FORBIDDEN_KEYS = ['system', 'model', 'messages', 'maxTokens'] as const;1
// …
const paused = Deno.env.get('AI_PAUSED');2
if (paused === 'true' || paused === '1') {
return jsonResponse({ error: 'ai_paused' }, 503);
}
// …
for (const key of FORBIDDEN_KEYS) {
if (key in body) {
return jsonResponse({ error: 'invalid_request' }, 400);
}
}
const op = body.op;
if (typeof op !== 'string' || !(op in OPS)) {3
return jsonResponse({ error: 'invalid_op' }, 400);
}
const config = OPS[op]!;
Line 3. The app cannot send a prompt, a model or a token limit. Those live on the server, and the app can only name an operation on the allowlist.
Line 6. AI_PAUSED is a kill switch: one environment variable stops all AI spending without a redeploy.
Line 19. An unknown operation is rejected. The API key never exists anywhere in the app bundle.
Where it is now
The App Store material, the launch checklist, EAS builds, a promo video and pistochia.com with a waitlist are ready. iPhone first; the App Store release is next.