A 16-week solo build: behind Discito's launch
Discito shipped sixteen weeks after the first commit. One developer, no team, no investors, no server bill. The app you can install today contains roughly 1,200 source files, around 100,000 lines of Swift, six bundled starter decks, a vendored Rust FSRS optimizer, an on-device AI generation pipeline that runs on Apple's Foundation Models, and full .apkg import/export round-trip with the open format. It was not a calm sixteen weeks.
This post is the honest version of what happened — what made the cut, what got cut, what shipping solo on iOS in 2026 actually looks like.
Week 0: the bet
The pitch I gave myself: iOS users have been waiting for a truly modern, native flashcard app for a decade. The existing options on iPhone either show their age (sync configuration, onboarding, widgets, anything that requires a modern iOS pattern) or were built web-first and translate awkwardly to phone — usually behind a subscription. The category had a structural opening for "what a modern FSRS app would be if you built it on iOS 18+ from scratch, with the last decade of Apple frameworks available."
The constraints I set going in:
- iOS-only. No web app, no Android, no cross-platform compromise. SwiftUI everywhere.
- No server. Sync via CloudKit; nothing to operate; no recurring infrastructure costs.
- FSRS-6 bit-exact with the reference. Not "inspired by," not "FSRS-like," not "FSRS Light." Bit-exact.
- Open-source attribution everywhere. The FSRS algorithm and the
.apkgformat are open-source work. Credit the people whose shoulders this stands on, in the App Store listing, in the credits screen, in every blog post. - One-time purchase. $14.99, Family Sharing, no subscription. If the math doesn't work at that price, the project doesn't ship.
Sixteen weeks felt aggressive but doable. It was aggressive. It was also doable. Here's roughly how it went.
Weeks 1-4: the engine
Everything starts with the scheduling algorithm. If FSRS-6 doesn't work — if the port isn't bit-exact with py-fsrs — nothing else matters, because users will notice that their review intervals don't match the reference implementation and the app loses credibility immediately.
FSRSKit shipped in week 2 with 25 tests against parity fixtures generated by the Python reference. The one gotcha that took the longest to find: Swift's exp(x) and pow(M_E, x) diverge by one ULP at scattered inputs because they use different code paths in the C library. Python's math.e ** x routes through pow, so Discito's port has to use Foundation.pow(M_E, x) everywhere, never exp. That single discipline is the difference between "we shipped FSRS" and "we shipped FSRS that actually matches the reference."
Weeks 3-4 built DiscitoCore: the Core Data schema, the repository layer, the review session state machine, the streak calculator, the daily-goal logic. All of this gets unit-tested before any UI lands, because UI bugs are visible and data bugs are silent.
Weeks 5-8: the surface
The first real UI shipped in week 5: deck list, card list, basic review session, the Today tab. Onboarding, the import flow, the share extension. This is the phase where you find out which of your architectural decisions from week 1 still hold up.
Two big surprises here:
The .apkg format is harder than it looks. It's a ZIP file containing a SQLite database, a JSON media manifest, and zstd-compressed blobs. The schema has three format versions (legacy1, legacy2, latest), each with subtly different protobuf shapes, and real-world archives include edge cases the spec doesn't cover (JSON strings where the spec says int, ordinal-collision bugs, csum fields that aren't quite what the spec claims). The import pipeline was the largest single subsystem in the app and took roughly six weeks of calendar time, spread across this phase and the next.
CloudKit Just Works, except when it doesn't. NSPersistentCloudKitContainer is genuinely the right abstraction for sync-but-the-easy-way: you point it at a Core Data store, you give it an iCloud container ID, and your data syncs. The "except when it doesn't" parts are: schema deployments aren't automatic (you have to push schema to the production environment via the CloudKit Dashboard), CKAsset hydration latency is real and visible, and a no-iCloud-signed-in device will SIGTRAP your app inside the CloudKit framework if you call initializeCloudKitSchema without first checking FileManager.ubiquityIdentityToken != nil. We learned that one the hard way on a TestFlight build.
Weeks 9-12: the AI pass
The AI features are what make Discito a "next-decade flashcard app" rather than a refresh of an old one. All four — card generation from PDFs and text, AI image generation for cards, lecture audio capture, smart MCQ distractors — run on Apple's Foundation Models, on the user's device, never touching a server. No OpenAI bill. No "we processed your notes on our servers" disclosure. Your study material stays on your iPhone.
The trade-off is that Foundation Models requires Apple Intelligence, which means iPhone 15 Pro or later. Users on older hardware see the AI features in Settings with a "Requires Apple Intelligence" badge and a deep-link to Apple's explainer. The non-AI features — FSRS scheduling, review, import/export, widgets, Live Activity, audio playback, IO authoring, all quiz modes — work on every iOS 18.6+ device without exception.
This was the right trade-off. The alternative was paying for cloud AI per-user-per-month, which would have meant a subscription, which would have meant building the wrong app. We picked "powerful AI on the hardware that can run it" over "watered-down AI on every device, plus a subscription," and it preserved both the privacy story and the price story.
"Powerful AI on the hardware that can run it" beats "watered-down AI on every device, plus a subscription." That single trade-off preserved both the privacy story and the price story.
Weeks 13-14: the visual pass
Plan 21 — the premium UI theming pass — was the highest-leverage two weeks of the whole build. The pre-Plan-21 app worked correctly; it just read as "FSRS app with reasonable defaults." The post-Plan-21 app reads at Things 3 / Reeder / Bear / Halide fidelity. Same code, dramatically different impression.
The pivot moment was Plan 21 Part D's review of card chrome. The spec called for a card-on-elevated-background treatment that should have looked great. After 24 paint variants and 12 backdrop-tint prototypes, the chrome was empirically invisible in light mode — not a contrast bug, a perception bug. The fix wasn't "tune the chrome harder." The fix was: drop the chrome, ship full-bleed content surfaces, add a reading-themes picker. A survey of premium iOS reading apps showed most use full-bleed content surfaces (Bear, Reeder, Apple Books, Apple Notes, Kindle); only a few task-manager apps use the card-on-tinted-desk reverse-elevation pattern, which doesn't translate to a long-form reading surface.
So Discito ships six reading themes (Paper, Cream, Sepia, Charcoal, Midnight, System), Apple-Books-style. Dark mode is the default; light is the adapted variant. Brand identity is navy + gold, propagated to every surface via semantic color tokens with WCAG AA contrast pinned in regression tests.
Weeks 15-16: the launch sprint
The last two weeks are the part nobody talks about because it isn't fun: marketing site, App Store Connect setup, screenshot capture across the post-Plan-21 build, blog posts (including this one), press kit, Apple Featuring nomination, TestFlight ramp with real testers from the spaced-repetition study communities on Reddit, localization sweep into Japanese, Spanish (Mexico), German, Korean. Privacy questionnaire, age rating, content disclosure. The "boring infrastructure" of actually shipping.
None of this is hard exactly, but all of it is detail-work that has to be right. ASC keyword choices that respect Apple's trademark filter. Hardware-disclosure copy on the AI features. Custom Product Pages for inbound traffic. Sandbox tester accounts for the StoreKit purchase flow. Cloudflare DNS for discito.app. Email routing so [email protected] reaches a real inbox.
What got cut
Things that were in the original plan and didn't ship in v1:
- Apple Watch app. Deferred to v1.1 post-launch. The watchOS app is real work — independent target, WCSession bridging, complications — and we wanted to validate the iOS experience first.
- Streak freeze. The "pause your streak for a missed day" feature. We had it in the spec; we cut it because it was gimmicky and not a real conversion lever.
- Curated deck library. The "marketplace" of hand-curated decks. Cut for v1 because the 5 bundled starter decks (Spanish 100, French 100, World Capitals 100, Cognitive Biases 25, Hiragana + Katakana 92) cover the empty-on-launch UX without an ongoing content-curation burden. May revisit post-launch if there's demand.
- Deck-share viral loop. The "share a deck preview as a web link" feature. Cut on the final pivot because it would have meant operating Cloudflare R2 storage + App Attest, and the marketing site stays anonymous-only.
- iPad-optimized layout. Discito runs on iPad (it's a universal build), but the layouts are phone-first. Real iPad treatment is v2 work.
- Subscription tier. We prototyped a $4.99/month + $39.99/year subscription mid-build. We reverted it before launch — the recurring revenue wasn't worth the recurring-infrastructure-cost-zero/audience-anti-subscription mismatch.
What I'd do differently
Three things, in retrospect:
Onboarding should have been week 1, not week 8. The original onboarding was a forced 3-screen create-first-card flow that confused even me when I tested the TestFlight build. We rewrote it in Plan 16 with a tutorial deck + post-first-review banner. That should have been the onboarding from week 1; doing it twice cost real time.
The agent-driven UX review framework should have been week 4. Plan 16 Part H built a system where 6 XCUITest "journey tours" capture screenshot sets at 3 conditions (light/dark/AX5), a Python script sorts the screenshots into a browsable index, and a Claude agent reads the inventory and writes a friction report. It works really well — and it would have caught a dozen UX issues in week 5 that I instead fixed in week 14. Friction reviews are a force multiplier; build them early.
Don't put off the visual pass. Plan 21 was the right work, but it could have been earlier. The pre-Plan-21 app would not have been a successful launch — it worked correctly, but it didn't feel premium. If you're building an indie iOS app in 2026, the visual quality bar is set by Things 3 and Reeder and Bear, and you have to be at that bar before users will take you seriously. Build that bar in earlier than feels comfortable.
What's next
Post-launch v1.1: Apple Watch app, real-device feedback triage, language-pack TTS UX, deeper iPad treatment. Beyond v1.1: whatever the actual users actually need, which I don't know yet, and that's the right answer.
What I do know: the app is shipping. The TestFlight cohort is in. The signing chain works. The IAP works. The reviews are starting to land. The sixteen weeks were real, the bet was the right bet, and Discito on the App Store today is the app I wanted to use for the last decade. That's a good feeling.
And to the open-source communities — Open Spaced Repetition, the fsrs-rs contributors, the swift-bridge maintainers — thank you. None of this would exist without your work. Discito's contribution is an iOS-native shell around your science. The science is yours.