Skip to content

Commit 6d4ef16

Browse files
Kasper Jungeclaude
authored andcommitted
feat: add run overview stats card with pass rate ring and contextual hints
When users select a run, a new overview card appears at the top of the timeline view showing a visual pass rate ring, iteration/pass/fail/timeout counts, and a contextual hint about run health. This gives users an immediate at-a-glance understanding of how their run is performing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3664111 commit 6d4ef16

2 files changed

Lines changed: 248 additions & 0 deletions

File tree

β€Žsrc/ralphify/ui/static/dashboard.cssβ€Ž

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,176 @@ body {
330330
padding: 24px;
331331
}
332332

333+
/* ── Run Overview ────────────────────────────────────────────────── */
334+
335+
.run-overview {
336+
background: var(--surface);
337+
border-radius: var(--radius-lg);
338+
border: 1px solid var(--border);
339+
box-shadow: var(--shadow);
340+
margin-bottom: 20px;
341+
overflow: hidden;
342+
}
343+
344+
.run-overview-header {
345+
display: flex;
346+
align-items: center;
347+
justify-content: space-between;
348+
padding: 20px 24px;
349+
border-bottom: 1px solid var(--border);
350+
}
351+
352+
.run-overview-title {
353+
display: flex;
354+
align-items: center;
355+
gap: 12px;
356+
}
357+
358+
.run-overview-title h2 {
359+
font-size: 17px;
360+
font-weight: 700;
361+
color: var(--text);
362+
}
363+
364+
.run-status-badge {
365+
font-size: 12px;
366+
font-weight: 600;
367+
padding: 4px 12px;
368+
border-radius: 20px;
369+
text-transform: capitalize;
370+
}
371+
372+
.run-status-badge.running {
373+
background: var(--green-bg);
374+
color: #16a34a;
375+
}
376+
377+
.run-status-badge.paused {
378+
background: var(--yellow-bg);
379+
color: #d97706;
380+
}
381+
382+
.run-status-badge.completed {
383+
background: rgba(109, 74, 232, 0.1);
384+
color: var(--primary);
385+
}
386+
387+
.run-status-badge.stopped {
388+
background: var(--bg);
389+
color: var(--text-secondary);
390+
}
391+
392+
.run-status-badge.failed {
393+
background: var(--red-bg);
394+
color: #dc2626;
395+
}
396+
397+
.run-overview-body {
398+
display: flex;
399+
padding: 24px;
400+
gap: 32px;
401+
align-items: center;
402+
}
403+
404+
.run-progress-ring {
405+
position: relative;
406+
width: 96px;
407+
height: 96px;
408+
flex-shrink: 0;
409+
}
410+
411+
.run-progress-ring svg {
412+
transform: rotate(-90deg);
413+
}
414+
415+
.run-progress-ring circle {
416+
fill: none;
417+
stroke-width: 8;
418+
stroke-linecap: round;
419+
}
420+
421+
.run-progress-ring .ring-bg {
422+
stroke: var(--border);
423+
}
424+
425+
.run-progress-ring .ring-pass {
426+
stroke: var(--green);
427+
transition: stroke-dashoffset 0.5s ease;
428+
}
429+
430+
.run-progress-ring .ring-fail {
431+
stroke: var(--red);
432+
transition: stroke-dashoffset 0.5s ease;
433+
}
434+
435+
.run-progress-label {
436+
position: absolute;
437+
inset: 0;
438+
display: flex;
439+
flex-direction: column;
440+
align-items: center;
441+
justify-content: center;
442+
}
443+
444+
.run-progress-pct {
445+
font-size: 22px;
446+
font-weight: 700;
447+
color: var(--text);
448+
line-height: 1;
449+
}
450+
451+
.run-progress-sub {
452+
font-size: 11px;
453+
color: var(--text-secondary);
454+
margin-top: 2px;
455+
}
456+
457+
.run-stats-grid {
458+
display: flex;
459+
gap: 24px;
460+
flex: 1;
461+
}
462+
463+
.run-stat {
464+
display: flex;
465+
flex-direction: column;
466+
gap: 4px;
467+
min-width: 80px;
468+
}
469+
470+
.run-stat-value {
471+
font-size: 28px;
472+
font-weight: 700;
473+
line-height: 1;
474+
font-family: var(--font-mono);
475+
}
476+
477+
.run-stat-value.green { color: #16a34a; }
478+
.run-stat-value.red { color: #dc2626; }
479+
.run-stat-value.yellow { color: #d97706; }
480+
.run-stat-value.primary { color: var(--primary); }
481+
482+
.run-stat-label {
483+
font-size: 12px;
484+
color: var(--text-secondary);
485+
font-weight: 500;
486+
}
487+
488+
.run-overview-hint {
489+
display: flex;
490+
align-items: center;
491+
gap: 8px;
492+
padding: 12px 24px;
493+
background: var(--bg);
494+
border-top: 1px solid var(--border);
495+
font-size: 12px;
496+
color: var(--text-secondary);
497+
}
498+
499+
.run-overview-hint svg {
500+
flex-shrink: 0;
501+
}
502+
333503
/* ── Timeline ────────────────────────────────────────────────────── */
334504

335505
.timeline {

β€Žsrc/ralphify/ui/static/dashboard.jsβ€Ž

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,83 @@ function RunControls({ run }) {
360360
`;
361361
}
362362

363+
// ── Run Overview ────────────────────────────────────────────────────
364+
365+
function RunOverview({ run }) {
366+
const total = run.completed + run.failed;
367+
const passRate = total > 0 ? Math.round((run.completed / total) * 100) : 0;
368+
const timedOut = run.timed_out || 0;
369+
const failedOnly = run.failed - timedOut;
370+
371+
// SVG ring calculations
372+
const radius = 40;
373+
const circumference = 2 * Math.PI * radius;
374+
const passOffset = total > 0 ? circumference * (1 - run.completed / total) : circumference;
375+
const failOffset = total > 0 ? circumference * (1 - run.failed / total) : circumference;
376+
377+
// Hint text based on run state
378+
const isRunning = run.status === 'running';
379+
const isHealthy = run.failed === 0 || passRate >= 80;
380+
const hint = isRunning
381+
? (isHealthy
382+
? 'All looking good β€” your agent is making progress.'
383+
: `Pass rate is ${passRate}%. Check the health sparklines below for stuck checks.`)
384+
: (run.status === 'completed'
385+
? `Run completed with ${passRate}% pass rate across ${total} iterations.`
386+
: `Run ${run.status}. ${total} iterations completed.`);
387+
388+
return html`
389+
<div class="run-overview">
390+
<div class="run-overview-header">
391+
<div class="run-overview-title">
392+
<h2>${run.run_id}</h2>
393+
<span class="run-status-badge ${run.status}">${run.status}</span>
394+
</div>
395+
</div>
396+
<div class="run-overview-body">
397+
<div class="run-progress-ring">
398+
<svg width="96" height="96" viewBox="0 0 96 96">
399+
<circle class="ring-bg" cx="48" cy="48" r="${radius}" />
400+
<circle class="ring-pass" cx="48" cy="48" r="${radius}"
401+
stroke-dasharray="${circumference}"
402+
stroke-dashoffset="${passOffset}" />
403+
</svg>
404+
<div class="run-progress-label">
405+
<span class="run-progress-pct">${total > 0 ? `${passRate}%` : 'β€”'}</span>
406+
<span class="run-progress-sub">pass rate</span>
407+
</div>
408+
</div>
409+
<div class="run-stats-grid">
410+
<div class="run-stat">
411+
<span class="run-stat-value primary">${run.iteration || 0}</span>
412+
<span class="run-stat-label">Iterations</span>
413+
</div>
414+
<div class="run-stat">
415+
<span class="run-stat-value green">${run.completed}</span>
416+
<span class="run-stat-label">Passed</span>
417+
</div>
418+
<div class="run-stat">
419+
<span class="run-stat-value red">${failedOnly > 0 ? failedOnly : 0}</span>
420+
<span class="run-stat-label">Failed</span>
421+
</div>
422+
<div class="run-stat">
423+
<span class="run-stat-value yellow">${timedOut}</span>
424+
<span class="run-stat-label">Timed Out</span>
425+
</div>
426+
</div>
427+
</div>
428+
<div class="run-overview-hint">
429+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
430+
<circle cx="12" cy="12" r="10"/>
431+
<path d="M12 16v-4"/>
432+
<path d="M12 8h.01"/>
433+
</svg>
434+
${hint}
435+
</div>
436+
</div>
437+
`;
438+
}
439+
363440
// ── Timeline view ──────────────────────────────────────────────────
364441

365442
function TimelineView({ run }) {
@@ -369,6 +446,7 @@ function TimelineView({ run }) {
369446
const health = checkHealth.value[run.run_id] || {};
370447

371448
return html`
449+
<${RunOverview} run=${run} />
372450
<${Timeline} iterations=${runIters} selectedIteration=${selectedIter} />
373451
${selected && html`<${IterationPanel} iteration=${selected} />`}
374452
${Object.keys(health).length > 0 && html`<${CheckHealthPanel} health=${health} />`}

0 commit comments

Comments
Β (0)