diff --git a/frontend/src/lib/shared/primitives/DatePrecisionField.svelte b/frontend/src/lib/shared/primitives/DatePrecisionField.svelte index 9f54132b..a1b21f63 100644 --- a/frontend/src/lib/shared/primitives/DatePrecisionField.svelte +++ b/frontend/src/lib/shared/primitives/DatePrecisionField.svelte @@ -35,6 +35,7 @@ let { suggestedDateIso = '', dateLabel = m.form_label_date(), dateRequired = true, + dateError = '', dateTestId = undefined, precisionTestId = undefined, endDateInnerTestId = undefined @@ -49,6 +50,8 @@ let { suggestedDateIso?: string; dateLabel?: string; dateRequired?: boolean; + /** Server-side date error (e.g. blank required field) wired to the field's aria-invalid. */ + dateError?: string; dateTestId?: string; precisionTestId?: string; endDateInnerTestId?: string; @@ -83,6 +86,9 @@ onMount(() => { }); const dateInvalid = $derived(dateDirty && dateDisplay.length > 0 && dateIso === ''); +// Either the client-side malformed-date cue or a server-provided required-field +// error marks the field invalid (REQ-011 per-field aria-invalid). +const dateFieldInvalid = $derived(dateInvalid || dateError.length > 0); // Inline mirror of the server guard (#678). ISO YYYY-MM-DD strings compare // lexicographically, so no Date object is needed. Server stays the gate — @@ -128,17 +134,21 @@ $effect(() => { maxlength="10" aria-required={dateRequired ? 'true' : undefined} class="block min-h-[48px] w-full rounded border border-line px-2 py-3 text-sm shadow-sm - {dateInvalid + {dateFieldInvalid ? 'border-red-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500' : 'focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring'}" - aria-invalid={dateInvalid ? 'true' : undefined} - aria-describedby={dateInvalid ? `${dateInputName}-error` : undefined} + aria-invalid={dateFieldInvalid ? 'true' : undefined} + aria-describedby={dateFieldInvalid ? `${dateInputName}-error` : undefined} /> {#if dateInvalid} -
+
{m.form_date_error()}
+ {:else if dateError} ++ {dateError} +
{/if} @@ -183,7 +193,7 @@ $effect(() => { /> {#if endBeforeStart} -+
{m.error_invalid_date_range()}
{/if} diff --git a/frontend/src/lib/timeline/EventForm.svelte b/frontend/src/lib/timeline/EventForm.svelte index eb323232..46e51e98 100644 --- a/frontend/src/lib/timeline/EventForm.svelte +++ b/frontend/src/lib/timeline/EventForm.svelte @@ -221,16 +221,12 @@ async function confirmDelete(e: SubmitEvent) { endDateInputName="eventDateEnd" precisionInputName="precision" dateLabel={m.form_label_date()} + dateError={dateError} dateTestId="event-date" precisionTestId="event-precision" endDateInnerTestId="event-end-date" /> - {#if dateError} -- {dateError} -
- {/if} diff --git a/frontend/src/lib/timeline/EventForm.svelte.spec.ts b/frontend/src/lib/timeline/EventForm.svelte.spec.ts index 05610533..4a3d9998 100644 --- a/frontend/src/lib/timeline/EventForm.svelte.spec.ts +++ b/frontend/src/lib/timeline/EventForm.svelte.spec.ts @@ -73,6 +73,15 @@ describe('EventForm — required-field error (REQ-010)', () => { }); }); +describe('EventForm — server date error wired per-field (REQ-011)', () => { + it('marks the date field aria-invalid and shows the message on a server date error', async () => { + render(EventForm, { form: { dateError: 'Bitte ein Datum eingeben.' } }); + await expect.element(page.getByText('Bitte ein Datum eingeben.')).toBeInTheDocument(); + const dateInput = document.querySelector('#eventDate') as HTMLInputElement; + expect(dateInput.getAttribute('aria-invalid')).toBe('true'); + }); +}); + describe('EventForm — submitting state (named AC)', () => { it('renders an enabled submit button initially', async () => { render(EventForm, { event: makeEvent() });