A RANGE event with a blank end date passed validateEventForm and reached
the backend, which 400s with a generic INVALID_DATE_RANGE mapped to "end
must not be before start" — wrong for a missing end date, and shown only as
a top-of-form alert. Validate it before the API call and surface a dedicated
event_editor_end_date_required message on the end-date field via a new
DatePrecisionField endDateError prop (defaults '', so the document form is
unchanged).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
preservedFormFields echoed back only title/type/description/pickers, and
EventForm seeded dateIso/precision/endDateIso solely from `event` (undefined
on /new). So a no-JS validation-error reload silently dropped the entire
When-section the curator had entered, while every other field survived.
Echo eventDate/precision/eventDateEnd in the fail payload and seed the date
controls from `form` ahead of `event`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decision 6 / REQ-010 promised the pickers survive a fail(400) "including
pre-selected persons/documents", but EventForm only seeded them from
event/initialPersons — never from the fail payload — and the payload carried
only bare ids, which can't rebuild a chip (chips need displayName/title). On
the use:enhance path the in-memory selection survived; on a no-JS full reload
the chips were silently dropped.
Now the save action re-fetches the selected persons/documents by id
(lookupSelections, non-ok swallowed like the prefill path) and returns full
chip data; EventForm seeds the pickers from form.persons/documents ahead of
the seeded event. Extract preservedFormFields() to DRY the four fail payloads;
validateEventForm now returns the error pair and the route owns the fail().
Component test pins the rehydration; the server spec now asserts the fail
payload carries labelled chips, not just ids.
Addresses PR #832 review (Developer + Requirements Engineer concern, REQ-010).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Validate the submitted precision against the DatePrecision allow-list in
parseEventForm (falls back to DAY) so an untrusted token can't flow into
the request body — symmetric with the existing `type` narrowing.
- Parameterize the precision input name via DatePrecisionField's new
precisionInputName prop; the timeline form now submits `precision` instead
of the misleading document-domain `metaDatePrecision`. Document form keeps
the default, so its behaviour is unchanged.
- Capture EventTypeSelect's onchange into EventForm's `type` state so it no
longer goes stale (the submitted value was already correct via the hidden
input; this keeps the local state in sync).
Addresses PR #832 review (#781).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Server load gates on hasWriteAll with a null-user guard first (403 error page,
the persons/new idiom — not a redirect); prefills ?personId/?documentId via
Promise.all, swallowing 404/403 so unknown ids never leak. The save action
parses the form, surfaces title+date required errors simultaneously via
fail(400) preserving picker arrays, builds a TimelineEventRequest (eventDateEnd
explicit null off RANGE), POSTs, maps API/409 errors via getErrorMessage without
redirecting, and redirects to a UUID-validated nav target (CWE-601). Shared
parse/validate/build/nav helpers live in eventFormServer.ts for reuse by the
edit route. 11/11 server specs green.
Refs #781
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>