diff --git a/src/app/p/[clientSlug]/[projectSlug]/c/[cardId]/page.tsx b/src/app/p/[clientSlug]/[projectSlug]/c/[cardId]/page.tsx index 9a5ad43..3f6666d 100644 --- a/src/app/p/[clientSlug]/[projectSlug]/c/[cardId]/page.tsx +++ b/src/app/p/[clientSlug]/[projectSlug]/c/[cardId]/page.tsx @@ -6,6 +6,7 @@ import { AppShell } from "@/components/shell/app-shell"; import { TopBar } from "@/components/shell/top-bar"; import { CardHeader } from "@/components/card/card-header"; import { CardActions } from "@/components/card/card-actions"; +import { CardBrief } from "@/components/card/card-brief"; import { CardNarrative } from "@/components/card/card-narrative"; import { CommitHistory } from "@/components/card/commit-history"; import { SubtaskList } from "@/components/card/subtask-list"; @@ -66,6 +67,12 @@ export default async function CardPage({ projectSlug={projectSlug} /> + + diff --git a/src/components/card/card-brief.tsx b/src/components/card/card-brief.tsx new file mode 100644 index 0000000..5b8f1b4 --- /dev/null +++ b/src/components/card/card-brief.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { useState } from "react"; +import { MessageSquare } from "lucide-react"; + +import { Pill } from "@/components/ui/pill"; + +interface StoredBrief { + title?: string; + summary?: string; + subtasks?: string[]; + openQuestions?: string[]; +} + +interface StoredTurn { + question?: string; + answer?: string; +} + +const BAND_LABEL: Record<"small" | "medium" | "large", string> = { + small: "Looks small", + medium: "Looks medium", + large: "Looks large", +}; + +/** + * What the client asked for, as they asked for it. + * + * Shown to both roles. The developer needs it to build the right thing; the + * client needs to see their request recorded faithfully. The transcript is + * behind a toggle — it is the evidence, not the summary. + * + * Everything is rendered as text. This is client-authored content that passed + * through a model, which is the least trustworthy string in the product. + */ +export function CardBrief({ + brief, + transcript, + sizeBand, +}: { + brief: unknown; + transcript: unknown; + sizeBand: string; +}) { + const [showTranscript, setShowTranscript] = useState(false); + + const parsed = (brief ?? {}) as StoredBrief; + const turns = Array.isArray(transcript) ? (transcript as StoredTurn[]) : []; + + const openQuestions = Array.isArray(parsed.openQuestions) ? parsed.openQuestions : []; + + if (!parsed.summary && turns.length === 0) return null; + + const band = BAND_LABEL[sizeBand as keyof typeof BAND_LABEL]; + + return ( + + + + What was asked for + + {band && {band}} + {turns.length > 0 && ( + setShowTranscript((value) => !value)} + aria-pressed={showTranscript} + className="ml-auto inline-flex items-center gap-1.5 text-[11px] text-text-muted hover:text-text-secondary" + > + + {showTranscript ? "Hide the conversation" : "Show the conversation"} + + )} + + + {parsed.summary && ( + + {parsed.summary} + + )} + + {openQuestions.length > 0 && ( + + + Still to confirm + + + {openQuestions.map((question, index) => ( + + {question} + + ))} + + + )} + + {showTranscript && turns.length > 0 && ( + + {turns.map((turn, index) => ( + + {turn.question} + + {turn.answer} + + + ))} + + )} + + ); +}
+ {parsed.summary} +
{turn.question}
+ {turn.answer} +