From 8137c98a1f9b61cd6f158cbe6ab8c62cc8481901 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 3 Jun 2026 09:54:24 +0200 Subject: [PATCH] Add WSJF triage dashboard review page --- dashboard/observablehq.config.js | 2 + dashboard/src/components/wsjf-triage.js | 216 ++++++++++ dashboard/src/decisions.md | 1 + dashboard/src/docs/workstreams.md | 5 + dashboard/src/docs/wsjf-triage.md | 57 +++ dashboard/src/reference.md | 1 + dashboard/src/wsjf-triage.md | 398 ++++++++++++++++++ dashboard/test/wsjf-triage.test.mjs | 117 +++++ .../STATE-WP-0053-wsjf-triage-review-page.md | 16 +- 9 files changed, 805 insertions(+), 8 deletions(-) create mode 100644 dashboard/src/components/wsjf-triage.js create mode 100644 dashboard/src/docs/wsjf-triage.md create mode 100644 dashboard/src/wsjf-triage.md create mode 100644 dashboard/test/wsjf-triage.test.mjs diff --git a/dashboard/observablehq.config.js b/dashboard/observablehq.config.js index 490de96..e72fdba 100644 --- a/dashboard/observablehq.config.js +++ b/dashboard/observablehq.config.js @@ -71,6 +71,7 @@ export default { { name: "Interventions", path: "/interventions" }, { name: "Tasks", path: "/tasks" }, { name: "UI Feedback", path: "/ui-feedback" }, + { name: "WSJF Triage", path: "/wsjf-triage" }, ], }, // ── Reference (always last) ─────────────────────────────────────────────── @@ -110,6 +111,7 @@ export default { { name: "Workstream Health", path: "/docs/workstream-health-index" }, { name: "Workstream Lifecycle", path: "/docs/workstream-lifecycle" }, { name: "Workstreams", path: "/docs/workstreams" }, + { name: "WSJF Triage", path: "/docs/wsjf-triage" }, ], }, ], diff --git a/dashboard/src/components/wsjf-triage.js b/dashboard/src/components/wsjf-triage.js new file mode 100644 index 0000000..20210f2 --- /dev/null +++ b/dashboard/src/components/wsjf-triage.js @@ -0,0 +1,216 @@ +export const ACTION_META = { + "work-next": { + tone: "good", + label: "work-next", + description: "Best next executable task.", + }, + "close-out": { + tone: "good", + label: "close-out", + description: "Finish closure review and mark done when appropriate.", + }, + revisit: { + tone: "warn", + label: "revisit", + description: "Re-read and refresh before execution.", + }, + split: { + tone: "warn", + label: "split", + description: "Break an oversized workplan into smaller plans.", + }, + park: { + tone: "muted", + label: "park", + description: "Move out of active focus.", + }, + "needs-human": { + tone: "bad", + label: "needs-human", + description: "Human decision or approval needed.", + }, + "needs-cross-agent": { + tone: "bad", + label: "needs-cross-agent", + description: "Another repo or agent is the right owner.", + }, + "needs-consistency-sync": { + tone: "bad", + label: "needs-consistency-sync", + description: "File, DB, or index state should be reconciled.", + }, +}; + +const WORKPLAN_ID_RE = /([a-z0-9]+-wp-\d+[a-z]?)/i; +const ADHOC_ID_RE = /(adhoc-\d{4}-\d{2}-\d{2})/i; + +export function normalizeCandidate(value) { + return String(value ?? "") + .trim() + .replace(/\.md$/i, "") + .replace(/^workplans\//i, "") + .replace(/^archived\//i, "") + .replace(/_/g, "-") + .replace(/\s+/g, "-") + .toLowerCase(); +} + +function filenameStem(filename) { + const base = String(filename ?? "").split("/").pop() ?? ""; + return base.replace(/\.md$/i, "").replace(/^\d{6}-/, ""); +} + +export function candidateKeysForWorkplan(item = {}) { + const stem = filenameStem(item.filename ?? item.relative_path); + const keys = new Set(); + const normalizedStem = normalizeCandidate(stem); + if (normalizedStem) keys.add(normalizedStem); + + const idMatch = stem.match(WORKPLAN_ID_RE) ?? stem.match(ADHOC_ID_RE); + if (idMatch) { + const idKey = normalizeCandidate(idMatch[1]); + keys.add(idKey); + + const suffix = normalizeCandidate(stem.slice(idMatch.index + idMatch[1].length).replace(/^-/, "")); + if (suffix) keys.add(suffix); + } + + return [...keys].filter(Boolean); +} + +export function buildCandidateIndex(workplanIndex = {}) { + const byCandidate = new Map(); + const workstreams = workplanIndex.workstreams ?? {}; + for (const [id, item] of Object.entries(workstreams)) { + const resolved = {id, ...item}; + byCandidate.set(normalizeCandidate(id), resolved); + for (const key of candidateKeysForWorkplan(item)) { + if (!byCandidate.has(key)) byCandidate.set(key, resolved); + } + } + return byCandidate; +} + +export function resolveCandidate(candidate, candidateIndex) { + if (!candidateIndex) return null; + const key = normalizeCandidate(candidate); + return candidateIndex.get(key) ?? null; +} + +export function actionMeta(action) { + return ACTION_META[normalizeCandidate(action)] ?? { + tone: "muted", + label: String(action ?? "unknown"), + description: "Unrecognized recommendation action.", + }; +} + +export function normalizeTriageReports(events = []) { + return events + .map((event, index) => { + const detail = event?.detail ?? {}; + const report = detail.report ?? {}; + const recommendations = Array.isArray(report.recommendations) + ? report.recommendations.map((rec, recIndex) => ({ + rank: recIndex + 1, + candidate: String(rec?.candidate ?? "").trim(), + action: normalizeCandidate(rec?.action) || "unknown", + confidence: String(rec?.confidence ?? "").trim() || "unknown", + why: String(rec?.why ?? "").trim(), + })) + : []; + const scheduledFor = detail.scheduled_for ?? null; + const runId = detail.activity_core_run_id ?? null; + const dateSource = scheduledFor ?? event?.created_at ?? ""; + const date = String(dateSource).slice(0, 10) || null; + + return { + id: String(event?.id ?? `${event?.created_at ?? "report"}-${index}`), + created_at: event?.created_at ?? null, + date, + summary: String(report.summary ?? event?.summary ?? "").trim(), + recommendations, + scheduled_for: scheduledFor, + activity_core_run_id: runId, + instruction_id: detail.instruction_id ?? null, + activity_id: detail.activity_id ?? null, + author: event?.author ?? null, + memory_path: detail.memory_path ?? detail.working_memory_path ?? deriveMemoryPath(date, runId), + }; + }) + .sort((a, b) => String(b.created_at ?? "").localeCompare(String(a.created_at ?? ""))); +} + +export function deriveMemoryPath(date, runId) { + if (!date || !runId) return null; + return `the-custodian/memory/working/daily-triage-${date}-${String(runId).slice(0, 8)}.md`; +} + +export function truncateSummary(summary, maxLength = 120) { + const text = String(summary ?? "").trim(); + if (text.length <= maxLength) return text; + return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`; +} + +export function topAction(recommendations = []) { + const counts = new Map(); + for (const rec of recommendations) { + const action = normalizeCandidate(rec?.action) || "unknown"; + counts.set(action, (counts.get(action) ?? 0) + 1); + } + const [action, count] = [...counts.entries()].sort((a, b) => { + const countCompare = b[1] - a[1]; + return countCompare || a[0].localeCompare(b[0]); + })[0] ?? []; + return action ? {action, count} : null; +} + +export function formatActionCount(row) { + return row ? `${row.action} x${row.count}` : "-"; +} + +export function isWithinDays(iso, days, now = new Date()) { + if (!iso) return false; + const ts = new Date(iso).getTime(); + if (Number.isNaN(ts)) return false; + return now.getTime() - ts <= days * 24 * 60 * 60 * 1000; +} + +export function buildPatternRows(reports = []) { + const rows = new Map(); + for (const report of reports) { + for (const rec of report.recommendations ?? []) { + const key = normalizeCandidate(rec.candidate); + if (!key) continue; + const row = rows.get(key) ?? { + candidate: rec.candidate, + candidateKey: key, + count: 0, + actionCounts: new Map(), + }; + row.count += 1; + const action = normalizeCandidate(rec.action) || "unknown"; + row.actionCounts.set(action, (row.actionCounts.get(action) ?? 0) + 1); + rows.set(key, row); + } + } + + return [...rows.values()] + .map(row => { + const top = [...row.actionCounts.entries()].sort((a, b) => { + const countCompare = b[1] - a[1]; + return countCompare || a[0].localeCompare(b[0]); + })[0]; + return { + candidate: row.candidate, + candidateKey: row.candidateKey, + count: row.count, + action: top?.[0] ?? "unknown", + actionCount: top?.[1] ?? 0, + }; + }) + .sort((a, b) => { + const countCompare = b.count - a.count; + return countCompare || a.candidateKey.localeCompare(b.candidateKey); + }); +} diff --git a/dashboard/src/decisions.md b/dashboard/src/decisions.md index efba13a..e23c8f5 100644 --- a/dashboard/src/decisions.md +++ b/dashboard/src/decisions.md @@ -172,6 +172,7 @@ withDocHelp(_liveEl, "/docs/live-data"); const _h1 = document.querySelector("#observablehq-main h1"); if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/decisions"); } +display(html`

