generated from coulomb/repo-seed
feat: show overview workstream mode counts
This commit is contained in:
@@ -151,43 +151,51 @@ display(html`<div class="warning" style="display:${summary.error ? '' : 'none'}"
|
||||
## Workstreams by Repository
|
||||
|
||||
```js
|
||||
// view() is the idiomatic Observable Framework reactive input:
|
||||
// it displays the element AND returns a reactive value that re-runs dependent blocks.
|
||||
const _chartMode = view(html`<select class="ws-mode-select">
|
||||
<optgroup label="Lifecycle">
|
||||
<option value="ready">ready</option>
|
||||
<option value="active" selected>active</option>
|
||||
<option value="blocked">blocked</option>
|
||||
<option value="proposed">proposed</option>
|
||||
<option value="backlog">backlog</option>
|
||||
<option value="finished">finished</option>
|
||||
<option value="archived">archived</option>
|
||||
</optgroup>
|
||||
<optgroup label="Health">
|
||||
<option value="needs_review">needs review</option>
|
||||
<option value="stalled">stalled</option>
|
||||
</optgroup>
|
||||
<optgroup label="Recently Changed">
|
||||
<option value="1h">last 1 hour</option>
|
||||
<option value="1d">last 24 hours</option>
|
||||
<option value="7d">last 7 days</option>
|
||||
<option value="30d">last 30 days</option>
|
||||
<option value="today">today</option>
|
||||
<option value="week">this week</option>
|
||||
<option value="month">this month</option>
|
||||
</optgroup>
|
||||
</select>`);
|
||||
```
|
||||
|
||||
```js
|
||||
import * as Plot from "npm:@observablehq/plot";
|
||||
|
||||
// ── Filter workstreams by selected mode ───────────────────────────────────────
|
||||
// Lifecycle modes match stored canonical status values.
|
||||
// Health modes are derived labels; they are not stored lifecycle states.
|
||||
// Time modes filter by updated_at / created_at.
|
||||
const _STATUS_MODES = new Set(WORKSTREAM_STATUSES);
|
||||
const _HEALTH_MODES = new Set(["needs_review", "stalled"]);
|
||||
const _MODE_GROUPS = [
|
||||
{
|
||||
label: "Lifecycle",
|
||||
options: [
|
||||
["ready", "ready"],
|
||||
["active", "active"],
|
||||
["blocked", "blocked"],
|
||||
["proposed", "proposed"],
|
||||
["backlog", "backlog"],
|
||||
["finished", "finished"],
|
||||
["archived", "archived"],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Health",
|
||||
options: [
|
||||
["needs_review", "needs review"],
|
||||
["stalled", "stalled"],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Recently Changed",
|
||||
options: [
|
||||
["1h", "last 1 hour"],
|
||||
["1d", "last 24 hours"],
|
||||
["7d", "last 7 days"],
|
||||
["30d", "last 30 days"],
|
||||
["today", "today"],
|
||||
["week", "this week"],
|
||||
["month", "this month"],
|
||||
],
|
||||
},
|
||||
];
|
||||
const _MODE_VALUES = new Set(_MODE_GROUPS.flatMap(group => group.options.map(([value]) => value)));
|
||||
|
||||
function _modeValue(mode) {
|
||||
const value = typeof mode === "string" ? mode : mode?.value;
|
||||
return _MODE_VALUES.has(value) ? value : "active";
|
||||
}
|
||||
|
||||
function _timeCutoff(mode) {
|
||||
const now = new Date();
|
||||
@@ -205,20 +213,54 @@ function _timeCutoff(mode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const _chartWsFiltered = (
|
||||
_STATUS_MODES.has(_chartMode)
|
||||
? wsAll.filter(w => normalizeWorkstreamStatus(w.status) === _chartMode)
|
||||
: _chartMode === "needs_review"
|
||||
? wsAll.filter(needsReviewWorkstream)
|
||||
: _chartMode === "stalled"
|
||||
? wsAll.filter(isStalledWorkstream)
|
||||
: (() => {
|
||||
const since = _timeCutoff(_chartMode);
|
||||
return wsAll.filter(w =>
|
||||
new Date(w.updated_at) >= since || new Date(w.created_at) >= since
|
||||
);
|
||||
})()
|
||||
);
|
||||
function _validDate(value) {
|
||||
const date = new Date(value);
|
||||
return Number.isFinite(date.getTime()) ? date : null;
|
||||
}
|
||||
|
||||
function _workstreamsForMode(mode, rows) {
|
||||
const modeValue = _modeValue(mode);
|
||||
const allRows = Array.isArray(rows) ? rows : [];
|
||||
if (_STATUS_MODES.has(modeValue)) {
|
||||
return allRows.filter(w => normalizeWorkstreamStatus(w.status) === modeValue);
|
||||
}
|
||||
if (modeValue === "needs_review") return allRows.filter(needsReviewWorkstream);
|
||||
if (modeValue === "stalled") return allRows.filter(isStalledWorkstream);
|
||||
|
||||
const since = _timeCutoff(modeValue);
|
||||
if (!since) return allRows.filter(w => normalizeWorkstreamStatus(w.status) === "active");
|
||||
return allRows.filter(w => {
|
||||
const updatedAt = _validDate(w.updated_at);
|
||||
const createdAt = _validDate(w.created_at);
|
||||
return (updatedAt && updatedAt >= since) || (createdAt && createdAt >= since);
|
||||
});
|
||||
}
|
||||
|
||||
const _savedChartMode = _MODE_VALUES.has(globalThis.__stateHubOverviewChartMode)
|
||||
? globalThis.__stateHubOverviewChartMode
|
||||
: "active";
|
||||
const _modeSelect = html`<select
|
||||
class="ws-mode-select"
|
||||
aria-label="Workstream chart mode with matching workstream counts"
|
||||
title="Choose which workstreams to show; counts are matching workstreams"
|
||||
>
|
||||
${_MODE_GROUPS.map(group => html`<optgroup label=${group.label}>
|
||||
${group.options.map(([value, label]) => html`<option value=${value}>${label} (${_workstreamsForMode(value, wsAll).length})</option>`)}
|
||||
</optgroup>`)}
|
||||
</select>`;
|
||||
_modeSelect.value = _savedChartMode;
|
||||
_modeSelect.addEventListener("input", () => {
|
||||
globalThis.__stateHubOverviewChartMode = _modeSelect.value;
|
||||
});
|
||||
|
||||
// view() is the idiomatic Observable Framework reactive input:
|
||||
// it displays the element AND returns a reactive value that re-runs dependent blocks.
|
||||
const _chartMode = _modeValue(view(_modeSelect));
|
||||
const _chartWsFiltered = _workstreamsForMode(_chartMode, wsAll);
|
||||
```
|
||||
|
||||
```js
|
||||
import * as Plot from "npm:@observablehq/plot";
|
||||
|
||||
// Sort by domain, then repository, then most recently updated workstream.
|
||||
// The axis labels show each domain/repo group once.
|
||||
@@ -625,6 +667,7 @@ display(Inputs.table((summary.recent_progress ?? []).map(e => ({
|
||||
<style>
|
||||
.live-indicator { font-size: 0.8rem; color: gray; position: relative; padding: 0.55rem 1.8rem 0.55rem 0.7rem; margin-bottom: 0.75rem; }
|
||||
.ws-mode-bar { margin-bottom: 0.75rem; }
|
||||
.ws-mode-select { min-width: 18rem; max-width: 100%; padding: 0.35rem 0.5rem; border-radius: 4px; border: 1px solid var(--theme-foreground-faint, #ddd); background: var(--theme-background); color: var(--theme-foreground); font: inherit; }
|
||||
.card { background: var(--theme-background-alt); border-radius: 8px; padding: 1rem; }
|
||||
.card.warn { border: 2px solid orange; }
|
||||
.card-link { cursor: pointer; transition: box-shadow 0.15s, transform 0.1s; text-decoration: none; color: inherit; display: block; }
|
||||
|
||||
Reference in New Issue
Block a user