From 90c5ea50f7c3d31c026cdfadec8b71f2e9fcd134 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 11 May 2026 17:58:18 +0200 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20poll=20optimisation=20?= =?UTF-8?q?=E2=80=94=20T4,=20T5,=20T6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- dashboard/src/capability-requests.md | 6 +- dashboard/src/components/config.js | 19 +++- dashboard/src/contributions.md | 4 +- dashboard/src/decisions.md | 4 +- dashboard/src/dependencies.md | 24 ++--- dashboard/src/domains.md | 4 +- dashboard/src/extensions.md | 4 +- dashboard/src/goals.md | 4 +- dashboard/src/inbox.md | 4 +- dashboard/src/index.md | 147 +++++++++------------------ dashboard/src/interventions.md | 4 +- dashboard/src/progress.md | 4 +- dashboard/src/tasks.md | 4 +- dashboard/src/techdept.md | 4 +- dashboard/src/todo.md | 4 +- dashboard/src/token-cost.md | 4 +- dashboard/src/ui-feedback.md | 4 +- dashboard/src/workstreams.md | 18 ++-- 18 files changed, 111 insertions(+), 155 deletions(-) diff --git a/dashboard/src/capability-requests.md b/dashboard/src/capability-requests.md index 0786952..db7d043 100644 --- a/dashboard/src/capability-requests.md +++ b/dashboard/src/capability-requests.md @@ -3,7 +3,7 @@ title: Capability Requests --- ```js -import {API, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; const POLL = 30_000; ``` @@ -20,7 +20,7 @@ const reqState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {data, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL, failures})); + await waitForVisible(pollDelay({ok, base: POLL, failures})); } })(); ``` @@ -210,7 +210,7 @@ const catalogState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield data; - await sleep(pollDelay({ok, base: POLL, failures})); + await waitForVisible(pollDelay({ok, base: POLL, failures})); } })(); ``` diff --git a/dashboard/src/components/config.js b/dashboard/src/components/config.js index 881baec..3499f94 100644 --- a/dashboard/src/components/config.js +++ b/dashboard/src/components/config.js @@ -1,19 +1,30 @@ export const API = "http://127.0.0.1:8000"; export const POLL = 15_000; export const POLL_HEAVY = 60_000; -export const POLL_HIDDEN = 120_000; export const FETCH_TIMEOUT = 12_000; export function pollDelay({ok = true, base = POLL, failures = 0} = {}) { - const hidden = typeof document !== "undefined" && document.visibilityState === "hidden"; - const failureDelay = ok ? base : Math.min(base * 2 ** Math.min(failures, 4), 300_000); - return hidden ? Math.max(failureDelay, POLL_HIDDEN) : failureDelay; + return ok ? base : Math.min(base * 2 ** Math.min(failures, 4), 300_000); } export function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } +// Waits `ms` if the tab is visible; pauses until the tab becomes visible if hidden, +// then returns immediately so the next poll fires as soon as the user returns. +export async function waitForVisible(ms) { + if (typeof document === "undefined") return sleep(ms); + if (document.visibilityState === "visible") return sleep(ms); + return new Promise(resolve => { + const handler = () => { + document.removeEventListener("visibilitychange", handler); + resolve(); + }; + document.addEventListener("visibilitychange", handler); + }); +} + export async function apiFetch(path, options = {}) { const url = path.startsWith("http") ? path : `${API}${path}`; const timeout = options.timeout ?? FETCH_TIMEOUT; diff --git a/dashboard/src/contributions.md b/dashboard/src/contributions.md index d5c43ff..3a676ea 100644 --- a/dashboard/src/contributions.md +++ b/dashboard/src/contributions.md @@ -3,7 +3,7 @@ title: Contributions --- ```js -import {API, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; const POLL = 30_000; ``` @@ -20,7 +20,7 @@ const contribState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {data, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL, failures})); + await waitForVisible(pollDelay({ok, base: POLL, failures})); } })(); ``` diff --git a/dashboard/src/decisions.md b/dashboard/src/decisions.md index fe9382f..efba13a 100644 --- a/dashboard/src/decisions.md +++ b/dashboard/src/decisions.md @@ -3,7 +3,7 @@ title: Decisions --- ```js -import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -46,7 +46,7 @@ const decState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {data, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/dependencies.md b/dashboard/src/dependencies.md index bdee5c3..a4dbce9 100644 --- a/dashboard/src/dependencies.md +++ b/dashboard/src/dependencies.md @@ -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})); } })(); ``` diff --git a/dashboard/src/domains.md b/dashboard/src/domains.md index 5d7ecd4..4b8ff4b 100644 --- a/dashboard/src/domains.md +++ b/dashboard/src/domains.md @@ -3,7 +3,7 @@ title: Domains --- ```js -import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -23,7 +23,7 @@ const domainsState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {domains, repos, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/extensions.md b/dashboard/src/extensions.md index 2002993..6fa5dce 100644 --- a/dashboard/src/extensions.md +++ b/dashboard/src/extensions.md @@ -3,7 +3,7 @@ title: Extension Points --- ```js -import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -39,7 +39,7 @@ const epState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {data, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/goals.md b/dashboard/src/goals.md index abf44d4..8bf70b4 100644 --- a/dashboard/src/goals.md +++ b/dashboard/src/goals.md @@ -3,7 +3,7 @@ title: Goals --- ```js -import {API, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; const POLL = 20_000; ``` @@ -28,7 +28,7 @@ const goalsState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {domains, domainGoals, repoGoals, repos, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL, failures})); + await waitForVisible(pollDelay({ok, base: POLL, failures})); } })(); ``` diff --git a/dashboard/src/inbox.md b/dashboard/src/inbox.md index df6f448..b089015 100644 --- a/dashboard/src/inbox.md +++ b/dashboard/src/inbox.md @@ -3,7 +3,7 @@ title: Agent Inbox --- ```js -import {API, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -19,7 +19,7 @@ const inboxState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {messages, ok, ts: new Date()}; - await sleep(pollDelay({ok, failures})); + await waitForVisible(pollDelay({ok, failures})); } })(); ``` diff --git a/dashboard/src/index.md b/dashboard/src/index.md index 8f4a22b..749ab3f 100644 --- a/dashboard/src/index.md +++ b/dashboard/src/index.md @@ -3,117 +3,39 @@ title: Overview --- ```js -import {API, POLL, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js -// Live polling — yields {data, ok, ts}; backs off when the API is slow/offline. -const summaryState = (async function*() { +// Single polling loop — fetches all data in one Promise.all batch, backs off uniformly. +const pageState = (async function*() { let failures = 0; while (true) { - let data, ok = false; + let summary = {}, snapshots = [], totalPkgs = 0, milestones = [], wsAll = [], ok = false; try { - const r = await apiFetch("/state/summary", {timeout: 20_000}); - ok = r.ok; - data = ok ? await r.json() : {error: `HTTP ${r.status}`}; - } catch (e) { - data = {error: "API unreachable"}; - } - failures = ok ? 0 : failures + 1; - yield {data, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); - } -})(); -``` - -```js -const summary = summaryState.data ?? {}; -const _ok = summaryState.ok ?? false; -const _ts = summaryState.ts; -const totals = summary.totals ?? {}; -const ws = totals.workstreams ?? {}; -const tasks = totals.tasks ?? {}; -const decisions = totals.decisions ?? {}; -``` - -```js -// Blocking decisions — fetched once on load, refreshed only after a resolve action. -// Kept separate from the summary poll so in-progress form inputs aren't wiped every 15 s. -const blockingDecisions = Mutable([]); -const refreshDecisions = async () => { - const r = await fetch(`${API}/decisions/?decision_type=pending`).catch(() => null); - const all = r?.ok ? await r.json() : []; - blockingDecisions.value = all.filter(d => ["open", "escalated"].includes(d.status)); -}; -refreshDecisions(); -``` - -```js -// SBOM snapshots — repo coverage and total package count -const sbomSnapState = (async function*() { - let failures = 0; - while (true) { - let snapshots = [], totalPkgs = 0, ok = false; - try { - const r = await apiFetch("/sbom/snapshots/"); - ok = r.ok; - if (r.ok) { - snapshots = await r.json(); - totalPkgs = snapshots.reduce((s, sn) => s + (sn.entry_count ?? 0), 0); - } - } catch {} - failures = ok ? 0 : failures + 1; - yield {snapshots, totalPkgs}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); - } -})(); -``` - -```js -// Registered projects — milestone events tagged with registration -const regsState = (async function*() { - let failures = 0; - while (true) { - let rows = [], ok = false; - try { - const r = await apiFetch("/progress/?event_type=milestone&limit=500"); - ok = r.ok; - if (r.ok) { - const all = await r.json(); - rows = all.filter(e => e.summary?.startsWith("Project registered with State Hub:")); - } - } catch {} - failures = ok ? 0 : failures + 1; - yield rows; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); - } -})(); -``` - -```js -// All-workstreams + all-tasks poll — drives the multi-mode chart -const wsChartState = (async function*() { - let failures = 0; - while (true) { - let wsAll = [], ok = false; - try { - const [rw, rt, rto, rr, rwi] = await Promise.all([ + const [rSum, rSnap, rRegs, rw, rt, rto, rr, rwi] = await Promise.all([ + apiFetch("/state/summary", {timeout: 20_000}), + apiFetch("/sbom/snapshots/"), + apiFetch("/progress/?event_type=milestone&limit=500"), apiFetch("/workstreams/"), apiFetch("/tasks/?limit=2000"), apiFetch("/topics/"), apiFetch("/repos/"), apiFetch("/workstreams/workplan-index"), ]); - ok = rw.ok && rt.ok && rto.ok && rr.ok; + ok = rSum.ok && rSnap.ok && rRegs.ok && rw.ok && rt.ok && rto.ok && rr.ok; if (ok) { - const [wsList, taskList, topicList, repoList] = await Promise.all([ - rw.json(), rt.json(), rto.json(), rr.json(), + const [summaryData, snapList, allEvents, wsList, taskList, topicList, repoList] = await Promise.all([ + rSum.json(), rSnap.json(), rRegs.json(), rw.json(), rt.json(), rto.json(), rr.json(), ]); + summary = summaryData; + snapshots = snapList; + totalPkgs = snapshots.reduce((s, sn) => s + (sn.entry_count ?? 0), 0); + milestones = allEvents.filter(e => e.summary?.startsWith("Project registered with State Hub:")); const workplanIndex = rwi.ok ? await rwi.json() : {workstreams: {}}; const workplanMap = workplanIndex.workstreams ?? {}; const topicMap = Object.fromEntries(topicList.map(t => [t.id, t])); const repoMap = Object.fromEntries(repoList.map(r => [r.id, r])); - // Aggregate task counts per workstream const counts = {}; for (const t of taskList) { const wid = t.workstream_id; @@ -139,17 +61,40 @@ const wsChartState = (async function*() { ...(counts[w.id] ?? {done: 0, in_progress: 0, blocked: 0, todo: 0, total: 0}), }; }); + } else { + summary = {error: "API unreachable"}; } - } catch {} + } catch (e) { + summary = {error: "API unreachable"}; + } failures = ok ? 0 : failures + 1; - yield {wsAll, ok}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + yield {summary, snapshots, totalPkgs, milestones, wsAll, ok, ts: new Date()}; + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` ```js -const wsAll = wsChartState.wsAll ?? []; +const summary = pageState.summary ?? {}; +const _ok = pageState.ok ?? false; +const _ts = pageState.ts; +const totals = summary.totals ?? {}; +const ws = totals.workstreams ?? {}; +const tasks = totals.tasks ?? {}; +const decisions = totals.decisions ?? {}; +const wsAll = pageState.wsAll ?? []; +``` + +```js +// Blocking decisions — fetched once on load, refreshed only after a resolve action. +// Kept separate from the main poll so in-progress form inputs aren't wiped every 60 s. +const blockingDecisions = Mutable([]); +const refreshDecisions = async () => { + const r = await fetch(`${API}/decisions/?decision_type=pending`).catch(() => null); + const all = r?.ok ? await r.json() : []; + blockingDecisions.value = all.filter(d => ["open", "escalated"].includes(d.status)); +}; +refreshDecisions(); ``` # Custodian State Hub @@ -390,8 +335,8 @@ const contribCounts = summary.contribution_counts ?? {}; const licenceRisk = summary.licence_risk_count ?? 0; const totalContribs = ["br","fr","ep","upr"].reduce((s, t) => s + (contribCounts[t] ?? 0), 0); const needsFollowUp = (contribCounts["submitted"] ?? 0) + (contribCounts["acknowledged"] ?? 0); -const sbomSnaps = sbomSnapState.snapshots ?? []; -const totalPkgs = sbomSnapState.totalPkgs ?? 0; +const sbomSnaps = pageState.snapshots ?? []; +const totalPkgs = pageState.totalPkgs ?? 0; display(html`
@@ -508,7 +453,7 @@ if (nextSteps.length === 0) { ## Registered Projects ```js -const regs = regsState ?? []; +const regs = pageState.milestones ?? []; if (regs.length === 0) { display(html`

No projects registered yet. Run custodian register-project inside a repo.

`); } else { @@ -523,7 +468,7 @@ if (regs.length === 0) { ```js // Registered domains with no workstreams yet — show a getting-started hint -const regs = regsState ?? []; +const regs = pageState.milestones ?? []; const registeredDomains = new Set(regs.map(e => e.detail?.domain).filter(Boolean)); const emptyRegistered = (summary.topics ?? []).filter(t => registeredDomains.has(t.domain_slug) && (t.workstreams ?? []).length === 0 diff --git a/dashboard/src/interventions.md b/dashboard/src/interventions.md index 299dd6c..e84cbac 100644 --- a/dashboard/src/interventions.md +++ b/dashboard/src/interventions.md @@ -3,7 +3,7 @@ title: Interventions --- ```js -import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -39,7 +39,7 @@ const interventionState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {tasks, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/progress.md b/dashboard/src/progress.md index 907ffae..5f1f4bb 100644 --- a/dashboard/src/progress.md +++ b/dashboard/src/progress.md @@ -3,7 +3,7 @@ title: Progress --- ```js -import {POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -22,7 +22,7 @@ const progState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {data, tokenEvents, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/tasks.md b/dashboard/src/tasks.md index 016421d..6a83d00 100644 --- a/dashboard/src/tasks.md +++ b/dashboard/src/tasks.md @@ -3,7 +3,7 @@ title: Tasks --- ```js -import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -36,7 +36,7 @@ const taskState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {data, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/techdept.md b/dashboard/src/techdept.md index cb09bfb..4c41c70 100644 --- a/dashboard/src/techdept.md +++ b/dashboard/src/techdept.md @@ -3,7 +3,7 @@ title: Technical Debt --- ```js -import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -39,7 +39,7 @@ const tdState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {data, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/todo.md b/dashboard/src/todo.md index 960d8a3..8a6b6c7 100644 --- a/dashboard/src/todo.md +++ b/dashboard/src/todo.md @@ -3,7 +3,7 @@ title: Todo --- ```js -import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; const THIS_REPO = "the-custodian"; ``` @@ -45,7 +45,7 @@ const todoState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {tasks, contribs, improvements, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/token-cost.md b/dashboard/src/token-cost.md index 8516717..1649e4d 100644 --- a/dashboard/src/token-cost.md +++ b/dashboard/src/token-cost.md @@ -3,7 +3,7 @@ title: Token Cost --- ```js -import {apiFetch, pollDelay, sleep} from "./components/config.js"; +import {apiFetch, pollDelay, waitForVisible} from "./components/config.js"; import {refCell} from "./components/ref-cell.js"; const POLL = 60_000; ``` @@ -37,7 +37,7 @@ const tokenState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {byRepo, events, wsMap, taskMap, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL, failures})); + await waitForVisible(pollDelay({ok, base: POLL, failures})); } })(); ``` diff --git a/dashboard/src/ui-feedback.md b/dashboard/src/ui-feedback.md index e64a763..e42c26c 100644 --- a/dashboard/src/ui-feedback.md +++ b/dashboard/src/ui-feedback.md @@ -3,7 +3,7 @@ title: UI Feedback --- ```js -import {API, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js"; +import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js @@ -65,7 +65,7 @@ const feedbackState = (async function*() { } catch {} failures = ok ? 0 : failures + 1; yield {data, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` diff --git a/dashboard/src/workstreams.md b/dashboard/src/workstreams.md index 21ced66..9aafa27 100644 --- a/dashboard/src/workstreams.md +++ b/dashboard/src/workstreams.md @@ -3,25 +3,26 @@ title: Workstreams --- ```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 (for dep graph) in parallel +// Fetch workstreams + topics + dep edges in parallel; /state/deps replaces the +// heavier /state/summary which was only used here to extract dependency edges. const wsState = (async function*() { let failures = 0; while (true) { let data = [], openWs = [], ok = false; try { - const [rw, rt, rr, rs] = await Promise.all([ + const [rw, rt, rr, rd] = await Promise.all([ apiFetch("/workstreams/"), apiFetch("/topics/"), apiFetch("/repos/"), - apiFetch("/state/summary", {timeout: 20_000}), + apiFetch("/state/deps"), ]); - ok = rw.ok && rt.ok && rr.ok && rs.ok; + ok = rw.ok && rt.ok && rr.ok && rd.ok; if (ok) { - const [wsList, topicList, repoList, summary] = await Promise.all([rw.json(), rt.json(), rr.json(), rs.json()]); + const [wsList, topicList, repoList, depsList] = await Promise.all([rw.json(), rt.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])); data = wsList.map(w => ({ @@ -29,13 +30,12 @@ const wsState = (async function*() { domain: repoMap[w.repo_id]?.domain_slug ?? topicMap[w.topic_id]?.domain_slug ?? "unknown", topic_title: topicMap[w.topic_id]?.title ?? "—", })); - // open_workstreams from summary carry depends_on / blocks lists - openWs = summary.open_workstreams ?? []; + openWs = depsList; } } catch {} failures = ok ? 0 : failures + 1; yield {data, openWs, ok, ts: new Date()}; - await sleep(pollDelay({ok, base: POLL_HEAVY, failures})); + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ```