Fix: late draft-resume no longer clobbers in-progress wizard input
draftSync.resume() does async network work and dispatched Seed on completion, which could land after the user's first action and reset the machine (cursor + fields) — the "click Volgende twice" symptom. Guard centrally: applyResume() skips when the user already has progress (snapshot() != null) or there's nothing to restore. onResume is now only ever called with a real draft on a pristine machine, so the three wizard callbacks drop the dead `?? initial`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -102,7 +102,7 @@ export class HerregistratieWizardComponent {
|
|||||||
const documentIds = deliveryRefs(s.upload).filter((r) => r.channel === 'digital' && r.documentId).map((r) => r.documentId!);
|
const documentIds = deliveryRefs(s.upload).filter((r) => r.channel === 'digital' && r.documentId).map((r) => r.documentId!);
|
||||||
return { draft: s, stepIndex: s.step - 1, stepCount: this.stepLabels.length, documentIds };
|
return { draft: s, stepIndex: s.step - 1, stepCount: this.stepLabels.length, documentIds };
|
||||||
},
|
},
|
||||||
onResume: (draft) => this.dispatch({ tag: 'Seed', state: (draft as WizardState | null) ?? initial }),
|
onResume: (draft) => this.dispatch({ tag: 'Seed', state: draft as WizardState }),
|
||||||
enabled: () => this.seed() === initial,
|
enabled: () => this.seed() === initial,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export class IntakeWizardComponent {
|
|||||||
if (s.tag !== 'Answering' || !hasProgress(s)) return null;
|
if (s.tag !== 'Answering' || !hasProgress(s)) return null;
|
||||||
return { draft: s, stepIndex: s.cursor, stepCount: STEPS.length, documentIds: [] };
|
return { draft: s, stepIndex: s.cursor, stepCount: STEPS.length, documentIds: [] };
|
||||||
},
|
},
|
||||||
onResume: (draft) => this.dispatch({ tag: 'Seed', state: (draft as IntakeState | null) ?? initial }),
|
onResume: (draft) => this.dispatch({ tag: 'Seed', state: draft as IntakeState }),
|
||||||
enabled: () => this.seed() === initial,
|
enabled: () => this.seed() === initial,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ export interface DraftSyncDeps {
|
|||||||
type: AanvraagType;
|
type: AanvraagType;
|
||||||
/** The machine snapshot while it's worth persisting; null when not (pristine/done). */
|
/** The machine snapshot while it's worth persisting; null when not (pristine/done). */
|
||||||
snapshot: () => DraftSnapshot | null;
|
snapshot: () => DraftSnapshot | null;
|
||||||
/** Seed the machine from a resumed draft (null → start fresh). Called once, on init. */
|
/** Seed the machine from a resumed draft. Called at most once, on init, and ONLY
|
||||||
onResume: (draft: unknown | null) => void;
|
with a real draft on a still-pristine machine — see `applyResume`. */
|
||||||
|
onResume: (draft: unknown) => void;
|
||||||
/** Draft-sync only runs in the real app — false in Storybook/tests (explicit seed). */
|
/** Draft-sync only runs in the real app — false in Storybook/tests (explicit seed). */
|
||||||
enabled: () => boolean;
|
enabled: () => boolean;
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,15 @@ export function createDraftSync(deps: DraftSyncDeps) {
|
|||||||
return ensuring;
|
return ensuring;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Apply a resumed draft only when it's safe to: a late lookup must never clobber
|
||||||
|
// progress the user already made while it was in flight, and "start fresh" needs no
|
||||||
|
// dispatch (the machine already starts fresh). snapshot() is non-null once the user
|
||||||
|
// has real progress.
|
||||||
|
const applyResume = (draft: unknown | null) => {
|
||||||
|
if (draft == null || deps.snapshot() != null) return;
|
||||||
|
deps.onResume(draft);
|
||||||
|
};
|
||||||
|
|
||||||
const flush = async () => {
|
const flush = async () => {
|
||||||
const snap = deps.snapshot();
|
const snap = deps.snapshot();
|
||||||
if (!snap) return;
|
if (!snap) return;
|
||||||
@@ -92,14 +102,14 @@ export function createDraftSync(deps: DraftSyncDeps) {
|
|||||||
.then((dto) => {
|
.then((dto) => {
|
||||||
if (dto.status && dto.status.tag !== 'Concept') {
|
if (dto.status && dto.status.tag !== 'Concept') {
|
||||||
id = undefined;
|
id = undefined;
|
||||||
deps.onResume(null);
|
applyResume(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
deps.onResume(dto.draft ?? null);
|
applyResume(dto.draft ?? null);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
id = undefined;
|
id = undefined;
|
||||||
deps.onResume(null); // unknown/deleted id → start fresh
|
applyResume(null); // unknown/deleted id → start fresh
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,7 +131,7 @@ export function createDraftSync(deps: DraftSyncDeps) {
|
|||||||
resumeGate = new Promise<void>((r) => (release = r));
|
resumeGate = new Promise<void>((r) => (release = r));
|
||||||
try {
|
try {
|
||||||
if (!active()) {
|
if (!active()) {
|
||||||
deps.onResume(null);
|
applyResume(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const linked = route!.snapshot.queryParamMap.get('aanvraag');
|
const linked = route!.snapshot.queryParamMap.get('aanvraag');
|
||||||
@@ -136,7 +146,7 @@ export function createDraftSync(deps: DraftSyncDeps) {
|
|||||||
void router!.navigate([], { relativeTo: route!, queryParams: { aanvraag: existing }, queryParamsHandling: 'merge', replaceUrl: true });
|
void router!.navigate([], { relativeTo: route!, queryParams: { aanvraag: existing }, queryParamsHandling: 'merge', replaceUrl: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
deps.onResume(null);
|
applyResume(null);
|
||||||
} finally {
|
} finally {
|
||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user