Daily WSJF triage surfaces recurring decision and human-approval recommendations.

`); // ── Inject into TOC sidebar: KPI first (prepend → bottom), live last (→ top) ─ const _toc = document.querySelector("#observablehq-toc"); diff --git a/dashboard/src/docs/workstreams.md b/dashboard/src/docs/workstreams.md index b99bc46..eb65168 100644 --- a/dashboard/src/docs/workstreams.md +++ b/dashboard/src/docs/workstreams.md @@ -10,6 +10,11 @@ belongs to exactly one project domain. The Workstreams page gives you a filtered, visual overview of active work, derived blocked state, and the dependency graph between workstreams. +The [Daily WSJF Triage](/wsjf-triage) page is a companion review surface for +activity-core's daily recommendations. It links recommendation candidates back +to workstream detail pages when the candidate can be resolved through the +workplan index. + --- ## Workstation Distribution chart diff --git a/dashboard/src/docs/wsjf-triage.md b/dashboard/src/docs/wsjf-triage.md new file mode 100644 index 0000000..88a9326 --- /dev/null +++ b/dashboard/src/docs/wsjf-triage.md @@ -0,0 +1,57 @@ +--- +title: WSJF Triage +--- + +# WSJF Triage + +The Daily WSJF Triage page reviews the append-only reports produced by the +Custodian daily triage runner. The producer is tracked by `CUST-WP-0044`; the +dashboard page is only a read surface for `daily_triage` progress events. + +The page loads the latest reports from: + +```text +GET /progress/?event_type=daily_triage&limit=14 +``` + +Each event carries the report under `detail.report`, with a summary and a list +of recommendations. Candidate values are resolved through +`/workstreams/workplan-index` so file-backed workplans can link to their +workstream detail pages. + +## How to read recommendations + +Recommendations are advisory, not authoritative. They help decide where to look +first, but they do not change workplan status, task status, ownership, or +priority. Acting on a recommendation still belongs to the operator, the +workplan owner, or a follow-up implementation workplan. + +Confidence labels mean: + +| Confidence | Meaning | +|---|---| +| high | State Hub summary, workplan file, and recent progress agree | +| medium | Summary data is supported by at least one corroborating source | +| low | The signal is stale, incomplete, or mostly inferred | + +## Action vocabulary + +| Action | Meaning | +|---|---| +| `work-next` | Best next executable task | +| `close-out` | Finish closure review and mark done when appropriate | +| `revisit` | Re-read and refresh before execution | +| `split` | Break an oversized workplan into smaller plans | +| `park` | Move out of active focus | +| `needs-human` | Human decision or approval needed | +| `needs-cross-agent` | Another repo or agent is the right owner | +| `needs-consistency-sync` | File, DB, or index state should be reconciled | + +## Pattern view + +The pattern table aggregates recommendations in the loaded 14-day window. It is +useful for spotting recurring human gates, stale revisit signals, or workstreams +that keep surfacing as the next best piece of work. + +No write controls live on this page. It is intentionally a review page so the +daily runner remains a focus surface, not an execution loop. diff --git a/dashboard/src/reference.md b/dashboard/src/reference.md index 070c99a..e5118d4 100644 --- a/dashboard/src/reference.md +++ b/dashboard/src/reference.md @@ -38,6 +38,7 @@ convention used in the Custodian State Hub. | [Todo](/docs/todo) | Internal/Ecosystem/Third-party classification, data sources | | [Workstream Health](/docs/workstream-health-index) | WHI formula, six base metrics, per-domain breakdown | | [Workstreams](/docs/workstreams) | Workstream statuses, dependency edges, WHI KPI card | +| [WSJF Triage](/docs/wsjf-triage) | Daily triage reports, action vocabulary, advisory review workflow | --- diff --git a/dashboard/src/wsjf-triage.md b/dashboard/src/wsjf-triage.md new file mode 100644 index 0000000..221ef3e --- /dev/null +++ b/dashboard/src/wsjf-triage.md @@ -0,0 +1,398 @@ +--- +title: Daily WSJF Triage +--- + +```js +import {POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; +import {injectTocTop} from "./components/toc-sidebar.js"; +import {withDocHelp} from "./components/doc-overlay.js"; +import { + ACTION_META, + actionMeta, + buildCandidateIndex, + buildPatternRows, + isWithinDays, + normalizeTriageReports, + resolveCandidate, + topAction, + truncateSummary, +} from "./components/wsjf-triage.js"; +``` + +```js +const triageState = (async function*() { + let failures = 0; + while (true) { + let events = [], workplanIndex = {workstreams: {}}, ok = false; + try { + const [reportsResp, indexResp] = await Promise.all([ + apiFetch("/progress/?event_type=daily_triage&limit=14"), + apiFetch("/workstreams/workplan-index"), + ]); + ok = reportsResp.ok && indexResp.ok; + events = reportsResp.ok ? await reportsResp.json() : []; + workplanIndex = indexResp.ok ? await indexResp.json() : {workstreams: {}}; + } catch {} + failures = ok ? 0 : failures + 1; + yield {events, workplanIndex, ok, ts: new Date()}; + await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); + } +})(); +``` + +```js +const reports = normalizeTriageReports(triageState.events ?? []); +const candidateIndex = buildCandidateIndex(triageState.workplanIndex ?? {workstreams: {}}); +const _ok = triageState.ok ?? false; +const _ts = triageState.ts; +const latestReport = reports[0] ?? null; +``` + +# Daily WSJF Triage + +```js +function fmtDateTime(iso) { + if (!iso) return "-"; + const d = new Date(iso); + return Number.isNaN(d.getTime()) ? String(iso) : d.toLocaleString(); +} + +function fmtDate(iso) { + if (!iso) return "-"; + const d = new Date(iso); + return Number.isNaN(d.getTime()) ? String(iso).slice(0, 10) : d.toLocaleDateString(undefined, {year: "numeric", month: "short", day: "numeric"}); +} + +function candidateNode(candidate, index) { + const resolved = resolveCandidate(candidate, index); + return resolved + ? html`${candidate}` + : html`${candidate || "-"}`; +} + +function actionBadge(action, extraText = "") { + const meta = actionMeta(action); + return html`${meta.label}${extraText}`; +} + +function recommendationTable(report, index) { + const recommendations = report.recommendations ?? []; + if (recommendations.length === 0) { + return html`

No recommendations were recorded for this report.

`; + } + return html`
+ + + ${recommendations.map(rec => html` + + + + + + `)} +
#CandidateActionConfidenceWhy
${rec.rank}${candidateNode(rec.candidate, index)}${actionBadge(rec.action)}${rec.confidence}${rec.why || "-"}
+
+ ${Object.values(ACTION_META).map(meta => html`${actionBadge(meta.label)} ${meta.description}`)} +
+
`; +} + +function reportMetadata(report) { + return html`
+
Scheduled for${fmtDateTime(report.scheduled_for)}
+
Created${fmtDateTime(report.created_at)}
+
Instruction${report.instruction_id ?? "-"}
+
Activity run${report.activity_core_run_id ?? "-"}
+
Activity${report.activity_id ?? "-"}
+
Memory note${report.memory_path ?? "-"}
+
`; +} + +function renderReportDetail(report, index) { + return html`
+
+

Report Detail

+ ${fmtDate(report.scheduled_for ?? report.created_at)} +
+
+

Summary

+

${report.summary || "-"}

+
+
+

Recommendations

+ ${recommendationTable(report, index)} +
+
+

Run Metadata

+ ${reportMetadata(report)} +
+
`; +} + +function renderPatterns(reports, index) { + const windowReports = reports.filter(report => isWithinDays(report.created_at, 14)); + const rows = buildPatternRows(windowReports); + return html`
+
+

Patterns

+ Last 14 days +
+ ${rows.length === 0 + ? html`

No repeated recommendations are visible in the loaded 14-day window.

` + : html` + + ${rows.map(row => html` + + + + `)} +
WorkstreamTimes RecommendedMost Frequent Action
${candidateNode(row.candidate, index)}${row.count} / ${Math.max(1, windowReports.length)} reports${actionBadge(row.action, ` x${row.actionCount}`)}
`} +
`; +} + +function renderExplorer(reports, index) { + const root = html`
`; + const detail = html`
`; + const tableBody = html``; + const rows = []; + let selectedId = reports[0]?.id; + + function selectReport(report, {scroll = true} = {}) { + selectedId = report.id; + for (const row of rows) { + row.classList.toggle("is-selected", row.dataset.reportId === selectedId); + row.setAttribute("aria-selected", row.dataset.reportId === selectedId ? "true" : "false"); + } + detail.replaceChildren(renderReportDetail(report, index)); + if (scroll) detail.scrollIntoView({behavior: "smooth", block: "start"}); + } + + for (const report of reports) { + const top = topAction(report.recommendations); + const row = html` + ${fmtDate(report.scheduled_for ?? report.created_at)} + ${truncateSummary(report.summary)} + ${report.recommendations.length} + ${top ? actionBadge(top.action, ` x${top.count}`) : "-"} + `; + row.addEventListener("click", () => selectReport(report)); + row.addEventListener("keydown", event => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + selectReport(report); + } + }); + rows.push(row); + } + tableBody.append(...rows); + + root.append( + html`
+
+

Recent Reports

+ ${reports.length} loaded +
+ + + ${tableBody} +
DateSummary# RecsTop Action
+
`, + detail, + renderPatterns(reports, index), + ); + + if (reports[0]) selectReport(reports[0], {scroll: false}); + return root; +} +``` + +```js +const _liveEl = html`
+ + ${_ok + ? `Live - updated ${_ts?.toLocaleTimeString()} - ${reports.length} triage reports` + : html`Offline - run: make api`} +
`; +withDocHelp(_liveEl, "/docs/live-data"); +injectTocTop("live-indicator", _liveEl); + +const _h1 = document.querySelector("#observablehq-main h1"); +if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/wsjf-triage"); } + +display(html`

