Syncing (Duo)
In cloud mode the plan belongs to a household, and users are linked to it. A second person joins with an invite code.
Sharing model
- One household = one shared state (
jsonb). - Members are linked through a membership table.
- Access is enforced by Row Level Security — only a member can read and change their household's state.
- The state is end-to-end encrypted; the household's encryption key is shared with the invited partner via key exchange, so neither we nor third parties can read it.
Save with merge
The state is written as a whole, with optimistic concurrency control:
- The app remembers the
updated_atmarker of the state it last fetched. - On save, it sends that marker. If someone wrote in the meantime, the markers diverge → a conflict is detected.
- The app then fetches the fresh state and merges: month-keyed maps (entries,
snapshots) are combined, and the remaining fields are taken from the newer side
by
updated_at. This way "two people filling in different months" doesn't lose data.
Fetching and refreshing
The state is fetched when you enter the household and when you return to the tab/window, keeping both people in sync.
Leaving and deleting
- Leave the household — revokes this account's access; when the last member leaves, the household (and its state) is removed so no orphaned data is left.
- Delete account — removes any household where you're the only member (GDPR).