From d0cf5a98c7f1b384f354684bcaef2ad5bb26dbae Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 30 May 2026 21:58:13 +0530 Subject: [PATCH] feat(inspect-ui): render extractor-change breadcrumbs at violations Show prev -> curr for each extractor whose value changed on the selected step, anchored under the violation row in ActionList. Long values collapse into
so the inline diff stays readable while the full payload is one click away. --- inspect-ui/src/panels/ActionList.css | 51 +++++++++++++++++++++ inspect-ui/src/panels/ActionList.tsx | 68 ++++++++++++++++++++++++++++ inspect-ui/src/types.ts | 6 +++ 3 files changed, 125 insertions(+) diff --git a/inspect-ui/src/panels/ActionList.css b/inspect-ui/src/panels/ActionList.css index c322f97..b9a1925 100644 --- a/inspect-ui/src/panels/ActionList.css +++ b/inspect-ui/src/panels/ActionList.css @@ -130,3 +130,54 @@ text-overflow: ellipsis; white-space: nowrap; } + +.action-list-extractor-changes { + grid-column: 2; + display: flex; + flex-direction: column; + gap: 2px; + margin-top: 4px; + padding-top: 4px; + border-top: 1px solid var(--surface-border); +} + +.action-list-extractor-changes-heading { + color: var(--text-muted); + text-transform: lowercase; + letter-spacing: 0.5px; +} + +.action-list-extractor-change-row { + display: grid; + grid-template-columns: minmax(80px, max-content) 1fr; + gap: 6px; + color: var(--text-primary); +} + +.action-list-extractor-change-name { + color: var(--text-muted); +} + +.action-list-extractor-change-diff { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.action-list-extractor-change-row-collapsed > summary { + display: grid; + grid-template-columns: minmax(80px, max-content) 1fr; + gap: 6px; + cursor: pointer; +} + +.action-list-extractor-change-full { + grid-column: 1 / -1; + margin: 4px 0 0; + white-space: pre-wrap; + word-break: break-word; + background: var(--surface-base); + padding: 4px 6px; + border-radius: 2px; + font-size: 10px; +} diff --git a/inspect-ui/src/panels/ActionList.tsx b/inspect-ui/src/panels/ActionList.tsx index 75b03bd..9e6bbc1 100644 --- a/inspect-ui/src/panels/ActionList.tsx +++ b/inspect-ui/src/panels/ActionList.tsx @@ -121,6 +121,24 @@ function contentTextForStep(step: Step): string { return ""; } +const MAX_INLINE_VALUE = 80; + +function formatExtractorValue(value: unknown): string { + if (value === null) return "null"; + if (value === undefined) return "undefined"; + if (typeof value === "string") return JSON.stringify(value); + try { + return JSON.stringify(value); + } catch { + return String(value); + } +} + +function truncateForInline(text: string): string { + if (text.length <= MAX_INLINE_VALUE) return text; + return `${text.slice(0, MAX_INLINE_VALUE - 1)}...`; +} + function positionTextForStep(step: Step): string | null { const action = step.action; if (!action) { @@ -289,6 +307,56 @@ export default function ActionList({ {contentText ? contentText : `""`} + {step.has_violations && selectedStep?.extractor_changes + ? (() => { + const entries = Object.entries(selectedStep.extractor_changes); + if (entries.length === 0) return null; + entries.sort(([a], [b]) => a.localeCompare(b)); + return ( +
+
+ extractor changes +
+ {entries.map(([name, change]) => { + const prev = formatExtractorValue(change.prev); + const curr = formatExtractorValue(change.curr); + const inlineFits = + prev.length + curr.length <= MAX_INLINE_VALUE * 2; + if (inlineFits) { + return ( +
+ + {name} + + + {truncateForInline(prev)} → {truncateForInline(curr)} + +
+ ); + } + return ( +
+ + + {name} + + + {truncateForInline(prev)} → {truncateForInline(curr)} + + +
+                                  {`prev: ${prev}\ncurr: ${curr}`}
+                                
+
+ ); + })} +
+ ); + })() + : null} ) : null} diff --git a/inspect-ui/src/types.ts b/inspect-ui/src/types.ts index 107f069..106c060 100644 --- a/inspect-ui/src/types.ts +++ b/inspect-ui/src/types.ts @@ -115,6 +115,11 @@ export interface Metrics { total_memory_bytes?: number; } +export interface ExtractorChange { + prev: unknown; + curr: unknown; +} + export interface Step { step: number; timestamp: string; @@ -126,4 +131,5 @@ export interface Step { hierarchy?: Hierarchy; residuals?: Record; metrics?: Metrics; + extractor_changes?: Record; }