Update a re-imported event rather than skipping it #301

Closed
opened 2026-09-07 19:56:58 +00:00 by jcoffey-dev · 0 comments
Owner

Closes #279.

Contacts and calendars disagreed about a re-import and only one half had been decided. A vCard whose UID a book holds, or an LDIF entry whose dn it holds, overwrites the card that is there (#242, #274). An event whose UID a calendar holds was counted and dropped (#222).

@mbunkus answered on #279: update in general, but do not overwrite changes made since — so that is what this does.

What updates, and what does not

An event whose UID this calendar already holds is updated from the file. Two properties are held back:

  • participants — every attendee's accepted/declined.
  • recurrenceOverrides — every "just this Wednesday" edit made here.

Both are answers and decisions taken after the file was written, and a file that mentions them at all describes them as they were at export. Writing either one over would destroy work silently, with no error returned anywhere. So a corrected export fixes the time, the title and the location, and leaves who said yes alone.

uid is held back with them — it is what the two were matched on, so it is already equal, and it is the event's identity rather than a field of it.

The cost runs the other way and is in FEATURES.md rather than left to be discovered: an attendee added at the source since the last import does not arrive, because nothing here can tell that apart from an answer given in ihasmail.

Scheduling messages

Still none, on an update as much as on a create. That is a genuine cost — an event a re-import moves is moved here and not in anybody else's copy — and it is the lesser one. An import is not the place to start mailing a room full of people who never asked for it; whoever is organising can send the update from the event itself. Said plainly in FEATURES.md.

Mechanics

  • The scan returns uid -> id rather than a Set<string>, since an update needs something to address — the same shape as contacts' scanBook.
  • Creates and updates share one maxObjectsInSet budget, as contacts' writeCards does. Batched separately, a file of 300 new and 300 changed events would go as two calls of 300 — neither over a ceiling of 500, both refused.
  • The counts become created/updated and the toast reports them the way the contacts import does.

Verification

25 store tests, including the hold-back, the addressing, sendSchedulingMessages: false on an update, a refused update surfacing its reason, and creates and updates splitting against one ceiling. Full suite: 1113 passing.

Driven against the mock end to end, since a store test does not exercise the component. Imported a file, gave the event an accepted RSVP and a per-occurrence override, then imported a corrected version of the same file carrying participants of its own:

  • one event, same id — title, start and location took the file's values
  • the accepted RSVP and the override were untouched
  • toast read "Updated 1 event, nothing new", and "Imported 1 event · 1 updated" for a file holding one of each

Translations

One new key in all nine catalogues: "Updated {n} events, nothing new". Two removed, now unreachable: "Already here: {n} events, nothing imported" and "{n} were already here". "{n} updated" was already there from the contacts import and is reused. Fallback count is 16 in every language before and after; the denominator moves 1279 → 1278.

Merged 2026-09-07 as coffey-labs/ihasmail@35935f2d3c

Rebuilt from: git history, session transcript.

Closes #279. Contacts and calendars disagreed about a re-import and only one half had been decided. A vCard whose UID a book holds, or an LDIF entry whose `dn` it holds, overwrites the card that is there (#242, #274). An event whose UID a calendar holds was counted and dropped (#222). @mbunkus answered on #279: update in general, but do not overwrite changes made since — so that is what this does. ## What updates, and what does not An event whose UID this calendar already holds is updated from the file. Two properties are held back: - **`participants`** — every attendee's accepted/declined. - **`recurrenceOverrides`** — every "just this Wednesday" edit made here. Both are answers and decisions taken after the file was written, and a file that mentions them at all describes them as they were at export. Writing either one over would destroy work silently, with no error returned anywhere. So a corrected export fixes the time, the title and the location, and leaves who said yes alone. `uid` is held back with them — it is what the two were matched on, so it is already equal, and it is the event's identity rather than a field of it. The cost runs the other way and is in FEATURES.md rather than left to be discovered: an attendee added at the source since the last import does not arrive, because nothing here can tell that apart from an answer given in ihasmail. ## Scheduling messages Still none, on an update as much as on a create. That is a genuine cost — an event a re-import moves is moved here and not in anybody else's copy — and it is the lesser one. An import is not the place to start mailing a room full of people who never asked for it; whoever is organising can send the update from the event itself. Said plainly in FEATURES.md. ## Mechanics - The scan returns `uid -> id` rather than a `Set<string>`, since an update needs something to address — the same shape as contacts' `scanBook`. - Creates and updates share one `maxObjectsInSet` budget, as contacts' `writeCards` does. Batched separately, a file of 300 new and 300 changed events would go as two calls of 300 — neither over a ceiling of 500, both refused. - The counts become created/updated and the toast reports them the way the contacts import does. ## Verification 25 store tests, including the hold-back, the addressing, `sendSchedulingMessages: false` on an update, a refused update surfacing its reason, and creates and updates splitting against one ceiling. Full suite: 1113 passing. Driven against the mock end to end, since a store test does not exercise the component. Imported a file, gave the event an accepted RSVP and a per-occurrence override, then imported a corrected version of the same file carrying participants of its own: - one event, same id — title, start and location took the file's values - the accepted RSVP and the override were untouched - toast read **"Updated 1 event, nothing new"**, and **"Imported 1 event · 1 updated"** for a file holding one of each ## Translations One new key in all nine catalogues: `"Updated {n} events, nothing new"`. Two removed, now unreachable: `"Already here: {n} events, nothing imported"` and `"{n} were already here"`. `"{n} updated"` was already there from the contacts import and is reused. Fallback count is **16 in every language before and after**; the denominator moves 1279 → 1278. **Merged** 2026-09-07 as coffey-labs/ihasmail@35935f2d3c34 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.