← All posts Home jacob@stephens.page
Case Study · Chart35 · 2026

A privacy-first fertility chart, encrypted on your device.

Chart35 (chart35.com) is a fertility-cycle charting app for the Creighton Model FertilityCare System. It is local-first, works offline, auto-computes the method's stamps and codes, and - when you sync - encrypts everything on your device so the server only ever stores ciphertext.

How it works

The trust boundary is the whole point: plaintext never leaves the device.

YOUR DEVICE  ·  BROWSER (PWA) / ANDROID Daily observation entrybleeding, mucus, peak, notes CrMS stamp & code logicpure, unit-tested IndexedDB (Dexie)source of truth, plaintext, on-device Chart & calendar render35-column CrMS chart Web Crypto - PBKDF2 + AES-256-GCM encrypts a payload from a passphrase-derived key, on the device Service worker (PWA) offline-always; installable; Capacitor wraps the same app on Android HTTPS - CIPHERTEXT ONLY SYNC SERVER  ·  PRIVATE REPO Node + Express + JWTaccounts, email verification SQLite - encrypted blobs onlyserver never sees plaintext health data Transactional emailverification, password reset Provider sharing: a time-limited, read-only share link renders a filtered chart for a FertilityCare Practitioner - no account, no write access, expires.

Why I built it

Sensitive data, a precise method, and no good private option.

The Creighton Model is a fertility-awareness method with a strict charting system: each day's observations map onto a colored stamp and a set of codes, and the rules are exact. Paper works but is unforgiving, and existing apps generally require an account and a cloud round-trip to log a single day - putting some of the most sensitive health data a person has on someone else's server in plaintext.

Chart35 keeps the precision of the paper method and adds what software is good at - auto-computed stamps, instant charts, cross-device backup - without that privacy cost. The data lives on your device; sync is optional; and when you do sync, the server is untrusted with respect to your health data. It was found organically through search and is used by real people beyond me, the bar I cared about.

Engineering highlights

Four pieces, each problem → approach → outcome.

Faithful CrMS computation

Problem. A wrong stamp is not a cosmetic bug - it is incorrect health information. Approach. The clinical logic (stamps: green/red/white/yellow, peak day, post-peak count 1/2/3, Base Infertile Pattern) lives in pure, unit-tested functions separate from storage and UI, implemented to the authoritative reference, with manual overrides for cycle start, peak, and BIP. Outcome. A chart that matches the method, computed automatically but user-correctable.

End-to-end encrypted sync

Problem. Cross-device backup is useful, but the server must never read a user's observations. Approach. A passphrase-derived key (PBKDF2) encrypts payloads with AES-256-GCM via the Web Crypto API, on the device. Only ciphertext crosses the network; the server stores encrypted blobs under its own at-rest protection. Outcome. Even a fully compromised server yields opaque ciphertext. In-app Delete Account wipes the server row, encrypted blobs, share tokens, and on-device cache in one transaction.

Local-first, offline-always

Problem. A daily tracker has to be instant and work with no connection and no account. Approach. IndexedDB (via Dexie) is the source of truth; the app is an installable PWA with a service worker, plus a Capacitor wrapper on Android. Accounts and sync are purely additive. Outcome. Offline is the normal mode, not a degraded one; you can log an entire cycle in airplane mode.

Provider sharing

Problem. Users need to show their chart to a FertilityCare Practitioner without handing over their account. Approach. A separate read-only entry point renders a filtered chart from a time-limited share link - no write access, and it expires. Outcome. Practitioners review the chart in a browser; the link can't mutate data and lapses on its own.

Stack & why

The reasoning, not just the list.

TypeScript, no UI framework - a small, auditable client; views render to the DOM directly, so there is less between a reader and the privacy-critical code.
Dexie / IndexedDB - local-first source of truth, so the app works fully offline and the network is optional.
Web Crypto - PBKDF2 + AES-256-GCM - end-to-end encryption so the server is untrusted for health data, not just "encrypted at rest."
vite-plugin-pwa - installable, offline-capable, with a service worker caching the shell.
Capacitor (Android) - a native wrapper over chart35.com, shipping to the Play Store from the same codebase.
AGPL-3.0 - anyone redeploying a modified version as a service must publish their changes, keeping every deployed build auditable for a privacy app.

Auditability: the entire client - encryption, IndexedDB layer, chart rendering, provider-share filtering - is open at github.com/JacobStephens2/Chart35Client (see its ARCHITECTURE.md). The sync server is a separate private repository; the client talks to it only over documented HTTPS endpoints.

The Creighton Model FertilityCare System is a trademark of FertilityCare Centers of America, used descriptively. Chart35 is an independent project, not affiliated with or endorsed by them.

← Back to portfolio