From 90d38755a1518cc2465c9a9305ed12f2bba38055 Mon Sep 17 00:00:00 2001 From: Davis Martini Date: Fri, 3 Jul 2026 09:56:40 +0300 Subject: [PATCH] fix(pulse): make effort badge tier-aware (E1-E5) via a shared resolver work.json's effort field carries two writer vocabularies: a session-init placeholder (standard/starting) and the real Algorithm tier written by ISASync (E1..E5). The three Observability renderers each hard-coded an inline legacy quality->E-level map and never reconciled the two, producing two defects: 1. A real tier (E3/E4) had no map key -> EffortBadge returned null (no badge); the ladder lit nothing. 2. The placeholder 'standard' mapped to E1 -> an unclassified session showed a confident, false 'E1 STANDARD' badge. Adds src/lib/effort-tier.ts (single resolveEffortTier() + tier class maps) and rewires EffortBadge / PhaseDetailPanel / UnifiedWorkDashboard to it, deleting three copies of the inline map. Placeholders now resolve to a neutral pending state, never a false E1. --- .../src/components/activity/EffortBadge.tsx | 23 +++---- .../components/activity/PhaseDetailPanel.tsx | 33 +++------- .../activity/UnifiedWorkDashboard.tsx | 66 +++++-------------- .../Observability/src/lib/effort-tier.ts | 50 ++++++++++++++ 4 files changed, 87 insertions(+), 85 deletions(-) create mode 100644 Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/lib/effort-tier.ts diff --git a/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/EffortBadge.tsx b/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/EffortBadge.tsx index 10d7e4aeac..37a37913ba 100644 --- a/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/EffortBadge.tsx +++ b/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/EffortBadge.tsx @@ -1,26 +1,21 @@ -const EFFORT_CONFIG: Record = { - standard: { bg: "bg-amber-500/15", border: "border-amber-500/30", text: "text-amber-400", eLevel: "E1" }, - extended: { bg: "bg-orange-500/15", border: "border-orange-500/30", text: "text-orange-400", eLevel: "E2" }, - advanced: { bg: "bg-rose-500/15", border: "border-rose-500/30", text: "text-rose-400", eLevel: "E3" }, - deep: { bg: "bg-purple-500/15", border: "border-purple-500/30", text: "text-purple-400", eLevel: "E4" }, - comprehensive: { bg: "bg-red-500/15", border: "border-red-500/30", text: "text-red-400", eLevel: "E5" }, -}; +import { resolveEffortTier, TIER_BADGE_CLASSES } from "@/lib/effort-tier"; interface EffortBadgeProps { effort: string; + /** When true, render nothing for not-yet-classified placeholders instead of a pending chip. */ + hidePending?: boolean; } -export default function EffortBadge({ effort }: EffortBadgeProps) { - const key = effort.toLowerCase(); - const config = EFFORT_CONFIG[key]; - if (!config) return null; +export default function EffortBadge({ effort, hidePending }: EffortBadgeProps) { + const { tier, pending, label } = resolveEffortTier(effort); + if (pending && hidePending) return null; + const classes = tier ? TIER_BADGE_CLASSES[tier] : TIER_BADGE_CLASSES.pending; return ( - {config.eLevel} - {effort.toUpperCase()} + {label} ); } diff --git a/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/PhaseDetailPanel.tsx b/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/PhaseDetailPanel.tsx index b76ff952bc..b1cdd50601 100644 --- a/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/PhaseDetailPanel.tsx +++ b/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/PhaseDetailPanel.tsx @@ -2,6 +2,7 @@ import type { AlgorithmState, AlgorithmPhase } from "@/types/algorithm"; import { Eye, Brain, ClipboardList, Hammer, Zap, CheckCircle2, BookOpen, Clock, Users, Layers, type LucideIcon } from "lucide-react"; +import { resolveEffortTier, TIER_BADGE_CLASSES } from "@/lib/effort-tier"; // ─── Phase config ─── @@ -45,23 +46,6 @@ const PHASE_META: Record = { - Native: "bg-emerald-500/20 text-emerald-400 border-emerald-500/30", - Standard: "bg-amber-500/20 text-amber-400 border-amber-500/30", - Extended: "bg-orange-500/20 text-orange-400 border-orange-500/30", - Advanced: "bg-rose-500/20 text-rose-400 border-rose-500/30", - Deep: "bg-purple-500/20 text-purple-400 border-purple-500/30", - Comprehensive: "bg-red-500/20 text-red-400 border-red-500/30", -}; - -const EFFORT_E_LEVEL: Record = { - Standard: "E1", - Extended: "E2", - Advanced: "E3", - Deep: "E4", - Comprehensive: "E5", -}; - const AGENT_TYPE_COLORS: Record = { Engineer: "bg-orange-500/15 text-orange-400 border-orange-500/20", Architect: "bg-blue-500/15 text-blue-400 border-blue-500/20", @@ -118,12 +102,15 @@ export default function PhaseDetailPanel({ state }: { state: AlgorithmState }) { {state.currentPhase} - - {EFFORT_E_LEVEL[state.effortLevel || state.sla] && ( - {EFFORT_E_LEVEL[state.effortLevel || state.sla]} - )} - {state.effortLevel || state.sla} - + {(() => { + const eff = resolveEffortTier(state.effortLevel || state.sla); + const cls = eff.tier ? TIER_BADGE_CLASSES[eff.tier] : TIER_BADGE_CLASSES.pending; + return ( + + {eff.label} + + ); + })()}

