fix(timeline): rehydrate event-form pickers after a validation failure
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>
This commit is contained in:
@@ -30,6 +30,10 @@ interface FormResult {
|
||||
type?: string;
|
||||
personIds?: string[];
|
||||
documentIds?: string[];
|
||||
// Rehydrated chip data (id + label) so the pickers re-render after a fail(400)
|
||||
// even on a no-JS full reload — bare ids alone can't rebuild a chip (REQ-010).
|
||||
persons?: PersonOption[];
|
||||
documents?: DocumentOption[];
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -46,8 +50,10 @@ let {
|
||||
form?: FormResult | null;
|
||||
} = $props();
|
||||
|
||||
// Initial-state snapshot from incoming props (event > preserved fail payload).
|
||||
// The form owns these after mount; re-mount with a different `event` to reset.
|
||||
// Initial-state snapshot from incoming props, preferring a preserved fail payload
|
||||
// over the seeded `event`. This component is intentionally single-shot: props are
|
||||
// snapshotted into $state once, so a parent re-render with a different `event`
|
||||
// won't update the form — the two dedicated routes always remount, which is fine.
|
||||
let title = $state(form?.title ?? event?.title ?? '');
|
||||
let description = $state(form?.description ?? event?.description ?? '');
|
||||
let type = $state<string>(form?.type ?? event?.type ?? 'PERSONAL');
|
||||
@@ -55,19 +61,23 @@ let dateIso = $state(event?.eventDate ?? '');
|
||||
let precision = $state<DatePrecision>((event?.precision as DatePrecision) ?? 'DAY');
|
||||
let endDateIso = $state(event?.eventDateEnd ?? '');
|
||||
|
||||
// On a fail(400) the server returns rehydrated chip data (form.persons/documents)
|
||||
// so the pickers survive the round-trip — even without JS — ahead of the seeded
|
||||
// `event` or the prefill initials (REQ-010 / Decision 6).
|
||||
let selectedPersons = $state<PersonOption[]>(
|
||||
event?.persons ? event.persons.map(toPersonOption) : initialPersons
|
||||
form?.persons ?? (event?.persons ? event.persons.map(toPersonOption) : initialPersons)
|
||||
);
|
||||
let selectedDocuments = $state<DocumentOption[]>(
|
||||
event?.documents
|
||||
? event.documents.map((d) => ({
|
||||
// Graceful degradation: DocumentRef has no precision fields. formatDocumentOption
|
||||
// defaults a missing precision to DAY, so the chip shows the full documentDate.
|
||||
id: d.id,
|
||||
title: d.title,
|
||||
documentDate: d.documentDate
|
||||
}))
|
||||
: initialDocuments
|
||||
form?.documents ??
|
||||
(event?.documents
|
||||
? event.documents.map((d) => ({
|
||||
// Graceful degradation: DocumentRef has no precision fields. formatDocumentOption
|
||||
// defaults a missing precision to DAY, so the chip shows the full documentDate.
|
||||
id: d.id,
|
||||
title: d.title,
|
||||
documentDate: d.documentDate
|
||||
}))
|
||||
: initialDocuments)
|
||||
);
|
||||
|
||||
const isEdit = $derived(event !== undefined);
|
||||
|
||||
Reference in New Issue
Block a user