diff --git a/frontend/src/lib/timeline/EventForm.svelte b/frontend/src/lib/timeline/EventForm.svelte index 36184adf..3cce8d3e 100644 --- a/frontend/src/lib/timeline/EventForm.svelte +++ b/frontend/src/lib/timeline/EventForm.svelte @@ -110,15 +110,6 @@ function markDirty() { dirty = true; } -// Guards a submit with a blank title client-side. The server re-validates and -// owns the authoritative fail(400) with per-field flags. -function handleSubmit(e: SubmitEvent) { - titleTouched = true; - if (titleEmpty) { - e.preventDefault(); - } -} - async function confirmDelete(e: SubmitEvent) { e.preventDefault(); const { confirm } = getConfirmService(); @@ -153,8 +144,15 @@ async function confirmDelete(e: SubmitEvent) {
{ + use:enhance={({ cancel }) => { + // Client-side guard against a blank title. enhance ignores onsubmit + // preventDefault(), so cancel() is the only thing that actually stops the + // POST; the server still re-validates and owns the authoritative fail(400). + titleTouched = true; + if (titleEmpty) { + cancel(); + return; + } submitting = true; return async ({ update }) => { submitting = false; diff --git a/frontend/src/lib/timeline/EventForm.svelte.spec.ts b/frontend/src/lib/timeline/EventForm.svelte.spec.ts index 4e3a0d96..b473c703 100644 --- a/frontend/src/lib/timeline/EventForm.svelte.spec.ts +++ b/frontend/src/lib/timeline/EventForm.svelte.spec.ts @@ -62,6 +62,18 @@ describe('EventForm — required-field error (REQ-010)', () => { await expect.element(page.getByText('Bitte einen Titel eingeben.')).toBeInTheDocument(); }); + it('cancels the submission — fires no network POST — when title is blank', async () => { + // The client-side guard must actually CANCEL the enhance submission, not just + // show a message: enhance ignores onsubmit preventDefault(), so without cancel() + // a blank-title Save still POSTs (and update()->applyAction crashes with no app). + const fetchSpy = vi.fn(() => new Promise(() => {})); + vi.stubGlobal('fetch', fetchSpy); + render(EventForm, {}); + await page.getByRole('button', { name: 'Speichern' }).click(); + await expect.element(page.getByText('Bitte einen Titel eingeben.')).toBeInTheDocument(); + expect(fetchSpy).not.toHaveBeenCalled(); + }); + it('rehydrates the pickers from the fail payload (Decision 6)', async () => { render(EventForm, { form: {