/** * PBL v2 — Milestone reflection + handover (combined) card. * * Per PR 6 D7-B: we merge what the refactor/openmaic-pbl repo had as * TWO separate cards (MilestoneNarrativeCard + HandoverCard) into ONE * card so the learner doesn't have to scroll past evaluation prose * to reach the "继续到下一阶段" button. * * Card sections (top → bottom): * 1. Header strip: 阶段完成 ribbon + StarRating (plain, no /5) * 2. Narrative paragraph (the LLM's prose, with the JSON tail * stripped via stripEvaluationTail) * 3. "你学到了" bullets (from evaluation.strengths a.k.a. `learned`) * 4. "这阶段你..." prose line (from evaluation.improvements[0] * a.k.a. `performance`) * 5. Handover footer: next-milestone label + "继续到下一阶段" CTA. * The button is the ONLY way to cross into the next stage — * while `pendingHandover.consumed === false` the next milestone * stays LOCKED in the store (gate enforced by continueAfter * Handover in operations/kernel/progress.ts). * * Visual scope: * - full-width (not a bubble); breaks out of the chat's max-width * cell so the card feels like a "moment", not a message. * - gentle gradient background to distinguish from regular bubbles. * * Final-milestone case: when there is no `pendingHandover` (we just * completed the LAST milestone of the project) the footer renders a * "项目即将完成" label instead of the Continue button. The * CompletionCtaCard renders separately AFTER the final evaluation * finishes streaming. */ 'use client'; import { ArrowRight, CheckCircle2, Flag } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/hooks/use-i18n'; import { sanitizeMilestoneEvaluationFeedback, stripEvaluationTail, } from '@/lib/pbl/v2/operations/runtime/eval-tail-parser'; import { MarkdownText } from '../markdown-text'; import { StarRating } from './star-rating'; import type { PBLEvaluation, PBLHandover } from '@/lib/pbl/v2/types'; import { trimmedPBLText } from '@/lib/pbl/v2/readers'; interface Props { evaluation: PBLEvaluation; /** When present, render the Continue CTA. When absent (final * milestone), render the project-completion label instead. */ handover?: PBLHandover; /** Called when the learner clicks "继续到下一阶段". The wiring * (continueAfterHandover + open-task setup) lives in PR 6.6. */ onContinue?: () => void; className?: string; } export function MilestoneCard({ evaluation, handover, onContinue, className }: Props) { const { t } = useI18n(); if (evaluation.kind !== 'milestone') return null; const learned = evaluation.strengths ?? []; const performance = (evaluation.improvements ?? [])[0] ?? ''; const stars = typeof evaluation.stars === 'number' ? evaluation.stars : null; const rawNarrative = stripEvaluationTail( sanitizeMilestoneEvaluationFeedback(trimmedPBLText(evaluation.feedback)), ); const narrative = handover ? rawNarrative : stripFinalMilestoneContinueGuidance(rawNarrative); const ctaState = milestoneHandoverCtaState(handover); const consumed = ctaState === 'consumed'; return (