generated from coulomb/repo-seed
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:
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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`<div class="grid grid-cols-3" style="gap:1rem;margin-bottom:1.5rem">
|
||||
<a class="card card-link" href="./contributions">
|
||||
@@ -508,7 +453,7 @@ if (nextSteps.length === 0) {
|
||||
## Registered Projects
|
||||
|
||||
```js
|
||||
const regs = regsState ?? [];
|
||||
const regs = pageState.milestones ?? [];
|
||||
if (regs.length === 0) {
|
||||
display(html`<p style="color:gray">No projects registered yet. Run <code>custodian register-project</code> inside a repo.</p>`);
|
||||
} 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
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user