feat(card): show the intake brief and transcript
This commit is contained in:
parent
d5cf51c944
commit
5a9d1cb97f
2 changed files with 118 additions and 0 deletions
|
|
@ -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}
|
||||
/>
|
||||
|
||||
<CardBrief
|
||||
brief={detail.card.brief}
|
||||
transcript={detail.card.transcript}
|
||||
sizeBand={detail.card.size_band ?? ""}
|
||||
/>
|
||||
|
||||
<CardNarrative narrative={detail.card.narrative ?? ""} />
|
||||
<CommitHistory commits={detail.commits} />
|
||||
|
||||
|
|
|
|||
111
src/components/card/card-brief.tsx
Normal file
111
src/components/card/card-brief.tsx
Normal file
|
|
@ -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 (
|
||||
<section className="space-y-3 rounded-[var(--radius-tile)] border border-border-subtle bg-surface p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wide text-text-secondary">
|
||||
What was asked for
|
||||
</h2>
|
||||
{band && <Pill tone="neutral">{band}</Pill>}
|
||||
{turns.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => 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"
|
||||
>
|
||||
<MessageSquare size={12} strokeWidth={2} />
|
||||
{showTranscript ? "Hide the conversation" : "Show the conversation"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{parsed.summary && (
|
||||
<p className="whitespace-pre-wrap text-sm text-text-secondary">
|
||||
{parsed.summary}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{openQuestions.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-[11px] font-medium uppercase tracking-wide text-text-muted">
|
||||
Still to confirm
|
||||
</h3>
|
||||
<ul className="space-y-0.5">
|
||||
{openQuestions.map((question, index) => (
|
||||
<li key={index} className="text-sm text-text-muted">
|
||||
{question}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showTranscript && turns.length > 0 && (
|
||||
<ol className="space-y-2.5 border-t border-border-subtle pt-3">
|
||||
{turns.map((turn, index) => (
|
||||
<li key={index} className="space-y-0.5">
|
||||
<p className="text-xs text-text-muted">{turn.question}</p>
|
||||
<p className="whitespace-pre-wrap text-sm text-text-secondary">
|
||||
{turn.answer}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue