fix(timeline): gate event delete behind the confirm dialog

The delete <form> combined onsubmit={confirmDelete} with use:enhance.
SvelteKit's enhance ignores event.defaultPrevented, so the DELETE fired on
the bare click — before the dialog was answered — and the post-DELETE
redirect ran regardless of the user's choice. Reading getConfirmService()
lazily inside the handler also threw (Svelte context is init-only), so the
dialog never appeared even with <ConfirmDialog> mounted.

Capture confirm at init and run the gate inside the enhance submit phase,
calling cancel() on "no". Clear dirty in the result callback so the
beforeNavigate guard no longer prompts "unsaved changes" on the post-delete
redirect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-14 09:00:45 +02:00
parent b8c8fcb1fb
commit 5f2cf5f2c2
2 changed files with 85 additions and 29 deletions

View File

@@ -82,6 +82,10 @@ let selectedDocuments = $state<DocumentOption[]>(
const isEdit = $derived(event !== undefined);
// Captured at init — Svelte context is init-only, so reading it lazily inside an
// event handler throws even when <ConfirmDialog> is mounted. Gates the delete.
const { confirm } = getConfirmService();
let titleTouched = $state(false);
let submitting = $state(false);
let dirty = $state(false);
@@ -109,18 +113,6 @@ beforeNavigate(({ cancel }) => {
function markDirty() {
dirty = true;
}
async function confirmDelete(e: SubmitEvent) {
e.preventDefault();
const { confirm } = getConfirmService();
const ok = await confirm({
title: m.event_editor_delete_confirm_title(),
body: m.event_editor_delete_confirm_body(),
destructive: true,
confirmLabel: m.event_editor_delete()
});
if (ok) (e.target as HTMLFormElement).requestSubmit();
}
</script>
<div class="mx-auto max-w-5xl px-4 py-8">
@@ -309,9 +301,32 @@ async function confirmDelete(e: SubmitEvent) {
{#if isEdit}
<!-- Delete lives in its own form so it posts to the dedicated ?/delete action.
getConfirmService() is read lazily inside the handler so the component
mounts cleanly outside a layout (tests) where no confirm context exists. -->
<form method="POST" action="?/delete" onsubmit={confirmDelete} use:enhance class="mt-4">
The confirm gate runs inside the enhance submit phase: enhance ignores an
onsubmit preventDefault(), so awaiting confirm() and calling cancel() is the
only thing that actually stops the destructive POST. -->
<form
method="POST"
action="?/delete"
use:enhance={async ({ cancel }) => {
const ok = await confirm({
title: m.event_editor_delete_confirm_title(),
body: m.event_editor_delete_confirm_body(),
destructive: true,
confirmLabel: m.event_editor_delete()
});
if (!ok) {
cancel();
return;
}
return async ({ update }) => {
// Clear dirtiness so beforeNavigate doesn't prompt "unsaved changes"
// on the post-delete redirect.
dirty = false;
await update();
};
}}
class="mt-4"
>
<input type="hidden" name="originPersonId" value={originPersonId} />
<button
type="submit"