v3.28.8: the batch panel's scroll preservation

The attempts box's content is replaced on every render (the batch panel is
static markup; the render fills the dynamic bits) and replacing a
scrollable element's content resets its scroll.  That was harmless while
the render ran once per attempt, but v3.28.7 made it run on every 2 s
poll, so scrolling back through the attempts during a run snapped to the
top each poll.

The render now captures the box's scrollTop before refilling it and
restores it afterwards.

Test: the stub box resets its scroll on an innerHTML write (like a
browser) and the render must keep the reader's position - verified to
fail without the restore.  920 frontend + 928 Python; sw cache
simple-v446.
This commit is contained in:
2026-10-14 23:32:04 +03:00
parent 269e507580
commit 5ae8b1fdd1
5 changed files with 39 additions and 5 deletions
+28
View File
@@ -45,6 +45,13 @@ function setEl(id, value) {
contains(c) { return !!els[id].classes[c]; },
},
};
if (id === 'ram-batch-attempts') {
// like a browser: replacing the content resets the scroll position
Object.defineProperty(els[id], 'innerHTML', {
get() { return this._html || ''; },
set(v) { this._html = v; this.scrollTop = 0; },
});
}
return els[id];
}
function clearEls() {
@@ -302,6 +309,27 @@ test('a hung detailed fetch does not freeze the counts (v3.28.7)', async () => {
app.hangs = false;
});
test('the attempts table keeps its scroll position across a poll render (v3.28.8)', () => {
clearEls();
setEl('ram-op', 'batch');
setEl('ram-batch-panel', '');
setEl('ram-batch-start', '');
setEl('ram-batch-stop', '');
setEl('ram-batch-clear', '');
setEl('ram-batch-spin', '');
setEl('ram-batch-state', '');
setEl('ram-batch-notice', '');
setEl('ram-batch-counts', '');
setEl('ram-batch-attempts', '');
app.setLast({ active: true, phase: 'waiting_card', attempts: [
{ ts: 1, iccid: '8970119000004600098', preset: 'p', result: 'installed', duration_ms: 1 }],
counts: { installed: 1, failed: 0, skipped: 0 }, attempts_total: 1 });
app.ramBatchRender();
els['ram-batch-attempts'].scrollTop = 120; // the reader scrolled back
app.ramBatchRender(); // the next 2 s poll render
assert.strictEqual(els['ram-batch-attempts'].scrollTop, 120);
});
test('a stale fetch guard is released so the report recovers (v3.28.7)', async () => {
clearEls();
setEl('ram-op', 'batch');