Wave-5 refinement round 5 (owner): glyph-shaped SVG marks, vertically centered

- Option rows: items-start → items-center — the icon span's top-misalign
  came from the row alignment; now marks center across the row.
- The literal ✓/✗ look (owner preference over the round-3 lucide set)
  re-rendered as hand-rolled SVG with thick rounded strokes (strokeWidth
  3, round caps/joins) — the glyph shape, as a true vector, sized like
  the row text. Test pins updated to data-mark=check|x.
This commit is contained in:
amosblomqvist
2026-09-13 01:57:17 +03:00
parent 5f4d962168
commit cdc697749c
2 changed files with 24 additions and 11 deletions
+21 -8
View File
@@ -1,6 +1,5 @@
"use client"; "use client";
import { CheckIcon, XIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { MessageResponse } from "@/components/ai-elements/message"; import { MessageResponse } from "@/components/ai-elements/message";
import type { QuestionAnswerData } from "@/src/core"; import type { QuestionAnswerData } from "@/src/core";
@@ -51,7 +50,7 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) {
<div <div
key={opt.index} key={opt.index}
className={cn( className={cn(
"flex items-start gap-2 rounded-lg border px-3 py-1.5", "flex items-center gap-2 rounded-lg border px-3 py-1.5",
sel && key && "border-green-500/60 bg-green-500/10", sel && key && "border-green-500/60 bg-green-500/10",
sel && !key && isQuiz && "border-red-500/60 bg-red-500/10", sel && !key && isQuiz && "border-red-500/60 bg-red-500/10",
sel && !isQuiz && "border-primary bg-accent", sel && !isQuiz && "border-primary bg-accent",
@@ -59,19 +58,33 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) {
)} )}
> >
{isQuiz && ( {isQuiz && (
// SVG icons, not glyphs — exactly one text-size box (size-4), // Hand-rolled SVG in the ✓/✗ GLYPH look (thick rounded strokes —
// vertically centered in the flex row (owner). // owner prefers that shape but as vectors, round 5). Sized like
// the row text, centered (row is items-center).
<span <span
aria-hidden aria-hidden
data-mark={sel && key ? "check" : sel && !key ? "x" : !sel && key ? "check" : undefined}
className={cn( className={cn(
"flex size-4 shrink-0 items-center justify-center", "flex shrink-0 items-center",
sel && key && "text-green-500", sel && key && "text-green-500",
sel && !key && "text-red-500", sel && !key && "text-red-500",
!sel && key && "text-green-500", !sel && key && "text-green-500",
)} )}
> >
{key && <CheckIcon className="size-4" strokeWidth={2.5} />} {(sel && key) || (!sel && key) ? (
{sel && !key && <XIcon className="size-4" strokeWidth={2.5} />} <svg viewBox="0 0 16 16" className="size-4" fill="none" stroke="currentColor"
strokeWidth={3} strokeLinecap="round" strokeLinejoin="round">
<path d="M3 8.5 L6.5 12 L13 4.5" />
</svg>
) : (
sel &&
!key && (
<svg viewBox="0 0 16 16" className="size-4" fill="none" stroke="currentColor"
strokeWidth={3} strokeLinecap="round">
<path d="M4 4 L12 12 M12 4 L4 12" />
</svg>
)
)}
</span> </span>
)} )}
<span className={cn(!sel && "text-muted-foreground")}> <span className={cn(!sel && "text-muted-foreground")}>
@@ -86,7 +99,7 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) {
{answer.other && ( {answer.other && (
<div <div
data-testid="answered-other" data-testid="answered-other"
className="flex items-start gap-2 rounded-lg border border-primary bg-accent px-3 py-1.5" className="flex items-center gap-2 rounded-lg border border-primary bg-accent px-3 py-1.5"
> >
<span>“{answer.other}”</span> <span>“{answer.other}”</span>
</div> </div>
+3 -3
View File
@@ -168,8 +168,8 @@ describe("render smoke tests", () => {
const html = renderToStaticMarkup(<AnsweredCard answer={answer} />); const html = renderToStaticMarkup(<AnsweredCard answer={answer} />);
assert.match(html, /incorrect/); assert.match(html, /incorrect/);
assert.match(html, /innermost/, "explanation renders post-answer"); assert.match(html, /innermost/, "explanation renders post-answer");
// Vector marks (lucide SVG) — not glyphs — sized like the row text (owner). // Glyph-shaped SVG marks (owner preference) — pinned via data-mark.
assert.ok(html.includes("lucide-check") && html.includes("lucide-x")); assert.ok(html.includes('data-mark="check"') && html.includes('data-mark="x"'));
}); });
it("AnsweredCard renders the learner's own Other answer AS an option row", () => { it("AnsweredCard renders the learner's own Other answer AS an option row", () => {
@@ -214,7 +214,7 @@ describe("render smoke tests", () => {
}; };
const answer = buildAnswer(qd, { values: ["Somewhat"] }); const answer = buildAnswer(qd, { values: ["Somewhat"] });
const html = renderToStaticMarkup(<AnsweredCard answer={answer} />); const html = renderToStaticMarkup(<AnsweredCard answer={answer} />);
assert.ok(!html.includes("incorrect") && !html.includes("lucide-x"), "ask answers never get quiz grading UI"); assert.ok(!html.includes("incorrect") && !html.includes('data-mark="x"'), "ask answers never get quiz grading UI");
assert.match(html, /Somewhat/); assert.match(html, /Somewhat/);
}); });