From 4d5fa7a26f26676c7656691d3470e07d45b3726d Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 14 Jun 2026 09:03:11 +0200 Subject: [PATCH] fix(timeline): clear required-field errors when the field is corrected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit titleError/dateError short-circuited on the server fail payload (`form?.titleError ?? …`, `form?.dateError ?? ''`), so after a fail(400) the red border and message stuck until the next submit even once the user typed a valid value. Derive both from the current field value instead: the server error still seeds the message, but a non-empty title/date clears it immediately. Co-Authored-By: Claude Opus 4.8 --- frontend/src/lib/timeline/EventForm.svelte | 9 +++---- .../src/lib/timeline/EventForm.svelte.spec.ts | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/timeline/EventForm.svelte b/frontend/src/lib/timeline/EventForm.svelte index 74231eb1..ee627cf2 100644 --- a/frontend/src/lib/timeline/EventForm.svelte +++ b/frontend/src/lib/timeline/EventForm.svelte @@ -91,12 +91,13 @@ let submitting = $state(false); let dirty = $state(false); const titleEmpty = $derived(title.trim().length === 0); -// Client-side title error fires instantly on a save attempt; the server's -// titleError is the simultaneous-multi-field source on a real round-trip. +// Required-field errors derive from the CURRENT field value, not the stale server +// payload: a server titleError/dateError seeds the message, but typing a valid +// value clears it immediately instead of sticking until the next submit. const titleError = $derived( - form?.titleError ?? (titleTouched && titleEmpty ? m.event_editor_title_required() : '') + titleEmpty && (titleTouched || !!form?.titleError) ? m.event_editor_title_required() : '' ); -const dateError = $derived(form?.dateError ?? ''); +const dateError = $derived(dateIso ? '' : (form?.dateError ?? '')); beforeNavigate(({ cancel }) => { if (dirty && !submitting) { diff --git a/frontend/src/lib/timeline/EventForm.svelte.spec.ts b/frontend/src/lib/timeline/EventForm.svelte.spec.ts index 2ee80174..8e7f5f89 100644 --- a/frontend/src/lib/timeline/EventForm.svelte.spec.ts +++ b/frontend/src/lib/timeline/EventForm.svelte.spec.ts @@ -141,6 +141,30 @@ describe('EventForm — server date error wired per-field (REQ-011)', () => { }); }); +describe('EventForm — validation errors clear on correction (review #4)', () => { + it('clears the server title error once a valid title is entered', async () => { + renderForm({ form: { titleError: 'Bitte einen Titel eingeben.', title: '' } }); + await expect.element(page.getByText('Bitte einen Titel eingeben.')).toBeInTheDocument(); + + const titleInput = document.querySelector('#event-title') as HTMLInputElement; + titleInput.value = 'Ein Titel'; + titleInput.dispatchEvent(new Event('input', { bubbles: true })); + + await expect.element(page.getByText('Bitte einen Titel eingeben.')).not.toBeInTheDocument(); + }); + + it('clears the server date error once a valid date is entered', async () => { + renderForm({ form: { dateError: 'Bitte ein Datum eingeben.' } }); + await expect.element(page.getByText('Bitte ein Datum eingeben.')).toBeInTheDocument(); + + const dateInput = document.querySelector('#eventDate') as HTMLInputElement; + dateInput.value = '01.04.1925'; + dateInput.dispatchEvent(new Event('input', { bubbles: true })); + + await expect.element(page.getByText('Bitte ein Datum eingeben.')).not.toBeInTheDocument(); + }); +}); + describe('EventForm — submitting state (named AC, Decision 8)', () => { it('disables the submit button while submitting', async () => { // A never-resolving fetch keeps use:enhance in flight so the disabled