From d750676f5e810dfa63b628c47640995311cac2d6 Mon Sep 17 00:00:00 2001 From: Janpeter Visser <30029041+madhura68@users.noreply.github.com> Date: Thu, 7 May 2026 17:36:44 +0200 Subject: [PATCH] =?UTF-8?q?PBI-56=20+=20ST-1275:=20PLAN=5FREADY=20?= =?UTF-8?q?=E2=86=92=20GRILLING=20re-grill=20+=20SKIPPED=20status=20render?= =?UTF-8?q?ing=20(#147)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ST-1272): allow PLAN_READY → GRILLING re-grill transition actions/ideas.ts already lists PLAN_READY in GRILL_TRIGGERABLE_FROM, but lib/idea-status.ts ALLOWED_TRANSITIONS was missing the PLAN_READY → GRILLING edge. As a result, clicking Grill on a PLAN_READY idea returned 422 "Status-transitie ongeldig" while the UI button was enabled. Mirrors the existing PLANNED → GRILLING re-grill behaviour. - lib/idea-status.ts: PLAN_READY allows GRILLING in addition to PLANNING/PLANNED - __tests__/lib/idea-status.test.ts: explicit assert for PLAN_READY → GRILLING and PLAN_READY added to the regrill loop covering every GRILL_TRIGGERABLE_FROM status Co-Authored-By: Claude Opus 4.7 (1M context) * feat(ST-1275): render SKIPPED job status in chart-colors and insights Closing the gap left when ClaudeJobStatus.SKIPPED was added to the schema: the badge map and case-mapper already covered it, but the chart palette, the per-day insights aggregator and the stacked-bar chart did not. SKIPPED jobs (e.g. cmovkur8 manually flipped during the no-op-exit hotfix) now render with a muted style consistent with cancelled. - lib/chart-colors.ts: JOB_STATUS_COLORS gains a 'skipped' entry (var(--muted-foreground), same intensity as cancelled — neither rood/orange) - lib/insights/agent-throughput.ts: DayCount + STATUSES + perDay zero-fill now include 'skipped'; the SQL terminal_7d filter already counted SKIPPED - app/(app)/insights/components/agent-throughput.tsx: STACKED_STATUSES and the empty-state guard include 'skipped' - __tests__: chart-colors keys list, job-status round-trip ('all 7 statuses') and the insights non-zero filter all account for SKIPPED Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- __tests__/lib/chart-colors.test.ts | 2 +- __tests__/lib/idea-status.test.ts | 5 +++-- __tests__/lib/insights/agent-throughput.test.ts | 2 +- __tests__/lib/job-status.test.ts | 3 ++- app/(app)/insights/components/agent-throughput.tsx | 4 ++-- lib/chart-colors.ts | 1 + lib/idea-status.ts | 2 +- lib/insights/agent-throughput.ts | 4 ++++ 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/__tests__/lib/chart-colors.test.ts b/__tests__/lib/chart-colors.test.ts index b8d0be2..dc316bd 100644 --- a/__tests__/lib/chart-colors.test.ts +++ b/__tests__/lib/chart-colors.test.ts @@ -34,7 +34,7 @@ describe('chart-colors', () => { it('JOB_STATUS_COLORS has all ClaudeJobStatus keys and non-empty values', () => { const keys: (keyof typeof JOB_STATUS_COLORS)[] = [ - 'queued', 'claimed', 'running', 'done', 'failed', 'cancelled', + 'queued', 'claimed', 'running', 'done', 'failed', 'cancelled', 'skipped', ] for (const key of keys) { expect(JOB_STATUS_COLORS[key]).toBeTruthy() diff --git a/__tests__/lib/idea-status.test.ts b/__tests__/lib/idea-status.test.ts index 9bedd32..b72692c 100644 --- a/__tests__/lib/idea-status.test.ts +++ b/__tests__/lib/idea-status.test.ts @@ -41,6 +41,7 @@ describe('canTransition', () => { it('allows re-grill from GRILLED and PLAN_READY-ish states', () => { expect(canTransition('GRILLED', 'GRILLING')).toBe(true) expect(canTransition('PLAN_FAILED', 'PLANNING')).toBe(true) + expect(canTransition('PLAN_READY', 'GRILLING')).toBe(true) }) it('allows fail-side transitions', () => { @@ -60,8 +61,8 @@ describe('canTransition', () => { }) it('canTransition to GRILLING from all statuses that allow re-grill', () => { - // DRAFT, GRILLED, GRILL_FAILED, PLANNED are in GRILL_TRIGGERABLE_FROM and support the transition. - const regrill = ['DRAFT', 'GRILLED', 'GRILL_FAILED', 'PLANNED'] as const + // GRILL_TRIGGERABLE_FROM in actions/ideas.ts — alle statussen die re-grill ondersteunen. + const regrill = ['DRAFT', 'GRILLED', 'GRILL_FAILED', 'PLAN_READY', 'PLANNED'] as const for (const status of regrill) { expect(canTransition(status, 'GRILLING')).toBe(true) } diff --git a/__tests__/lib/insights/agent-throughput.test.ts b/__tests__/lib/insights/agent-throughput.test.ts index 3465dd4..31bf46d 100644 --- a/__tests__/lib/insights/agent-throughput.test.ts +++ b/__tests__/lib/insights/agent-throughput.test.ts @@ -48,7 +48,7 @@ describe('getJobsPerDay', () => { // All days should have zero counts except the three we seeded const nonZero = result.perDay.filter( - d => d.done + d.failed + d.queued + d.claimed + d.running + d.cancelled > 0, + d => d.done + d.failed + d.queued + d.claimed + d.running + d.cancelled + d.skipped > 0, ) expect(nonZero).toHaveLength(3) diff --git a/__tests__/lib/job-status.test.ts b/__tests__/lib/job-status.test.ts index db8d1ab..dee082e 100644 --- a/__tests__/lib/job-status.test.ts +++ b/__tests__/lib/job-status.test.ts @@ -27,13 +27,14 @@ describe('job-status mappers', () => { expect(jobStatusFromApi('QUEUED')).toBe('QUEUED') }) - it('maps all 6 DB statuses to API', () => { + it('maps all 7 DB statuses to API', () => { expect(jobStatusToApi('QUEUED')).toBe('queued') expect(jobStatusToApi('CLAIMED')).toBe('claimed') expect(jobStatusToApi('RUNNING')).toBe('running') expect(jobStatusToApi('DONE')).toBe('done') expect(jobStatusToApi('FAILED')).toBe('failed') expect(jobStatusToApi('CANCELLED')).toBe('cancelled') + expect(jobStatusToApi('SKIPPED')).toBe('skipped') }) it('ACTIVE_JOB_STATUSES contains exactly QUEUED, CLAIMED, RUNNING', () => { diff --git a/app/(app)/insights/components/agent-throughput.tsx b/app/(app)/insights/components/agent-throughput.tsx index 820e64f..a43dd96 100644 --- a/app/(app)/insights/components/agent-throughput.tsx +++ b/app/(app)/insights/components/agent-throughput.tsx @@ -33,7 +33,7 @@ function formatDuration(seconds: number | null): string { return m > 0 ? `${m}m ${s}s` : `${s}s` } -const STACKED_STATUSES = ['queued', 'claimed', 'running', 'done', 'failed', 'cancelled'] as const +const STACKED_STATUSES = ['queued', 'claimed', 'running', 'done', 'failed', 'cancelled', 'skipped'] as const export function AgentThroughputCard({ data, productList, currentProductId }: Props) { const router = useRouter() @@ -44,7 +44,7 @@ export function AgentThroughputCard({ data, productList, currentProductId }: Pro const { perDay, kpi } = data const isEmpty = perDay.every( - d => d.done + d.failed + d.queued + d.claimed + d.running + d.cancelled === 0, + d => d.done + d.failed + d.queued + d.claimed + d.running + d.cancelled + d.skipped === 0, ) function handleProductChange(value: string | null) { diff --git a/lib/chart-colors.ts b/lib/chart-colors.ts index 561d4dc..e7b2d9f 100644 --- a/lib/chart-colors.ts +++ b/lib/chart-colors.ts @@ -28,6 +28,7 @@ export const JOB_STATUS_COLORS = { done: 'var(--status-done)', failed: 'var(--priority-critical)', cancelled: 'var(--muted-foreground)', + skipped: 'var(--muted-foreground)', } as const export const SERIES_COLORS = [ diff --git a/lib/idea-status.ts b/lib/idea-status.ts index 1cc94b2..e513245 100644 --- a/lib/idea-status.ts +++ b/lib/idea-status.ts @@ -53,7 +53,7 @@ const ALLOWED_TRANSITIONS: Record> = { GRILLED: ['GRILLING', 'PLANNING'], PLANNING: ['PLAN_READY', 'PLAN_FAILED'], PLAN_FAILED: ['PLANNING', 'GRILLED'], - PLAN_READY: ['PLANNING', 'PLANNED'], + PLAN_READY: ['PLANNING', 'PLANNED', 'GRILLING'], // GRILLING via startGrillJobAction (re-grill) PLANNED: ['PLAN_READY', 'GRILLING'], // PLAN_READY via relinkIdeaPlanAction; GRILLING via startGrillJobAction } diff --git a/lib/insights/agent-throughput.ts b/lib/insights/agent-throughput.ts index b5017d2..3e172aa 100644 --- a/lib/insights/agent-throughput.ts +++ b/lib/insights/agent-throughput.ts @@ -8,6 +8,7 @@ export interface DayCount { done: number failed: number cancelled: number + skipped: number } export interface ThroughputKpi { @@ -21,6 +22,8 @@ export interface JobsPerDayResult { kpi: ThroughputKpi } +const STATUSES = ['queued', 'claimed', 'running', 'done', 'failed', 'cancelled', 'skipped'] as const + type RawDayRow = { day: Date; status: string; count: bigint } type RawKpiRow = { today_count: bigint; done_7d: bigint; terminal_7d: bigint; avg_seconds: number | null } @@ -98,6 +101,7 @@ export async function getJobsPerDay( done: statusMap.get('done') ?? 0, failed: statusMap.get('failed') ?? 0, cancelled: statusMap.get('cancelled') ?? 0, + skipped: statusMap.get('skipped') ?? 0, }) }