Stop calling every event a series #28

Closed
opened 2026-08-25 14:01:35 +00:00 by jcoffey-dev · 1 comment
Owner

Fixes #25, including the addendum about the delete dialog.

Editing a one-time event showed "This is a recurring event — changes apply to the whole series", and deleting one asked "Delete all occurrences?" / "This will delete the entire series" for an event that has exactly one.

Three places treated the presence of baseEventId as proof of recurrence:

  • EventEditor.tsx — the info box
  • EventPopover.tsx — the delete confirmation
  • CalendarContextMenu.tsx — the same confirmation from the right-click menu

That is not what the field means. loadRange queries with expandRecurrences: true, and Stalwart sets baseEventId on everything it returns that way — a one-off event included, pointing at itself. The mock never sets the field at all, which is why nothing caught this until it met a real server.

All three now share isRecurring() in store/calendar.ts. It asks about recurrence rules, which are what actually make a series, and also treats a base that is a different event as an occurrence of one — so if an expanded instance ever arrives without its rules attached, the delete dialog still tells the truth about what it is deleting. Deletion behaviour is unchanged; only the wording was wrong.

One thing worth a glance against the live 0.16.19 when convenient: this assumes Stalwart gives a non-recurring event a baseEventId equal to its own id, rather than minting a synthetic instance id for it. That matches the reported symptom and the spec reading. If it turns out to mint one, the one-off case needs the rules check alone — the test file has both shapes written out.

Tested: npm run typecheck, npm test (184 web + 88 server, all passing), with a new store/__tests__/recurrence.test.ts covering a one-off with a self-referential base, a bare event, a series by rules, and an occurrence whose base is another event.

🤖 Generated with Claude Code

Merged 2026-08-25 as coffey-labs/ihasmail@9a7f37b540

Rebuilt from: git history, session transcript.

Fixes #25, including the addendum about the delete dialog. Editing a one-time event showed "This is a recurring event — changes apply to the whole series", and deleting one asked "Delete all occurrences?" / "This will delete the entire series" for an event that has exactly one. Three places treated the presence of `baseEventId` as proof of recurrence: - `EventEditor.tsx` — the info box - `EventPopover.tsx` — the delete confirmation - `CalendarContextMenu.tsx` — the same confirmation from the right-click menu That is not what the field means. `loadRange` queries with `expandRecurrences: true`, and Stalwart sets `baseEventId` on everything it returns that way — a one-off event included, pointing at itself. The mock never sets the field at all, which is why nothing caught this until it met a real server. All three now share `isRecurring()` in `store/calendar.ts`. It asks about recurrence rules, which are what actually make a series, and also treats a base that is a *different* event as an occurrence of one — so if an expanded instance ever arrives without its rules attached, the delete dialog still tells the truth about what it is deleting. Deletion behaviour is unchanged; only the wording was wrong. One thing worth a glance against the live 0.16.19 when convenient: this assumes Stalwart gives a non-recurring event a `baseEventId` equal to its own id, rather than minting a synthetic instance id for it. That matches the reported symptom and the spec reading. If it turns out to mint one, the one-off case needs the rules check alone — the test file has both shapes written out. Tested: `npm run typecheck`, `npm test` (184 web + 88 server, all passing), with a new `store/__tests__/recurrence.test.ts` covering a one-off with a self-referential base, a bare event, a series by rules, and an occurrence whose base is another event. 🤖 Generated with [Claude Code](https://claude.com/claude-code) **Merged** 2026-08-25 as coffey-labs/ihasmail@9a7f37b5404f <sub>Rebuilt from: git history, session transcript.</sub>
Author
Owner

Checked against the live 0.16.19 rather than leaving it as an assumption — and the assumption was wrong.

A one-off event comes back from the expanded query as id: "eaaaaai", baseEventId: "i": Stalwart mints an instance id for it, and the base is a different id. So the "a base that is another event means it is an occurrence" clause would have kept calling every event a series, i.e. would not have fixed this at all on a real server.

Pushed 62e3b7a: recurrence rules alone decide it now, and the test carries the real id shape.

What that gives up is an expanded instance that arrives without its rules attached. The account I probed has a single non-recurring event, so that case is still unverified — it needs a real series to check, which means writing to a live calendar. Following that up separately.

Checked against the live 0.16.19 rather than leaving it as an assumption — and the assumption was wrong. A one-off event comes back from the expanded query as `id: "eaaaaai"`, `baseEventId: "i"`: Stalwart mints an instance id for it, and the base is a *different* id. So the "a base that is another event means it is an occurrence" clause would have kept calling every event a series, i.e. would not have fixed this at all on a real server. Pushed 62e3b7a: recurrence rules alone decide it now, and the test carries the real id shape. What that gives up is an expanded instance that arrives without its rules attached. The account I probed has a single non-recurring event, so that case is still unverified — it needs a real series to check, which means writing to a live calendar. Following that up separately.
This repo is archived. You cannot comment on issues.