feat(dashboard): entity list UX — REF column, name cells, detail pages (CUST-WP-0030)

- ref-cell.js: REF column component — click=copy deeplink, dblclick=open
- field-help.js: field registry + fieldRow helper with help-tip decoration;
  FK fields (task_id, workstream_id, repo_id) render as async-linked cells
  with entity-title bubble-help on hover
- GET /token-events/{id} endpoint + get-by-id tests
- GET /repos/by-id/{repo_id} UUID lookup endpoint
- Landing pages: /token-events/[id], /workstreams/[id], /repos/[slug], /tasks/[id]
- token-cost.md: REF + Name columns on all three tables; parallel fetch of
  workstreams/tasks for title resolution
- reference.md: entity detail page URL scheme documented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 22:35:35 +02:00
parent acb30978cd
commit b28298a2ec
14 changed files with 748 additions and 15 deletions

View File

@@ -0,0 +1,41 @@
---
title: Workstream
---
```js
import {API} from "../components/config.js";
import {fieldRow} from "../components/field-help.js";
```
```js
const wsId = observable.params.id;
const raw = await fetch(`${API}/workstreams/${wsId}`)
.then(r => r.ok ? r.json() : r.json().then(e => ({error: e.detail ?? `HTTP ${r.status}`})))
.catch(e => ({error: String(e)}));
```
```js
if (raw.error) {
display(html`<div style="color:red;padding:1rem">⚠️ ${raw.error}</div>`);
} else {
const name = raw.title || raw.slug || wsId;
const shortName = name.length > 60 ? name.slice(0, 60) + "…" : name;
display(html`<h1 style="font-size:1.1rem;margin-bottom:0.25rem">Workstream · <em>${shortName}</em></h1>`);
display(html`<p style="margin-top:0"><a href="/workstreams">← Workstreams</a> &nbsp;|&nbsp; <a href="/token-cost">← Token Cost</a></p>`);
const FIELD_ORDER = [
"id","slug","title","status","topic_id","repo_id","repo_goal_id",
"created_at","updated_at",
];
const rows = FIELD_ORDER.map(k => fieldRow(k, raw[k] ?? null));
for (const k of Object.keys(raw)) {
if (!FIELD_ORDER.includes(k)) rows.push(fieldRow(k, raw[k]));
}
display(html`<table style="border-collapse:collapse;width:100%;max-width:640px">
<colgroup><col style="width:160px"><col></colgroup>
<tbody>${rows}</tbody>
</table>`);
}
```