fix(timeline): cancel blank-title event save instead of posting
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 5m35s
CI / OCR Service Tests (pull_request) Successful in 25s
CI / Backend Unit Tests (pull_request) Successful in 6m14s
CI / fail2ban Regex (pull_request) Successful in 53s
CI / Semgrep Security Scan (pull_request) Successful in 24s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m5s
SDD Gate / RTM Check (pull_request) Successful in 16s
SDD Gate / Contract Validate (pull_request) Successful in 28s
SDD Gate / Constitution Impact (pull_request) Successful in 17s

The EventForm onsubmit guard called e.preventDefault() on a blank title,
but use:enhance ignores defaultPrevented (forms.js only bails on cancel()),
so a blank-title Save still fired a network POST. In a component unit test
the resulting update() -> applyAction() dereferenced an undefined root
($set on undefined), surfacing as an unhandled rejection.

Move the guard into the enhance submit phase and call cancel() so the POST
is actually stopped; the server still owns the authoritative fail(400).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-14 08:34:59 +02:00
parent 6150fc7be5
commit b8c8fcb1fb
2 changed files with 21 additions and 11 deletions

View File

@@ -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) {
<form
method="POST"
action="?/save"
onsubmit={handleSubmit}
use:enhance={() => {
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;