diff --git a/apps/pwa/src/components/EventForm.tsx b/apps/pwa/src/components/EventForm.tsx index d3bf3d8..10a99ab 100644 --- a/apps/pwa/src/components/EventForm.tsx +++ b/apps/pwa/src/components/EventForm.tsx @@ -352,8 +352,18 @@ export function EventForm() { if (recurrence !== 'none') { if (recurrenceBound === 'count' && recurrenceCount < 1) { newErrors.recurrenceBound = 'Must be at least 1 occurrence' - } else if (recurrenceBound === 'until' && recurrenceUntil) { - if (recurrenceUntil < startDate) { + } else if (recurrenceBound === 'until') { + // WR-02: a blank end date with bound='until' must be a validation error. + // Previously it passed validation and the payload spread dropped + // recurrenceUntil, silently creating an UNBOUNDED series — the opposite + // of the user's stated "Ends: On date" intent. + if (!recurrenceUntil) { + newErrors.recurrenceBound = 'Choose an end date' + } else if (startDate && recurrenceUntil < startDate) { + // WR-07: only compare when startDate is non-empty. Both values are + // zero-padded ISO DATE strings here, so lexicographic compare is valid; + // guarding on a non-empty startDate avoids `recurrenceUntil < ''` (always + // false) silently skipping the bound-before-start guard. newErrors.recurrenceBound = 'End date must be after the event starts' } }