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;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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 "";
|
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 {
|
function positionTextForStep(step: Step): string | null {
|
||||||
const action = step.action;
|
const action = step.action;
|
||||||
if (!action) {
|
if (!action) {
|
||||||
@@ -289,6 +307,56 @@ export default function ActionList({
|
|||||||
{contentText ? contentText : `""`}
|
{contentText ? contentText : `""`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ export interface Metrics {
|
|||||||
total_memory_bytes?: number;
|
total_memory_bytes?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExtractorChange {
|
||||||
|
prev: unknown;
|
||||||
|
curr: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Step {
|
export interface Step {
|
||||||
step: number;
|
step: number;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
@@ -126,4 +131,5 @@ export interface Step {
|
|||||||
hierarchy?: Hierarchy;
|
hierarchy?: Hierarchy;
|
||||||
residuals?: Record<string, ResidualNode>;
|
residuals?: Record<string, ResidualNode>;
|
||||||
metrics?: Metrics;
|
metrics?: Metrics;
|
||||||
|
extractor_changes?: Record<string, ExtractorChange>;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user