fix(control-pane): let the newest finished load decide the board

A newer refresh that failed was shown, and then an older one that
succeeded cleared the message, so the board looked live although no
load that started after the failed one had succeeded.

The board now follows the newest load that has finished, with its data
or with its failure. A load that finishes after a newer one leaves the
board alone: its data is not rendered and its failure is not reported.
This also keeps a late response from replacing newer data.
This commit is contained in:
L4XB
2026-09-25 03:12:40 +02:00
parent 029f850bd0
commit 928a295247
2 changed files with 29 additions and 8 deletions
+9 -8
View File
@@ -439,10 +439,11 @@ function renderControlPaneHtml() {
// The board keeps the last snapshot on screen, so a failed refresh has to
// say that the data is no longer live, and since when.
let loadedAt = null;
// Loads are numbered as they start, so a failed refresh can tell whether
// the board already shows data from a load that started after it.
// Loads are numbered as they start. The board follows the newest load that
// has finished, with its data or with its failure, so a load that finishes
// after a newer one leaves the board alone.
let loadsStarted = 0;
let shownLoad = 0;
let newestFinished = 0;
function showRefreshFailure(error) {
const since = loadedAt
? ' The data below is from ' + loadedAt.toLocaleString() + '.'
@@ -644,6 +645,8 @@ function renderControlPaneHtml() {
if (state.query) url.searchParams.set('query', state.query);
const response = await fetch(url);
const snapshot = await readJsonResponse(response);
if (id < newestFinished) return;
newestFinished = id;
$('#query').value = snapshot.knowledge.query || state.query;
$('#db-path').textContent = snapshot.database.exists ? snapshot.dbPath : 'database missing';
state.allowActions = Boolean(snapshot.execution.allowActions);
@@ -658,7 +661,6 @@ function renderControlPaneHtml() {
executable: snapshot.execution.allowActions && action.executable
})));
loadedAt = new Date();
shownLoad = id;
clearError('#app');
}
@@ -703,10 +705,9 @@ function renderControlPaneHtml() {
// load() numbers itself before its first await.
const id = loadsStarted;
refresh.catch(error => {
// Data from a load that started later is already on the board, so
// this failure no longer describes it. An older load that succeeded
// in the meantime does not make the board live.
if (shownLoad < id) showRefreshFailure(error);
if (id < newestFinished) return;
newestFinished = id;
showRefreshFailure(error);
});
}, 15000);
+20
View File
@@ -180,6 +180,26 @@ async function runTests() {
passed++;
else failed++;
if (
await test('an older refresh that succeeds after a newer one failed leaves the failure up', async () => {
const page = openPage(snapshot);
await settle();
page.hold = true;
page.refresh();
page.refresh();
page.pending[1].fail();
await settle();
page.pending[0].succeed();
await settle();
const box = page.element('#app');
assert.strictEqual(box.hidden, false, 'no load that started after the failed one has succeeded');
assert.match(box.textContent, /Live refresh failed\. The data below is from /);
})
)
passed++;
else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}