)}
);
}
function asText(v: unknown): string {
return typeof v === "string" ? v : v == null ? "" : JSON.stringify(v, null, 2);
}
// Q&A: each entry is the answer text for a turn (a string), or an older
// {turn, question_id, answer} dict. Render each as a turn card with the answer
// as markdown so numbered sub-answers / bold render readably.
function QASection({ entries }: { entries: unknown[] }) {
return (
);
}
// Review verdicts: each {verdict, round_index, outcome, findings} → a card with a
// verdict badge + round + outcome header and the findings rendered as markdown.
function VerdictsSection({ entries }: { entries: unknown[] }) {
return (
);
}
// Plan: {summary, phases:[{name, steps:[...]}]} → summary (markdown) + a phase
// list with nested steps. Any extra scalar keys render as a small label/value
// list; an unrecognized shape falls back to a collapsed JSON view.
function PlanSection({ plan }: { plan: Record }) {
const summary = asText(plan.summary);
const phases = Array.isArray(plan.phases) ? plan.phases : null;
const extra = Object.entries(plan).filter(
([k, val]) =>
k !== "summary" && k !== "phases" && (typeof val !== "object" || val === null),
);
if (!summary && !phases) {
return (