Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
const EFFORT_CONFIG: Record<string, { bg: string; border: string; text: string; eLevel: string }> = {
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 (
<span
className={`inline-flex items-center gap-1 h-6 px-2.5 text-xs font-bold uppercase tracking-widest border rounded shrink-0 ${config.bg} ${config.border} ${config.text}`}
className={`inline-flex items-center gap-1 h-6 px-2.5 text-xs font-bold uppercase tracking-widest border rounded shrink-0 ${classes}`}
>
<span className="opacity-70">{config.eLevel}</span>
{effort.toUpperCase()}
{label}
</span>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 ───

Expand Down Expand Up @@ -45,23 +46,6 @@ const PHASE_META: Record<string, { color: string; icon: LucideIcon; activeNarrat
},
};

const EFFORT_COLORS: Record<string, string> = {
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<string, string> = {
Standard: "E1",
Extended: "E2",
Advanced: "E3",
Deep: "E4",
Comprehensive: "E5",
};

const AGENT_TYPE_COLORS: Record<string, string> = {
Engineer: "bg-orange-500/15 text-orange-400 border-orange-500/20",
Architect: "bg-blue-500/15 text-blue-400 border-blue-500/20",
Expand Down Expand Up @@ -118,12 +102,15 @@ export default function PhaseDetailPanel({ state }: { state: AlgorithmState }) {
<span className="text-sm font-semibold" style={{ color: phaseMeta?.color ?? "#666" }}>
{state.currentPhase}
</span>
<span className={`px-2 py-0.5 rounded text-[14px] font-medium border ${EFFORT_COLORS[state.effortLevel || state.sla] ?? "bg-zinc-700 text-zinc-400 border-zinc-600/30"}`}>
{EFFORT_E_LEVEL[state.effortLevel || state.sla] && (
<span className="opacity-60 mr-1 font-semibold">{EFFORT_E_LEVEL[state.effortLevel || state.sla]}</span>
)}
{state.effortLevel || state.sla}
</span>
{(() => {
const eff = resolveEffortTier(state.effortLevel || state.sla);
const cls = eff.tier ? TIER_BADGE_CLASSES[eff.tier] : TIER_BADGE_CLASSES.pending;
return (
<span className={`px-2 py-0.5 rounded text-[14px] font-medium border ${cls}`}>
{eff.label}
</span>
);
})()}
</div>
<p className="text-[15px] text-zinc-400 leading-relaxed">
{narrativeText}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -39,34 +40,6 @@ import {

// ─── Effort Level Colors ───

const EFFORT_COLORS: Record<string, string> = {
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<string, string> = {
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<string, string> = {
Standard: "E1",
Extended: "E2",
Advanced: "E3",
Deep: "E4",
Comprehensive: "E5",
};

// ─── Phase Config ───

const PHASE_META: Record<
Expand Down Expand Up @@ -1322,26 +1295,23 @@ function WorkRow({
<div className="pl-[26px] flex items-center gap-3">
{/* Effort level scale — all levels shown, active one colored */}
<div className="flex items-center gap-1.5">
{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 (
<Fragment key={level}>
<span
className={`text-xs font-medium uppercase tracking-wide transition-colors ${
isActive
? `${EFFORT_TEXT_COLORS[level] ?? "text-zinc-300"} font-bold`
: "text-zinc-700"
}`}
>
{eLevel && <span className="opacity-60 mr-0.5">{eLevel}</span>}
{level}
</span>
{level === "Native" && <span className="text-zinc-700 text-xs">|</span>}
</Fragment>
);
})}
{(() => {
const resolved = resolveEffortTier(item.algorithmState?.effortLevel || item.algorithmState?.sla);
return EFFORT_TIERS.map((tier) => {
const isActive = resolved.tier === tier;
return (
<Fragment key={tier}>
<span
className={`text-xs font-medium uppercase tracking-wide transition-colors ${
isActive ? `${TIER_TEXT_CLASSES[tier]} font-bold` : "text-zinc-700"
}`}
>
{tier}
</span>
</Fragment>
);
});
})()}
</div>

<span className="text-zinc-700 text-xs">|</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, EffortTier> = {
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<EffortTier | "pending", string> = {
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<EffortTier, string> = {
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"];