diff --git a/ops/diagnostics/dashboard_epistemic.py b/ops/diagnostics/dashboard_epistemic.py
index c0e1c093f..cb3dd5ef7 100644
--- a/ops/diagnostics/dashboard_epistemic.py
+++ b/ops/diagnostics/dashboard_epistemic.py
@@ -74,7 +74,7 @@ def render_epistemic_page(vital_signs: dict, now: datetime) -> str:
Current eval models: Haiku (triage), GPT-4o (domain), Sonnet/Opus (Leo).
diff --git a/ops/diagnostics/dashboard_prs.py b/ops/diagnostics/dashboard_prs.py
index 638ab52a1..e1ca5c08c 100644
--- a/ops/diagnostics/dashboard_prs.py
+++ b/ops/diagnostics/dashboard_prs.py
@@ -1,8 +1,8 @@
"""PR Lifecycle dashboard — single-page view of every PR through the pipeline.
-Sortable table: PR#, summary, claims, domain, contributor, outcome, evals, evaluator, cost, date.
-Click any row to expand: claim titles, eval chain, timeline, reviews, issues.
-Hero cards: total PRs, merge rate, total claims, est. cost.
+Sortable table: PR#, summary, claims, domain, outcome, evals, evaluator, cost, date.
+Click any row to expand: timeline, claim list, issues summary.
+Hero cards: total PRs, merge rate, median eval rounds, total claims, total cost.
Data sources: prs table, audit_log (eval rounds), review_records.
Owner: Ship
@@ -14,7 +14,7 @@ from shared_ui import render_page
EXTRA_CSS = """
- .content-wrapper { max-width: 1600px !important; }
+ .page-content { max-width: 1600px !important; }
.filters { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.filters select, .filters input {
background: #161b22; color: #c9d1d9; border: 1px solid #30363d;
@@ -22,15 +22,14 @@ EXTRA_CSS = """
.filters select:focus, .filters input:focus { border-color: #58a6ff; outline: none; }
.pr-table { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; }
.pr-table th:nth-child(1) { width: 50px; } /* PR# */
- .pr-table th:nth-child(2) { width: 28%; } /* Summary */
+ .pr-table th:nth-child(2) { width: 30%; } /* Summary */
.pr-table th:nth-child(3) { width: 50px; } /* Claims */
- .pr-table th:nth-child(4) { width: 11%; } /* Domain */
- .pr-table th:nth-child(5) { width: 10%; } /* Contributor */
- .pr-table th:nth-child(6) { width: 10%; } /* Outcome */
- .pr-table th:nth-child(7) { width: 44px; } /* Evals */
- .pr-table th:nth-child(8) { width: 12%; } /* Evaluator */
- .pr-table th:nth-child(9) { width: 60px; } /* Cost */
- .pr-table th:nth-child(10) { width: 80px; } /* Date */
+ .pr-table th:nth-child(4) { width: 12%; } /* Domain */
+ .pr-table th:nth-child(5) { width: 10%; } /* Outcome */
+ .pr-table th:nth-child(6) { width: 50px; } /* Evals */
+ .pr-table th:nth-child(7) { width: 16%; } /* Evaluator */
+ .pr-table th:nth-child(8) { width: 70px; } /* Cost */
+ .pr-table th:nth-child(9) { width: 90px; } /* Date */
.pr-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 8px 6px; }
.pr-table td:nth-child(2) { white-space: normal; overflow: visible; line-height: 1.4; }
.pr-table th { cursor: pointer; user-select: none; position: relative; padding: 8px 18px 8px 6px; }
@@ -49,24 +48,22 @@ EXTRA_CSS = """
.pr-table .pr-link:hover { text-decoration: underline; }
.pr-table td .summary-text { font-size: 12px; color: #c9d1d9; }
.pr-table td .review-snippet { font-size: 11px; color: #f85149; margin-top: 2px; opacity: 0.8; }
- .pr-table td .model-tag { font-size: 10px; color: #6e7681; background: #161b22; border-radius: 3px; padding: 1px 4px; }
- .pr-table td .contributor-tag { font-size: 11px; color: #d2a8ff; }
- .pr-table td .contributor-self { font-size: 11px; color: #6e7681; font-style: italic; }
+ .pr-table td .model-tag { font-size: 9px; color: #6e7681; background: #21262d; border-radius: 3px; padding: 1px 4px; display: inline-block; margin: 1px 0; }
.pr-table td .expand-chevron { display: inline-block; width: 12px; color: #484f58; font-size: 10px; transition: transform 0.2s; }
.pr-table tr.expanded .expand-chevron { transform: rotate(90deg); color: #58a6ff; }
+ .pr-table td .cost-val { font-size: 12px; color: #8b949e; }
+ .pr-table td .claims-count { font-size: 13px; color: #c9d1d9; text-align: center; }
+ .pr-table td .evals-count { font-size: 13px; text-align: center; }
.trace-panel { background: #0d1117; border: 1px solid #30363d; border-radius: 8px;
padding: 16px; margin: 4px 0 8px 0; font-size: 12px; display: none; }
.trace-panel.open { display: block; }
- .trace-panel h4 { color: #58a6ff; font-size: 12px; margin: 12px 0 6px 0; }
- .trace-panel h4:first-child { margin-top: 0; }
- .claim-list { list-style: none; padding: 0; margin: 0; }
- .claim-list li { padding: 4px 0 4px 16px; border-left: 2px solid #238636; color: #c9d1d9; font-size: 12px; line-height: 1.5; }
- .claim-list li .claim-confidence { font-size: 10px; color: #8b949e; margin-left: 6px; }
- .issues-box { background: #1c1210; border: 1px solid #f8514933; border-radius: 6px;
+ .trace-panel .section-title { color: #58a6ff; font-size: 12px; font-weight: 600; margin: 12px 0 6px; }
+ .trace-panel .section-title:first-child { margin-top: 0; }
+ .trace-panel .claim-list { list-style: none; padding: 0; margin: 0; }
+ .trace-panel .claim-list li { padding: 4px 0; border-bottom: 1px solid #21262d; color: #c9d1d9; font-size: 12px; }
+ .trace-panel .claim-list li:last-child { border-bottom: none; }
+ .trace-panel .issues-box { background: #1c1017; border: 1px solid #f8514930; border-radius: 6px;
padding: 8px 12px; margin: 4px 0; font-size: 12px; color: #f85149; }
- .eval-chain { background: #161b22; border-radius: 6px; padding: 8px 12px; margin: 4px 0; font-size: 12px; }
- .eval-chain .chain-step { display: inline-block; margin-right: 6px; }
- .eval-chain .chain-arrow { color: #484f58; margin: 0 4px; }
.trace-timeline { list-style: none; padding: 0; }
.trace-timeline li { padding: 4px 0; border-left: 2px solid #30363d; padding-left: 12px; margin-left: 8px; }
.trace-timeline li .ts { color: #484f58; font-size: 11px; }
@@ -76,6 +73,12 @@ EXTRA_CSS = """
.trace-timeline li.ev-changes .ev { color: #d29922; }
.review-text { background: #161b22; padding: 8px 12px; border-radius: 4px;
margin: 4px 0; white-space: pre-wrap; font-size: 11px; color: #8b949e; max-height: 200px; overflow-y: auto; }
+ .eval-chain { background: #161b22; border-radius: 6px; padding: 8px 12px; margin: 4px 0 8px;
+ font-size: 12px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
+ .eval-chain .step { display: flex; align-items: center; gap: 4px; }
+ .eval-chain .step-label { color: #8b949e; font-size: 11px; }
+ .eval-chain .step-model { color: #c9d1d9; font-size: 11px; font-weight: 600; }
+ .eval-chain .arrow { color: #484f58; }
.pagination { display: flex; gap: 8px; align-items: center; justify-content: center; margin-top: 16px; }
.pagination button { background: #161b22; color: #c9d1d9; border: 1px solid #30363d;
border-radius: 4px; padding: 4px 12px; cursor: pointer; font-size: 12px; }
@@ -93,6 +96,7 @@ def render_prs_page(now: datetime) -> str:
@@ -100,7 +104,6 @@ def render_prs_page(now: datetime) -> str:
-
"""
+ # Use single-quoted JS strings throughout to avoid Python/HTML escaping issues
scripts = """