Complete workplan state model cleanup

This commit is contained in:
2026-05-18 01:31:36 +02:00
parent 98b2cb6484
commit d6522a9a40
42 changed files with 789 additions and 310 deletions

View File

@@ -4,6 +4,13 @@ title: Overview
```js
import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js";
import {
WORKSTREAM_STATUSES,
isClosedWorkstream,
isStalledWorkstream,
needsReviewWorkstream,
normalizeWorkstreamStatus,
} from "./components/workplan-status.js";
```
```js
@@ -52,11 +59,13 @@ const pageState = (async function*() {
const workplan = workplanMap[w.id] ?? {};
return {
...w,
status: normalizeWorkstreamStatus(w.status),
domain: repo?.domain_slug ?? topic?.domain_slug ?? "unknown",
repo_label: repo?.slug ?? workplan.repo_slug ?? "unassigned",
workplan_filename: workplan.filename ?? null,
workplan_relative_path: workplan.relative_path ?? null,
workplan_archived: workplan.archived ?? false,
health_labels: workplan.health_labels ?? [],
href: `./workstreams/${w.id}`,
...(counts[w.id] ?? {done: 0, in_progress: 0, blocked: 0, todo: 0, total: 0}),
};
@@ -126,13 +135,18 @@ display(html`<div class="warning" style="display:${summary.error ? '' : 'none'}"
// 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="By Status">
<option value="active" selected>active</option>
<option value="accepted">accepted</option>
<option value="finished">finished</option>
<optgroup label="Lifecycle">
<option value="ready" selected>ready</option>
<option value="active">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>
<option value="oldies">oldies</option>
</optgroup>
<optgroup label="Recently Changed">
<option value="1h">last 1 hour</option>
@@ -150,12 +164,11 @@ const _chartMode = view(html`<select class="ws-mode-select">
import * as Plot from "npm:@observablehq/plot";
// ── Filter workstreams by selected mode ───────────────────────────────────────
// "active" matches the DB status field directly.
// "accepted" = DB status "completed" (explicitly reviewed and signed off).
// "finished" = no open tasks remaining (derived from task counts).
// "blocked" = has ≥1 blocked task; "stalled" / "oldies" = activity-based.
// 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(["active"]);
const _STATUS_MODES = new Set(WORKSTREAM_STATUSES);
const _HEALTH_MODES = new Set(["needs_review", "stalled"]);
function _timeCutoff(mode) {
const now = new Date();
@@ -175,27 +188,11 @@ function _timeCutoff(mode) {
const _chartWsFiltered = (
_STATUS_MODES.has(_chartMode)
? wsAll.filter(w => w.status === _chartMode)
: _chartMode === "accepted"
? wsAll.filter(w => w.status === "completed")
: _chartMode === "finished"
? wsAll.filter(w => (w.todo ?? 0) + (w.in_progress ?? 0) + (w.blocked ?? 0) === 0)
: _chartMode === "blocked"
? wsAll.filter(w => (w.blocked ?? 0) > 0)
? wsAll.filter(w => normalizeWorkstreamStatus(w.status) === _chartMode)
: _chartMode === "needs_review"
? wsAll.filter(needsReviewWorkstream)
: _chartMode === "stalled"
? wsAll.filter(w => {
const staleAt = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
return new Date(w.updated_at) < staleAt
&& (w.done ?? 0) > 0
&& (w.todo ?? 0) + (w.in_progress ?? 0) + (w.blocked ?? 0) > 0;
})
: _chartMode === "oldies"
? wsAll.filter(w => {
const oldAt = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
return new Date(w.created_at) < oldAt
&& (w.done ?? 0) === 0
&& (w.todo ?? 0) + (w.in_progress ?? 0) + (w.blocked ?? 0) > 0;
})
? wsAll.filter(isStalledWorkstream)
: (() => {
const since = _timeCutoff(_chartMode);
return wsAll.filter(w =>
@@ -215,9 +212,9 @@ const chartWs = [..._chartWsFiltered].sort((a, b) => {
});
// ── Status weight: bold for notable statuses in mixed-status modes ─────────────
// Color is NOT used for status — avoids green-on-green when completed bars fill the row.
const _isTimeBased = !_STATUS_MODES.has(_chartMode);
function _wsWeight(s) { return (s === "accepted" || s === "blocked" || s === "stalled") ? "bold" : "normal"; }
// Color is NOT used for status — avoids green-on-green when finished bars fill the row.
const _isTimeBased = !_STATUS_MODES.has(_chartMode) && !_HEALTH_MODES.has(_chartMode);
function _wsWeight(s) { return (isClosedWorkstream(s) || normalizeWorkstreamStatus(s) === "blocked") ? "bold" : "normal"; }
// ── y-axis: domain/repo label for first workstream per repository only ────────
const _yLabels = {};
@@ -251,10 +248,15 @@ function _wsTitle(d) {
// ── Render ────────────────────────────────────────────────────────────────────
if (chartWs.length === 0) {
const _emptyMsg = {
active: "No active workstreams.", accepted: "No accepted workstreams.",
finished: "No finished workstreams.", blocked: "No blocked workstreams.",
proposed: "No proposed workstreams.",
ready: "No ready workstreams.",
active: "No active workstreams.",
blocked: "No blocked workstreams.",
backlog: "No backlog workstreams.",
finished: "No finished workstreams.",
archived: "No archived workstreams.",
needs_review: "No ready workstreams need review.",
stalled: "No stalled workstreams — everything is moving.",
oldies: "No oldies — all older workstreams have at least one task done.",
"1h": "No workstreams changed in the last hour.",
"1d": "No workstreams changed in the last 24 hours.",
"7d": "No workstreams changed in the last 7 days.",