Elemental app icon: a dark letter E inside OCR scan corners, crossed by a green line ending in a tan dot.

iOS Case Study

Elemental

A private supplement and medication log that pairs what you actually took with changes in your own health trends — processed entirely on device.

Version 1.0 iPhone, iOS 26+ Submitted to App Store review, September 2026

Role
Independent — product design, iOS engineering, release
Stack
Swift, SwiftUI, GRDB/SQLite, WidgetKit, App Intents, HealthKit, Vision
Architecture
Platform-neutral domain package behind a SwiftUI app layer
Network surface
None in the shipping app — no account, no server, no analytics SDK

1. The constraint that shaped the build

A health log is only worth keeping if the person keeping it trusts where the data goes. Supplement and medication routines are exactly the kind of record people are reluctant to hand to an account system, so privacy here is an architectural property rather than a promise in a policy document.

That decision cascades. There is no server to move recognition to, so label parsing had to run on device. There is no backend to compute statistics, so the association analysis had to be pure, testable domain logic that runs locally and on a background task. There is no analytics SDK, so behaviour questions had to be answered by reading the code and the tests instead of a dashboard.

The result is a shipping app with a deliberately small surface: local storage, on-device recognition, read-only Apple Health access, and no network calls at all.

2. What it does

Four root journeys. Today resolves the next scheduled intake and the current batch, and records taken or skipped. Products builds a product profile from a photographed label. Ledger holds the actual-intake history and the outcome records. Insights surfaces observed associations between a confirmed change in intake and what followed it.

3. Architecture

Domain logic lives in ElementalCore, a platform-neutral Swift package organised by bounded context: ontology, supply, scheduling, intake, dosage, lab, outcomes, analysis and reference data. Core models are value types and Sendable, so the same logic runs in the app, in the widget extension and in tests without a simulator.

The SwiftUI layer is organised by user journey rather than by screen type, and shares a design-system module with the widget extension. Widgets and App Intents resolve the next dose through the same resolver the main app uses, and write through the same idempotent path — a check-in from the Home Screen cannot double-log the same scheduled entry.

Persistence is GRDB/SQLite in an App Group container that the app, the widget and App Intents all open. Schema changes go through explicit migrations, and a database created by an earlier private-container build migrates into the shared container without overwriting data that is already there.

Two decisions worth calling out

4. From a label photo to a product profile

Reading a Supplement Facts label is a good test case for on-device extraction, because the cost of a wrong answer is high and the source is messy: small type, unit noise, undisclosed blends, and marketing copy mixed with facts.

  1. CaptureCamera or photo library. The image is used in memory for recognition and is not retained as a separate copy.
  2. RecogniseVision OCR extracts text on device, with an optional Foundation Models enhancement when the device supports it.
  3. StructureRule-based parsing turns text into named ingredients, amounts and units rather than passing raw strings forward.
  4. NormaliseEntries are matched against a bundled catalog with synonyms and common dose ranges.
  5. Reject what cannot be trustedLow confidence, unknown ingredients, unit anomalies, impossible quantities, out-of-range amounts and undisclosed content are all flagged before saving.
  6. ConfirmThe user reviews and edits the parsed facts. Recognition proposes; the person decides. Nothing is saved unconfirmed.

Why this order matters. The interesting failure mode is not a recognition error — it is a recognition error that gets saved silently and corrupts months of downstream analysis. Treating the parser as an advisor with an explicit review gate protects the data that later analysis depends on.

5. From logs to observations

The analysis asks a narrow question: when a change in actually recorded intake was confirmed, did anything shift in the outcome series around it? Everything about the design is in service of not overstating the answer.

The domain layer covers schedule resolution, exposure-change detection, two-sample estimation and outcome association with pure unit tests, which is what makes the statistical behaviour reviewable without a device.

6. Correctness details that decided the design

Several parts of the domain are small, unglamorous and load-bearing. Each of these exists because the naive version produces a wrong number that looks plausible:

7. Verification and release engineering

Verification is evidence-based: each release step is recorded with the command, the log and the resulting artifact, and nothing is marked done because it was intended to be.

83tests in 10 suites, platform-neutral core package
272app tests in 44 suites on the iOS target
111Python release-tooling tests
5validated 6.9-inch release screenshots

Release path

On the review iteration. App Review feedback drove real fixes back into the shipping build — including validation behaviour when a user fills in an unrecognised ingredient by hand. Handling that loop is part of the work, not an interruption to it.

8. What it deliberately does not do

An early version of a health product accumulates tempting features. These are out of scope on purpose, and the app is built and shipped without them:

Drawing this line early kept the domain honest: every feature that survived had to justify itself against the analytical guarantees above.

Elemental 1.0 is complete, signed and delivered, and has been submitted to App Store review as of September 2026. The app is iPhone-only, requires iOS 26 or later, and contains no developer-operated network endpoint.