From 7dec3ac9ee824abfac092ed41a56e9622c7b2a7a Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 20 Mar 2026 01:31:48 +0100 Subject: [PATCH] perf(dashboard): lazy-load DoI tiers on Repositories page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Page now renders in ~200ms. DoI badges and KPI card show a spinner while the background fetch resolves (~6s), then update reactively via Observable Mutable pattern (doiData / doiLoading). Fast path: repos, SBOM, domains, workstreams — immediate render. Slow path: /repos/doi/summary — background, non-blocking. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- state-hub/dashboard/src/repos.md | 44 ++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/state-hub/dashboard/src/repos.md b/state-hub/dashboard/src/repos.md index d8f35ff..8ef63fd 100644 --- a/state-hub/dashboard/src/repos.md +++ b/state-hub/dashboard/src/repos.md @@ -7,20 +7,32 @@ import {API} from "./components/config.js"; ``` ```js -let _repos = [], _domains = [], _sbom = [], _eps = [], _tds = [], _workstreams = [], _doi = []; +// Fast data — page renders as soon as this resolves (~200ms) +let _repos = [], _domains = [], _sbom = [], _eps = [], _tds = [], _workstreams = []; try { - [_repos, _domains, _sbom, _eps, _tds, _workstreams, _doi] = await Promise.all([ + [_repos, _domains, _sbom, _eps, _tds, _workstreams] = await Promise.all([ fetch(`${API}/repos/`).then(r => r.ok ? r.json() : []), fetch(`${API}/domains/`).then(r => r.ok ? r.json() : []), fetch(`${API}/sbom/`).then(r => r.ok ? r.json() : []), fetch(`${API}/extension-points/`).then(r => r.ok ? r.json() : []), fetch(`${API}/technical-debt/`).then(r => r.ok ? r.json() : []), fetch(`${API}/workstreams/`).then(r => r.ok ? r.json() : []), - fetch(`${API}/repos/doi/summary`).then(r => r.ok ? r.json() : []), ]); } catch {} ``` +```js +// DoI data — lazy-loaded. Starts as [] so the page renders immediately. +// When the fetch resolves (~6s), doiData updates and DoI badges appear. +import {Mutable} from "observablehq:stdlib"; +const doiData = Mutable([]); +const doiLoading = Mutable(true); +fetch(`${API}/repos/doi/summary`) + .then(r => r.ok ? r.json() : []) + .catch(() => []) + .then(data => { doiData.value = data; doiLoading.value = false; }); +``` + ```js const repos = _repos ?? []; const domains = _domains ?? []; @@ -28,7 +40,7 @@ const sbom = _sbom ?? []; const eps = _eps ?? []; const tds = _tds ?? []; const workstreams = _workstreams ?? []; -const doi = _doi ?? []; +const doi = doiData; // reactive — updates when lazy fetch completes // DoI lookups const doiBySlug = Object.fromEntries(doi.map(d => [d.repo_slug, d])); @@ -121,12 +133,15 @@ const doiNoneCount = repoRows.filter(r => r._doiTier === "none").length; import {withDocHelp} from "./components/doc-overlay.js"; const _h1 = document.querySelector("#observablehq-main h1"); if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/repos"); } -display(html`

- DoI tiers: None → +display(html`

+ DoI tiers: NoneCoreStandardFull — - Definition of Integrated policy ↗ + Definition of Integrated policy ↗ + ${doiLoading ? html` + Loading DoI tiers… + ` : html`✓ DoI tiers loaded`}

`); ``` @@ -155,10 +170,10 @@ display(html`

${gapCount}

${gapCount === 0 ? "✓ All repos covered" : `⚠ ${gapCount} repo(s) not ingested`}
-
+

DoI: Fully Integrated

-

${doiFullCount} / ${repoRows.length}

- ${doiNoneCount > 0 ? `⚠ ${doiNoneCount} at tier None` : "✓ All pass Core tier"} +

${doiLoading ? html`` : `${doiFullCount} / ${repoRows.length}`}

+ ${doiLoading ? "Loading…" : doiNoneCount > 0 ? `⚠ ${doiNoneCount} at tier None` : "✓ All pass Core tier"}
`); ``` @@ -174,6 +189,7 @@ function _sbomGap() { } function _doiBadge(tier) { + if (doiLoading) return html``; const color = DOI_TIER_COLOR[tier] || "#9ca3af"; const bg = DOI_TIER_BG[tier] || "#f9fafb"; const label = DOI_TIER_LABEL[tier] || tier; @@ -363,4 +379,12 @@ custodian register-project --domain <slug> .onboard-step strong { font-size: 0.9rem; display: block; margin-bottom: 0.3rem; } .onboard-step pre { background: var(--theme-background); border-radius: 4px; padding: 0.4rem 0.7rem; font-size: 0.8rem; overflow-x: auto; margin: 0 0 0.35rem; } .onboard-note { font-size: 0.82rem; color: var(--theme-foreground-muted, gray); margin: 0; line-height: 1.45; } + +.doi-spinner { + display: inline-block; width: 0.9rem; height: 0.9rem; + border: 2px solid #e5e7eb; border-top-color: #9ca3af; + border-radius: 50%; animation: doi-spin 0.7s linear infinite; + vertical-align: middle; +} +@keyframes doi-spin { to { transform: rotate(360deg); } }