ui: conclude card — quiz-chip outcomes, verdict-first; YOUR THINKING / AGENT THINKING

Owner round 3 (2026-10-05):

- CONCLUDE CARD: the stacked same-weight headings are gone. Plain
  'Concluded' eyebrow + the outcome in the quiz cards' EXACT chip
  register. Colors per outcome: solved green (the 'correct' twin),
  solvedWithHints amber (succeeded with cost), surrendered red (the
  problem won), movedOn muted (the agent's call, not a verdict against
  the learner). Verdict as plain 50% prose under the heading — the
  'Verdict:' label is gone — then a divider, then the labeled model
  solution.
- ALL-CAPS: 'YOUR THINKING' and 'AGENT THINKING' — display strings only
  (the model-facing marker label stays sentence-case). The two rows are
  equal-length over the shared rail, so the chevrons align vertically.
  Dock field labels match.
This commit is contained in:
amosblomqvist
2026-10-05 04:14:21 +03:00
parent b770834f29
commit 05f083748b
8 changed files with 62 additions and 35 deletions
+1 -1
View File
@@ -137,7 +137,7 @@ const defaultGetThinkingMessage = (isStreaming: boolean) => {
if (isStreaming) {
return <Shimmer duration={1}>Thinking...</Shimmer>;
}
return <p>Agent's thinking</p>;
return <p>AGENT THINKING</p>;
};
export const ReasoningTrigger = memo(
+2 -2
View File
@@ -41,7 +41,7 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) {
ThinkingBlock, instead of an inset inside the answer. Same muted
rail as before; the label is now plain "Your thinking". */}
{answer.readingThinking && (
<ThinkingBlock testId="reading-thinking-block" label="Your thinking" text={answer.readingThinking} />
<ThinkingBlock testId="reading-thinking-block" label="YOUR THINKING" text={answer.readingThinking} />
)}
<div className="rounded-xl border bg-card p-4 text-card-foreground" data-testid="answered-card">
<div className="mb-1 flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
@@ -139,7 +139,7 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) {
thinking is the identical collapsible ThinkingBlock — it just
happens to sit inside the quiz container. Same placement, same
trigger, same 50%-italic quote block on expand. */
<ThinkingBlock testId="your-thinking" label="Your thinking" text={answer.thinking ?? answer.note ?? ""} />
<ThinkingBlock testId="your-thinking" label="YOUR THINKING" text={answer.thinking ?? answer.note ?? ""} />
)}
{/* px-3→removed: card p-4 + box px-3 double-inset sat the "Why" label deeper
+42 -18
View File
@@ -1,6 +1,7 @@
"use client";
import { MessageResponse } from "@/components/ai-elements/message";
import { cn } from "@/lib/utils";
import type { ArtifactPartProps } from "@/src/core";
/**
@@ -54,6 +55,19 @@ const OUTCOME_LABEL: Record<string, string> = {
dismissed: "Dismissed",
};
/* The outcome chips (owner round 3, 2026-10-05): the EXACT register of the
* quiz cards' correct/incorrect bubbles. Colors per outcome — solved reads
* green (the "correct" twin), solved-with-hints amber (succeeded with
* cost), surrendered red (the problem beat the learner), moved on muted
* (the agent's call, not a verdict against him). */
const OUTCOME_CHIP: Record<string, string> = {
solved: "bg-green-500/15 text-green-500",
solvedWithHints: "bg-amber-500/15 text-amber-500",
movedOn: "bg-muted text-muted-foreground",
surrendered: "bg-red-500/15 text-red-500",
dismissed: "bg-muted text-muted-foreground",
};
export function ConcludeCard({ part }: ArtifactPartProps) {
// The conclusion redesigned (owner round, 2026-10-05): the SOLUTION is
// the hero — the modelSolution committed at pose time (part.reveal, the
@@ -79,26 +93,36 @@ export function ConcludeCard({ part }: ArtifactPartProps) {
return (
<div data-testid="conclude-card" className="lh-card-enter my-1 rounded-xl border bg-card p-4">
<div className="mb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground">
{OUTCOME_LABEL[outcome] ?? "Concluded"}
{/* The heading row (owner round 3, 2026-10-05): plain 'Concluded'
eyebrow + the outcome in the quiz cards' EXACT chip register —
no more two same-weight headings stacked. */}
<div className="mb-1 flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
Concluded
{(OUTCOME_LABEL[outcome] ?? "") !== "" && outcome !== "dismissed" && (
<span className={cn("rounded px-1.5 py-0.5", OUTCOME_CHIP[outcome] ?? OUTCOME_CHIP.movedOn)}>
{OUTCOME_LABEL[outcome]}
</span>
)}
</div>
{/* LABELED SECTIONS (owner round 2, 2026-10-05): 'Solved' alone over
unlabeled prose didn't read as the model solution — the hero now
carries its own label, and the verdict section mirrors it. Same
register as the quiz cards' section labels. */}
{reveal.length > 0 && (
<div>
<div className="mb-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">Model solution</div>
<div className="lh-prose text-[15px]">
<MessageResponse>{reveal}</MessageResponse>
</div>
{/* The verdict — plain prose under the heading, no 'Verdict:' label
(owner round 3): the same quiet 50% register as quiz explanations. */}
{verdict.length > 0 && (
<div className="text-sm text-foreground/50">
<MessageResponse>{verdict}</MessageResponse>
</div>
)}
{verdict.length > 0 && (
<div className="mt-3 rounded-lg bg-muted py-2 text-sm text-foreground/50">
<span className="font-medium text-muted-foreground">Verdict: </span>
<MessageResponse className="inline [&_p]:inline [&_p]:m-0">{verdict}</MessageResponse>
</div>
{/* Then the divider, then the labeled model solution (Slice G: the
hero is the pose's commitment, verbatim). */}
{reveal.length > 0 && (
<>
<div className="my-3 border-t" />
<div>
<div className="mb-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">Model solution</div>
<div className="lh-prose text-[15px]">
<MessageResponse>{reveal}</MessageResponse>
</div>
</div>
</>
)}
</div>
);
@@ -108,5 +132,5 @@ export function ThinkingCard({ data }: { data: PracticeThinkingData }) {
// The unified row (owner rounds, 2026-10-05): one style, one label —
// collapsed arrival, "Your thinking", 50%-italic quote block on expand.
// Legacy records' commit flag is deliberately unread (one lane now).
return <ThinkingBlock testId="thinking-card" label="Your thinking" text={data.text} />;
return <ThinkingBlock testId="thinking-card" label="YOUR THINKING" text={data.text} />;
}
+1 -1
View File
@@ -105,7 +105,7 @@ export function PracticeDock({ problemCallId, input, expanded, onExpand, onSubmi
className="m-1.5 rounded-xl border border-border/70 bg-background/60 transition-colors focus-within:border-primary/50"
>
<div className="px-2.5 pt-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
Your thinking
YOUR THINKING
</div>
<textarea
value={thinking}
+1 -1
View File
@@ -772,7 +772,7 @@ function QuestionDockForm({
className="rounded-xl border border-border/70 bg-background/60 transition-colors focus-within:border-primary/50"
>
<div className="px-2.5 pt-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
{qd.mode === "text" ? "Your answer" : "Your thinking"}
{qd.mode === "text" ? "Your answer" : "YOUR THINKING"}
</div>
{qd.mode === "text" ? (
<textarea
+1 -1
View File
@@ -18,7 +18,7 @@ import { cn } from "@/lib/utils";
* agent thinking = no rail. */
export function ThinkingBlock({
label = "Your thinking",
label = "YOUR THINKING",
text,
testId,
dataCommit,