generated from coulomb/repo-seed
dashboard: move Open Workstreams by Domain chart to top of overview page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,77 @@ injectTocTop("live-indicator", _liveEl);
|
|||||||
if (summary.error) display(html`<div class="warning">⚠️ ${summary.error}</div>`);
|
if (summary.error) display(html`<div class="warning">⚠️ ${summary.error}</div>`);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Open Workstreams by Domain
|
||||||
|
|
||||||
|
```js
|
||||||
|
import * as Plot from "npm:@observablehq/plot";
|
||||||
|
|
||||||
|
const topicById = Object.fromEntries((summary.topics ?? []).map(t => [t.id, t.domain]));
|
||||||
|
|
||||||
|
const openWs = (summary.open_workstreams ?? []).map(w => ({
|
||||||
|
title: w.title,
|
||||||
|
domain: topicById[w.topic_id] ?? "unknown",
|
||||||
|
done: w.tasks_done ?? 0,
|
||||||
|
in_progress: w.tasks_in_progress ?? 0,
|
||||||
|
blocked: w.tasks_blocked ?? 0,
|
||||||
|
todo: w.tasks_todo ?? 0,
|
||||||
|
total: w.tasks_total ?? 0,
|
||||||
|
})).sort((a, b) => a.domain.localeCompare(b.domain) || a.title.localeCompare(b.title));
|
||||||
|
|
||||||
|
const statusOrder = ["done", "in progress", "blocked", "todo"];
|
||||||
|
const statusColors = ["#4caf50", "steelblue", "#ff7043", "#e0e0e0"];
|
||||||
|
|
||||||
|
const taskRows = openWs.flatMap(w => [
|
||||||
|
{label: w.title, domain: w.domain, status: "done", count: w.done},
|
||||||
|
{label: w.title, domain: w.domain, status: "in progress", count: w.in_progress},
|
||||||
|
{label: w.title, domain: w.domain, status: "blocked", count: w.blocked},
|
||||||
|
{label: w.title, domain: w.domain, status: "todo", count: w.todo},
|
||||||
|
]).filter(d => d.count > 0);
|
||||||
|
|
||||||
|
// y-axis shows domain (only for the first workstream in each domain group)
|
||||||
|
const yLabels = {};
|
||||||
|
const _seenDomains = new Set();
|
||||||
|
for (const w of openWs) {
|
||||||
|
yLabels[w.title] = _seenDomains.has(w.domain) ? "" : w.domain;
|
||||||
|
_seenDomains.add(w.domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openWs.length === 0) {
|
||||||
|
display(html`<p style="color:gray">No open workstreams.</p>`);
|
||||||
|
} else {
|
||||||
|
display(Plot.plot({
|
||||||
|
y: {label: null, tickSize: 0, domain: openWs.map(w => w.title), tickFormat: t => yLabels[t] ?? ""},
|
||||||
|
x: {label: "Tasks", grid: true},
|
||||||
|
color: {domain: statusOrder, range: statusColors, legend: true},
|
||||||
|
marks: [
|
||||||
|
Plot.barX(taskRows, {y: "label", x: "count", fill: "status", tip: true}),
|
||||||
|
// Workstream title inside the bar
|
||||||
|
Plot.text(openWs.filter(w => w.total > 0), {
|
||||||
|
y: "title", x: 0, dx: 6,
|
||||||
|
text: d => d.title.length > 36 ? d.title.slice(0, 34) + "…" : d.title,
|
||||||
|
textAnchor: "start", fontSize: 10, fill: "#333",
|
||||||
|
}),
|
||||||
|
Plot.text(openWs.filter(w => w.total === 0), {
|
||||||
|
y: "title", x: 0, dx: 6,
|
||||||
|
text: d => `${d.title.length > 24 ? d.title.slice(0, 22) + "…" : d.title} — no tasks yet`,
|
||||||
|
textAnchor: "start", fontSize: 10, fill: "#aaa",
|
||||||
|
}),
|
||||||
|
// "done / total" label after the bar
|
||||||
|
Plot.text(openWs.filter(w => w.total > 0), {
|
||||||
|
y: "title", x: "total",
|
||||||
|
text: d => ` ${d.done}/${d.total}`,
|
||||||
|
dx: 4, textAnchor: "start", fontSize: 11, fill: "gray",
|
||||||
|
}),
|
||||||
|
Plot.ruleX([0]),
|
||||||
|
],
|
||||||
|
marginLeft: 160,
|
||||||
|
marginRight: 70,
|
||||||
|
height: Math.max(80, openWs.length * 44 + 50),
|
||||||
|
width: 700,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@@ -194,77 +265,6 @@ if (regs.length === 0) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Open Workstreams by Domain
|
|
||||||
|
|
||||||
```js
|
|
||||||
import * as Plot from "npm:@observablehq/plot";
|
|
||||||
|
|
||||||
const topicById = Object.fromEntries((summary.topics ?? []).map(t => [t.id, t.domain]));
|
|
||||||
|
|
||||||
const openWs = (summary.open_workstreams ?? []).map(w => ({
|
|
||||||
title: w.title,
|
|
||||||
domain: topicById[w.topic_id] ?? "unknown",
|
|
||||||
done: w.tasks_done ?? 0,
|
|
||||||
in_progress: w.tasks_in_progress ?? 0,
|
|
||||||
blocked: w.tasks_blocked ?? 0,
|
|
||||||
todo: w.tasks_todo ?? 0,
|
|
||||||
total: w.tasks_total ?? 0,
|
|
||||||
})).sort((a, b) => a.domain.localeCompare(b.domain) || a.title.localeCompare(b.title));
|
|
||||||
|
|
||||||
const statusOrder = ["done", "in progress", "blocked", "todo"];
|
|
||||||
const statusColors = ["#4caf50", "steelblue", "#ff7043", "#e0e0e0"];
|
|
||||||
|
|
||||||
const taskRows = openWs.flatMap(w => [
|
|
||||||
{label: w.title, domain: w.domain, status: "done", count: w.done},
|
|
||||||
{label: w.title, domain: w.domain, status: "in progress", count: w.in_progress},
|
|
||||||
{label: w.title, domain: w.domain, status: "blocked", count: w.blocked},
|
|
||||||
{label: w.title, domain: w.domain, status: "todo", count: w.todo},
|
|
||||||
]).filter(d => d.count > 0);
|
|
||||||
|
|
||||||
// y-axis shows domain (only for the first workstream in each domain group)
|
|
||||||
const yLabels = {};
|
|
||||||
const _seenDomains = new Set();
|
|
||||||
for (const w of openWs) {
|
|
||||||
yLabels[w.title] = _seenDomains.has(w.domain) ? "" : w.domain;
|
|
||||||
_seenDomains.add(w.domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openWs.length === 0) {
|
|
||||||
display(html`<p style="color:gray">No open workstreams.</p>`);
|
|
||||||
} else {
|
|
||||||
display(Plot.plot({
|
|
||||||
y: {label: null, tickSize: 0, domain: openWs.map(w => w.title), tickFormat: t => yLabels[t] ?? ""},
|
|
||||||
x: {label: "Tasks", grid: true},
|
|
||||||
color: {domain: statusOrder, range: statusColors, legend: true},
|
|
||||||
marks: [
|
|
||||||
Plot.barX(taskRows, {y: "label", x: "count", fill: "status", tip: true}),
|
|
||||||
// Workstream title inside the bar
|
|
||||||
Plot.text(openWs.filter(w => w.total > 0), {
|
|
||||||
y: "title", x: 0, dx: 6,
|
|
||||||
text: d => d.title.length > 36 ? d.title.slice(0, 34) + "…" : d.title,
|
|
||||||
textAnchor: "start", fontSize: 10, fill: "#333",
|
|
||||||
}),
|
|
||||||
Plot.text(openWs.filter(w => w.total === 0), {
|
|
||||||
y: "title", x: 0, dx: 6,
|
|
||||||
text: d => `${d.title.length > 24 ? d.title.slice(0, 22) + "…" : d.title} — no tasks yet`,
|
|
||||||
textAnchor: "start", fontSize: 10, fill: "#aaa",
|
|
||||||
}),
|
|
||||||
// "done / total" label after the bar
|
|
||||||
Plot.text(openWs.filter(w => w.total > 0), {
|
|
||||||
y: "title", x: "total",
|
|
||||||
text: d => ` ${d.done}/${d.total}`,
|
|
||||||
dx: 4, textAnchor: "start", fontSize: 11, fill: "gray",
|
|
||||||
}),
|
|
||||||
Plot.ruleX([0]),
|
|
||||||
],
|
|
||||||
marginLeft: 160,
|
|
||||||
marginRight: 70,
|
|
||||||
height: Math.max(80, openWs.length * 44 + 50),
|
|
||||||
width: 700,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Registered domains with no workstreams yet — show a getting-started hint
|
// Registered domains with no workstreams yet — show a getting-started hint
|
||||||
const regs = regsState ?? [];
|
const regs = regsState ?? [];
|
||||||
|
|||||||
Reference in New Issue
Block a user