fix(frontend): give the list reload one owner, taken by both exits

Finish reloaded immediately while dismissal waited for the run to stop,
so Finish pressed during a retry — `done` survives one, which is what
makes the button clickable then — read the list mid-write, and its
`finishing` flag stopped the deferred reload from correcting it.

Both exits now go through the same wait. One reload per closing, always
after the writing stops, whichever way the dialog was left.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9
This commit is contained in:
Guilhem Lemouel
2026-09-07 15:10:12 +02:00
co-authored by Claude Opus 5
parent 1d9eed3103
commit 51a76830ad
@@ -89,11 +89,13 @@
}
/**
* Reloads the caller's list once the run is no longer writing. Immediate for a run that
* has finished, which is the common dismissal — but `abandon()` only stops the *next*
* phase, and the request already sent still lands, so a reload issued at that moment can
* read the list before that write commits and leave it stale again. The cap is there so a
* run that never settles still ends in a reload rather than in nothing.
* The one way this dialog reloads the caller's list: once the run is no longer writing.
* Both exits use it, because both can be taken mid-write — `abandon()` only stops the
* *next* phase and the request already sent still lands, and `done` survives a retry so
* Finish is clickable while the run is going again. A reload issued at either of those
* moments reads the list before the write commits and leaves it stale, which is what the
* reload exists to prevent. Immediate when nothing is running, which is the common case.
* The cap is there so a run that never settles still ends in a reload rather than nothing.
*/
async function reloadWhenSettled(run: ImportExecution, reload: (() => void) | undefined) {
for (let i = 0; i < 60 && run.running; i++) {
@@ -205,7 +207,12 @@
if (slug) logFeatureUsage('home', 'template_import', { key: slug })
logFeatureUsage('home', 'template_setup', { key: setupKey(setupOutcome, outstanding) })
finishing = true
onImported?.()
// Through the same deferred reload every closing uses. `done` survives a retry, so
// Finish is clickable while the run is going again — reloading here would read the
// list mid-write, and `finishing` then stops `dismiss()` from reloading after it.
// One reload per closing, always after the writing stops.
if (execution) void reloadWhenSettled(execution, onImported)
else onImported?.()
onClose()
}