case 02 / 05live
ProbaBaby
A twins-first baby tracker I designed and built, shaped every day by its first real user.
- role
- Designer and developer: brand, design, iOS, watchOS, backend · with Esma Kahvecioglu, first user and QA
- period
- first release 14 May 2026 · v1.0.4, 4 Sept 2026
- platforms
- iPhone · iPad · Apple Watch
- public
- App Store
- public
- proba.baby
at a glance
- Shaped by real use: its first user tested it daily, and life in Serbia as foreigners led to the doctor-visit report.
- Twins-first since v1.0.4: timers, Live Activities, widgets and Watch pages for each baby at once.
- Offline-first. Every write saves on the phone, then reaches the rest of the family.
- 32 languages, right-to-left included, guarded by five checks on every commit.
- Free to track. Only the optional prediction uses credits, and it computes genetics before any image.
- users
- 500+
- monthly active users
- 360
- languages, right-to-left included
- 32
- localization keys, about 480,000 localized cells
- 15,023
Context
ProbaBaby is a pregnancy, newborn and postpartum companion for iPhone, iPad and Apple Watch, on the App Store as Pregnancy & Baby Tracker - PB. It covers planning, pregnancy week by week (a kick counter, a contraction timer with 5-1-1 rules, 51 medical tests) and the months after birth: feeds, sleep, diapers, WHO growth percentiles, 58 milestones and a red-light night console.
I designed and built it, from the first prototype to the App Store: the brand, the design, the iPhone, iPad and Watch apps, the backend, 32 languages and the website proba.baby. It was never built without a user. Esma Kahvecioglu, my wife, was its first real user and its daily QA, and her feedback, often hour by hour, shaped the product.
Living in Serbia as foreigners shaped it too. Nobody told us which tests pregnancy calls for, and the language barrier followed us into every appointment, so the app carries the tests and vaccine schedules offline and makes a doctor-visit report in the doctor’s language: see a report for a doctor who reads another language.
Family sharing covers mom, dad, grandparents and caregivers, synced in real time. Tracking, tools, PDF reports and sharing are free; only the optional prediction uses credits, and no subscription is required.
Pain points
A web app in a frame
In October 2025 the app was a Lovable web prototype inside a Capacitor wrapper. A photo picked on an iPhone didn’t appear until the phone was locked and unlocked. It cost about four weeks and 17 troubleshooting documents, with commit titles escalating to “ULTIMATE RADICAL FIX”. What fixed it was to stop. A native SwiftUI repository started ten minutes after the wrapper was given up, and its first night fixed the photo bug with a native image view. Four platforms in four months
Sheets that closed themselves
In April and May 2026, sheets on iPad and iOS 26 flashed open and closed, vanished mid-edit or reset whole flows. In May alone it took 62 commits across 203 Swift files. One sheet got nine commits in twelve hours, until the logs showed iOS removing it underneath SwiftUI. The result is a two-tier modal policy with four yes/no triggers, and an audit script whose three gating classes are empty today. The sheet that closed itself
An AI that said “done”
Nearly two in three commits carry an AI co-author. Agents reported “100% COMPLETE” on translations later found to be partly wrong, and five discovery agents listed 653 icon sites where a plain scan then found 165 more. Every rule now names the incident behind it, repeat offenders became 15 pre-commit checks, and an agent’s list counts as a floor, not a ceiling. Rules born from mistakes
Notifications that never arrived
Push broke three separate ways in 2026, each silently: a gateway that rejected internal calls for five weeks, builds that picked the wrong Apple push environment, and a key-format change that failed every send through the main push function from May to September. The environment now comes from the signing profile, internal calls share one authentication scheme, errors carry the real status, and there is a test button. What Apple, Postgres and 32 languages taught me
Data that looked researched
Crisis hotlines for “241 countries” turned out to be partly generated: 24 countries shared generic entries with 92 of 96 links dead, one country’s four hotlines ran in sequence, and a decoding bug left 23 regions with no hotlines at all. Four of the five growth datasets under a WHO citation were invented. 213 entries were removed, re-checked entries now record their source, and growth percentiles come from WHO’s own tables. What Apple, Postgres and 32 languages taught me
32 languages that passed every check
The gates checked that every translation existed, not that it was in the right language. The Ukrainian column was the Russian one run through find-and-replace, about 9,500 cells, and a later repair fixed about 7,600 more. Commit gates now catch the wrong script, a sibling language’s words and untranslated placeholders. What Apple, Postgres and 32 languages taught me
A newborn app, with a newborn
The first release reached the App Store about a week before our baby was born, and May and June 2026 became my busiest months on the project. My wife and I stopped trusting the app’s own feed reminder at night; Apple denied Critical Alerts, and the alarm moved to AlarmKit the same day. On 22 September I chose polish over new features. A newborn app, with a newborn
Constraints
- iOS 26 or later, on iPhone, iPad and Apple Watch; there is no Android app.
- Widgets keep their rendered views, so the design system’s palette is mirrored as generated colour sets.
- iOS allows a background push only a few times an hour, so what must arrive travels as a visible push or a Live Activity.
- Critical Alerts was denied, so the wake alarm runs on AlarmKit.
- Tracking stays free: only the optional prediction uses credits, and no subscription unlocks anything.
- One designer and developer for the brand, the design, the apps, the backend and 32 languages, with Esma testing it every day.
Architecture
The app target is thin. Ten infrastructure packages sit under eleven feature packages, and no feature package imports another; services reach app-level pieces through closures wired at launch, so nothing couples at compile time. The infrastructure packages also build for watchOS, so the Watch app shares the models and the store.
SwiftData on the phone is the single source of truth. Every write saves locally first and joins a prioritized sync queue; the sync engine pushes when online and pulls changes from a checkpoint. Conflicts go to the last writer, the server wins ties, and tombstones keep deleted rows deleted.
The rules a whole family depends on live in Postgres, not only in the app: one active session per family, type and baby; feeding and sleep exclusive for each baby; row-level security scoped to family membership.
Decision log
Web or native
- options
- keep the Capacitor wrapper
- React Native with Expo
- native SwiftUI
- chose
- native SwiftUI
- why
- In the web view a picked photo needed a lock and unlock to appear, and four weeks of fixes and 17 troubleshooting documents didn’t change that. A plan priced native for a team at $400K–700K; I shipped it with AI tools and Esma’s daily testing in about six months.
- trade-off
- No Android app, and the web codebase’s 173,909 lines were deleted.
Where the truth lives
- options
- network-first
- offline-first: the phone’s store, synced
- chose
- offline-first: the phone’s store, synced
- why
- The old network-first start fired more than 25 requests at once and showed a blank screen after about 3 seconds offline.
Sheets on iOS 26
- options
- keep patching SwiftUI sheets
- two tiers of modal
- chose
- two tiers of modal
- why
- Eight SwiftUI-level fixes failed: UIKit dismissed the sheet under a burst of Live Activity, widget and scene updates. An audit script checks the policy; its three gating classes are empty today.
Growth percentiles
- options
- the five bundled datasets
- WHO’s own tables
- chose
- WHO’s own tables
- why
- Four of the five datasets turned out to be invented, and one never read the baby’s sex. The new engine reproduces WHO’s published z-score tables.
A report for a doctor who reads another language
deep diveWe live in Serbia as foreigners. Nobody told us which tests pregnancy and the months after birth call for, and our doctor warned us about a few we had already missed. So the app carries the checklist itself, offline: 51 pregnancy and newborn tests, and the vaccine schedules of 241 countries and regions, bundled in the app.
The language barrier shaped the doctor visit. The Doctor Visit and Pediatrician summaries switch to the doctor’s language, any of the 32, without changing the app’s. The doctor can scan a QR code and open the report on their own phone; the link opens once, expires after 24 hours, and the parent can revoke it. Or the parent hands over their phone: a full-screen mode shows one topic per swipe, up to ten pages, with buttons that make the text larger, because many of the older doctors I saw in Serbia read with glasses.
All of it is free, so no family pays to understand its own check-ups.
One timer, two phones
deep diveWhen one parent starts a feed, sleep, kick or contraction session, every other device in the family gets a read-only Lock Screen mirror through push-to-start, even with the app closed.
Making that dependable took a stack of separate fixes in June and July 2026:
- a database trigger that had lost its authentication after a platform key change;
- start tokens that were registered only after notification permission;
- a session row that never reached the server, now written directly;
- sandbox tokens labelled as production, now retried on the other push environment;
- zombie sessions, now prevented by a database index, age-aware timeouts and duration caps;
- mirrors that could never update, because their tokens were never observed.
What it learned is written down as a lifecycle contract that every Live Activity manager must meet.
live activities 8widgets 17sync scenarios 25
No login freeze, no lost offline writes
deep diveLogin froze because the first sync pulled the full history of about 34 tables on the main thread, and every import reloaded the screen. The fix imports in chunks of 50 with a pause between them, checks each chunk against edits still pending, and batches screen updates.
A variant that paused inside the bridge was rejected after a 60-agent, three-pass adversarial review showed that an edit made during a pause could be overwritten.
Offline writes were lost to three stacked causes: the queue had stopped draining after a refactor, an id lookup could never see the stored models, and passing network errors used up the retry budget. All three were fixed and checked against 25 adversarial scenarios.
- SwiftUI views clientrenders local
- to Repositories: writes
- Repositories processplain structs
- to SwiftData: saves
- SwiftData storesource of truth
- to Sync queue: enqueue
- Sync engine processchunks of 50
- to SwiftData: merge
- Sync queue queueevery write
- to Postgres: push
- Postgres storeRLS, triggers
- to Sync engine: changes
- A write goes through a repository and saves on the phone first.
- It joins a prioritized sync queue.
- Online, the queue pushes to the server.
- Changes come back in chunks of 50, each checked against edits still pending.
- The screen renders from the phone, and never waits on the network.
32 languages through five gates
deep diveThe app ships 32 languages, Arabic and Hebrew right-to-left among them: 15,023 keys and about 480,000 localized cells. Five checks run on every commit, and a manual audit checks display width, counting a CJK character as two cells.
A repair in September 2026 fixed about 7,600 cells, among them Simplified Chinese that had been machine-converted from Traditional, and lists of warning signs that had shifted out of line. Read-only agents wrote the patches, and each was reviewed before it was applied.
- String files process123 catalogs
- to Drift: commit
- Drift processgate 1
- to Orphans
- Orphans processgate 2
- to Router reach
- Router reach processgate 3
- to Placeholders
- The app clientRTL included
- Sibling leaks processgate 5
- to The app: ships
- Placeholders processgate 4
- to Sibling leaks
- A change to a string catalog starts a commit.
- No language may fall below the global floor.
- No reference may point at a missing key.
- Every key must be reachable by the prefix router.
- Non-Latin scripts may not keep untranslated placeholders.
- No language may borrow a sibling’s strings.
- Then it ships, in 32 languages.
An alarm that wakes a parent on a silenced phone
deep diveApple denied the Critical Alerts entitlement on 4 June 2026. A wake alarm on AlarmKit shipped the same day: a real system alarm that rings through the silent switch and Focus, with Snooze and Stop.
Backstop alarms that escalate every ten minutes until a feed starts, and cancellation when a partner logs the feed, came with it. The core alarm is verified on a device; those two are still on the device-test list.
Twins first
deep divePer-baby Home cards, Live Activities and widgets shipped in v1.0.3 in June 2026, with a short note about them in What’s New. From August, sign-ups from families expecting twins stood out, so v1.0.4 rebuilt the app twins-first: timers for each baby at once, delivery timelines adjusted for chorionicity, and corrected age. Sign-ups went from about 1.8 a day in the three weeks before it to about 10 a day after. The whole story is in Twins found us.
Genetics before images
deep diveThe optional prediction computes trait probabilities in code, with Punnett squares and no randomness, before any image is generated: eye colour from a two-gene model, hair colour with red hair modelled on its own, hair type, a three-gene skin model and five single-gene traits. Its confidence starts at 60% and grows with the data it is given, up to 95%, and the report’s text only narrates the numbers it is handed.
Tools and why
frontend
- Next.js 16
- the proba.baby website
backend
- Supabase
- Postgres, auth, realtime, storage and edge functions
Lovable set it up for the prototype. In October 2025 I moved to a project I own and kept it, because Postgres with row-level security can hold the rules a whole family depends on.
the story
On 14 June 2026 one shared trigger read a column most of its tables don’t have, and their writes failed until a same-day hotfix. Since late September the rule is that every non-trivial migration runs on a throwaway local Postgres first, twice.
- Apple Push Notification service
- push-to-start for Live Activities
Push-to-start is the only way to put a Live Activity on a partner’s lock screen while the app is closed.
the story
The app once picked Apple’s test or production servers from a build flag, so archived builds sent pushes that Apple accepted and never delivered. It now reads the environment from the signing profile, and Live Activity and family pushes retry the other one.
data
- SwiftData
- the offline-first store on the phone
A parent logging a feed at night can’t wait for the network.
the story
Starting a feed once wrote to the cloud and to memory but not to SwiftData, so the running feed vanished on the next reload. Every write now lands on the phone first.
- GRDB and SQLite
- the bundled reference and names databases
Reference data and 7,029 baby names ship inside the app and are searched offline.
the story
The app reads the compiled database, not the JSON it is built from, so fixing a file did nothing until the database was rebuilt. The commit hook now refuses a data change staged without the rebuilt database.
ai
- Claude Code
- plans, memory and adversarial reviews while building
It wrote most of the code, inside rules, plans and audits I wrote around it; Esma’s daily testing and users’ reports caught what it missed.
the story
1,833 commits carry its co-author line, from its first commit on 27 December 2025. Almost every rule it follows names the mistake that caused it.
- Lovable
- the first web prototype, September–December 2025
It turned an idea into a flow I could tap through within days.
the story
721 commits in its first two weeks, 140 of them on 30 September 2025. Its Capacitor wrap could not show a picked photo without a lock and unlock, and on 25 January 2026 the web codebase, 173,909 lines, was deleted.
- Codex
- a second opinion on audits and plans
the story
It caught a nested sheet that a twenty-agent Claude audit had missed.
mobile
- Swift and SwiftUI
- the iPhone, iPad and Watch apps
In the web view a picked photo needed a lock and unlock to appear, and a native Vision plugin couldn’t even be found from inside the wrapper. On native’s first night, a plain image view fixed the photo bug.
the story
SwiftUI sheets on iPad and iOS 26 kept closing themselves. After 55 commits of fixes, every modal follows a two-tier policy that an audit script checks.
- WidgetKit and ActivityKit
- widgets and Live Activities
the story
In the app’s Home Screen widgets on iOS 26, live time text came out blank in the stored snapshot. Durations are frozen into each update instead.
- App Intents
- Siri, Shortcuts and the buttons inside Live Activities
- AlarmKit
- the wake alarm
Apple denied Critical Alerts on 4 June 2026, and the alarm moved to AlarmKit the same day.
- StoreKit 2
- credits and Supporter subscriptions
design
- Remotion
- promo films rendered from code
other
- XCTest and Swift Testing
- the test suites
Design
The brand, the app icon and the motion are mine. The in-app design system sits on iOS 26’s Liquid Glass. 43 custom glyphs are built as real SF Symbols templates, so they take the system’s weights and the Lock Screen’s vibrancy, and they replaced the emoji the app once used as icons.
wc -l
as of Oct 2026- users
- 500+
- monthly active users205 weekly, 110+ daily
- 360
- languages, right-to-left included
- 32
- localization keys, about 480,000 localized cells
- 15,023
- widgets, 8 of them set per baby
- 17
- Live Activities, mirrored across the family
- 8
- Apple Watch screens
- 11
- local Swift packages; no feature imports another
- 21
- Deno edge functions
- 48
- Postgres migrations
- 306
- agents in a three-pass review of the sync fix
- 60
- adversarial sync scenarios
- 25
Timeline
- A web prototype, written by Lovable721 commits in its first two weeks
- The Capacitor wrap gets its own app ID and its first iOS builds
- The web view given up; a native SwiftUI repository starts that night127 commits there before 15 December
- The SwiftUI app moves into the main repository: 233 Swift files in one commit
- Claude Code’s first commit
- The web codebase deleted: 173,909 lines
- The app split into 21 Swift packagesMarch–April 2026
- First App Store release
- A two-tier modal policy, after 55 commits of sheet fixes
- v1.0.3: Home cards, Live Activities and widgets per baby
- A wake alarm on AlarmKit, the day Critical Alerts was denied
- v1.0.4, the twins release
- proba.baby’s new homepage goes live
Status and next
Live on the App Store, with the next version in development. More than 500 people have signed up; in early October about 110 used the app every day and 360 every month, and total revenue so far is $32. Since 22 September 2026 the focus is polish over new features.
- v1.0.4 on the App Storethe twins release, 4 Sept 2026
- proba.babythe new homepage is live
- v1.0.5in development
- the website’s inner pagesredesign in progress
Lessons
What I would do differently:
- Go native the day the camera matters. Nearly every bug in the Capacitor weeks belonged to the web view, not to the app.
- Write the rule and its reason on the day of the mistake, and turn anything that breaks twice into a gate. The modal audit still isn’t in the pre-commit hook, and it shows.
- Treat an agent’s list as a floor and its “done” as a claim. Ask for the delta, the log line and the device.
- Verify life-safety data entry by entry before it ships, not six months later.
- Check that a translation is in the right language, not just that it exists, before adding the next language.
- Test where the bug lives: on an iPad, on a real phone, and against the build that is actually in the store.
- Escalate to an escape hatch only on a named trigger, and check its accessibility the day it ships.
The whole story is in seven notes, starting with Four platforms in four months.