How Dawnlet actually works
These pages describe the design of each part of the app — the data it keeps, the rules it enforces, and the decisions that shaped it. They're blueprints, not source code: enough to understand the mechanism and argue with it, without reproducing the implementation.
Start here
If you only read one, read Architecture — the rest assume its vocabulary (repositories, view models, the module pattern every feature repeats).
Architecture
The shape every module repeats: protocol-backed repositories, observable view models, and the shared row components that make one habit look identical in three places.
Data & sync
Where your data lives, how row-level security scopes every table to exactly one account, and the grant that has to accompany it or nothing works.
Agenda & the day model
How one day is assembled from eleven modules, why the sections are reorderable, and why the whole screen is a list rather than a scroll view.
Habits & streaks
An append-only log of days, with every streak, week strip and percentage derived from it rather than stored — and what that buys you.
Todos & events
One recurrence engine serving three modules, the difference between a definition and an occurrence, and how skipping works without corrupting a series.
Money
Per-month budgets with no rollover, recurring transactions that generate real rows, and the redaction rule that keeps amounts out of notifications.
Time tracking
Why a session stores accumulated segments instead of a start time, and how switching activities stays atomic.
Notifications
The longest write-up, and the most interesting: planning a two-week horizon into 64 scheduling slots, on-device, with the app closed.
Categories & tags
One taxonomy across eight modules, scoped by which modules a category applies to.
Purchases & gating
Two subscription products in one group, no server-side tier, and why the gate is enforced twice.
The shape of the whole thing
Dawnlet is a native SwiftUI app talking to a Supabase Postgres database. There is no backend service of ours in between: the app authenticates against Supabase directly and queries tables over PostgREST, with row-level security doing the work a server-side authorization layer would otherwise do.
| Layer | What it is | What it deliberately isn't |
|---|---|---|
| Client | SwiftUI. An iPhone and iPad app, Home Screen widgets, an Apple Watch app and its complications — four targets, one codebase | Not cross-platform and not a web view. The Watch app is the only target that talks to the database itself; the widgets and complications draw a file the app wrote, because an extension cannot see the app's keychain. |
| Data | Supabase Postgres, one table per concept, RLS on every one | No API service of ours, so no endpoint to secure separately or keep in sync with the client. |
| Auth | Sign in with Apple, exchanged directly for a Supabase session | No password and no custom token minting. Identity is limited to the account ID, the email Apple supplies, and a name only if you share one. |
| Privileged work | One Edge Function, for account deletion | Not a general-purpose API. It exists because deleting an auth user needs a key that must never ship in an app. |
| Notifications | Computed and scheduled entirely on the device | No APNs, no push payloads, no notification server. |
| Purchases | StoreKit, two subscription products in one group | No server-side record of who pays: the entitlement is read from StoreKit on the device, never mirrored into the database. |
Every piece of server-side machinery is a thing that can break, leak, or need paying for. The parts of Dawnlet that genuinely need a server — durable storage and cross-device sync — use one. The parts that don't, like deciding whether to remind you about a habit tonight, run where the data already is.
A note on honesty
The app is under active development, and these pages describe what's built, not what's planned. Widgets and the Watch app were listed here as unbuilt until they shipped, and this paragraph changed only once they had. Where something is still a placeholder, the relevant page says so rather than describing it in the present tense.