fix(02): CSS-safe occurrence ids + error boundary to surface render errors
Schedule-X rejects ids containing ':' '[' ']' (the old ${uid}::${iso} form) — mint ev-<uid>-<epochMs> instead. Add an ErrorBoundary so a render throw shows the error instead of a blank page.
This commit is contained in:
@@ -35,7 +35,7 @@ import ICAL from 'ical.js'
|
||||
* The DB calendarId is also present for reference but NOT used as the Schedule-X calendarId.
|
||||
*/
|
||||
export interface CalendarOccurrence {
|
||||
/** `${uid}::${startIso}` — stable unique identity for this occurrence */
|
||||
/** `ev-<sanitized-uid>-<epochMs>` — Schedule-X-safe stable id (see makeOccurrenceId) */
|
||||
id: string
|
||||
uid: string
|
||||
calendarId: number
|
||||
@@ -72,6 +72,27 @@ export interface OccurrenceMeta {
|
||||
isShared: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Schedule-X-safe occurrence id.
|
||||
*
|
||||
* Schedule-X validates event ids against `document.querySelector` — the id must be a valid
|
||||
* CSS identifier (letters, digits, '-', '_'), must NOT contain ':', '[', ']', '+', and must
|
||||
* NOT start with a digit. The old `${uid}::${iso}` format violated this (the ISO timestamp
|
||||
* carries ':' and the '[IANA/Zone]' bracket), so eventsService.set() threw and blanked the
|
||||
* calendar.
|
||||
*
|
||||
* Format: `ev-<sanitized-uid>-<epochMs>`
|
||||
* - 'ev-' prefix guarantees a non-digit first character.
|
||||
* - uid is sanitized (any non [A-Za-z0-9_-] char → '_') since iCal UIDs may contain '@', '.'.
|
||||
* - epochMs (occurrence start instant) disambiguates recurring occurrences and is stable
|
||||
* across refetches/windows, so Schedule-X dedup and the popover id lookup keep matching.
|
||||
*/
|
||||
function makeOccurrenceId(uid: string, start: ICAL.Time): string {
|
||||
const safeUid = uid.replace(/[^A-Za-z0-9_-]/g, '_')
|
||||
const epochMs = start.toJSDate().getTime()
|
||||
return `ev-${safeUid}-${epochMs}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a UTC offset (in seconds) as ±HH:MM.
|
||||
* ICAL.Time.utcOffset() returns total seconds (positive = east of UTC).
|
||||
@@ -190,7 +211,7 @@ export function expandOccurrences(
|
||||
const start = serializeTime(dtstart, allDay)
|
||||
const end = serializeTime(dtend, allDay)
|
||||
occurrences.push({
|
||||
id: `${uid}::${start}`,
|
||||
id: makeOccurrenceId(uid, dtstart),
|
||||
uid,
|
||||
calendarId,
|
||||
calendarName,
|
||||
@@ -226,7 +247,7 @@ export function expandOccurrences(
|
||||
const end = serializeTime(occEnd, allDay)
|
||||
|
||||
occurrences.push({
|
||||
id: `${uid}::${start}`,
|
||||
id: makeOccurrenceId(uid, next),
|
||||
uid,
|
||||
calendarId,
|
||||
calendarName,
|
||||
|
||||
Reference in New Issue
Block a user