SHIPPEDapp

twoThings

An iOS app that holds at most two things. The constraint is the feature: with room for only two, you finish before you add.

CREATED JUN 7, 2026 · UPDATED AUG 23, 2026

PROBLEM

A todo list with no ceiling becomes a place to put guilt. The list grows faster than it shrinks, and every item on it makes the others harder to start.

HYPOTHESIS

Cap it at two. The add slot simply disappears when it is full, so the app never nags — the shape of the screen does the arguing.

ARCHITECTURE

Soft-delete is the whole backbone. Finishing sets one timestamp, discarding sets another, and nothing leaves the store at the moment you act on it. That single decision buys four things: the rule becomes 'two active things' with one shared predicate the app, the widget and the Siri intent all read; undo is clearing a timestamp; streaks derive only from completions, so throwing away a typo never pads the count; and nothing is destroyed mid-animation while SwiftUI still holds it. App and widget share one SwiftData store in an App Group, synced through the user's private CloudKit database — and opening that store degrades through four fallbacks to an in-memory store with a visible warning, rather than trapping before the app draws a pixel.

STACK

SwiftSwiftUISwiftDataWidgetKitApp IntentsCloudKit

RESULT

Shipped. Completions from the Home Screen widget feed the same streak as completions in the app.

MEASURED

metricvaluebaseline
maximum active things2 · enforced by one shared predicate—
completed history retained60 days · pruned on launch—
store-open fallbacks4 · App Group + CloudKit → App Group → sandbox → memory—

LEARNINGS

  • One shared predicate for the core rule is what keeps an app, a widget and a voice intent from quietly disagreeing.
  • The store is the one thing that can kill a shipping build before it draws — it degrades or it takes the app with it.
  • SwiftData creates CloudKit record types in Development only; forgetting to promote the schema means sync works on your device and nobody else's.

TAGS

iosswiftdatawidgetsminimal