Skip to content

Commit dde1f78

Browse files
Kasper Jungeclaude
authored andcommitted
feat: redesign iteration panel with rich metadata, card-styled checks, and meaningful run labels
Iteration detail panel now shows duration, exit code, and check count as metadata tags in the header, with a dedicated detail callout block. Check results use colored left-border cards instead of plain list items. Run cards in the sidebar show prompt name as the primary title instead of opaque UUID fragments. Form inputs use sans-serif by default (mono only for code/path fields) per the design system spec. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2126f7a commit dde1f78

2 files changed

Lines changed: 155 additions & 31 deletions

File tree

src/ralphify/ui/static/dashboard.css

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -633,18 +633,51 @@ body {
633633
border-bottom: 1px solid var(--border);
634634
}
635635

636+
.iteration-header-left {
637+
display: flex;
638+
align-items: center;
639+
gap: 10px;
640+
}
641+
636642
.iteration-title {
637643
font-size: 15px;
638644
font-weight: 600;
639645
color: var(--text);
640646
}
641647

648+
.iteration-meta {
649+
display: flex;
650+
align-items: center;
651+
gap: 8px;
652+
}
653+
654+
.iteration-meta-tag {
655+
font-size: 11px;
656+
font-family: var(--font-mono);
657+
font-weight: 500;
658+
padding: 3px 10px;
659+
border-radius: 4px;
660+
background: var(--bg);
661+
color: var(--text-secondary);
662+
display: flex;
663+
align-items: center;
664+
gap: 4px;
665+
}
666+
667+
.iteration-meta-tag svg {
668+
flex-shrink: 0;
669+
opacity: 0.6;
670+
}
671+
642672
.iteration-status {
643673
font-size: 12px;
644674
font-family: var(--font-sans);
645-
font-weight: 500;
646-
padding: 4px 10px;
675+
font-weight: 600;
676+
padding: 4px 12px;
647677
border-radius: 20px;
678+
display: flex;
679+
align-items: center;
680+
gap: 5px;
648681
}
649682

