Your fertility chart, on every screen, readable only by you.
Chart35 is an offline-first, end-to-end-encrypted charting app for the Creighton Model FertilityCare™ System (CrMS). It runs as a web/PWA, as native iOS and watchOS apps, and as a native Android app, all sharing one charting engine and one zero-knowledge sync layer. Daily observations - bleeding, mucus, peak day, intercourse - become the classic 35-column Creighton chart with auto-computed stamps, a monthly calendar, and cycle-trend insights.
This is a public showcase of the architecture and engineering. The application source is private. The web client is also published, in full, under the AGPL as Chart35Client so the privacy-critical code anyone runs in a browser stays auditable.
CrMS charting is health data of the most sensitive kind, and most apps in this space ask you to hand it to a server in plaintext. Chart35 starts from the opposite premise: your observations live on your device, and if you opt into sync they are encrypted on the device before they leave it. The server stores ciphertext it cannot read. The same chart then has to look and behave identically whether you open it on a phone, a watch, a tablet, or the web - because the people who chart do it daily, often first thing in the morning, on whatever device is in reach.
| Concern | Choice |
|---|---|
| Web / PWA | TypeScript, Vite, Dexie.js (IndexedDB), Web Crypto API, vite-plugin-pwa |
| iOS / watchOS | SwiftUI, SwiftData, CryptoKit |
| Android | Kotlin, Jetpack Compose, Material 3 |
| Sync server | Node, Express, SQLite (private) |
| Cryptography | PBKDF2-HMAC-SHA256 (600k) -> AES-256-GCM, byte-compatible across all three clients |
| Hosting | Apache reverse proxy, Let's Encrypt TLS |
- Offline-first, account-optional. Every client stores observations locally and computes the full chart on-device - IndexedDB on web, SwiftData on Apple, a local store on Android. You can chart an entire cycle with no account and no network. Sync is a target, not a dependency.
- One CrMS engine, three implementations, one source of truth. The stamp rules (green/red/white/yellow, peak day P, post-peak 1/2/3, cycle detection) are reimplemented in TypeScript, Swift, and Kotlin and verified against a shared set of golden charts, so a given run of observations produces the same stamps on every platform. The clinical rules trace to the Hilgers Creighton Model text.
- Zero-knowledge sync. Data is encrypted client-side before upload; the server persists opaque blobs and never sees plaintext health data. The provider-share path uploads a separate filtered payload that strips free-text notes, so a shared chart shows the method-relevant codes and nothing personal.
- Native edges where they earn their place. watchOS integration is the reason the Apple client is SwiftUI rather than a wrapper; the Android client is native Compose rather than a WebView so it matches platform navigation and theming. The web app stays the fast path for everything cross-platform.
The wire format every client reads and writes is a single self-describing string:
base64(iv) : base64(tag) : base64(ciphertext)
The key is derived from the account passphrase with PBKDF2-HMAC-SHA256 at 600,000 iterations; payloads are sealed with AES-256-GCM using a 12-byte IV and a 128-bit auth tag. The PBKDF2 step is implemented over the same HMAC primitive on each platform (rather than each language's convenience wrapper) specifically so the derived bytes match across WebCrypto, CryptoKit, and the JVM - the cross-platform byte-compatibility is unit-tested against RFC 6070 vectors.
web/ TypeScript SPA — chart/calendar/insights views, Dexie store, crypto + sync services
apple/ SwiftUI + SwiftData app with a watchOS companion (CryptoKit)
android/ Kotlin/Jetpack Compose app — one CrMS engine port, Material 3 theming
server/ Node + Express + SQLite sync API (private)
| Phase | Scope |
|---|---|
| v1 | Web/PWA daily driver: Dexie store, the CrMS engine, the 35-column chart, calendar, JSON/CSV export. Fully offline. |
| v2 | Accounts + end-to-end-encrypted sync, provider-share links, in-app account deletion that wipes server and device in one transaction. |
| v3 | Native reach: SwiftUI iOS/watchOS app and a native Kotlin/Compose Android app, both sharing the engine and wire format. |
![]() |
![]() |
| The classic 35-column chart with auto-computed stamps. | Cycle insights and length trend. |
![]() |
![]() |
| Trend zoom for scanning many cycles at once. | The native app in dark mode. |
Built and running at chart35.com, with native iOS/watchOS and Android apps. The full source is kept private; the AGPL Chart35Client mirror keeps the in-browser code auditable. Questions or interest: open an issue here, or reach me at jacob@stephens.page.
(c) 2026 Jacob Stephens. The Chart35 name, application, and source are proprietary. This repository documents the project; it does not grant a license to the application or its code. The separately linked Chart35Client web mirror is AGPL-3.0-or-later.
The Creighton Model FertilityCare™ System is a trademark of FertilityCare Centers of America, used here descriptively only. Chart35 is an independent project and is not affiliated with, endorsed by, or sponsored by FertilityCare Centers of America, Creighton University, or the Saint Paul VI Institute.




