fix(03): correct event-write timezone + per-user calendar identity (Gate 2 Part D)
BUG A — timed events written 4h off: EventForm sent a naive local wall-clock string with no offset; the UTC API container parsed it via new Date() as UTC, so 09:00 America/Toronto serialized to DTSTART:...090000Z. Fix: new apps/pwa/src/lib/eventDateTime.ts serializes timed events to an unambiguous UTC instant in the browser (where the operator's zone is known); all-day stays a DATE string. No backend change. BUG B — created events attached to the wrong user's calendar + duplicate calendar rows per poll: calendars had no unique key on url, and poller/sync matched calendars by url alone — so under the shared single Fastmail account (D-16) one member's collection resolved to the other member's row. Fix: composite unique(user_id, url); scope poller lookup + sync select to (userId, url); hand migration 0001 (dedup + add key), applied to the live DB. Regression tests fail against the buggy url-only predicate. API 98/98, PWA 140/140, tsc clean both packages.
This commit is contained in:
@@ -40,6 +40,7 @@ import {
|
||||
type RecurrencePreset,
|
||||
} from '../api/client.js'
|
||||
import type { CalendarOccurrence } from '../api/client.js'
|
||||
import { serializeEventDateTime } from '../lib/eventDateTime.js'
|
||||
|
||||
// ── Constants ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -262,11 +263,23 @@ export function EventForm() {
|
||||
const handleSubmit = () => {
|
||||
if (!validate()) return
|
||||
|
||||
// BUG A fix: serialize timed events to an unambiguous UTC instant here in
|
||||
// the browser (operator's zone is known) instead of sending a naive local
|
||||
// wall-clock string. The API container is UTC; a naive string was being read
|
||||
// as UTC, shifting 09:00 local to 09:00Z (4h off). See lib/eventDateTime.ts.
|
||||
const { start: serializedStart, end: serializedEnd } = serializeEventDateTime(
|
||||
allDay,
|
||||
startDate,
|
||||
startTime,
|
||||
endDate,
|
||||
endTime,
|
||||
)
|
||||
|
||||
const payload: CreateEventPayload = {
|
||||
title: title.trim(),
|
||||
allDay,
|
||||
start: allDay ? startDate : `${startDate}T${startTime}:00`,
|
||||
end: allDay ? endDate : `${endDate}T${endTime}:00`,
|
||||
start: serializedStart,
|
||||
end: serializedEnd,
|
||||
recurrence,
|
||||
...(location.trim() ? { location: location.trim() } : {}),
|
||||
...(description.trim() ? { description: description.trim() } : {}),
|
||||
|
||||
Reference in New Issue
Block a user