fix(timeline): track form dirtiness via change callbacks, not an $effect
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 4m17s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 5m23s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 26s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m8s
SDD Gate / RTM Check (pull_request) Successful in 16s
SDD Gate / Contract Validate (pull_request) Successful in 25s
SDD Gate / Constitution Impact (pull_request) Successful in 17s
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 4m17s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 5m23s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 26s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m8s
SDD Gate / RTM Check (pull_request) Successful in 16s
SDD Gate / Contract Validate (pull_request) Successful in 25s
SDD Gate / Constitution Impact (pull_request) Successful in 17s
The round-2 dirty-guard used an $effect that both read and wrote its `dirtyArmed` $state, so the self-write re-triggered the effect and `dirty` flipped true on mount — the beforeNavigate confirm then fired on every navigation away from an untouched form (caught by the round-3 clean-agent review + the Svelte autofixer, which flags assigning state inside $effect). Replace it with the component's existing idiomatic pattern: DatePrecisionField, PersonMultiSelect, and DocumentMultiSelect each gain an optional `onchange` callback fired on a real user edit, and EventForm passes `markDirty` to all three. Now date/precision/end-date and picker add/remove mark the form dirty exactly like title/type/description already did — no effect, no mount-timing trap. The new props are optional, so the other consumers (WhoWhenSection, the document forms) are unaffected. Svelte autofixer: clean. Addresses PR #832 review (round-3 clean-agent concern). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -101,26 +101,15 @@ beforeNavigate(({ cancel }) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Every editable control routes its change through markDirty so the
|
||||
// beforeNavigate guard catches edits to the date/precision/end-date and the
|
||||
// pickers too — not just title/type/description (their onchange callbacks call
|
||||
// this). No $effect: marking dirty from the actual edit events avoids a
|
||||
// snapshot-vs-effect mount-timing trap.
|
||||
function markDirty() {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// title/type/description set `dirty` through their own oninput/onchange hooks,
|
||||
// but the date/precision/end-date (inside DatePrecisionField) and the two pickers
|
||||
// have no such hook — so changing only a date or a linked person would otherwise
|
||||
// slip past the beforeNavigate guard. Watch those values and mark dirty on any
|
||||
// change after the initial prop snapshot (the first run only arms the watcher).
|
||||
let dirtyArmed = $state(false);
|
||||
$effect(() => {
|
||||
void dateIso;
|
||||
void precision;
|
||||
void endDateIso;
|
||||
void selectedPersons.length;
|
||||
void selectedDocuments.length;
|
||||
if (dirtyArmed) dirty = true;
|
||||
else dirtyArmed = 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) {
|
||||
@@ -238,6 +227,7 @@ async function confirmDelete(e: SubmitEvent) {
|
||||
precisionInputName="precision"
|
||||
dateLabel={m.form_label_date()}
|
||||
dateError={dateError}
|
||||
onchange={markDirty}
|
||||
dateTestId="event-date"
|
||||
precisionTestId="event-precision"
|
||||
endDateInnerTestId="event-end-date"
|
||||
@@ -279,6 +269,7 @@ async function confirmDelete(e: SubmitEvent) {
|
||||
inputId="event-persons-input"
|
||||
hiddenInputName="personIds"
|
||||
emptyLabel={m.event_editor_persons_empty()}
|
||||
onchange={markDirty}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -295,6 +286,7 @@ async function confirmDelete(e: SubmitEvent) {
|
||||
inputId="event-documents-input"
|
||||
hiddenInputName="documentIds"
|
||||
emptyLabel={m.event_editor_documents_empty()}
|
||||
onchange={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user