fix(control-pane): keep a superseded refresh failure off the board

A live refresh that fails after a newer load has already replaced the
board no longer describes what is shown, so it is only reported while
the data it was meant to replace is still on screen. The time of that
data now includes its date, for a pane left open overnight, and it is
kept in its own binding instead of on the shared state object.
This commit is contained in:
L4XB
2026-09-25 00:07:08 +02:00
parent 0ae2fa06a4
commit 10969f587b
2 changed files with 42 additions and 10 deletions
+10 -4
View File
@@ -438,9 +438,10 @@ 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;
function showRefreshFailure(error) {
const since = state.loadedAt
? ' The data below is from ' + state.loadedAt.toLocaleTimeString() + '.'
const since = loadedAt
? ' The data below is from ' + loadedAt.toLocaleString() + '.'
: '';
showError('#app', 'Live refresh failed.' + since + '\\n' + formatError(error));
}
@@ -651,7 +652,7 @@ function renderControlPaneHtml() {
...action,
executable: snapshot.execution.allowActions && action.executable
})));
state.loadedAt = new Date();
loadedAt = new Date();
clearError('#app');
}
@@ -692,7 +693,12 @@ function renderControlPaneHtml() {
// Live board: refresh on a gentle interval; pause while a prompt/tab is hidden.
setInterval(() => {
if (document.hidden) return;
load().catch(showRefreshFailure);
const shownSince = loadedAt;
load().catch(error => {
// A load that finished in the meantime already replaced the data this
// refresh was for, so its failure no longer describes the board.
if (loadedAt === shownSince) showRefreshFailure(error);
});
}, 15000);
load().catch(error => showError('#app', error));
+32 -6
View File
@@ -28,7 +28,8 @@ function inlineScript(html) {
// Runs the page script against a stand-in for the few browser APIs it uses:
// elements looked up by selector, fetch, and a setInterval whose callback the
// test fires itself.
// test fires itself. While `hold` is set, a fetch waits in `pending` until the
// test settles it.
function openPage(snapshot) {
const elements = new Map();
const element = selector => {
@@ -44,17 +45,23 @@ function openPage(snapshot) {
}
return elements.get(selector);
};
const page = { online: true, refresh: null, element };
const page = { online: true, hold: false, pending: [], refresh: null, element };
vm.runInNewContext(inlineScript(renderControlPaneHtml()), {
document: { hidden: false, querySelector: element, querySelectorAll: () => [] },
window: { location: { href: 'http://127.0.0.1:8765/' } },
URL,
Intl,
console,
fetch: async () => {
if (!page.online) throw new TypeError('Failed to fetch');
return { ok: true, status: 200, statusText: 'OK', json: async () => snapshot };
},
fetch: () =>
new Promise((resolve, reject) => {
const reply = {
succeed: () => resolve({ ok: true, status: 200, statusText: 'OK', json: async () => snapshot }),
fail: () => reject(new TypeError('Failed to fetch'))
};
if (page.hold) page.pending.push(reply);
else if (page.online) reply.succeed();
else reply.fail();
}),
setInterval: callback => {
page.refresh = callback;
}
@@ -92,6 +99,7 @@ async function runTests() {
const box = page.element('#app');
assert.strictEqual(box.hidden, false, 'the failure is shown');
assert.match(box.textContent, /Live refresh failed\. The data below is from /);
assert.ok(box.textContent.includes(String(new Date().getFullYear())), 'the time of the data includes its date');
assert.match(box.textContent, /Failed to fetch/);
page.online = true;
@@ -103,6 +111,24 @@ async function runTests() {
passed++;
else failed++;
if (
await test('a refresh that fails after a newer load succeeded is not reported', async () => {
const page = openPage(snapshot);
await settle();
page.hold = true;
page.refresh();
page.hold = false;
page.refresh();
await settle();
page.pending[0].fail();
await settle();
assert.strictEqual(page.element('#app').hidden, true, 'the newer data is not marked as stale');
})
)
passed++;
else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}