Navigation: - New order: Overview · Todo · Domains · Repos · Workstreams (collapsible, open:false, with atomic sub-entries: Decisions, Tasks, Debt, Extends, Dependencies) · Contributions · SBOM · Progress · Reference (collapsible) - Reference section gains path:/reference landing page; all 18 doc pages listed in nav (alphabetical) and in reference.md table New pages: - todo.md — Internal / Ecosystem / Third-party todo classification - dependencies.md — dependency edge table derived from state/summary - reference.md — Reference landing page with full doc index New reference doc pages (11): contributions, debt, dependencies, domains, extensions, overview, repos, tasks, todo + reference (meta) already added previously doc-overlay.js — lazy bubblehelp tooltip: - _titleCache Map + _fetchDocTitle(docPath): on first hover of any ? button, fetches the target doc page, parses <h1>, sets btn.title - Native browser tooltip appears exactly on the ? circle on subsequent hover Context-help wired on all 14 dashboard pages: - h1 withDocHelp added to: index, todo, domains, repos, tasks, techdept, extensions, dependencies (contributions/workstreams/decisions/sbom/ progress/reference were already wired) - domains.md + repos.md: added missing withDocHelp import and live-data link - tasks/techdept/extensions: removed duplicate _h1 const that caused SyntaxError: Identifier '_h1' has already been declared Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.7 KiB
7.7 KiB
| title |
|---|
| Domains |
const API = "http://127.0.0.1:8000";
const POLL = 15_000;
const domainsState = (async function*() {
while (true) {
let domains = [], repos = [], ok = false;
try {
const [rd, rr] = await Promise.all([
fetch(`${API}/domains/?status=all`),
fetch(`${API}/repos/`),
]);
ok = rd.ok && rr.ok;
if (ok) {
[domains, repos] = await Promise.all([rd.json(), rr.json()]);
}
} catch {}
yield {domains, repos, ok, ts: new Date()};
await new Promise(res => setTimeout(res, POLL));
}
})();
const domains = domainsState.domains ?? [];
const repos = domainsState.repos ?? [];
const _ok = domainsState.ok ?? false;
const _ts = domainsState.ts;
Domains
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
import {openEntityModal} from "./components/entity-modal.js";
// ── Live indicator ─────────────────────────────────────────────────────────────
const _liveEl = html`<div class="live-indicator">
<span style="color:${_ok ? 'var(--theme-foreground-focus)' : 'red'}">●</span>
${_ok
? `Live · updated ${_ts?.toLocaleTimeString()}`
: html`<span style="color:red">Offline — run: <code>make api</code></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/domains"); }
// ── KPI row ────────────────────────────────────────────────────────────────────
const activeDomains = domains.filter(d => d.status === "active");
const archivedDomains = domains.filter(d => d.status === "archived");
const newestDomain = [...domains].sort((a, b) => b.created_at?.localeCompare(a.created_at ?? "") ?? 0)[0];
display(html`<div class="kpi-row-top">
<div class="kpi-card">
<div class="kpi-card-value">${domains.length}</div>
<div class="kpi-card-label">total domains</div>
</div>
<div class="kpi-card">
<div class="kpi-card-value">${activeDomains.length}</div>
<div class="kpi-card-label">active</div>
</div>
<div class="kpi-card">
<div class="kpi-card-value">${repos.length}</div>
<div class="kpi-card-label">total repos</div>
</div>
<div class="kpi-card">
<div class="kpi-card-value">${newestDomain?.name ?? "—"}</div>
<div class="kpi-card-label">newest domain</div>
</div>
</div>`);
Domain Cards
// Build repo index by domain_id
const reposByDomain = {};
for (const repo of repos) {
if (!reposByDomain[repo.domain_id]) reposByDomain[repo.domain_id] = [];
reposByDomain[repo.domain_id].push(repo);
}
if (domains.length === 0) {
display(html`<p class="dim">No domains found. API may be offline.</p>`);
} else {
display(html`<div class="domain-grid">${domains.map(d => {
const domainRepos = reposByDomain[d.id] ?? [];
return html`<div class="domain-card domain-status-${d.status} entity-row"
title="Click to view details">
<div class="domain-card-header">
<span class="domain-slug">${d.slug}</span>
<span class="domain-status-badge domain-status-badge-${d.status}">${d.status}</span>
</div>
<div class="domain-name">${d.name}</div>
${d.description ? html`<div class="domain-desc">${d.description.slice(0, 160)}${d.description.length > 160 ? " …" : ""}</div>` : ""}
<div class="domain-repos">
${domainRepos.length === 0
? html`<span class="no-repos">no repos registered</span>`
: domainRepos.map(r => html`<div class="repo-row">
<span class="repo-name">${r.name}</span>
${r.local_path ? html`<code class="repo-path">${r.local_path}</code>` : ""}
${r.remote_url ? html`<a class="repo-url" href=${r.remote_url} target="_blank">${r.remote_url.replace(/^https?:\/\//, "")}</a>` : ""}
</div>`)
}
</div>
</div>`;
})}</div>`);
}