--- title: UI Feedback --- ```js import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js // Ordered workflow steps for dashboard-improvement suggestions const STEPS = ["submitted", "analyse", "plan", "implement", "test", "review", "finished"]; const STEP_LABEL = { submitted: "Submitted", analyse: "Analyse", plan: "Plan", implement: "Implement", test: "Test", review: "Review", finished: "Finished", addressed: "Addressed", resolved: "Resolved", wont_fix: "Won't Fix", }; const STEP_COLOR = { submitted: "#94a3b8", analyse: "#3b82f6", plan: "#8b5cf6", implement: "#f59e0b", test: "#06b6d4", review: "#6366f1", finished: "#16a34a", addressed: "#16a34a", resolved: "#16a34a", wont_fix: "#9ca3af", }; const STEP_HINT = { submitted: "New suggestion — not yet triaged.", analyse: "Investigating the issue and its scope.", plan: "Designing the solution approach.", implement: "Building the change.", test: "Verifying the implementation works correctly.", review: "Awaiting review by the original suggester.", finished: "Shipped and confirmed.", addressed: "Implemented before the current workflow labels existed.", resolved: "Resolved before the current workflow labels existed.", wont_fix: "Decided not to implement.", }; const CLOSED_STATUSES = new Set(["finished", "addressed", "resolved", "wont_fix"]); function nextStep(current) { const i = STEPS.indexOf(current); return i >= 0 && i < STEPS.length - 1 ? STEPS[i + 1] : null; } ``` ```js const feedbackState = (async function*() { let failures = 0; while (true) { let data = [], ok = false; try { const r = await apiFetch("/technical-debt/?debt_type=dashboard-improvement"); ok = r.ok; if (ok) { const items = await r.json(); data = items .filter(t => t.debt_type === "dashboard-improvement") .sort((a, b) => { const st = {submitted:0, analyse:1, plan:2, implement:3, test:4, review:5, finished:6, addressed:7, resolved:8, wont_fix:9, open:10, in_progress:11}; return (st[a.status] ?? 99) - (st[b.status] ?? 99); }); } } catch {} failures = ok ? 0 : failures + 1; yield {data, ok, ts: new Date()}; await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` ```js const data = feedbackState.data ?? []; const _ok = feedbackState.ok ?? false; const _ts = feedbackState.ts; ``` # UI Feedback ```js import {injectTocTop} from "./components/toc-sidebar.js"; import {withDocHelp} from "./components/doc-overlay.js"; const _active = data.filter(t => !CLOSED_STATUSES.has(t.status)); const _finished = data.filter(t => ["finished", "addressed", "resolved"].includes(t.status)); const _wontfix = data.filter(t => t.status === "wont_fix"); const _kpiBox = html`
UI Feedback
active
${_active.length}
finished
${_finished.length}
won't fix
${_wontfix.length}
`; const _liveEl = html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()}` : html`Offline — run: make api`}
`; injectTocTop("fb-kpi-box", _kpiBox); injectTocTop("live-indicator", _liveEl); ``` > Shift+click any widget on any dashboard page to submit a suggestion. > Each suggestion moves through the workflow below; the **Review** step > is for the original suggester to confirm the implementation is correct. ## Active ```js async function _advance(td, newStatus) { return fetch(`${API}/technical-debt/${td.id}`, { method: "PATCH", headers: {"Content-Type": "application/json"}, body: JSON.stringify({status: newStatus}), }); } async function _addNote(td_id, step, content) { return fetch(`${API}/technical-debt/${td_id}/notes/`, { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({step, content, author: "custodian"}), }); } function renderSuggestionCard(t) { const isWorkflow = STEPS.includes(t.status); const next = nextStep(t.status); const color = STEP_COLOR[t.status] ?? "#94a3b8"; const stepIdx = STEPS.indexOf(t.status); // ── Stepper ───────────────────────────────────────────────────────── const stepperEl = html`
${STEPS.map((s, i) => { const done = i < stepIdx; const current = i === stepIdx; return html`
${STEP_LABEL[s]}
${i < STEPS.length - 1 ? html`
` : ""}`; })}
`; // ── Notes ──────────────────────────────────────────────────────────── const notesEl = html`
${(t.notes ?? []).length === 0 ? html`

No notes yet for this step.

` : (t.notes ?? []).map(n => html`
${STEP_LABEL[n.step] ?? n.step} ${n.author ?? "anon"} ${new Date(n.created_at).toLocaleString()}
${n.content}
`)}
`; // ── Add note form ──────────────────────────────────────────────────── const noteTA = html``; const noteBtn = html``; noteBtn.addEventListener("click", async () => { const txt = noteTA.value.trim(); if (!txt) return; noteBtn.disabled = true; const r = await _addNote(t.id, t.status, txt); if (r.ok) { noteTA.value = ""; // Refresh: remove and re-fetch by reloading feedbackState on next poll } noteBtn.disabled = false; }); const noteForm = html`
${noteTA}${noteBtn}
`; // ── Actions ────────────────────────────────────────────────────────── const actionsEl = html`
${t.status === "review" ? html`⬆ Awaiting confirmation from the original suggester` : ""} ${next ? html`` : ""}
`; return html`
${t.td_id ? html`${t.td_id}` : ""} ${STEP_LABEL[t.status] ?? t.status} ${t.location ?? ""}
${t.title.replace(/^UI:\s*/, "")}
${t.description ?? ""}
${stepperEl}
Notes (${(t.notes ?? []).length}) ${notesEl} ${noteForm}
${actionsEl}
`; } if (_active.length === 0) { display(html`

No active suggestions. Shift+click any dashboard widget to submit one.

`); } else { display(html`
${_active.map(renderSuggestionCard)}
`); } ``` ## Finished & Won't Fix ```js const _closed = [..._finished, ..._wontfix]; if (_closed.length === 0) { display(html`

Nothing closed yet.

`); } else { display(html`
${_closed.map(t => { const color = STEP_COLOR[t.status] ?? "#94a3b8"; return html`
${t.td_id ? html`${t.td_id}` : ""} ${STEP_LABEL[t.status] ?? t.status} ${t.location ?? ""}
${t.title.replace(/^UI:\s*/, "")}
${t.description ?? ""}
${(t.notes ?? []).length > 0 ? html`
${t.notes.slice(-2).map(n => html`
${STEP_LABEL[n.step] ?? n.step}
${n.content}
`)}
` : ""}
`; })}
`); } ```