---
title: ProbaBaby
description: A twins-first baby tracker I designed and built, shaped every day by its first real user.
url: https://secretocho.design/work/probababy/
type: CreativeWork
status: live
published: 2026-05-14
---

# ProbaBaby

> A twins-first baby tracker I designed and built, shaped every day by its first real user.

case 02 / 05 · live

- **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](#doctor).

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.

**32** · languages, on iPhone, iPad and Apple Watch

## 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](https://secretocho.design/notes/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](https://secretocho.design/notes/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](https://secretocho.design/notes/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](https://secretocho.design/notes/apple-postgres-and-32-languages/)

### 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](https://secretocho.design/notes/apple-postgres-and-32-languages/)

### 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](https://secretocho.design/notes/apple-postgres-and-32-languages/)

### 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](https://secretocho.design/notes/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.

**10 + 11** · infrastructure and feature packages; no feature package imports another

## Decision log

### Web or native

- **options**: keep the Capacitor wrapperReact Native with Exponative 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-firstoffline-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 sheetstwo 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 datasetsWHO’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 dive

We 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.

**241** · countries and regions’ vaccine schedules, bundled offline

## One timer, two phones

· deep dive

When 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.

*Diagram: One timer, two phones: phone A starts a feed and writes it locally; the server upserts it under a one-active-session lock; a trigger sends push-to-start through APNs, and phone B's Lock Screen shows a read-only mirror of the same running timer. When phone B stops it, the canonical row closes and every mirror clears.*

live activities **8** · widgets **17** · sync scenarios **25**

*start it on one phone. **the other one already knows.***

## No login freeze, no lost offline writes

· deep dive

Login 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.

**25** · adversarial scenarios the offline fixes were checked against

1. **SwiftUI views** client · renders local
   - to Repositories: writes
2. **Repositories** process · plain structs
   - to SwiftData: saves
3. **SwiftData** store · source of truth
   - to Sync queue: enqueue
4. **Sync engine** process · chunks of 50
   - to SwiftData: merge
5. **Sync queue** queue · every write
   - to Postgres: push
6. **Postgres** store · RLS, triggers
   - to Sync engine: changes

1. A write goes through a repository and saves on the phone first.
2. It joins a prioritized sync queue.
3. Online, the queue pushes to the server.
4. Changes come back in chunks of 50, each checked against edits still pending.
5. The screen renders from the phone, and never waits on the network.

*the phone never waits on the network.*

## 32 languages through five gates

· deep dive

The 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.

**15,023** · keys, about 480,000 localized cells

1. **String files** process · 123 catalogs
   - to Drift: commit
2. **Drift** process · gate 1
   - to Orphans
3. **Orphans** process · gate 2
   - to Router reach
4. **Router reach** process · gate 3
   - to Placeholders
5. **The app** client · RTL included
6. **Sibling leaks** process · gate 5
   - to The app: ships
7. **Placeholders** process · gate 4
   - to Sibling leaks

1. A change to a string catalog starts a commit.
2. No language may fall below the global floor.
3. No reference may point at a missing key.
4. Every key must be reachable by the prefix router.
5. Non-Latin scripts may not keep untranslated placeholders.
6. No language may borrow a sibling’s strings.
7. Then it ships, in 32 languages.

*no language ships behind the others.*

## An alarm that wakes a parent on a silenced phone

· deep dive

Apple 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.

**4 June 2026** · Critical Alerts denied; AlarmKit shipped the same day

## Twins first

· deep dive

Per-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](https://secretocho.design/notes/twins-found-us/).

**v1.0.3 → v1.0.4** · a short note in What’s New, then a twins-first rebuild

## Genetics before images

· deep dive

The 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.

**60% → 95%** · confidence grows with the data, never past 95%

## Tools and why

### frontend

- **Next.js 16**: the proba.baby website

### backend

- **Supabase**: Postgres, auth, realtime, storage and edge functionsLovable 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 storyOn 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 ActivitiesPush-to-start is the only way to put a Live Activity on a partner’s lock screen while the app is closed.the storyThe 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 phoneA parent logging a feed at night can’t wait for the network.the storyStarting 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 databasesReference data and 7,029 baby names ship inside the app and are searched offline.the storyThe 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 buildingIt 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 story1,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 2025It turned an idea into a flow I could tap through within days.the story721 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 plansthe storyIt caught a nested sheet that a twenty-agent Claude audit had missed.

### mobile

- **Swift and SwiftUI**: the iPhone, iPad and Watch appsIn 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 storySwiftUI 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 Activitiesthe storyIn 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 alarmApple 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.

**43** · custom glyphs, built as SF Symbols templates

## wc -l

· as of Oct 2026

- **users**: 500+
- **monthly active users · 205 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

1. 29 Sept 2025 · A web prototype, written by Lovable · 721 commits in its first two weeks
2. 13 Oct 2025 · The Capacitor wrap gets its own app ID and its first iOS builds
3. 10 Nov 2025 · The web view given up; a native SwiftUI repository starts that night · 127 commits there before 15 December
4. 18 Dec 2025 · The SwiftUI app moves into the main repository: 233 Swift files in one commit
5. 27 Dec 2025 · Claude Code’s first commit
6. 25 Jan 2026 · The web codebase deleted: 173,909 lines
7. Mar 2026 · The app split into 21 Swift packages · March–April 2026
8. 14 May 2026 · First App Store release
9. 21 May 2026 · A two-tier modal policy, after 55 commits of sheet fixes
10. Jun 2026 · v1.0.3: Home cards, Live Activities and widgets per baby
11. 4 Jun 2026 · A wake alarm on AlarmKit, the day Critical Alerts was denied
12. 4 Sept 2026 · v1.0.4, the twins release
13. 6 Sept 2026 · 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.

**4 Sept 2026** · v1.0.4, the twins release, on the App Store

1. v1.0.4 on the App Store · the twins release, 4 Sept 2026
2. proba.baby · the new homepage is live
3. v1.0.5 · in development
4. the website’s inner pages · redesign 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](https://secretocho.design/notes/four-platforms-in-four-months/).

**4 weeks → 1 night** · the photo bug: fought inside the web view, then gone on native’s first night

## Proves

- [Native Apple platforms](https://secretocho.design/about/#capabilities)
- [Localization and accessibility](https://secretocho.design/about/#capabilities)
- [Product thinking and UX research](https://secretocho.design/about/#capabilities)
