Skip to content

Work / Aakalan / Wiki / Concepts

recurring-is-a-rule

Conceptverified 2026-10-30

recurring-is-a-rule

A recurring expense is a RULE, not a queue. It produces a real expense on its due date and nothing before then. No greyed-out "upcoming" rows exist anywhere: an expense that has not happened must not move a balance, and one that looks like it might is how people stop trusting a shared ledger.

  • Catch-up is explicit and one expense per occurrence, each dated to its own due date. Two months away means two expenses, never one merged bill: that would be a number nobody agreed, in the wrong month's insights. Capped at MAX_CATCH_UP so an old device cannot materialise years of rent on one launch.
  • Never silent. Every generated expense carries an activity entry naming the rule, and Activity shows what was just added with an Undo. See a-record-is-never-erased.
  • A rule whose group changed underneath it stops and says so. It never re-divides the bill among whoever is there now. isRuleStranded derives this from the group on every read rather than storing it, so a member who leaves and returns fixes the rule by itself.
  • Rules live at /users/{uid}/recurring even when their expense belongs to a group: a rule is a private arrangement with yourself. Unlike an expense, a rule may be deleted outright: it is a setting, not a record of something that happened.
  • Pro lapsing pauses rules rather than quietly not running them, and the screen says so. RecurringRunner holds the entitlement check, because the store has no business knowing about RevenueCat.
  • src/lib/recurrence.ts takes now as an argument, which makes "the app was closed for two months" a test rather than a bug report. The 31st in February falls back to the 28th and returns to the 31st the month after. It does not roll into March, which is what new Date(y, 1, 31) does.

Related: recurring, pro-never-gates-the-core-loop