650683
.iteration-status.success { background: var(--green-bg); color: #16a34a; }
@@ -655,6 +688,17 @@ body {
655688
padding: 20px;
656689
}
657690

691+
.iteration-detail {
692+
background: var(--bg);
693+
border: 1px solid var(--border);
694+
border-radius: var(--radius);
695+
padding: 12px 16px;
696+
margin-bottom: 16px;
697+
font-size: 13px;
698+
color: var(--text-secondary);
699+
line-height: 1.6;
700+
}
701+
658702
/* ── Agent output ────────────────────────────────────────────────── */
659703

660704
.agent-output {
@@ -675,25 +719,57 @@ body {
675719
/* ── Check results ───────────────────────────────────────────────── */
676720

677721
.check-results {
678-
margin-top: 8px;
722+
margin-top: 0;
723+
}
724+
725+
.checks-section-title {
726+
font-size: 12px;
727+
font-weight: 600;
728+
text-transform: uppercase;
729+
letter-spacing: 0.5px;
730+
color: var(--text-secondary);
731+
margin-bottom: 10px;
679732
}
680733

681734
.check-result {
682735
display: flex;
683736
align-items: center;
684-
gap: 10px;
685-
padding: 8px 0;
686-
border-bottom: 1px solid var(--border);
737+
gap: 12px;
738+
padding: 10px 14px;
739+
border-radius: var(--radius);
740+
border: 1px solid var(--border);
741+
margin-bottom: 6px;
742+
background: var(--bg);
743+
transition: all 0.15s ease;
744+
border-left: 3px solid var(--border);
687745
}
688746

689747
.check-result:last-child {
690-
border-bottom: none;
748+
margin-bottom: 0;
749+
}
750+
751+
.check-result:hover {
752+
border-color: var(--border-hover);
753+
box-shadow: var(--shadow-sm);
754+
}
755+
756+
.check-result.pass {
757+
border-left-color: var(--green);
758+
}
759+
760+
.check-result.fail {
761+
border-left-color: var(--red);
762+
}
763+
764+
.check-result.timeout {
765+
border-left-color: var(--yellow);
691766
}
692767

693768
.check-icon {
694769
font-size: 14px;
695770
width: 20px;
696771
text-align: center;
772+
flex-shrink: 0;
697773
}
698774
.check-icon.pass { color: #16a34a; }
699775
.check-icon.fail { color: #dc2626; }
@@ -703,12 +779,16 @@ body {
703779
font-weight: 500;
704780
font-size: 13px;
705781
color: var(--text);
782+
flex: 1;
706783
}
707784

708785
.check-detail {
709786
font-size: 11px;
710787
color: var(--text-secondary);
711788
font-family: var(--font-mono);
789+
background: var(--surface);
790+
padding: 2px 8px;
791+
border-radius: 4px;
712792
}
713793

714794
/* ── Check health sparklines ─────────────────────────────────────── */
@@ -1097,10 +1177,14 @@ body {
10971177
border-radius: var(--radius);
10981178
color: var(--text);
10991179
font-size: 13px;
1100-
font-family: var(--font-mono);
1180+
font-family: var(--font-sans);
11011181
transition: all 0.15s ease;
11021182
}
11031183

1184+
.form-input.mono {
1185+
font-family: var(--font-mono);
1186+
}
1187+
11041188
.form-input:focus {
11051189
outline: none;
11061190
border-color: var(--primary);

src/ralphify/ui/static/dashboard.js

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -237,20 +237,18 @@ function RunCard({ run }) {
237237
const isActive = activeRunId.value === run.run_id;
238238
const total = run.completed + run.failed;
239239
const passRate = total > 0 ? (run.completed / total) * 100 : 0;
240-
const shortId = run.run_id.length > 12 ? run.run_id.slice(0, 12) : run.run_id;
240+
const shortId = run.run_id.length > 8 ? run.run_id.slice(0, 8) : run.run_id;
241+
const displayTitle = run.prompt_name || shortId;
241242

242243
return html`
243244
<div class="run-card ${isActive ? 'active' : ''}" onClick=${() => activeRunId.value = run.run_id}>
244245
<div class="run-badge ${run.status}"></div>
245246
<div class="run-card-info">
246-
<div class="run-card-title">${shortId}</div>
247+
<div class="run-card-title">${displayTitle}</div>
247248
<div class="run-card-meta">
248-
iter ${run.iteration || 0}${total > 0 ? ` · ${Math.round(passRate)}% pass` : ` · ${run.status}`}
249+
${run.prompt_name ? shortId + ' · ' : ''}iter ${run.iteration || 0}${total > 0 ? ` · ${Math.round(passRate)}%` : ''}
249250
</div>
250251
</div>
251-
${run.prompt_name && html`
252-
<span class="run-card-tag">${run.prompt_name}</span>
253-
`}
254252
${total > 0 && html`
255253
<div class="run-card-bar">
256254
<div class="run-card-bar-fill ${passRate >= 50 ? 'pass' : 'fail'}" style="width: ${passRate}%"></div>
@@ -500,29 +498,71 @@ function IterationPanel({ iteration: it }) {
500498
it.status === 'failure' ? 'failure' :
501499
it.status === 'timeout' ? 'timeout' : '';
502500

501+
const statusLabel = it.status === 'success' ? 'Passed' :
502+
it.status === 'failure' ? 'Failed' :
503+
it.status === 'timeout' ? 'Timed out' :
504+
it.status === 'running' ? 'Running' : it.status;
505+
506+
const statusIcon = it.status === 'success' ? '\u2713' :
507+
it.status === 'failure' ? '\u2717' :
508+
it.status === 'timeout' ? '\u23f1' : '\u2022';
509+
510+
const passedCount = it.checks ? it.checks.filter(c => c.passed).length : 0;
511+
const totalChecks = it.checks ? it.checks.length : 0;
512+
503513
return html`
504514
<div class="iteration-panel">
505515
<div class="iteration-header">
506-
<span class="iteration-title">Iteration ${it.iteration}</span>
516+
<div class="iteration-header-left">
517+
<span class="iteration-title">Iteration ${it.iteration}</span>
518+
<div class="iteration-meta">
519+
${it.duration && html`
520+
<span class="iteration-meta-tag">
521+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
522+
<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>
523+
</svg>
524+
${it.duration}
525+
</span>
526+
`}
527+
${it.returncode !== undefined && it.returncode !== null && html`
528+
<span class="iteration-meta-tag">exit ${it.returncode}</span>
529+
`}
530+
${totalChecks > 0 && html`
531+
<span class="iteration-meta-tag">${passedCount}/${totalChecks} checks</span>
532+
`}
533+
</div>
534+
</div>
507535
<span class="iteration-status ${statusClass}">
508-
${it.detail || it.status}
536+
${statusIcon} ${statusLabel}
509537
</span>
510538
</div>
511539
<div class="iteration-body">
540+
${it.detail && it.detail !== it.status && html`
541+
<div class="iteration-detail">${it.detail}</div>
542+
`}
512543
${it.checks && it.checks.length > 0 && html`
513544
<div class="check-results">
514-
<div style="font-weight: 600; font-size: 13px; margin-bottom: 8px">Checks</div>
515-
${it.checks.map(c => html`
516-
<div class="check-result" key=${c.name}>
517-
<span class="check-icon ${c.passed ? 'pass' : c.timed_out ? 'timeout' : 'fail'}">
518-
${c.passed ? '\u2713' : c.timed_out ? '\u23f1' : '\u2717'}
519-
</span>
520-
<span class="check-name">${c.name}</span>
521-
${!c.passed && html`
522-
<span class="check-detail">exit ${c.exit_code}</span>
523-
`}
524-
</div>
525-
`)}
545+
<div class="checks-section-title">Check Results</div>
546+
${it.checks.map(c => {
547+
const checkStatus = c.passed ? 'pass' : c.timed_out ? 'timeout' : 'fail';
548+
return html`
549+
<div class="check-result ${checkStatus}" key=${c.name}>
550+
<span class="check-icon ${checkStatus}">
551+
${c.passed ? '\u2713' : c.timed_out ? '\u23f1' : '\u2717'}
552+
</span>
553+
<span class="check-name">${c.name}</span>
554+
${!c.passed && html`
555+
<span class="check-detail">exit ${c.exit_code}</span>
556+
`}
557+
</div>
558+
`;
559+
})}
560+
</div>
561+
`}
562+
${it.status === 'running' && html`
563+
<div style="color: var(--text-secondary); font-size: 13px; display: flex; align-items: center; gap: 8px">
564+
<div style="width: 8px; height: 8px; border-radius: 50%; background: var(--primary); animation: pulse 1.5s infinite"></div>
565+
Running...
526566
</div>
527567
`}
528568
</div>
@@ -720,7 +760,7 @@ function NewRunModal() {
720760
<div class="modal-title">New Run</div>
721761
<div class="form-group">
722762
<label class="form-label">Command</label>
723-
<input class="form-input" value=${config.command} onInput=${updateField('command')} />
763+
<input class="form-input mono" value=${config.command} onInput=${updateField('command')} />
724764
</div>
725765
<div class="form-group">
726766
<label class="form-label">Prompt</label>
@@ -738,7 +778,7 @@ function NewRunModal() {
738778
</div>
739779
`}
740780
${!config.prompt_name && html`
741-
<input class="form-input" value=${config.prompt_file}
781+
<input class="form-input mono" value=${config.prompt_file}
742782
onInput=${updateField('prompt_file')}
743783
placeholder="PROMPT.md" />
744784
`}
@@ -750,7 +790,7 @@ function NewRunModal() {
750790
</div>
751791
<div class="form-group">
752792
<label class="form-label">Project Directory</label>
753-
<input class="form-input" value=${config.project_dir} onInput=${updateField('project_dir')} />
793+
<input class="form-input mono" value=${config.project_dir} onInput=${updateField('project_dir')} />
754794
</div>
755795
<div class="form-row">
756796
<div class="form-group">

0 commit comments

Comments
 (0)