[pitboss/grind] deferred session-0003 (20260521T201327Z-3848)

This commit is contained in:
pitboss 2026-05-21 16:05:15 -05:00
parent d99361cff6
commit 368f628054
14 changed files with 672 additions and 86 deletions

View file

@ -2504,6 +2504,139 @@ tr.selected td {
color: var(--text);
}
/* ── Finding Detail: dynamic verification ─────────────────────────── */
.badge-dyn-confirmed {
background: var(--success-bg);
color: var(--success);
}
.badge-dyn-notconfirmed {
background: var(--bg-secondary);
color: var(--text-secondary);
}
.badge-dyn-inconclusive {
background: var(--sev-medium-bg);
color: var(--sev-medium);
}
.badge-dyn-unsupported {
background: var(--conf-low-bg);
color: var(--conf-low);
}
.dynamic-verdict-section {
display: flex;
flex-direction: column;
gap: var(--space-3);
font-size: var(--text-sm);
line-height: 1.45;
}
.dynamic-verdict-badge-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-2);
}
.dynamic-toolchain-match {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 2px 8px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-secondary);
color: var(--text-secondary);
font-size: var(--text-xs);
}
.repro-panel,
.dynamic-verdict-detail,
.dynamic-attempts {
border: 1px solid var(--border-light);
border-radius: 6px;
background: var(--bg);
padding: var(--space-3);
}
.repro-cmd-row {
display: flex;
align-items: center;
gap: var(--space-2);
min-width: 0;
flex-wrap: wrap;
}
.repro-label,
.dynamic-attempts > strong,
.dynamic-verdict-detail strong {
color: var(--text-secondary);
font-size: var(--text-xs);
font-weight: var(--weight-semibold);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.repro-cmd {
flex: 1 1 220px;
min-width: 0;
overflow-wrap: anywhere;
padding: 4px 7px;
border-radius: var(--radius-sm);
background: var(--terminal-bg);
color: var(--terminal-text);
font-size: 0.78rem;
}
.repro-copy-btn {
flex: 0 0 auto;
}
.dynamic-verdict-detail {
display: grid;
gap: var(--space-2);
}
.dynamic-verdict-detail-text {
color: var(--text-secondary);
overflow-wrap: anywhere;
}
.dynamic-attempt-list {
list-style: none;
display: grid;
gap: var(--space-2);
margin: var(--space-2) 0 0;
padding: 0;
}
.attempt-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
align-items: center;
gap: var(--space-2);
padding: 8px 10px;
border: 1px solid var(--border-light);
border-radius: 6px;
background: var(--surface);
}
.attempt-row.triggered {
border-color: color-mix(in srgb, var(--success) 35%, var(--border));
background: var(--success-bg);
}
.attempt-row code {
min-width: 0;
overflow-wrap: anywhere;
font-size: 0.8rem;
}
.attempt-outcome,
.attempt-exit-code {
color: var(--text-secondary);
font-size: var(--text-xs);
white-space: nowrap;
}
.attempt-row.triggered .attempt-outcome {
color: var(--success);
font-weight: var(--weight-semibold);
}
@media (max-width: 640px) {
.attempt-row {
grid-template-columns: 1fr;
align-items: start;
}
.attempt-outcome,
.attempt-exit-code {
white-space: normal;
}
}
/* ── Code Viewer Modal ────────────────────────────────────────────── */
.code-modal-overlay {
position: fixed;

View file

@ -28,7 +28,9 @@ describe('DynamicVerdictSection', () => {
it('renders Confirmed badge', () => {
render(
<DynamicVerdictSection
verdict={makeVerdict('Confirmed', { triggered_payload: 'sqli-tautology' })}
verdict={makeVerdict('Confirmed', {
triggered_payload: 'sqli-tautology',
})}
/>,
);
expect(screen.getByTestId('verdict-badge-confirmed')).toBeInTheDocument();
@ -36,7 +38,18 @@ describe('DynamicVerdictSection', () => {
it('renders NotConfirmed badge', () => {
render(<DynamicVerdictSection verdict={makeVerdict('NotConfirmed')} />);
expect(screen.getByTestId('verdict-badge-notconfirmed')).toBeInTheDocument();
expect(
screen.getByTestId('verdict-badge-notconfirmed'),
).toBeInTheDocument();
});
it('does not crash when the API omits an empty attempts array', () => {
render(
<DynamicVerdictSection
verdict={{ finding_id: 'no-attempts', status: 'Confirmed' }}
/>,
);
expect(screen.getByTestId('verdict-badge-confirmed')).toBeInTheDocument();
});
it('renders Unsupported badge', () => {
@ -51,10 +64,14 @@ describe('DynamicVerdictSection', () => {
it('renders Inconclusive badge', () => {
render(
<DynamicVerdictSection
verdict={makeVerdict('Inconclusive', { inconclusive_reason: 'BuildFailed' })}
verdict={makeVerdict('Inconclusive', {
inconclusive_reason: 'BuildFailed',
})}
/>,
);
expect(screen.getByTestId('verdict-badge-inconclusive')).toBeInTheDocument();
expect(
screen.getByTestId('verdict-badge-inconclusive'),
).toBeInTheDocument();
});
it('shows repro panel only for Confirmed status', () => {
@ -64,7 +81,11 @@ describe('DynamicVerdictSection', () => {
expect(screen.getByTestId('repro-panel')).toBeInTheDocument();
unmount();
for (const status of ['NotConfirmed', 'Unsupported', 'Inconclusive'] as const) {
for (const status of [
'NotConfirmed',
'Unsupported',
'Inconclusive',
] as const) {
const { unmount: u } = render(
<DynamicVerdictSection verdict={makeVerdict(status)} />,
);
@ -92,8 +113,9 @@ describe('DynamicVerdictSection', () => {
fireEvent.click(copyBtn);
expect(navigator.clipboard.writeText).toHaveBeenCalledOnce();
const calledWith = (navigator.clipboard.writeText as ReturnType<typeof vi.fn>).mock
.calls[0][0] as string;
const calledWith = (
navigator.clipboard.writeText as ReturnType<typeof vi.fn>
).mock.calls[0][0] as string;
expect(calledWith).toContain(findingId);
expect(calledWith).toContain('nyx repro');
});

View file

@ -24,7 +24,9 @@ describe('VerdictBadge', () => {
it('renders Confirmed badge with flame and correct class', () => {
render(
<VerdictBadge
verdict={makeVerdict('Confirmed', { triggered_payload: 'sqli-tautology' })}
verdict={makeVerdict('Confirmed', {
triggered_payload: 'sqli-tautology',
})}
/>,
);
const badge = screen.getByTestId('verdict-badge-confirmed');
@ -41,6 +43,17 @@ describe('VerdictBadge', () => {
expect(badge.textContent).not.toContain('🔥');
});
it('renders when attempts are omitted by the API', () => {
render(
<VerdictBadge
verdict={{ finding_id: 'test-finding-id', status: 'NotConfirmed' }}
/>,
);
expect(
screen.getByTestId('verdict-badge-notconfirmed'),
).toBeInTheDocument();
});
it('renders Unsupported badge with correct class', () => {
render(
<VerdictBadge
@ -68,7 +81,9 @@ describe('VerdictBadge', () => {
it('tooltip contains payload for Confirmed', () => {
render(
<VerdictBadge
verdict={makeVerdict('Confirmed', { triggered_payload: 'sqli-payload' })}
verdict={makeVerdict('Confirmed', {
triggered_payload: 'sqli-payload',
})}
/>,
);
const badge = screen.getByTestId('verdict-badge-confirmed');
@ -100,7 +115,9 @@ describe('VerdictBadge', () => {
'Inconclusive',
];
for (const status of statuses) {
const { unmount } = render(<VerdictBadge verdict={makeVerdict(status)} />);
const { unmount } = render(
<VerdictBadge verdict={makeVerdict(status)} />,
);
expect(
screen.getByTestId(`verdict-badge-${status.toLowerCase()}`),
).toBeInTheDocument();