Surface regressionCount as a pill on collapsed finding rows

regressionCount is the strongest "this is not a one-off" signal Pulse can
give an operator scanning a list, but until now it lived inside the expanded
card. A triaging operator should be able to see at a glance that a finding
has come back N times, alongside the existing investigation-confidence badge.

Adds an amber "regressed N×" pill in the collapsed row when
regressionCount > 0, sitting next to the confidence badge so trust signals
read as a single cluster. Pill stays absent on fresh detections so ordinary
finding rows stay clean.
This commit is contained in:
rcourtman 2026-05-09 10:55:35 +01:00
parent 95e797eac1
commit c326ecfe5a
3 changed files with 38 additions and 0 deletions

View file

@ -1108,3 +1108,10 @@ and must NOT appear for `will_fix_later` itself or for findings with no
prior regression. This is the operator-facing half of "Pulse learns from
operator dismissal patterns" — a recurring issue should not be silently
buried just because the operator is moving fast on triage.
The collapsed finding row must also surface `regressionCount` as a small
pill next to the investigation-confidence badge whenever
`regressionCount > 0`, so a triaging operator scanning the list can spot
"this is not a one-off" without expanding each card. The pill stays
absent on fresh detections (count == 0) so the row stays clean for
ordinary findings, and the styling (amber tone) reads as a recurrence
signal rather than a generic muted note.

View file

@ -745,6 +745,19 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
{finding.investigationRecord!.confidence!} confidence
</span>
</Show>
{/* Regression pill Pulse's "Learn" signal on the collapsed row.
A finding that has regressed before is not a one-off; it
needs to be triaged differently from a fresh detection.
Sits next to the confidence badge so trust + recurrence
can be scanned together without expanding the card. */}
<Show when={(finding.regressionCount || 0) > 0}>
<span
class="px-1.5 py-0.5 border text-[10px] font-medium rounded border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300"
title={`This finding has regressed ${finding.regressionCount} time${finding.regressionCount === 1 ? '' : 's'} after being resolved before.`}
>
regressed {finding.regressionCount}×
</span>
</Show>
{/* Title */}
<span
class={`font-medium text-sm truncate ${

View file

@ -124,6 +124,24 @@ describe('FindingsPanel assistant handoff', () => {
expect(findingsPanelSource).toContain('Pulse will permanently suppress');
});
it('surfaces regressionCount as a pill on the collapsed finding row', () => {
// regressionCount is the strongest "this is not a one-off" signal Pulse
// can give an operator scanning a list. The pill must appear in the
// collapsed row (alongside the confidence badge) so triage decisions
// can be made without expanding each card. The pill must only appear
// when regressionCount > 0 — fresh detections must stay clean.
expect(findingsPanelSource).toContain('(finding.regressionCount || 0) > 0');
expect(findingsPanelSource).toContain('regressed {finding.regressionCount}×');
expect(findingsPanelSource).toContain('text-amber-700 dark:text-amber-300');
// The regression pill must sit next to the confidence badge in source order
// so the row reads as one trust-related cluster.
const confidenceIndex = findingsPanelSource.indexOf('confidence');
const regressionIndex = findingsPanelSource.indexOf('regressed {finding.regressionCount}×');
expect(confidenceIndex).toBeGreaterThan(0);
expect(regressionIndex).toBeGreaterThan(0);
expect(regressionIndex).toBeGreaterThan(confidenceIndex);
});
it('warns the operator before dismissing a recurrent finding as not_an_issue or expected_behavior', () => {
// not_an_issue and expected_behavior both stay quiet forever after dismiss.
// If the finding has already regressed before, the operator may be