From e3583dca51ce4b1e5d045147d939a0544622b194 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 10 Nov 2026 10:42:36 +0100 Subject: perf: load and mount panels in parallel Boot imported, mounted and awaited the first poll of each panel in turn, so one slow source (the build VM over ssh, a printer that is off) held back every panel after it, and tabs appeared one by one. All panel code is now imported at once, panels mount in the server's order so cards and tabs do not shuffle, and first polls run without being awaited. Co-Authored-By: Claude Opus 5.5 --- static/app.js | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) (limited to 'static') diff --git a/static/app.js b/static/app.js index 31083a6..2f61c81 100644 --- a/static/app.js +++ b/static/app.js @@ -222,7 +222,9 @@ function makeCard(name, title) { return body; } -async function startPoll(name, panel, el) { +// The first poll is not awaited: a slow source (the build VM over ssh, a +// printer that is off) must not hold back the other panels. +function startPoll(name, panel, el) { const poll = async () => { try { const r = await fetch(`/api/${name}`); @@ -232,42 +234,43 @@ async function startPoll(name, panel, el) { } }; refreshers.push(poll); - await poll(); + poll(); setInterval(poll, (panel.interval ?? 1) * 1000); } -async function mountDashboard(name, panel) { +function mountDashboard(name, panel) { const body = makeCard(name, panel.title ?? name); panel.mount?.(body); - await startPoll(name, panel, body); + startPoll(name, panel, body); } -async function mountTab(name, panel, section) { +function mountTab(name, panel, section) { const wrap = document.createElement("div"); wrap.className = "tab-content"; section.append(wrap); panel.mount?.(wrap); - await startPoll(name, panel, wrap); + startPoll(name, panel, wrap); } async function boot() { const r = await fetch("/api/modules"); if (!r.ok) return; - for (const { name } of await r.json()) { - let panel; - try { - panel = (await import(`./modules/${name}.js`)).default; - } catch { - continue; - } + // Every panel's code at once; then mount in the server's order, so the + // cards and tabs do not shuffle with whichever import lands first. + const names = (await r.json()).map(({ name }) => name); + const loaded = await Promise.all(names.map((name) => + import(`./modules/${name}.js`).then((m) => m.default, () => null))); + names.forEach((name, i) => { + const panel = loaded[i]; + if (!panel) return; const tabName = panel.tab ?? "Dashboard"; try { - if (tabName === "Dashboard") await mountDashboard(name, panel); - else await mountTab(name, panel, ensureTab(tabName).section); + if (tabName === "Dashboard") mountDashboard(name, panel); + else mountTab(name, panel, ensureTab(tabName).section); } catch { // a broken panel must not take down the rest of the dashboard } - } + }); closeGaps(); ready = true; saveLayout(); -- cgit v1.2.3