[fix](trx-frontend-http): stop the decode history replay giving up at 20s
CI / lint (push) Successful in 2m19s
CI / test (push) Successful in 8m15s
CI / frontend (push) Successful in 3m37s
CI / reuse (push) Successful in 3s

Reloading a second time sometimes showed history the first load did not,
and the safety valve is why: it called one function that both released
the buffered live decodes and tore the history worker down, so any load
where the replay had not finished inside twenty seconds — a large
backlog, a cold cache, a slow link — dropped whatever had not arrived,
without a word.  A reload got another go at it, and the second one is
faster because everything is cached by then.

Those are two separate things now.  At the timeout the live decodes are
released so the panels are not held back, the replay carries on, and the
progress says so.  The fallback's error path retries once and then says
"Decode history unavailable" rather than leaving the operator to guess
whether there was anything to see.

The progress is no longer a scrim.  It was fixed to the whole viewport
with a wash over the page — the waterfall, the decode panels, all of it —
for the length of the replay, which is exactly when there is something
worth watching.  It is a corner card with a bar: indeterminate while the
payload is on the wire, then filling as N of M messages replay.

None of this was reachable from a test.  /decode/history answers in CBOR
and the worker reads the body as CBOR unconditionally, but the fixture
served JSON, so every browser run had been exercising the client's retry
path and never its history path.  It encodes CBOR now, including the
64-bit form the millisecond timestamps need, and decode-flow serves 1200
records and holds the client to restoring all of them on the first load,
showing progress while it does, and never covering the page with it.

Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
sjg
2026-08-04 22:14:13 +02:00
parent 84a99a3636
commit c1899229a0
6 changed files with 308 additions and 46 deletions
@@ -2065,6 +2065,7 @@ var loadingSub = requiredElement("loading-sub");
var decodeHistoryOverlayEl = document.getElementById("decode-history-overlay");
var decodeHistoryOverlayTitleEl = document.getElementById("decode-history-overlay-title");
var decodeHistoryOverlaySubEl = document.getElementById("decode-history-overlay-sub");
var decodeHistoryProgressBarEl = document.getElementById("decode-history-progress-bar");
var connLostOverlayEl = document.getElementById("conn-lost-overlay");
var connLostOverlayTitleEl = document.getElementById("conn-lost-overlay-title");
var connLostOverlaySubEl = document.getElementById("conn-lost-overlay-sub");
@@ -2214,10 +2215,19 @@ function syncTopBarAccess() {
}
}
var overviewDrawPending = false;
function setDecodeHistoryOverlayVisible(visible, title = "", sub = "") {
function setDecodeHistoryOverlayVisible(visible, title = "", sub = "", fraction = null) {
if (!decodeHistoryOverlayEl) return;
if (title && decodeHistoryOverlayTitleEl) decodeHistoryOverlayTitleEl.textContent = title;
if (decodeHistoryOverlaySubEl) decodeHistoryOverlaySubEl.textContent = sub || "";
if (decodeHistoryProgressBarEl) {
if (fraction == null) {
decodeHistoryOverlayEl.dataset.phase = "fetching";
decodeHistoryProgressBarEl.style.width = "";
} else {
delete decodeHistoryOverlayEl.dataset.phase;
decodeHistoryProgressBarEl.style.width = `${Math.round(Math.max(0, Math.min(1, fraction)) * 100)}%`;
}
}
decodeHistoryOverlayEl.classList.toggle("is-hidden", !visible);
}
function setConnLostOverlay(visible, title = "Connection lost", sub = "Retrying…", fullscreen = false) {
@@ -7519,16 +7529,15 @@ function connectDecode() {
let historySettled = false;
let historyWorkerDone = false;
let historyFallbackStarted = false;
let historyRetried = false;
let historyBatchDrainScheduled = false;
let historyTotal = 0;
let historyProcessed = 0;
const historyGroupQueue = [];
const liveBuffer = [];
function flushLiveBuffer() {
function releaseLiveBuffer() {
if (historySettled) return;
historySettled = true;
terminateDecodeHistoryWorker();
setDecodeHistoryReplayActive(false);
setDecodeHistoryOverlayVisible(false);
for (const msg of liveBuffer) {
try {
dispatchDecodeMessage(msg);
@@ -7537,19 +7546,23 @@ function connectDecode() {
}
liveBuffer.length = 0;
}
function finishHistoryReplay() {
clearTimeout(historyTimeout);
releaseLiveBuffer();
terminateDecodeHistoryWorker();
setDecodeHistoryReplayActive(false);
setDecodeHistoryOverlayVisible(false);
}
function updateHistoryReplayOverlay() {
setDecodeHistoryOverlayVisible(
true,
"Loading decode history…",
`Replaying ${historyProcessed} / ${historyTotal} decoded messages`
`Replaying ${historyProcessed} / ${historyTotal} decoded messages`,
historyTotal > 0 ? historyProcessed / historyTotal : null
);
}
function maybeFinishHistoryReplay() {
if (historySettled) return;
if (historyWorkerDone && historyGroupQueue.length === 0) {
clearTimeout(historyTimeout);
flushLiveBuffer();
}
if (historyWorkerDone && historyGroupQueue.length === 0) finishHistoryReplay();
}
function pumpDecodeHistoryGroupQueue() {
historyBatchDrainScheduled = false;
@@ -7603,17 +7616,25 @@ function connectDecode() {
if (historyFallbackStarted || historySettled) return;
historyFallbackStarted = true;
loadDecodeHistoryOnMainThread((groups) => {
clearTimeout(historyTimeout);
const total = totalDecodeHistoryMessages(groups);
if (total > 0) {
enqueueDecodeHistoryGroups(groups);
} else {
flushLiveBuffer();
finishHistoryReplay();
}
}, (err) => {
console.error("Decode history fallback failed", err);
clearTimeout(historyTimeout);
flushLiveBuffer();
if (historyRetried) {
showHint("Decode history unavailable", 3e3);
finishHistoryReplay();
return;
}
historyRetried = true;
historyFallbackStarted = false;
setDecodeHistoryOverlayVisible(true, "Loading decode history…", "Retrying");
setTimeout(() => {
startDecodeHistoryFallback();
}, 2e3);
});
}
function startDecodeHistoryWorkerReplay() {
@@ -7674,10 +7695,9 @@ function connectDecode() {
return true;
}
const historyTimeout = setTimeout(() => {
if (!historySettled) {
terminateDecodeHistoryWorker();
flushLiveBuffer();
}
if (historySettled) return;
releaseLiveBuffer();
setDecodeHistoryOverlayVisible(true, "Loading decode history…", "Still loading — live decodes are showing");
}, 2e4);
setDecodeHistoryOverlayVisible(true, "Loading decode history…", "Fetching recent decodes from the client buffer");
decodeSource = new EventSource("/decode");
@@ -7699,7 +7719,7 @@ function connectDecode() {
const wasClosed = source.readyState === 2;
source.close();
terminateDecodeHistoryWorker();
if (!historySettled) flushLiveBuffer();
if (!historySettled) releaseLiveBuffer();
if (wasClosed) {
updateDecodeStatus("Decode not available (check client audio config)");
setTimeout(connectDecode, 1e4);