Recurring events defaulted skip:"omit"+rscale:"gregorian" on every rule,
which Stalwart serialises into the CalDAV RRULE as RSCALE=GREGORIAN;SKIP=OMIT.
DAVx5 rejects that ("Invalid recurrence rule"), breaking Android sync. Those
RFC 7529 fields only matter for non-Gregorian scales / invalid-date handling,
so stop emitting them for plain Gregorian rules.
- lib/recurrence-rule.ts: createRecurrenceRule() helper with no skip/rscale.
- CalendarRecurrenceRule.rscale/skip are now optional.
- event-modal + recurrence-editor build rules via the helper.
- demo fixture cleaned; unit test asserts no skip/rscale.
Closes#805.
Stalwart lazily creates an account's default calendar on the first request
that touches it. Bulwark's login fires Calendar/get and CalendarEvent/query
concurrently, and on multi-node servers each node can create its own default,
leaving users with two identical "Personal" calendars. Hold later calendar and
address-book requests per account until the first one settles.
Stalwart seeds the default identity name from the principal "Full name" only
once, and addAccount never rewrote displayName after first login, so an admin
rename never showed up in Bulwark. Read the live name via x:Account/get
through the passthrough (falling back to the identity name elsewhere) and
prefer the synced registry name in account settings.
The composer chip only knew a boolean: `uploading` flipped on, an
indeterminate bar animated at 40% width, and nothing anywhere carried a
percentage, so a large attachment on a slow link looked frozen for the
whole transfer. The JMAP client has been able to report real progress
since #333 gave uploadBlob an XHR path with onProgress; the composer
just never passed it.
Stock path: pass onProgress and the existing AbortController's signal to
client.uploadBlob. The chip's bar becomes determinate as soon as bytes
are reported (falling back to the indeterminate animation otherwise),
and cancel now aborts the transfer itself instead of being checked only
once the upload has finished.
Plugin offload path (#751): a plugin that offloads an attachment moves
the bytes itself via api.http.post, which the composer cannot observe.
It also cannot be handed a callback: the sandbox bridge marshals
functions host-to-plugin only, and the File a plugin holds is a
structured clone, so object identity cannot correlate its request back
to an attachment. The staged file id can: the composer generated it and
handed it to onBeforeBlobUpload. A plugin echoes it back as the new
optional `progressFileId` on the post options; the host sends the Blob
via XMLHttpRequest (fetch exposes no upload progress events) and reports
bytes into a host-side registry keyed by that id, which the composer
subscribes to before running the hook. Progress never crosses into the
sandbox, and reports for an id the composer is not tracking are dropped,
so the plugin API grows exactly one optional field. Calls without
progressFileId keep the fetch path untouched.
Closes#754
addToast() minted toast ids with crypto.randomUUID(), which only exists in
secure contexts (https/localhost). On a plain-http LAN origin every
toast.success/toast.error threw a TypeError, so e.g. calendar event Save
crashed AFTER a successful CalendarEvent/set — before the dialog-close ran —
leaving the dialog open reporting failure while the server had the event;
retries created duplicates. Same class as the generateMessageId send bug.
Also guards the same-class contact uid fallback in lib/jmap/client.ts
(createContact). Both now use the guarded generateUUID() from lib/utils.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtFkcUA8VM2XAKZKzptpqy