Align Patrol header recency labels

Patrol header metadata now uses the canonical recency label so the top row no longer falls back to a generic last-run prefix when the page distinguishes activity from full verification.
This commit is contained in:
rcourtman 2026-03-25 12:30:26 +00:00
parent f9fc8203fa
commit 0703e68766
3 changed files with 15 additions and 4 deletions

View file

@ -146,6 +146,9 @@ The summary recency chip must follow the same governed scope distinction. When
the latest completed activity was only a scoped run, the summary should label
that timestamp as `Last activity` instead of `Last patrol`; `Last full patrol`
belongs only to the most recent completed full Patrol run.
That same recency contract also applies to the header metadata row. The top
header must not revert to a generic `Last:` timestamp when the rest of Patrol
is explicitly distinguishing activity from full verification recency.
That summary surface must also avoid reintroducing a second compact assessment
or verification layer beneath the primary card. Supporting metric strips
belong to counts and outcomes such as active findings, critical findings,

View file

@ -13,6 +13,7 @@ import { groupModelsByProvider } from '@/utils/patrolFormat';
import { getAIQuickstartCreditsPresentation } from '@/utils/aiQuickstartPresentation';
import { buildPatrolScheduleOptions } from '@/utils/aiPatrolSchedulePresentation';
import { getPatrolRuntimePresentation } from '@/utils/patrolRuntimePresentation';
import { getPatrolRecencyPresentation } from '@/utils/patrolSummaryPresentation';
import type { PatrolIntelligenceState } from './usePatrolIntelligenceState';
export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState }) {
@ -27,6 +28,12 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
const runtimePresentation = createMemo(() =>
getPatrolRuntimePresentation(state.runtimeState(), state.blockedReason()),
);
const recency = createMemo(() =>
getPatrolRecencyPresentation({
runs: state.patrolRunHistory() ?? [],
lastPatrolAt: state.patrolStatus()?.last_patrol_at,
}),
);
const showQuickstartStatus = createMemo(() => {
const patrolStatus = state.patrolStatus();
if (!patrolStatus) return false;
@ -61,11 +68,11 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
class="mb-3"
actions={
<div class="flex flex-wrap items-center justify-end gap-3">
<Show when={state.patrolStatus()?.last_patrol_at}>
<Show when={recency().timestamp}>
<div class="hidden sm:flex items-center gap-3 text-xs text-muted">
<span>
Last:{' '}
{formatRelativeTime(state.patrolStatus()?.last_patrol_at, {
{recency().label}:{' '}
{formatRelativeTime(recency().timestamp, {
compact: true,
emptyText: 'Never',
})}

View file

@ -817,12 +817,13 @@ describe('AIIntelligence entitlement gating', () => {
await waitFor(() => {
expect(screen.getAllByText('Coverage incomplete').length).toBeGreaterThan(0);
expect(screen.getByText('No recent full patrol')).toBeInTheDocument();
expect(screen.getByText(/Last activity/i)).toBeInTheDocument();
expect(screen.getAllByText(/Last activity/i).length).toBeGreaterThan(0);
expect(screen.getByText('Warnings')).toBeInTheDocument();
});
expect(screen.queryByText('No issues found')).not.toBeInTheDocument();
expect(screen.queryByText(/Last patrol/i)).not.toBeInTheDocument();
expect(screen.queryByText(/^Last:/i)).not.toBeInTheDocument();
expect(screen.queryByText('Partial verification')).not.toBeInTheDocument();
expect(
screen.getAllByText(