feat(dashboard): poll optimisation — T4, T5, T6

T4: workstreams.md and dependencies.md now call /state/deps instead of the
    full /state/summary — removes 2 heavy 10-table queries per 60 s cycle.

T5: index.md's 4 independent polling loops (summaryState, sbomSnapState,
    regsState, wsChartState) consolidated into a single pageState generator
    with one Promise.all batch and a shared backoff counter.

T6: config.js gains waitForVisible(ms) — pauses polling entirely while the
    tab is hidden and fires immediately on visibilitychange.  pollDelay()
    simplified (hidden-tab POLL_HIDDEN logic removed).  All 16 polling pages
    migrated from await sleep(pollDelay(...)) to await waitForVisible(pollDelay(...)).

CUST-WP-0039 complete — all 6 tasks done.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-11 17:58:18 +02:00
parent b832032cc3
commit 90c5ea50f7
18 changed files with 111 additions and 155 deletions

View File

@@ -3,26 +3,27 @@ title: Dependencies
---
```js
import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js";
import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js";
```
```js
// Fetch workstreams + topics + summary (summary carries dep edges on open_workstreams)
// Fetch workstreams + topics + dep edges; /state/deps replaces the heavier
// /state/summary which was only used here to extract dependency edges.
const depState = (async function*() {
let failures = 0;
while (true) {
let wsMap = {}, edges = [], ok = false;
try {
const [rw, rto, rr, rs] = await Promise.all([
const [rw, rto, rr, rd] = await Promise.all([
apiFetch("/workstreams/"),
apiFetch("/topics/"),
apiFetch("/repos/"),
apiFetch("/state/summary", {timeout: 20_000}),
apiFetch("/state/deps"),
]);
ok = rw.ok && rto.ok && rr.ok && rs.ok;
ok = rw.ok && rto.ok && rr.ok && rd.ok;
if (ok) {
const [wsList, topicList, repoList, summary] = await Promise.all([
rw.json(), rto.json(), rr.json(), rs.json(),
const [wsList, topicList, repoList, depsList] = await Promise.all([
rw.json(), rto.json(), rr.json(), rd.json(),
]);
const topicMap = Object.fromEntries(topicList.map(t => [t.id, t]));
const repoMap = Object.fromEntries(repoList.map(r => [r.id, r]));
@@ -31,17 +32,16 @@ const depState = (async function*() {
// Prefer repo→domain (GEMS primary); fall back to topic→domain
domain: repoMap[w.repo_id]?.domain_slug ?? topicMap[w.topic_id]?.domain_slug ?? "unknown",
}]));
// Build directed edge list from open_workstreams depends_on arrays
for (const ow of (summary.open_workstreams ?? [])) {
for (const depId of (ow.depends_on ?? [])) {
edges.push({from_id: ow.id, to_id: depId});
for (const ow of depsList) {
for (const depStub of (ow.depends_on ?? [])) {
edges.push({from_id: ow.id, to_id: depStub});
}
}
}
} catch {}
failures = ok ? 0 : failures + 1;
yield {wsMap, edges, ok, ts: new Date()};
await sleep(pollDelay({ok, base: POLL_HEAVY, failures}));
await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures}));
}
})();
```