)}
- {/* The reading channel (owner 2026-10-03): thoughts captured while
- reading the teaching prose — a separate block from the question
- thinking, same quiet voice. */}
- {answer.readingThinking && (
-
-
- Your thinking while reading
-
- {answer.readingThinking}
-
- )}
-
{/* px-3→removed: card p-4 + box px-3 double-inset sat the "Why" label deeper
than the option box edges (owner, round 3). Sides flush with everything
else; py-2 stays for vertical air. */}
@@ -190,5 +188,6 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) {
)}
+ >
);
}
diff --git a/components/practice-cards.tsx b/components/practice-cards.tsx
index fce3a3e..c895100 100644
--- a/components/practice-cards.tsx
+++ b/components/practice-cards.tsx
@@ -17,9 +17,13 @@ import { cn } from "@/lib/utils";
* (the committed solution, shown when the problem ended). A dismissal
* synthesis (outcome:"dismissed") renders as one muted line.
* - ThinkingCard — the learner's own submission, user-side: the register
- * is the two buttons' (Nudge = working trace, Commit = answer).
+ * is the two buttons' (Nudge = working trace, Commit = answer). The
+ * UNIFIED ThinkingBlock (owner round 2026-10-05): the quiz quote-block
+ * language in a faint user-side shell, never collapsible.
*/
+import { ThinkingBlock } from "@/components/thinking-block";
+
/** The practice-thinking marker's details (native-messages.ts). */
export interface PracticeThinkingData {
problemCallId: string;
@@ -115,24 +119,16 @@ export function ConcludeCard({ part }: ArtifactPartProps) {
}
export function ThinkingCard({ data }: { data: PracticeThinkingData }) {
- const [open, setOpen] = useState(true);
+ // One component, two registers (owner ruling): the committed register
+ // carries the primary rail (the quiz answer thinking's paint), the
+ // working register the muted one.
return (
-
-
- {open && (
-
{data.text}
- )}
-
+
);
}
diff --git a/components/thinking-block.tsx b/components/thinking-block.tsx
new file mode 100644
index 0000000..0299017
--- /dev/null
+++ b/components/thinking-block.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+
+/** The unified user-thinking block (owner round, 2026-10-05): ONE visual
+ * language for every surface where the learner's own thinking renders —
+ * practice thinking traces (nudge / commit), the reading channel lifted
+ * above the answered card. The recipe is the quiz cards' quote block
+ * verbatim (rail + uppercase label + prose), carried by a faint shell
+ * so it still reads user-side at a glance — right-aligned, message
+ * width, never collapsible (they arrive open; collapsing bought
+ * nothing and cost the chevron-on-the-right weirdness).
+ *
+ * Rails reuse the existing vocabulary: primary for the committed
+ * register (the quiz answer thinking, practice commits), muted for the
+ * working register (nudges, reading thoughts). */
+
+export function ThinkingBlock({
+ label,
+ text,
+ rail = "muted",
+ testId,
+ dataCommit,
+}: {
+ label: string;
+ text: string;
+ rail?: "primary" | "muted";
+ testId?: string;
+ /** The practice marker's register flag (commit vs nudge). */
+ dataCommit?: boolean;
+}) {
+ return (
+
+
+
+ {label}
+
+ {text}
+
+
+ );
+}
diff --git a/tests/render-cards.test.tsx b/tests/render-cards.test.tsx
index 58d59f1..acd7661 100644
--- a/tests/render-cards.test.tsx
+++ b/tests/render-cards.test.tsx
@@ -572,15 +572,24 @@ describe("predict vs check rendering", () => {
assert.ok(!html.includes("tucked-explanation"));
});
- it("the reading-thinking channel renders as its own block; legacy `note` still renders", () => {
+ it("the reading-thinking channel is LIFTED above the card as its own user-side block", () => {
const withReading = buildAnswer(quizQD(), {
values: ["Mercury"],
thinking: "answering thought",
readingThinking: "was unsure mid-paragraph",
});
const html = renderToStaticMarkup();
- assert.ok(html.includes("Your thinking while reading"), "the reading channel is labeled distinctly");
- assert.ok(html.includes("answering thought"));
+ // The unified ThinkingBlock (owner round, 2026-10-05): the reading
+ // thoughts render BEFORE the card, user-side (right shell), under the
+ // plain "Your thinking" label — no longer an inset inside the answer.
+ const blockAt = html.indexOf("reading-thinking-block");
+ const cardAt = html.indexOf("answered-card");
+ assert.ok(blockAt !== -1, "the lifted block renders");
+ assert.ok(blockAt < cardAt, "it sits ABOVE the answered card");
+ assert.ok(html.includes("Your thinking"), "the plain label");
+ assert.ok(!html.includes("Your thinking while reading"), "the old verbose label is gone");
+ assert.ok(!html.includes("your-reading-thinking"), "the in-card inset is gone");
+ assert.ok(html.includes("answering thought"), "the answer thinking stays inside the card");
assert.ok(html.includes("was unsure mid-paragraph"));
const legacy = { ...buildAnswer(quizQD(), { values: ["Mercury"] }), note: "a 2026-09 answer" };
const legacyHtml = renderToStaticMarkup();