state-hub/dashboard/src/interface-changes.md

155 lines
6.2 KiB
Markdown
Raw Normal View History

---
title: Interface Changes
---
```js
import {API} from "./components/config.js";
```
```js
const [published, draft, repos] = await Promise.all([
fetch(`${API}/interface-changes/?status=published`).then(r => r.ok ? r.json() : []),
fetch(`${API}/interface-changes/?status=draft`).then(r => r.ok ? r.json() : []),
fetch(`${API}/repos/`).then(r => r.ok ? r.json() : []),
]);
```
```js
const repoMap = Object.fromEntries(repos.map(r => [r.slug, r]));
const CHANGE_TYPE_COLOR = {
breaking: "#ef4444",
removal: "#f97316",
deprecation: "#f59e0b",
additive: "#22c55e",
};
function changeBadge(type) {
const color = CHANGE_TYPE_COLOR[type] ?? "#6b7280";
return htl.html`<span style="
background:${color};color:#fff;font-size:11px;font-weight:700;
padding:2px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.04em
">${type}</span>`;
}
function interfaceBadge(type) {
return htl.html`<span style="
background:#3b82f6;color:#fff;font-size:10px;font-weight:600;
padding:1px 6px;border-radius:8px
">${type}</span>`;
}
```
# Interface Changes
Track breaking and additive mutations to published interfaces across repos.
Publishing a change sends inbox notifications to all affected agents.
<div style="display:flex;gap:16px;margin-bottom:24px">
<div style="background:#fef2f2;border:1px solid #fca5a5;border-radius:8px;padding:12px 20px;text-align:center">
<div style="font-size:28px;font-weight:700;color:#dc2626">${published.filter(c => c.change_type === "breaking").length}</div>
<div style="font-size:12px;color:#6b7280">Breaking (live)</div>
</div>
<div style="background:#fff7ed;border:1px solid #fdba74;border-radius:8px;padding:12px 20px;text-align:center">
<div style="font-size:28px;font-weight:700;color:#ea580c">${published.filter(c => c.change_type === "deprecation").length}</div>
<div style="font-size:12px;color:#6b7280">Deprecations (live)</div>
</div>
<div style="background:#f0fdf4;border:1px solid #86efac;border-radius:8px;padding:12px 20px;text-align:center">
<div style="font-size:28px;font-weight:700;color:#16a34a">${published.filter(c => c.change_type === "additive").length}</div>
<div style="font-size:12px;color:#6b7280">Additive (live)</div>
</div>
<div style="background:#f9fafb;border:1px solid #d1d5db;border-radius:8px;padding:12px 20px;text-align:center">
<div style="font-size:28px;font-weight:700;color:#374151">${draft.length}</div>
<div style="font-size:12px;color:#6b7280">Drafts</div>
</div>
</div>
## Planned changes
```js
const planned = [...published, ...draft].filter(c => c.planned_for);
planned.sort((a, b) => a.planned_for.localeCompare(b.planned_for));
```
```js
if (planned.length === 0) {
display(htl.html`<p style="color:#6b7280;font-style:italic">No changes with a planned date.</p>`);
} else {
display(htl.html`<table style="width:100%;border-collapse:collapse;font-size:13px">
<thead><tr style="border-bottom:2px solid #e5e7eb">
<th style="text-align:left;padding:6px 8px">Date</th>
<th style="text-align:left;padding:6px 8px">Change</th>
<th style="text-align:left;padding:6px 8px">Repo</th>
<th style="text-align:left;padding:6px 8px">Status</th>
</tr></thead>
<tbody>
${planned.map(c => htl.html`<tr style="border-bottom:1px solid #f3f4f6">
<td style="padding:6px 8px;white-space:nowrap;color:#374151">${c.planned_for}</td>
<td style="padding:6px 8px">${changeBadge(c.change_type)} ${c.title}</td>
<td style="padding:6px 8px;color:#6b7280">${c.origin_repo_slug ?? c.repo_slug}</td>
<td style="padding:6px 8px">${c.status}</td>
</tr>`)}
</tbody>
</table>`);
}
```
## Published changes
```js
if (published.length === 0) {
display(htl.html`<p style="color:#6b7280;font-style:italic">No published changes.</p>`);
} else {
display(htl.html`<div style="display:flex;flex-direction:column;gap:12px">
${published.map(c => htl.html`
<div style="border:1px solid #e5e7eb;border-radius:8px;padding:14px 16px">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap">
${changeBadge(c.change_type)}
${interfaceBadge(c.interface_type)}
<strong style="font-size:14px">${c.title}</strong>
</div>
<div style="font-size:12px;color:#6b7280;margin-bottom:6px">
Repo: <strong>${c.origin_repo_slug ?? c.repo_slug}</strong>
${c.published_at ? htl.html` · Published ${c.published_at.slice(0,10)}` : ""}
${c.affected_repo_slugs?.length ? htl.html` · Affects: ${c.affected_repo_slugs.join(", ")}` : ""}
</div>
<details style="font-size:13px;color:#374151">
<summary style="cursor:pointer;color:#6b7280">Description</summary>
<pre style="margin-top:6px;white-space:pre-wrap;font-family:inherit">${c.description}</pre>
</details>
${c.affected_paths?.length ? htl.html`
<div style="margin-top:6px;font-size:12px">
<strong>Paths:</strong>
${c.affected_paths.map(p => htl.html`<code style="background:#f3f4f6;padding:1px 5px;border-radius:4px;margin:0 2px">${p}</code>`)}
</div>` : ""}
</div>
`)}
</div>`);
}
```
## Drafts
```js
if (draft.length === 0) {
display(htl.html`<p style="color:#6b7280;font-style:italic">No draft changes.</p>`);
} else {
display(htl.html`<table style="width:100%;border-collapse:collapse;font-size:13px">
<thead><tr style="border-bottom:2px solid #e5e7eb">
<th style="text-align:left;padding:6px 8px">Title</th>
<th style="text-align:left;padding:6px 8px">Repo</th>
<th style="text-align:left;padding:6px 8px">Type</th>
<th style="text-align:left;padding:6px 8px">Affected</th>
</tr></thead>
<tbody>
${draft.map(c => htl.html`<tr style="border-bottom:1px solid #f3f4f6">
<td style="padding:6px 8px">${c.title}</td>
<td style="padding:6px 8px;color:#6b7280">${c.repo_slug}</td>
<td style="padding:6px 8px">${changeBadge(c.change_type)} ${interfaceBadge(c.interface_type)}</td>
<td style="padding:6px 8px;color:#6b7280">${(c.affected_repo_slugs ?? []).join(", ") || "—"}</td>
</tr>`)}
</tbody>
</table>`);
}
```