generated from coulomb/repo-seed
feat(dashboard): Interventions page improvements and action-confirm modal
- Move Interventions under Workstreams in the navigator - Add action-confirm.js: shared modal component for actions requiring a mandatory comment (survives live-poll re-renders, unlike inline DOM mutation) - Wire action-confirm into Interventions (Mark done) and Decisions (Resolve) - Fix Interventions completed section: fetch all tasks and filter client-side so resolved interventions (needs_human=false) still appear under Completed - Add docs/interventions.md help page with ? button on the h1 - Replace all hardcoded "Bernd" with "human" across dashboard src and docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,13 +8,13 @@ const POLL = 15_000;
|
||||
```
|
||||
|
||||
```js
|
||||
// Live poll: human-flagged tasks + workstreams + topics
|
||||
// Live poll: all tasks (filtered client-side) + workstreams + topics
|
||||
const interventionState = (async function*() {
|
||||
while (true) {
|
||||
let tasks = [], wsMap = {}, ok = false;
|
||||
try {
|
||||
const [rt, rw, rto, rr] = await Promise.all([
|
||||
fetch(`${API}/tasks/?needs_human=true`),
|
||||
fetch(`${API}/tasks/?limit=500`),
|
||||
fetch(`${API}/workstreams/`),
|
||||
fetch(`${API}/topics/`),
|
||||
fetch(`${API}/repos/`),
|
||||
@@ -51,8 +51,10 @@ const _ts = interventionState.ts;
|
||||
|
||||
```js
|
||||
const OPEN_STATUSES = new Set(["todo", "in_progress", "blocked"]);
|
||||
const open = tasks.filter(t => OPEN_STATUSES.has(t.status));
|
||||
const closed = tasks.filter(t => !OPEN_STATUSES.has(t.status));
|
||||
// open = currently flagged for human action
|
||||
// closed = previously flagged (intervention_note records the resolution comment)
|
||||
const open = tasks.filter(t => t.needs_human === true);
|
||||
const closed = tasks.filter(t => t.intervention_note && !OPEN_STATUSES.has(t.status) && !t.needs_human);
|
||||
|
||||
// Domain breakdown for top-3
|
||||
const domainCounts = {};
|
||||
@@ -107,15 +109,20 @@ withDocHelp(_liveEl, "/docs/live-data");
|
||||
|
||||
injectTocTop("intervention-kpi-box", _kpiBox);
|
||||
injectTocTop("live-indicator", _liveEl);
|
||||
|
||||
const _h1 = document.querySelector("#observablehq-main h1");
|
||||
if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/interventions"); }
|
||||
```
|
||||
|
||||
Tasks flagged `needs_human=true` — actions only Bernd can take.
|
||||
Tasks flagged `needs_human=true` — actions only a human can take.
|
||||
|
||||
---
|
||||
|
||||
## Open
|
||||
|
||||
```js
|
||||
import {openActionConfirm} from "./components/action-confirm.js";
|
||||
|
||||
const PRIORITY_ORDER = {critical: 0, high: 1, medium: 2, low: 3};
|
||||
const STATUS_ORDER = {blocked: 0, in_progress: 1, todo: 2};
|
||||
|
||||
@@ -127,26 +134,35 @@ function sortTasks(arr) {
|
||||
});
|
||||
}
|
||||
|
||||
async function markDone(taskId) {
|
||||
try {
|
||||
await fetch(`${API}/tasks/${taskId}/`, {
|
||||
method: "PATCH",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({status: "done", needs_human: false, intervention_note: null}),
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function renderCard(t) {
|
||||
const isOpen = OPEN_STATUSES.has(t.status);
|
||||
const borderColor = isOpen ? "#f59e0b" : "#22c55e";
|
||||
|
||||
function onMarkDone() {
|
||||
openActionConfirm({
|
||||
title: "Mark Intervention as Done",
|
||||
entityTitle: t.title,
|
||||
label: "Resolution comment",
|
||||
placeholder: "What was done to resolve this?",
|
||||
confirmLabel: "Mark Done",
|
||||
onConfirm: async (comment) => {
|
||||
const res = await fetch(`${API}/tasks/${t.id}/`, {
|
||||
method: "PATCH",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({status: "done", needs_human: false, intervention_note: comment}),
|
||||
});
|
||||
if (!res.ok) throw new Error(`API error ${res.status}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return html`<div class="intervention-card" style="border-left-color:${borderColor}">
|
||||
<div class="int-card-header">
|
||||
<span class="task-badge task-priority-${t.priority}">${t.priority}</span>
|
||||
<span class="task-status-chip status-chip-${t.status}">${t.status.replace("_", " ")}</span>
|
||||
<span class="task-context">${t.domain}</span>
|
||||
<span class="task-context task-ws-name">${t.workstream_title}</span>
|
||||
${isOpen ? html`<button class="done-btn" onclick=${() => markDone(t.id)}>Mark done</button>` : ""}
|
||||
${isOpen ? html`<button class="done-btn" onclick=${onMarkDone}>Mark done</button>` : ""}
|
||||
</div>
|
||||
<div class="int-action">${t.intervention_note ?? "(no note)"}</div>
|
||||
${t.title !== t.intervention_note ? html`<details class="int-desc"><summary>Task: ${t.title}</summary>${t.description ?? ""}</details>` : ""}
|
||||
|
||||
Reference in New Issue
Block a user