generated from coulomb/repo-seed
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>
6.7 KiB
6.7 KiB
title
| title |
|---|
| Contributions |
import {API, apiFetch, pollDelay, waitForVisible} from "./components/config.js";
const POLL = 30_000;
// Live poll for contributions
const contribState = (async function*() {
let failures = 0;
while (true) {
let data = [], ok = false;
try {
const r = await apiFetch("/contributions/");
ok = r.ok;
data = ok ? await r.json() : [];
} catch {}
failures = ok ? 0 : failures + 1;
yield {data, ok, ts: new Date()};
await waitForVisible(pollDelay({ok, base: POLL, failures}));
}
})();
const contribs = contribState.data ?? [];
const _ok = contribState.ok ?? false;
const _ts = contribState.ts;
Contributions
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
const _liveEl = html`<div class="live-indicator">
<span style="color:${_ok ? 'var(--theme-foreground-focus)' : 'red'}">●</span>
${_ok ? `Live · ${_ts?.toLocaleTimeString()}` : html`<span style="color:red">API offline</span>`}
</div>`;
withDocHelp(_liveEl, "/docs/live-data");
injectTocTop("live-indicator", _liveEl);
const _h1 = document.querySelector("#observablehq-main h1");
if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/contributions"); }
// Filters
const typeFilter = Inputs.select(["all", "br", "fr", "ep", "upr"], {label: "Type", value: "all"});
const statFilter = Inputs.select(
["all", "draft", "submitted", "acknowledged", "accepted", "rejected", "merged", "withdrawn"],
{label: "Status", value: "all"}
);
const repoFilter = Inputs.text({label: "Target repo", placeholder: "filter by repo…"});
display(html`<div style="display:flex;gap:1rem;flex-wrap:wrap;margin-bottom:1rem">
${typeFilter}${statFilter}${repoFilter}
</div>`);
const tf = typeFilter.value;
const sf = statFilter.value;
const rf = repoFilter.value?.trim().toLowerCase() ?? "";
const filtered = contribs.filter(c =>
(tf === "all" || c.type === tf) &&
(sf === "all" || c.status === sf) &&
(!rf || (c.target_repo ?? "").toLowerCase().includes(rf))
);
Summary
const typeLabels = {br: "Bug Report", fr: "Feature Request", ep: "Extension Point", upr: "Upstream PR"};
const typeCounts = Object.fromEntries(["br","fr","ep","upr"].map(t => [
t, contribs.filter(c => c.type === t).length
]));
const needsFollowUp = contribs.filter(c => ["submitted","acknowledged"].includes(c.status)).length;
display(html`<div class="grid grid-cols-5" style="gap:1rem;margin-bottom:1.5rem">
<div class="card">
<h3>Total</h3>
<p class="big-num">${contribs.length}</p>
</div>
${["br","fr","ep","upr"].map(t => html`
<div class="card">
<h3>${typeLabels[t]}</h3>
<p class="big-num">${typeCounts[t]}</p>
</div>
`)}
</div>
${needsFollowUp > 0 ? html`<div class="follow-up-banner">⚠ ${needsFollowUp} contribution(s) awaiting upstream response (submitted / acknowledged)</div>` : ""}
`);
Status Kanban
const statusCols = [
{key: "draft", label: "Draft", color: "#aaa"},
{key: "submitted", label: "Submitted", color: "steelblue"},
{key: "acknowledged", label: "Acknowledged",color: "#f0a500"},
{key: "accepted", label: "Accepted", color: "#4caf50"},
{key: "merged", label: "Merged", color: "#2e7d32"},
{key: "rejected", label: "Rejected", color: "#e53935"},
{key: "withdrawn", label: "Withdrawn", color: "#bbb"},
];
const colMap = {};
for (const c of filtered) {
(colMap[c.status] = colMap[c.status] ?? []).push(c);
}
const activeCols = statusCols.filter(s => colMap[s.key]?.length);
if (activeCols.length === 0) {
display(html`<p style="color:gray">No contributions match the current filters.</p>`);
} else {
display(html`<div class="kanban">
${activeCols.map(s => html`
<div class="kanban-col">
<div class="kanban-header" style="border-bottom:2px solid ${s.color}">${s.label} <span class="kanban-count">${colMap[s.key].length}</span></div>
${colMap[s.key].map(c => html`
<div class="contrib-card">
<div class="contrib-badge contrib-badge-${c.type}">${c.type.toUpperCase()}</div>
<div class="contrib-title">${c.title}</div>
${c.target_org || c.target_repo ? html`<div class="contrib-repo">${[c.target_org, c.target_repo].filter(Boolean).join("/")}</div>` : ""}
${c.body_path ? html`<div class="contrib-path">${c.body_path}</div>` : ""}
<div class="contrib-date">${new Date(c.created_at).toLocaleDateString()}</div>
</div>
`)}
</div>
`)}
</div>`);
}
All Contributions
display(Inputs.table(filtered.map(c => ({
Type: c.type.toUpperCase(),
Title: c.title,
Status: c.status,
Target: [c.target_org, c.target_repo].filter(Boolean).join("/") || "—",
Created: new Date(c.created_at).toLocaleDateString(),
})), {maxWidth: 900}));