Dawnlet · under the hood

Todos & events

Todos and events look like different features and share almost all of their machinery. The interesting part is what happens when something repeats — and what it means to skip one instance of it.

One recurrence model Definitions vs. occurrences Skip without breaking Apple Calendar sync is Plus

§Definitions and occurrences

Every repeating thing in Dawnlet splits in two, and the same split appears in four modules:

ModuleThe definitionThe occurrence
TodosA repeating todo and its scheduleOne instance on one date, done or skipped
EventsA recurring event and its scheduleOne dated event
HabitsThe habit and its weekdaysAn entry for one day
MoneyA recurring transactionA real transaction row for one month

Each module has a screen for each half — a list of what repeats, separate from the list of what's actually on your day. Keeping them apart avoids the usual confusion where deleting today's instance appears to delete the whole series, or editing the series appears to leave today alone.

1The recurrence model

One recurrence shape covers weekly, monthly-by-date and monthly-by-nth-weekday ("the second Tuesday"), with an anchor date the pattern counts from.

It was written for todos, and recurring transactions reuse it wholesale rather than defining a parallel money-flavoured recurrence. The schedule editor is literally the same screen. A bug fixed in "every other Tuesday" is fixed for bills at the same moment.

What's deliberately missing

No hourly recurrence, no "every N days", no end-after-N-occurrences, and no full calendar standard. Dawnlet is a day organizer, not a calendar server; the patterns above cover what people actually put in one, and each addition multiplies the occurrence-generation edge cases.

2Virtual and stored

A repeating thing has infinite future instances, so they can't all exist as rows. But an instance you've interacted with — completed, skipped, or edited — has to exist somewhere.

So occurrences are generated on demand for the range being displayed, and merged with the stored rows for that range. A stored row wins wherever both exist. Fetching a day's events returns exactly the right set, already merged and already honouring skips, so nothing downstream — including the notification planner — has to know which instances were real rows and which were computed.

Money is the one module where occurrences are materialised eagerly instead: loading a month walks its days and creates a real transaction for every date the schedule matches, unless that pairing already has one. Money needs real rows because they participate in totals and budgets — a virtual bill that isn't in the sum is worse than useless.

3Skipping one instance

Skipping is a first-class, non-destructive operation: this Tuesday's instance is marked skipped, and the series carries on untouched.

The mechanism is the same everywhere: the skip is recorded rather than the occurrence being deleted. That matters because generation is idempotent — it creates what's missing. If a skip were a deletion, the very next generation pass would helpfully recreate the thing you just dismissed. Recording it means the generator can see that this pairing has been dealt with.

One rule, four modules

Skipped recurring todo, skipped event occurrence, skipped habit day, skipped monthly bill — all the same idea, all non-destructive, all invisible to the totals and reminders that shouldn't count them.

4Overdue

A one-off todo with a due date in the past is overdue, and says so on its row wherever it appears. It stays visible on today rather than being stranded on the day it was due — a todo you can't see is a todo you won't do.

Repeating todos don't accumulate overdue instances. Missing Tuesday's instance of a weekly todo doesn't leave it hanging over you forever; the next one comes round. That's a deliberate difference from one-off todos, and it's the behaviour that keeps a recurring list from becoming a wall of guilt.

5Apple Calendar & Reminders

A Plus feature, and the one place Dawnlet reads data it didn't create.

The integration works entirely on your device. Calendar events and Reminders are read through the system frameworks and shown alongside your todos and habits, and a Dawnlet event or todo can be written out as a real Calendar event or Reminder. None of it is ever sent to or stored in Dawnlet's database.

That's not a privacy nicety bolted on afterwards — it's the reason the feature is structured the way it is. Syncing a calendar through a server would mean storing a copy of everything on it, which is a much larger promise than this app wants to make.

An imported item is matched by the identifier its source system gives it, so re-importing updates what's there rather than creating duplicates.