2026-06-03 09:54:24 +02:00
---
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) {
2026-07-08 16:08:32 +02:00
let events = [], workplanIndex = {workplans: {}}, ok = false;
2026-06-03 09:54:24 +02:00
try {
const [reportsResp, indexResp] = await Promise.all([
apiFetch("/progress/?event_type=daily_triage& limit=14"),
2026-06-04 08:25:31 +02:00
apiFetch("/workplans/index"),
2026-06-03 09:54:24 +02:00
]);
ok = reportsResp.ok & & indexResp.ok;
events = reportsResp.ok ? await reportsResp.json() : [];
2026-07-08 16:08:32 +02:00
workplanIndex = indexResp.ok ? await indexResp.json() : {workplans: {}};
2026-06-03 09:54:24 +02:00
} 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 ?? []);
2026-07-08 16:08:32 +02:00
const candidateIndex = buildCandidateIndex(triageState.workplanIndex ?? {workplans: {}});
2026-06-03 09:54:24 +02:00
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`<a href="/workstreams/${resolved.id}" title=${resolved.filename ?? resolved.id}>${candidate}</a>`
: html`<span>${candidate || "-"}</span>` ;
}
function actionBadge(action, extraText = "") {
const meta = actionMeta(action);
return html`<span class="triage-action triage-action-${meta.tone}" title=${meta.description}>${meta.label}${extraText}</span>` ;
}
function recommendationTable(report, index) {
const recommendations = report.recommendations ?? [];
if (recommendations.length === 0) {
return html`<p class="triage-muted">No recommendations were recorded for this report.</p>` ;
}
return html`< div >
< table class = "triage-table triage-recommendations" >
< thead > < tr > < th > #< / th > < th > Candidate< / th > < th > Action< / th > < th > Confidence< / th > < th > Why< / th > < / tr > < / thead >
< tbody > ${recommendations.map(rec => html`< tr >
< td > ${rec.rank}< / td >
< td > ${candidateNode(rec.candidate, index)}< / td >
< td > ${actionBadge(rec.action)}< / td >
< td > < span class = "triage-confidence" > ${rec.confidence}< / span > < / td >
< td > ${rec.why || "-"}< / td >
< / tr > `)}< / tbody >
< / table >
< div class = "triage-legend" >
${Object.values(ACTION_META).map(meta => html`<span>${actionBadge(meta.label)} ${meta.description}</span>` )}
< / div >
< / div > `;
}
function reportMetadata(report) {
return html`< div class = "triage-metadata" >
< div > < span > Scheduled for< / span > < strong > ${fmtDateTime(report.scheduled_for)}< / strong > < / div >
< div > < span > Created< / span > < strong > ${fmtDateTime(report.created_at)}< / strong > < / div >
< div > < span > Instruction< / span > < strong > ${report.instruction_id ?? "-"}< / strong > < / div >
< div > < span > Activity run< / span > < strong > ${report.activity_core_run_id ?? "-"}< / strong > < / div >
< div > < span > Activity< / span > < strong > ${report.activity_id ?? "-"}< / strong > < / div >
< div > < span > Memory note< / span > < strong > ${report.memory_path ?? "-"}< / strong > < / div >
< / div > `;
}
function renderReportDetail(report, index) {
return html`< div >
< div class = "triage-section-heading" >
< h2 > Report Detail< / h2 >
< span > ${fmtDate(report.scheduled_for ?? report.created_at)}< / span >
< / div >
< section class = "triage-detail-block" >
< h3 > Summary< / h3 >
< p > ${report.summary || "-"}< / p >
< / section >
< section class = "triage-detail-block" >
< h3 > Recommendations< / h3 >
${recommendationTable(report, index)}
< / section >
< section class = "triage-detail-block" >
< h3 > Run Metadata< / h3 >
${reportMetadata(report)}
< / section >
< / div > `;
}
function renderPatterns(reports, index) {
const windowReports = reports.filter(report => isWithinDays(report.created_at, 14));
const rows = buildPatternRows(windowReports);
return html`< section class = "triage-section" >
< div class = "triage-section-heading" >
< h2 > Patterns< / h2 >
< span > Last 14 days< / span >
< / div >
${rows.length === 0
? html`<p class="triage-muted">No repeated recommendations are visible in the loaded 14-day window.</p>`
: html`< table class = "triage-table" >
2026-07-08 16:08:32 +02:00
< thead > < tr > < th > Workplan< / th > < th > Times Recommended< / th > < th > Most Frequent Action< / th > < / tr > < / thead >
2026-06-03 09:54:24 +02:00
< tbody > ${rows.map(row => html`< tr >
< td > ${candidateNode(row.candidate, index)}< / td >
< td > ${row.count} / ${Math.max(1, windowReports.length)} reports< / td >
< td > ${actionBadge(row.action, ` x${row.actionCount}` )}</ td >
< / tr > `)}< / tbody >
< / table > `}
< / section > `;
}
function renderExplorer(reports, index) {
const root = html`<div class="triage-explorer"></div>` ;
const detail = html`<section id="triage-report-detail" class="triage-section"></section>` ;
const tableBody = html`<tbody></tbody>` ;
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`< tr class = "triage-report-row" tabindex = "0" role = "button" data-report-id = ${report.id} aria-selected = "false" >
< td > ${fmtDate(report.scheduled_for ?? report.created_at)}< / td >
< td > ${truncateSummary(report.summary)}< / td >
< td > ${report.recommendations.length}< / td >
< td > ${top ? actionBadge(top.action, ` x${top.count}` ) : "-"}</ td >
< / tr > `;
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`< section class = "triage-section" >
< div class = "triage-section-heading" >
< h2 > Recent Reports< / h2 >
< span > ${reports.length} loaded< / span >
< / div >
< table class = "triage-table triage-reports" >
< thead > < tr > < th > Date< / th > < th > Summary< / th > < th > # Recs< / th > < th > Top Action< / th > < / tr > < / thead >
${tableBody}
< / table >
< / section > `,
detail,
renderPatterns(reports, index),
);
if (reports[0]) selectReport(reports[0], {scroll: false});
return root;
}
```
```js
const _liveEl = html`< div class = "live-indicator" >
< span class = "live-dot" style = "background:${_ok ? " var ( --theme-foreground-focus ) " : " red " } " > < / span >
${_ok
? `Live - updated ${_ts?.toLocaleTimeString()} - ${reports.length} triage reports`
: html`<span style="color:red">Offline - run: <code>make api</code></span>` }
< / div > `;
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"); }
2026-07-06 10:52:49 +02:00
display(html`<p class="triage-subtitle">Daily State Hub triage from activity-core. Recommendations are advisory; the operator and workplan owners decide what to act on. Ranked <a href="/suggestions">suggestion backlog</a> feeds the digest.</p>` );
2026-06-03 09:54:24 +02:00
display(html`< div class = "triage-latest" >
< span > Last updated< / span >
< strong > ${latestReport ? fmtDateTime(latestReport.created_at) : "No daily_triage events yet"}< / strong >
< / div > `);
if (reports.length === 0) {
const empty = html`< div class = "triage-empty" >
< h2 > No daily triage reports yet.< / h2 >
< p > The next run is scheduled for 07:20 Europe/Berlin (activity-core < code > daily-statehub-wsjf-triage< / code > ).< / p >
< / div > `;
withDocHelp(empty, "/docs/wsjf-triage");
display(empty);
} else {
display(renderExplorer(reports, candidateIndex));
}
```
< style >
.live-indicator {
color: gray;
font-size: 0.8rem;
margin-bottom: 0.75rem;
padding: 0.55rem 1.8rem 0.55rem 0.7rem;
position: relative;
}
.live-dot {
border-radius: 50%;
display: inline-block;
height: 0.5rem;
margin-right: 0.35rem;
width: 0.5rem;
}
.triage-subtitle {
color: var(--theme-foreground-muted, #666 );
margin-top: -0.2rem;
max-width: 820px;
}
.triage-latest {
align-items: baseline;
border: 1px solid var(--theme-foreground-faint, #ddd );
border-radius: 6px;
display: inline-flex;
gap: 0.45rem;
margin: 0.2rem 0 1rem;
padding: 0.35rem 0.55rem;
}
.triage-latest span,
.triage-section-heading span,
.triage-metadata span {
color: var(--theme-foreground-muted, #666 );
font-size: 0.72rem;
text-transform: uppercase;
}
.triage-empty {
border: 1px solid var(--theme-foreground-faint, #ddd );
border-radius: 6px;
margin-top: 1rem;
max-width: 760px;
padding: 1rem;
position: relative;
}
.triage-empty h2 {
margin-top: 0;
}
.triage-section {
margin: 1.4rem 0 1.8rem;
}
.triage-section-heading {
align-items: baseline;
display: flex;
gap: 0.65rem;
justify-content: space-between;
margin-bottom: 0.45rem;
}
.triage-section-heading h2 {
margin: 0;
}
.triage-detail-block {
margin: 0.9rem 0 1.2rem;
}
.triage-detail-block h3 {
font-size: 0.95rem;
margin-bottom: 0.35rem;
}
.triage-table {
border-collapse: collapse;
width: 100%;
}
.triage-table th,
.triage-table td {
border-bottom: 1px solid var(--theme-foreground-faint, #ddd );
padding: 0.45rem 0.5rem;
text-align: left;
vertical-align: top;
}
.triage-reports td:nth-child(2),
.triage-recommendations td:nth-child(5) {
max-width: 520px;
}
.triage-report-row {
cursor: pointer;
}
.triage-report-row:hover,
.triage-report-row:focus {
background: var(--theme-background-alt, #f7f7f7 );
outline: none;
}
.triage-report-row.is-selected {
background: color-mix(in srgb, var(--theme-foreground-focus, steelblue) 9%, transparent);
}
.triage-action {
border-radius: 4px;
display: inline-block;
font-size: 0.74rem;
font-weight: 700;
line-height: 1.2;
padding: 0.14rem 0.4rem;
white-space: nowrap;
}
.triage-action-good {
background: #dcfce7 ;
color: #166534 ;
}
.triage-action-warn {
background: #fef3c7 ;
color: #92400e ;
}
.triage-action-muted {
background: #f3f4f6 ;
color: #4b5563 ;
}
.triage-action-bad {
background: #fee2e2 ;
color: #991b1b ;
}
.triage-confidence {
color: var(--theme-foreground-muted, #666 );
font-size: 0.82rem;
}
.triage-legend {
color: var(--theme-foreground-muted, #666 );
display: flex;
flex-wrap: wrap;
gap: 0.45rem 0.8rem;
margin-top: 0.6rem;
}
.triage-legend span {
font-size: 0.76rem;
}
.triage-metadata {
display: grid;
gap: 0.65rem 1rem;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.triage-metadata div {
min-width: 0;
}
.triage-metadata strong {
display: block;
font-size: 0.86rem;
overflow-wrap: anywhere;
}
.triage-muted {
color: var(--theme-foreground-muted, #666 );
}
@media (max-width: 760px) {
.triage-section-heading,
.triage-latest {
align-items: flex-start;
flex-direction: column;
}
.triage-metadata {
grid-template-columns: 1fr;
}
}
< / style >