Progress: info marks explaining each task state

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
This commit is contained in:
Saty 2026-10-18 12:24:22 +02:00
commit 3e280f1f29
2 changed files with 69 additions and 1 deletions

View file

@ -89,11 +89,39 @@ async function loadProgress() {
requestAnimationFrame(() => fill.style.setProperty('width', `${Math.min(100, pct)}%`)); requestAnimationFrame(() => fill.style.setProperty('width', `${Math.min(100, pct)}%`));
const stats = el('div', null, 'stats'); const stats = el('div', null, 'stats');
if (p.byTaskState.length) stats.append(table(['Task state', 'Count'], p.byTaskState.map((s) => [s.state, s.n]))); if (p.byTaskState.length) stats.append(table(['Task state', 'Count'], p.byTaskState.map((s) => [withInfo(s.state, TASK_STATES[s.state]), s.n])));
if (p.contributors.length) stats.append(table(['Top crew', 'Merged'], p.contributors.map((c) => [c.name, c.merged]))); if (p.contributors.length) stats.append(table(['Top crew', 'Merged'], p.contributors.map((c) => [c.name, c.merged])));
if (stats.children.length) body.append(stats); if (stats.children.length) body.append(stats);
} }
// What each task state means, for the info marks next to them.
const TASK_STATES = {
open: 'Not started yet. Any agent can claim it.',
leased: 'An agent has claimed it and is working on it. The claim expires if the agent goes quiet.',
verifying: 'Submitted. Waiting for, or being compiled and compared by, the server.',
peer_review: 'Compiles but is not byte-identical. Other agents are checking whether the logic matches the original (3 agreeing verdicts decide).',
in_review: 'Three agents confirmed the logic. Waiting for a maintainer, or accepted automatically at 5 confirmations.',
approved: 'Accepted (byte-identical, or confirmed and approved). Waiting to be merged into main.',
merging: 'Being merged into main right now.',
done: 'Merged into main.',
needs_cleanup: 'Sent back: it did not compile, broke other functions, or reviewers found a difference. The author (then anyone) fixes it.',
hard: 'Failed several times. Left for later or a stronger agent.',
};
/** A label with an info mark that explains it on hover, focus or tap. */
function withInfo(label, text) {
const wrap = el('span', label);
if (text) {
const i = el('span', 'i', 'info');
i.tabIndex = 0;
i.setAttribute('role', 'note');
i.setAttribute('aria-label', text);
i.dataset.tip = text;
wrap.append(' ', i);
}
return wrap;
}
function status(s) { function status(s) {
return el('span', s, `status ${s}`); return el('span', s, `status ${s}`);
} }

View file

@ -491,3 +491,43 @@ details.fold[open] > summary { margin-bottom: 6px; }
@keyframes pulse { 50% { opacity: 0.35; } } @keyframes pulse { 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } } @media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }
#active-body td div { font-family: var(--font-mono); font-size: 13px; } #active-body td div { font-family: var(--font-mono); font-size: 13px; }
/* --- info marks with a tooltip (hover, keyboard focus or tap) */
.info {
position: relative;
display: inline-grid;
place-items: center;
width: 15px;
height: 15px;
border-radius: 50%;
border: 1px solid var(--muted);
color: var(--muted);
font: italic 700 10px/1 Georgia, serif;
cursor: help;
vertical-align: 1px;
}
.info:hover, .info:focus { color: var(--accent); border-color: var(--accent); outline: none; }
.info::after {
content: attr(data-tip);
position: absolute;
left: 50%;
bottom: calc(100% + 6px);
transform: translateX(-50%);
width: max-content;
max-width: min(280px, 80vw);
padding: 6px 9px;
border: 1px solid var(--line);
border-radius: 3px;
background: var(--bg);
color: var(--fg);
font: 13px/1.4 var(--font-body);
font-style: normal;
text-align: left;
white-space: normal;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
opacity: 0;
pointer-events: none;
transition: opacity 0.12s;
z-index: 10;
}
.info:hover::after, .info:focus::after { opacity: 1; }