fix(status): render pipeline phase for legacy statuses (analyst_verified)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

A case parked on a status the trimmed PHASES list dropped — but the agents
still set, e.g. `analyst_verified` (HEARTBEAT.md), `research_complete`
(legal-researcher) — rendered with NO active phase: phaseIndexOf returned -1,
so the pipeline stepper went fully blank and the chair couldn't see where the
case stood (reported on 1043-02-26, currently at analyst_verified).

Add LEGACY_STATUS_PHASE in case-status.ts mapping each legacy/intermediate
status to its display phase (analyst_verified/research_complete → "ניתוח וכיוון"),
and a single phaseIndexOf() exported from the SSoT. status-hero.tsx and
workflow-timeline.tsx both dropped their identical local phaseIndexOf (which had
the bug) and import the shared one — no parallel path (G2).

Display-only data-mapping fix; the stepper design is unchanged. Does NOT address
the root drift (agents still set analyst_verified) — that prompt-alignment is a
separate, carefully-sequenced follow-up needing cross-company agent sync.

Invariants: G2 (single phase-resolution path, legacy handled in the SSoT, no
new parallel logic). No backend / status-model change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 18:31:17 +00:00
parent 5987d50ebf
commit 7c48c8753e
3 changed files with 34 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { PHASES, type CaseStatus } from "@/lib/api/case-status";
import { PHASES, phaseIndexOf, type CaseStatus } from "@/lib/api/case-status";
import { statusLabel } from "@/lib/api/case-status";
import { StatusChanger } from "@/components/cases/status-changer";
import { StatusGuide } from "@/components/cases/status-guide";
@@ -22,11 +22,6 @@ const EXPECTED_OUTCOME_LABELS: Record<string, string> = Object.fromEntries(
expectedOutcomes.map((o) => [o.value, o.label]),
);
function phaseIndexOf(status?: CaseStatus): number {
if (!status) return -1;
return PHASES.findIndex((p) => p.statuses.includes(status));
}
/** Horizontal RTL stepper over the 5 lifecycle phases (mockup 18b .stepper). */
function HorizontalStepper({ status }: { status?: CaseStatus }) {
const currentIdx = phaseIndexOf(status);

View File

@@ -1,6 +1,6 @@
"use client";
import { PHASES, type CaseStatus, type PhaseKey } from "@/lib/api/case-status";
import { PHASES, phaseIndexOf, type CaseStatus, type PhaseKey } from "@/lib/api/case-status";
import { STATUS_LABELS, STATUS_ICONS, STATUS_DESCRIPTIONS } from "@/components/cases/status-badge";
import {
FolderInput, ClipboardList, Brain, PenLine, CheckCircle2,
@@ -21,11 +21,6 @@ const PHASE_ICONS: Record<PhaseKey, LucideIcon> = {
done: CheckCircle2,
};
function phaseIndexOf(status?: CaseStatus): number {
if (!status) return -1;
return PHASES.findIndex((p) => p.statuses.includes(status));
}
export function WorkflowTimeline({ status }: { status?: CaseStatus }) {
const currentIdx = phaseIndexOf(status);

View File

@@ -40,10 +40,40 @@ export const PHASES: { key: PhaseKey; label: string; statuses: CaseStatus[] }[]
{ key: "done", label: "סגירה", statuses: ["exported", "reviewed", "final"] },
];
/** Which phase a status belongs to (undefined for unknown/legacy values). */
/**
* Legacy/intermediate statuses that the trimmed PHASES list dropped but the
* Paperclip agents still set (e.g. the analyst writes `analyst_verified`, the
* researcher writes `research_complete`). Without this map a case parked on one
* of them renders with NO active phase — the pipeline stepper goes blank and the
* chair can't see where the case stands. Map each to the phase it belongs to for
* display purposes only (these are NOT selectable statuses).
*/
const LEGACY_STATUS_PHASE: Record<string, PhaseKey> = {
analyst_verified: "thinking",
research_complete: "thinking",
analysis_enriched: "thinking",
ready_for_writing: "writing",
drafting: "writing",
in_progress: "thinking",
};
/** Which phase a status belongs to (undefined only for truly unknown values). */
export function phaseOf(status?: CaseStatus | string): PhaseKey | undefined {
if (!status) return undefined;
return PHASES.find((p) => (p.statuses as string[]).includes(status))?.key;
return (
PHASES.find((p) => (p.statuses as string[]).includes(status))?.key ??
LEGACY_STATUS_PHASE[status]
);
}
/**
* Index of a status within the 5-phase pipeline (1 only for unknown values).
* Single source of truth for the stepper — handles legacy statuses via phaseOf,
* so a case on `analyst_verified` lands on "ניתוח וכיוון" instead of blank.
*/
export function phaseIndexOf(status?: CaseStatus | string): number {
const key = phaseOf(status);
return key ? PHASES.findIndex((p) => p.key === key) : -1;
}
export const STATUS_LABELS: Record<CaseStatus, string> = {