From 43facb6e77d9990343172d8b879c90eaebcc76fd Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 01:13:43 +0000 Subject: [PATCH] fix(web): keep event's own date selectable in recurrence "Ends on" picker Anchor the recurrence "Ends on" picker's minimum date to the event's start date instead of its end. The end-based floor disabled the event's own date for any event running past local midnight (the end lands on the next calendar day, and react-datepicker compares day cells by calendar day), leaving the date the user most naturally picks unselectable. Generated-By: PostHog Code Task-Id: 2f1dc9cd-27d1-450a-9ca7-3fb74c208a29 --- .../RecurrenceSection.test.tsx | 31 +++++++++++++++++++ .../RecurrenceSectionView.tsx | 17 +++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/RecurrenceSection.test.tsx b/packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/RecurrenceSection.test.tsx index 2302d2eb8e..99352ac34b 100644 --- a/packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/RecurrenceSection.test.tsx +++ b/packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/RecurrenceSection.test.tsx @@ -34,6 +34,17 @@ const baseDraft = () => timeZone: "UTC", }); +// A timed event that runs past local midnight: it starts on Aug 3 but its end +// lands on Aug 4. The "Ends on" floor is anchored to the start date, so the +// event's own date (Aug 3) stays selectable. +const pastMidnightDraft = () => + createGridEventDraft({ + kind: "timed", + start: new Date("2026-08-03T23:00:00.000Z"), + end: new Date("2026-08-04T00:30:00.000Z"), + timeZone: "UTC", + }); + const recurringDraft = () => { const source = createMockEvent({ schedule: SCHEDULE, @@ -127,6 +138,26 @@ describe("RecurrenceSection", () => { ]); }); + // Regression: the "Ends on" floor used to be the event's *end*, so an event + // running past local midnight (end on the next calendar day) disabled its own + // start date - the date the user most naturally picks. react-datepicker + // compares day cells by calendar day, so the fix is to anchor the floor to + // the start date. See RecurrenceSectionView's recurrenceMinDate. + it("keeps the event's own date selectable when the event ends after midnight", async () => { + const user = userEvent.setup(); + renderRecurrenceSection({ initialDraft: pastMidnightDraft() }); + + // Enable recurrence to reveal the "Ends on" picker, then open it via its + // input and assert the event's own (start) date is offered, not blocked. + await user.click(screen.getByRole("button", { name: /edit recurrence/i })); + await user.click(await screen.findByRole("textbox")); + + const ownDate = await screen.findByLabelText(/Monday, August 3rd, 2026/); + expect(ownDate.getAttribute("aria-label")).toMatch(/^Choose /); + expect(ownDate).not.toHaveClass("react-datepicker__day--disabled"); + expect(ownDate.getAttribute("aria-disabled")).not.toBe("true"); + }); + it("turning off Repeat on an existing recurring event clears the controls", async () => { // Guards against the toggle being a no-op on an edit draft: clearing // recurrence used to resolve to "preserve", which read the source diff --git a/packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/RecurrenceSectionView.tsx b/packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/RecurrenceSectionView.tsx index b5c814efb6..6ded81e647 100644 --- a/packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/RecurrenceSectionView.tsx +++ b/packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/RecurrenceSectionView.tsx @@ -25,10 +25,17 @@ export function RecurrenceSection({ const { setInterval, setFreq, setWeekDays, setUntil } = recurrenceHook; const { weekDays, interval, freq, until, toggleRecurrence } = recurrenceHook; const { hasRecurrence } = recurrenceHook; - const scheduleEndDate = - draft.values.schedule.kind === "allDay" - ? dayjs(draft.values.schedule.end).toYearMonthDayString() - : dayjs(draft.values.schedule.end).format(); + // Lower bound for the "Ends on" picker: the recurrence can't stop before the + // event's own occurrence, so the floor is the event's *start* date. Anchoring + // it to the *end* used to disable the event's own date whenever the event ran + // past local midnight (e.g. 23:00-00:30, or anything ending at 00:00 the next + // day): the end lands on the following calendar day, and react-datepicker + // compares day cells by calendar day, so the start date - the date the user + // sees and most naturally reaches for - rendered disabled. A bare + // YYYY-MM-DD keeps the bound date-only for both event kinds. + const recurrenceMinDate = dayjs( + draft.values.schedule.start, + ).toYearMonthDayString(); return (
@@ -52,7 +59,7 @@ export function RecurrenceSection({