← Blog

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:

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:

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.

Try Discito

Discito Lite is free forever. Pro is $14.99 once, lifetime, Family Sharing included. Pay once, own forever.

Read other posts