Clarify Patrol run-history snapshot affordance

This commit is contained in:
rcourtman
2026-03-25 23:46:52 +00:00
parent df4858c909
commit ad0d7fddc6
5 changed files with 145 additions and 2 deletions
@@ -296,6 +296,11 @@ That same rule applies to run-status badges. A legacy run without findings
snapshot ids must not keep a green `healthy` badge when the surrounding UI is
saying findings verification is unavailable; the canonical run-status
presentation should downgrade that state to a neutral `completed` badge.
That same truthfulness rule applies to the run-history shell copy. The `Recent
patrol runs` helper text must not promise that every visible run can filter
findings to a concrete snapshot; when visible runs include legacy entries
without `finding_ids`, or when the selected run itself predates findings
snapshots, the shell should say so explicitly.
That same findings surface should keep its section chrome functional rather
than promotional. Inside the Patrol findings tab, the selected tab already
names the surface, so the findings card should not add another in-card product
@@ -2,7 +2,10 @@ import { For, Show } from 'solid-js';
import type { Accessor } from 'solid-js';
import type { PatrolRunRecord } from '@/api/patrol';
import { getRunHistoryEmptyState } from '@/utils/patrolEmptyStatePresentation';
import { getRunHistoryLoadingState } from '@/utils/patrolRunPresentation';
import {
getRunHistoryLoadingState,
getRunHistorySelectionHint,
} from '@/utils/patrolRunPresentation';
import { RunHistoryEntry } from './RunHistoryEntry';
import RefreshCwIcon from 'lucide-solid/icons/refresh-cw';
@@ -35,7 +38,9 @@ export function RunHistoryPanel(props: RunHistoryPanelProps) {
<div class="flex items-center justify-between mb-4">
<div>
<h2 class="text-sm font-semibold text-base-content">Recent patrol runs</h2>
<p class="text-xs text-muted">Select a run to filter findings to that snapshot</p>
<p class="text-xs text-muted">
{getRunHistorySelectionHint(props.runs, props.selectedRun)}
</p>
</div>
<Show when={props.selectedRun}>
<button
@@ -0,0 +1,98 @@
import { cleanup, render, screen } from '@solidjs/testing-library';
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { PatrolRunRecord } from '@/api/patrol';
import { RunHistoryPanel } from '../RunHistoryPanel';
vi.mock('../RunHistoryEntry', () => ({
RunHistoryEntry: () => <div data-testid="run-history-entry" />,
}));
describe('RunHistoryPanel', () => {
const patrolStream = {
phase: () => '',
currentTool: () => '',
tokens: () => 0,
resynced: () => false,
resyncReason: () => '',
bufferStartSeq: () => 0,
bufferEndSeq: () => 0,
outputTruncated: () => false,
reconnectCount: () => 0,
isStreaming: () => false,
errorMessage: () => '',
};
const baseRun: PatrolRunRecord = {
id: 'run-1',
started_at: '2026-03-12T10:00:00Z',
completed_at: '2026-03-12T10:01:00Z',
duration_ms: 60000,
type: 'patrol',
trigger_reason: 'scheduled',
scope_resource_ids: [],
effective_scope_resource_ids: [],
scope_resource_types: [],
resources_checked: 58,
nodes_checked: 0,
guests_checked: 0,
docker_checked: 0,
storage_checked: 0,
hosts_checked: 0,
pbs_checked: 0,
pmg_checked: 0,
kubernetes_checked: 0,
new_findings: 0,
existing_findings: 0,
rejected_findings: 0,
resolved_findings: 0,
auto_fix_count: 0,
findings_summary: 'All clear',
finding_ids: [],
error_count: 0,
status: 'healthy',
triage_flags: 0,
tool_call_count: 0,
};
afterEach(() => {
cleanup();
});
it('warns that some older runs do not include findings snapshots', () => {
render(() => (
<RunHistoryPanel
runs={[baseRun, { ...baseRun, id: 'run-legacy', finding_ids: undefined }]}
loading={false}
selectedRun={null}
onSelectRun={vi.fn()}
patrolStream={patrolStream}
/>
));
expect(
screen.getByText(
'Select a run to filter findings when available. Some older runs do not include findings snapshots.',
),
).toBeInTheDocument();
});
it('explains when the selected run predates findings snapshots', () => {
const legacyRun = { ...baseRun, id: 'run-legacy', finding_ids: undefined };
render(() => (
<RunHistoryPanel
runs={[legacyRun]}
loading={false}
selectedRun={legacyRun}
onSelectRun={vi.fn()}
patrolStream={patrolStream}
/>
));
expect(
screen.getByText(
'Selected run predates findings snapshots; run-scoped findings cannot be fully verified.',
),
).toBeInTheDocument();
});
});
@@ -7,6 +7,7 @@ import {
getPatrolRunStatusPresentation,
isPatrolRunHealthy,
getRunHistoryLoadingState,
getRunHistorySelectionHint,
getToolCallsLoadingState,
getToolCallsUnavailableState,
getToolCallResultBadgeClass,
@@ -115,4 +116,23 @@ describe('patrolRunPresentation', () => {
expect(getToolCallsLoadingState()).toBe('Loading tool calls...');
expect(getToolCallsUnavailableState()).toBe('Tool call details not available for this run.');
});
it('warns when visible runs include legacy findings snapshots', () => {
expect(
getRunHistorySelectionHint([
{ finding_ids: [] },
{ finding_ids: undefined },
]),
).toBe(
'Select a run to filter findings when available. Some older runs do not include findings snapshots.',
);
});
it('explains selected legacy runs in the run-history shell', () => {
expect(
getRunHistorySelectionHint([{ finding_ids: [] }], { finding_ids: undefined }),
).toBe(
'Selected run predates findings snapshots; run-scoped findings cannot be fully verified.',
);
});
});
@@ -132,6 +132,21 @@ export function getRunHistoryLoadingState(): string {
return 'Loading run history…';
}
export function getRunHistorySelectionHint(
runs: Array<Pick<PatrolRunRecord, 'finding_ids'>>,
selectedRun?: Pick<PatrolRunRecord, 'finding_ids'> | null,
): string {
if (selectedRun && selectedRun.finding_ids === undefined) {
return 'Selected run predates findings snapshots; run-scoped findings cannot be fully verified.';
}
if (runs.some((run) => run.finding_ids === undefined)) {
return 'Select a run to filter findings when available. Some older runs do not include findings snapshots.';
}
return 'Select a run to filter findings to that snapshot';
}
export function getToolCallsLoadingState(): string {
return 'Loading tool calls...';
}