{narrativeText} diff --git a/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/UnifiedWorkDashboard.tsx b/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/UnifiedWorkDashboard.tsx index 6bda07759e..878822c764 100644 --- a/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/UnifiedWorkDashboard.tsx +++ b/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/components/activity/UnifiedWorkDashboard.tsx @@ -13,6 +13,7 @@ import QuickPulseStrip from "./QuickPulseStrip"; import SessionCard from "./SessionCard"; import CompletedSessionRow from "./CompletedSessionRow"; import EmptyStateGuide from "@/components/EmptyStateGuide"; +import { resolveEffortTier, TIER_TEXT_CLASSES, EFFORT_TIERS } from "@/lib/effort-tier"; import { Eye, Brain, @@ -39,34 +40,6 @@ import { // ─── Effort Level Colors ─── -const EFFORT_COLORS: Record = { - Native: "bg-amber-600/20 text-amber-500 border-amber-600/30", - Standard: "bg-amber-500/20 text-amber-400 border-amber-500/30", - Extended: "bg-orange-500/20 text-orange-400 border-orange-500/30", - Advanced: "bg-rose-500/20 text-rose-400 border-rose-500/30", - Deep: "bg-purple-500/20 text-purple-400 border-purple-500/30", - Comprehensive: "bg-red-500/20 text-red-400 border-red-500/30", -}; - -const EFFORT_TEXT_COLORS: Record = { - Native: "text-amber-500", - Standard: "text-amber-400", - Extended: "text-orange-400", - Advanced: "text-rose-400", - Deep: "text-purple-400", - Comprehensive: "text-red-400", -}; - -const EFFORT_LEVELS = ["Native", "Standard", "Extended", "Advanced", "Deep", "Comprehensive"] as const; - -const EFFORT_E_LEVEL: Record = { - Standard: "E1", - Extended: "E2", - Advanced: "E3", - Deep: "E4", - Comprehensive: "E5", -}; - // ─── Phase Config ─── const PHASE_META: Record< @@ -1322,26 +1295,23 @@ function WorkRow({

{/* Effort level scale — all levels shown, active one colored */}
- {EFFORT_LEVELS.map((level, i) => { - const tier = (item.algorithmState?.effortLevel || item.algorithmState?.sla || "Standard") as string; - const isActive = level === tier; - const eLevel = EFFORT_E_LEVEL[level]; - return ( - - - {eLevel && {eLevel}} - {level} - - {level === "Native" && |} - - ); - })} + {(() => { + const resolved = resolveEffortTier(item.algorithmState?.effortLevel || item.algorithmState?.sla); + return EFFORT_TIERS.map((tier) => { + const isActive = resolved.tier === tier; + return ( + + + {tier} + + + ); + }); + })()}
| diff --git a/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/lib/effort-tier.ts b/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/lib/effort-tier.ts new file mode 100644 index 0000000000..ad41d05a94 --- /dev/null +++ b/Releases/v5.0.0/.claude/PAI/PULSE/Observability/src/lib/effort-tier.ts @@ -0,0 +1,50 @@ +export type EffortTier = "E1" | "E2" | "E3" | "E4" | "E5"; + +// Legacy quality word → tier. NOTE: "standard" is intentionally absent (it is a +// not-yet-classified placeholder; rendering it as E1 was the original bug). +const LEGACY_QUALITY_TIER: Record = { + extended: "E2", + advanced: "E3", + deep: "E4", + comprehensive: "E5", +}; + +export interface EffortBadgeModel { + tier: EffortTier | null; // resolved tier, or null when not yet classified + pending: boolean; // true → render a neutral pending state + label: string; // the tier ("E4") or "Pending" +} + +/** + * Resolve the work.json `effort` value into a badge model. + * - A real tier "E1".."E5" (any case) → that tier. + * - A legacy quality word (extended/advanced/deep/comprehensive) → its tier. + * - Anything else (standard / starting / native / turbo / empty / unknown) → pending. NEVER a false E1. + */ +export function resolveEffortTier(raw: string | null | undefined): EffortBadgeModel { + const v = (raw ?? "").trim().toLowerCase(); + const tierMatch = v.match(/^e([1-5])$/); + if (tierMatch) { + const tier = `E${tierMatch[1]}` as EffortTier; + return { tier, pending: false, label: tier }; + } + const legacy = LEGACY_QUALITY_TIER[v]; + if (legacy) return { tier: legacy, pending: false, label: legacy }; + return { tier: null, pending: true, label: "Pending" }; +} + +export const TIER_BADGE_CLASSES: Record = { + E1: "bg-amber-500/15 border-amber-500/30 text-amber-400", + E2: "bg-orange-500/15 border-orange-500/30 text-orange-400", + E3: "bg-rose-500/15 border-rose-500/30 text-rose-400", + E4: "bg-purple-500/15 border-purple-500/30 text-purple-400", + E5: "bg-red-500/15 border-red-500/30 text-red-400", + pending: "bg-zinc-700/40 border-zinc-600/40 text-zinc-500", +}; + +export const TIER_TEXT_CLASSES: Record = { + E1: "text-amber-400", E2: "text-orange-400", E3: "text-rose-400", + E4: "text-purple-400", E5: "text-red-400", +}; + +export const EFFORT_TIERS: readonly EffortTier[] = ["E1", "E2", "E3", "E4", "E5"];