mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
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 <details> so the inline diff stays readable while the full payload is one click away.
This commit is contained in:
1 parent
e44ad56749
commit
d0cf5a98c7
3 files changed
+125
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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 : `""`}
|
||||
</span>
|
||||
</div>
|
||||
{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 (
|
||||
<div className="action-list-extractor-changes">
|
||||
<div className="action-list-extractor-changes-heading">
|
||||
extractor changes
|
||||
</div>
|
||||
{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 (
|
||||
<div key={name} className="action-list-extractor-change-row">
|
||||
<span className="action-list-extractor-change-name">
|
||||
{name}
|
||||
</span>
|
||||
<span className="action-list-extractor-change-diff">
|
||||
{truncateForInline(prev)} → {truncateForInline(curr)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<details
|
||||
key={name}
|
||||
className="action-list-extractor-change-row action-list-extractor-change-row-collapsed"
|
||||
>
|
||||
<summary>
|
||||
<span className="action-list-extractor-change-name">
|
||||
{name}
|
||||
</span>
|
||||
<span className="action-list-extractor-change-diff">
|
||||
{truncateForInline(prev)} → {truncateForInline(curr)}
|
||||
</span>
|
||||
</summary>
|
||||
<pre className="action-list-extractor-change-full">
|
||||
{`prev: ${prev}\ncurr: ${curr}`}
|
||||
</pre>
|
||||
</details>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})()
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</li>
|
||||
|
||||
@@ -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<string, ResidualNode>;
|
||||
metrics?: Metrics;
|
||||
extractor_changes?: Record<string, ExtractorChange>;
|
||||
}
|
||||
Reference in new issue
Block a user