Ne plus réadopter les runs de génération vidés au rechargement de la page

- Le bouton « Vider » ne retirait que les tâches du côté client : au
  rechargement, la « dernière génération » du serveur
  (.generation.job.json) était réadoptée et les tâches finies
  réappaissaient.
- Les runs retirés sont mémorisés par horodatage serveur (sessionStorage) ;
  l'adoption au chargement les ignore désormais.
- Les tâches lancées immédiatement (sans passer par la file) captent
  l'horodatage de leur run à la fin, pour être retirables sans réadoption.
This commit is contained in:
Antoine Jacquin
2026-09-16 23:48:27 +02:00
parent f024514427
commit 6fb2c4ee97
2 changed files with 30 additions and 1 deletions

View File

@ -3533,6 +3533,17 @@ function saveGenTasks() {
try { sessionStorage.setItem(GEN_QKEY, JSON.stringify(genTasks)); } catch (e) { /* privé */ } try { sessionStorage.setItem(GEN_QKEY, JSON.stringify(genTasks)); } catch (e) { /* privé */ }
} }
// Runs dont l'utilisateur a retiré la tâche « finie » de la liste : on les
// mémorise (par horodatage serveur `started`) pour que le rechargement de la
// page — qui réadopte la « dernière génération » depuis .generation.job.json
// — ne les re-crée pas à chaque rafraîchissement.
const GEN_DKEY = 'lidarGenDismissed';
let dismissedRuns = new Set();
try { dismissedRuns = new Set(JSON.parse(sessionStorage.getItem(GEN_DKEY) || '[]')); } catch (e) { dismissedRuns = new Set(); }
function saveDismissed() {
try { sessionStorage.setItem(GEN_DKEY, JSON.stringify([...dismissedRuns])); } catch (e) { /* privé */ }
}
function genStepEl(s) { function genStepEl(s) {
const el = document.createElement('span'); const el = document.createElement('span');
el.className = 'gen-step ' + s.state; el.className = 'gen-step ' + s.state;
@ -3658,6 +3669,10 @@ function finishRunningTasks(d) {
t.state = d.returncode === 0 ? 'done' t.state = d.returncode === 0 ? 'done'
: (d.returncode === null || d.returncode < 0) ? 'stopped' : 'failed'; : (d.returncode === null || d.returncode < 0) ? 'stopped' : 'failed';
t.returncode = d.returncode; t.returncode = d.returncode;
// Sans identité de run capturée (tâche lancée immédiatement, jamais en
// file) : on mémorise l'horodatage du run pour pouvoir la retirer sans
// qu'elle soit réadoptée au rechargement de la page.
if (t.started === undefined) t.started = d.started;
changed = true; changed = true;
} }
if (changed) saveGenTasks(); if (changed) saveGenTasks();
@ -3844,8 +3859,15 @@ function syncThenReload() {
} }
genQueueClear.addEventListener('click', () => { genQueueClear.addEventListener('click', () => {
// Les tâches retirées (finies, interrompues, échecs) sont mémorisées : sans
// cela le rechargement réadopterait la « dernière génération » du serveur
// (.generation.job.json) et les ferait réapparaître.
for (const t of genTasks) {
if (t.state === 'running' || t.state === 'queued') continue;
if (t.started !== undefined) dismissedRuns.add(t.started);
}
genTasks = genTasks.filter(t => t.state === 'running' || t.state === 'queued'); genTasks = genTasks.filter(t => t.state === 'running' || t.state === 'queued');
saveGenTasks(); renderGenQueue(); saveGenTasks(); saveDismissed(); renderGenQueue();
}); });
// Arrêt de la génération en cours : SIGTERM au pipeline côté serveur, qui // Arrêt de la génération en cours : SIGTERM au pipeline côté serveur, qui
@ -3887,6 +3909,8 @@ renderGenQueue();
if (genTasks.some(t => t.state === 'running' || t.state === 'queued')) pollGenStatus(); if (genTasks.some(t => t.state === 'running' || t.state === 'queued')) pollGenStatus();
else fetch('/api/status').then(r => r.ok ? r.json() : null).then(s => { else fetch('/api/status').then(r => r.ok ? r.json() : null).then(s => {
if (!s || genTasks.some(t => t.started === s.started)) return; if (!s || genTasks.some(t => t.started === s.started)) return;
// Déjà retirée manuellement de la liste : ne pas réadopter ce run.
if (s.started != null && dismissedRuns.has(s.started)) return;
// Déjà suivie par une tâche (en cours ou en file côté serveur) : pas // Déjà suivie par une tâche (en cours ou en file côté serveur) : pas
// d'adoption en doublon d'un run lancé depuis cet onglet. // d'adoption en doublon d'un run lancé depuis cet onglet.
const trackedQids = new Set(genTasks.filter(t => t.qid !== undefined).map(t => t.qid)); const trackedQids = new Set(genTasks.filter(t => t.qid !== undefined).map(t => t.qid));

View File

@ -255,6 +255,11 @@ def test_build_index_generates_html(tmp_path):
# un run terminé réapparaît dans la file avec ses tuiles, sans doublon. # un run terminé réapparaît dans la file avec ses tuiles, sans doublon.
assert "Dernière génération" in app_js assert "Dernière génération" in app_js
assert "t.started === s.started" in app_js assert "t.started === s.started" in app_js
# Vidage de la file : les runs retirés sont mémorisés (par horodatage
# serveur) pour que le rechargement ne réadopte pas la « dernière
# génération » du serveur et ne refasse pas réapparaître les tâches finies.
assert "dismissedRuns" in app_js
assert "saveDismissed()" in app_js
# Bouton « Compléter » : visualisations manquantes des dalles présentes # Bouton « Compléter » : visualisations manquantes des dalles présentes
# dans input/ (scope all_missing envoyé au preview et au generate). # dans input/ (scope all_missing envoyé au preview et au generate).
# « + Zone » et « Compléter » ne vivent que dans l'onglet Génération, # « + Zone » et « Compléter » ne vivent que dans l'onglet Génération,