Daily State Hub triage from activity-core. Recommendations are advisory; the operator and workplan owners decide what to act on.

`); +display(html`
+ Last updated + ${latestReport ? fmtDateTime(latestReport.created_at) : "No daily_triage events yet"} +
`); + +if (reports.length === 0) { + const empty = html`
+

No daily triage reports yet.

+

The next run is scheduled for 07:20 Europe/Berlin (activity-core daily-statehub-wsjf-triage).

+
`; + withDocHelp(empty, "/docs/wsjf-triage"); + display(empty); +} else { + display(renderExplorer(reports, candidateIndex)); +} +``` + + diff --git a/dashboard/test/wsjf-triage.test.mjs b/dashboard/test/wsjf-triage.test.mjs new file mode 100644 index 0000000..5e4df62 --- /dev/null +++ b/dashboard/test/wsjf-triage.test.mjs @@ -0,0 +1,117 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + buildCandidateIndex, + buildPatternRows, + candidateKeysForWorkplan, + formatActionCount, + normalizeTriageReports, + resolveCandidate, + topAction, + truncateSummary, +} from "../src/components/wsjf-triage.js"; + +test("candidate keys resolve workplan ids and filename slugs", () => { + assert.deepEqual( + candidateKeysForWorkplan({filename: "CUST-WP-0003-whi-kpi-card.md"}).sort(), + ["cust-wp-0003", "cust-wp-0003-whi-kpi-card", "whi-kpi-card"], + ); + assert.deepEqual( + candidateKeysForWorkplan({filename: "260501-CUST-WP-0028-e2e-sandbox-framework.md"}).sort(), + ["cust-wp-0028", "cust-wp-0028-e2e-sandbox-framework", "e2e-sandbox-framework"], + ); +}); + +test("candidate index resolves daily report candidates to workstream ids", () => { + const index = buildCandidateIndex({ + workstreams: { + "d9d9a3ec-f736-4041-beac-bb92c7ad314e": { + filename: "CUST-WP-0045-activity-core-daily-triage-runner.md", + }, + "9cc32158-2f5c-4ef6-9713-aacce4623d5e": { + filename: "CUST-WP-0003-whi-kpi-card.md", + }, + "36162ff0-9b47-47c4-8602-56767f9b7a1c": { + filename: "ADHOC-2026-06-01.md", + }, + }, + }); + + assert.equal(resolveCandidate("cust-wp-0045", index).id, "d9d9a3ec-f736-4041-beac-bb92c7ad314e"); + assert.equal(resolveCandidate("whi-kpi-card", index).id, "9cc32158-2f5c-4ef6-9713-aacce4623d5e"); + assert.equal(resolveCandidate("adhoc-2026-06-01", index).id, "36162ff0-9b47-47c4-8602-56767f9b7a1c"); + assert.equal(resolveCandidate("missing-wp-0001", index), null); +}); + +test("triage events normalize reports and action summaries", () => { + const [report] = normalizeTriageReports([ + { + id: "935244fa-b438-488c-a11a-42e1a84e3d59", + summary: "event summary", + created_at: "2026-06-02T12:52:14.460214Z", + author: "activity-core", + detail: { + scheduled_for: "2026-06-02T12:52:01.690214+00:00", + instruction_id: "daily-triage-report", + activity_core_run_id: "f9b97749-c1d0-5746-ab18-89932bef47c1", + report: { + summary: "daily summary", + recommendations: [ + {candidate: "cust-wp-0045", action: "work-next", confidence: "high", why: "runner"}, + {candidate: "cust-wp-0046", action: "needs-human", confidence: "high", why: "blocked"}, + {candidate: "whi-kpi-card", action: "work-next", confidence: "medium", why: "health"}, + ], + }, + }, + }, + ]); + + assert.equal(report.summary, "daily summary"); + assert.equal(report.date, "2026-06-02"); + assert.equal(report.recommendations.length, 3); + assert.equal(report.memory_path, "the-custodian/memory/working/daily-triage-2026-06-02-f9b97749.md"); + assert.deepEqual(topAction(report.recommendations), {action: "work-next", count: 2}); + assert.equal(formatActionCount(topAction(report.recommendations)), "work-next x2"); +}); + +test("pattern rows aggregate repeated candidates by action", () => { + const reports = normalizeTriageReports([ + { + id: "one", + created_at: "2026-06-03T06:00:00Z", + detail: { + report: { + recommendations: [ + {candidate: "cust-wp-0046", action: "needs-human"}, + {candidate: "cust-wp-0045", action: "work-next"}, + ], + }, + }, + }, + { + id: "two", + created_at: "2026-06-02T06:00:00Z", + detail: { + report: { + recommendations: [ + {candidate: "cust-wp-0046", action: "needs-human"}, + {candidate: "cust-wp-0046", action: "revisit"}, + ], + }, + }, + }, + ]); + + const [first, second] = buildPatternRows(reports); + assert.equal(first.candidateKey, "cust-wp-0046"); + assert.equal(first.count, 3); + assert.equal(first.action, "needs-human"); + assert.equal(first.actionCount, 2); + assert.equal(second.candidateKey, "cust-wp-0045"); +}); + +test("summary truncation keeps compact table rows", () => { + assert.equal(truncateSummary("short", 120), "short"); + assert.equal(truncateSummary("abcdefghijklmnopqrstuvwxyz", 10), "abcdefg..."); +}); diff --git a/workplans/STATE-WP-0053-wsjf-triage-review-page.md b/workplans/STATE-WP-0053-wsjf-triage-review-page.md index 5c26018..e25633b 100644 --- a/workplans/STATE-WP-0053-wsjf-triage-review-page.md +++ b/workplans/STATE-WP-0053-wsjf-triage-review-page.md @@ -4,11 +4,11 @@ type: workplan title: "WSJF Triage Review Page (Workstreams section)" domain: custodian repo: state-hub -status: proposed +status: finished owner: custodian topic_slug: custodian created: "2026-06-02" -updated: "2026-06-02" +updated: "2026-06-03" state_hub_workstream_id: "0cca23a9-9640-491a-92db-6414db891019" --- @@ -182,7 +182,7 @@ dashboard); fall back to plain text if the slug is not indexed. ```task id: STATE-WP-0053-T01 -status: todo +status: done priority: medium state_hub_task_id: "6d33aeed-03d2-4a04-95f4-d28fe35e22c7" ``` @@ -198,7 +198,7 @@ state in a dev preview. ```task id: STATE-WP-0053-T02 -status: todo +status: done priority: medium depends_on: [STATE-WP-0053-T01] state_hub_task_id: "b3e569df-861f-4a65-8aa5-b011547e1fdb" @@ -215,7 +215,7 @@ in the table, summary truncated to ~120 characters. ```task id: STATE-WP-0053-T03 -status: todo +status: done priority: medium depends_on: [STATE-WP-0053-T02] state_hub_task_id: "fcbaca4d-3186-4192-aa79-a2ebb12074ed" @@ -233,7 +233,7 @@ workstream detail page on the existing dashboard. ```task id: STATE-WP-0053-T04 -status: todo +status: done priority: low depends_on: [STATE-WP-0053-T03] state_hub_task_id: "45441829-6f01-4b53-8ca7-f4d6f644f77c" @@ -252,7 +252,7 @@ and the legend matches the action vocabulary documented in ```task id: STATE-WP-0053-T05 -status: todo +status: done priority: medium depends_on: [STATE-WP-0053-T02] state_hub_task_id: "8403b3b5-9f37-4ae7-a141-6fc7afbe8053" @@ -269,7 +269,7 @@ three or more days surfaces near the top of the pattern table. ```task id: STATE-WP-0053-T06 -status: todo +status: done priority: low depends_on: [STATE-WP-0053-T05] state_hub_task_id: "ce0abbbd-9418-4016-9679-3b3abe0efd6b"