ui: round three — the tight band, one geometry everywhere
Owner round three on the user rows: (1) the band padding halves, py-2.5 -> py-[5px] — subtle, not tall; same for the messages. (2) The in-card thinking joins the band: identical styling and heights for collapsed thinking in the chat and inside question cards (card-on-card is quiet by design; revert if it reads too light). (3) The left gap MATCHES the new 5px padding, so the rail fills the rest of the 3ch inset - border-l-[calc(3ch-5px)] pl-[5px]: a thick muted rail, total inset still 3ch (the chevron-alignment law survives). The banded prop dies - one geometry, cards included.
This commit is contained in:
@@ -143,16 +143,11 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) {
|
|||||||
|
|
||||||
{(answer.thinking ?? answer.note) && (
|
{(answer.thinking ?? answer.note) && (
|
||||||
/* The SAME unified row (owner round, 2026-10-05): the quiz answer's
|
/* The SAME unified row (owner round, 2026-10-05): the quiz answer's
|
||||||
thinking is the identical collapsible ThinkingBlock — it just
|
thinking is the identical collapsible ThinkingBlock — and since
|
||||||
happens to sit INSIDE the card, so it takes the rail but no band
|
owner round three (2026-10-07) IDENTICALLY styled, band and all:
|
||||||
(owner round two, 2026-10-07: card-color on card-color would be
|
collapsed thinking matches in height everywhere, card or chat
|
||||||
invisible; geometry alone carries it there). */
|
(card-on-card is quiet by design; too light → revert). */
|
||||||
<ThinkingBlock
|
<ThinkingBlock testId="your-thinking" label="YOUR THINKING" text={answer.thinking ?? answer.note ?? ""} />
|
||||||
testId="your-thinking"
|
|
||||||
label="YOUR THINKING"
|
|
||||||
text={answer.thinking ?? answer.note ?? ""}
|
|
||||||
banded={false}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* px-3→removed: card p-4 + box px-3 double-inset sat the "Why" label deeper
|
{/* px-3→removed: card p-4 + box px-3 double-inset sat the "Why" label deeper
|
||||||
|
|||||||
+6
-5
@@ -1399,13 +1399,14 @@ export function Chat({
|
|||||||
Rendered WITHOUT the Message wrapper — that is the bubble. */
|
Rendered WITHOUT the Message wrapper — that is the bubble. */
|
||||||
<div
|
<div
|
||||||
data-testid="user-message"
|
data-testid="user-message"
|
||||||
/* THE DOCUMENT, ALL-IN (owner round two, 2026-10-07): no
|
/* THE DOCUMENT, ALL-IN (owner rounds two+three, 2026-10-07):
|
||||||
chat side — the user message is a full-width card-color
|
no chat side — the user message is a full-width card-color
|
||||||
BAND with the 6px rail, the same geometry as the thinking
|
BAND, the IDENTICAL geometry as the thinking rows: 5px band
|
||||||
rows (the band is the authorship mark against the agent's
|
padding, the left gap matches it, the rail fills the 3ch
|
||||||
|
inset (the band is the authorship mark against the agent's
|
||||||
transparent prose). The "You" label stays — one register
|
transparent prose). The "You" label stays — one register
|
||||||
with the thinking labels. */
|
with the thinking labels. */
|
||||||
className="my-2 w-full border-l-[6px] border-muted-foreground/30 bg-card py-2.5 pl-[calc(3ch-6px)] pr-2 font-ui text-xs"
|
className="my-2 w-full border-l-[calc(3ch-5px)] border-muted-foreground/30 bg-card py-[5px] pl-[5px] pr-2 font-ui text-xs"
|
||||||
>
|
>
|
||||||
{/* The rail GROUPS (owner round 4, 2026-10-05): the YOU heading
|
{/* The rail GROUPS (owner round 4, 2026-10-05): the YOU heading
|
||||||
sits INSIDE the rail, same mono-xs register and same 1ch
|
sits INSIDE the rail, same mono-xs register and same 1ch
|
||||||
|
|||||||
@@ -17,17 +17,19 @@ import { cn } from "@/lib/utils";
|
|||||||
* The glance-level law (owner): user thinking = rail + 50% italic;
|
* The glance-level law (owner): user thinking = rail + 50% italic;
|
||||||
* agent thinking = no rail.
|
* agent thinking = no rail.
|
||||||
*
|
*
|
||||||
* THE DOCUMENT, ALL-IN (owner round two, 2026-10-07): no chat sides —
|
* THE DOCUMENT, ALL-IN (owner rounds two+three, 2026-10-07): no chat
|
||||||
* transcript-level user rows (this block, the "You" message) are
|
* sides — user rows are full-width CARD-COLOR BANDS (the band is the
|
||||||
* full-width CARD-COLOR BANDS: the band is the authorship mark against
|
* authorship mark against the agent's transparent prose) with the rail
|
||||||
* the agent's transparent prose, and the rail runs along the band's left
|
* along the left edge. ONE geometry everywhere, in cards and out: the
|
||||||
* edge. The rail keeps its muted color but is now 6px wide, eating the
|
* band padding is a tight 5px (round three: halved — subtle, not tall),
|
||||||
* artificial air of the old 3ch inset — the total inset STAYS 3ch so the
|
* and the left gap MATCHES it, so the rail fills the rest of the 3ch
|
||||||
* chevron-alignment law with the agent's flush rows survives; the gap
|
* inset — border-l-[calc(3ch-5px)] pl-[5px]. Total inset stays 3ch (the
|
||||||
* between rail and text drops to natural (3ch - 6px). In-card rows
|
* chevron-alignment law with the agent's flush rows survives); the rail
|
||||||
* (inside the answered card, on the card's own background) take the rail
|
* grows as the padding shrinks. In-card rows carry the identical
|
||||||
* but NO band (`banded: false`). `defaultOpen` marks transcript rows,
|
* styling (owner round three: same band, same heights — card-on-card is
|
||||||
* which arrive EXPANDED (clearly conveyed that the thinking was sent).
|
* quiet by design; if it reads too light we revert). `defaultOpen`
|
||||||
|
* marks transcript rows, which arrive EXPANDED (clearly conveyed that
|
||||||
|
* the thinking was sent).
|
||||||
*
|
*
|
||||||
* ⌘T (toggle-user-thinking) sets EVERY row to one state via the module
|
* ⌘T (toggle-user-thinking) sets EVERY row to one state via the module
|
||||||
* store below — one source of truth, no provider tree: rows subscribe on
|
* store below — one source of truth, no provider tree: rows subscribe on
|
||||||
@@ -66,7 +68,6 @@ export function ThinkingBlock({
|
|||||||
text,
|
text,
|
||||||
testId,
|
testId,
|
||||||
dataCommit,
|
dataCommit,
|
||||||
banded = true,
|
|
||||||
defaultOpen = false,
|
defaultOpen = false,
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -74,8 +75,6 @@ export function ThinkingBlock({
|
|||||||
testId?: string;
|
testId?: string;
|
||||||
/** The practice marker's register flag (commit vs nudge). */
|
/** The practice marker's register flag (commit vs nudge). */
|
||||||
dataCommit?: boolean;
|
dataCommit?: boolean;
|
||||||
/** Transcript rows carry the card-color band; in-card rows rail only. */
|
|
||||||
banded?: boolean;
|
|
||||||
/** Transcript rows arrive expanded (owner, 2026-10-07). */
|
/** Transcript rows arrive expanded (owner, 2026-10-07). */
|
||||||
defaultOpen?: boolean;
|
defaultOpen?: boolean;
|
||||||
}) {
|
}) {
|
||||||
@@ -101,13 +100,12 @@ export function ThinkingBlock({
|
|||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
data-commit={dataCommit === undefined ? undefined : dataCommit ? "true" : "false"}
|
data-commit={dataCommit === undefined ? undefined : dataCommit ? "true" : "false"}
|
||||||
className={cn(
|
className={cn(
|
||||||
// The rail geometry (owner round two, 2026-10-07): total inset
|
// The rail geometry (owner round three, 2026-10-07): total inset
|
||||||
// STAYS 3ch (the chevron-alignment law with the agent's flush
|
// STAYS 3ch (the chevron-alignment law with the agent's flush
|
||||||
// rows), re-split — 6px rail (was 2px) + a natural 3ch-6px gap
|
// rows). The band padding is a tight 5px (halved from 10px) and
|
||||||
// (was the too-wide 3ch-2px). In-card rows keep the 12px my-3
|
// the left gap MATCHES it — the rail fills the rest:
|
||||||
// rhythm; banded rows sit my-2 apart (the band's own air).
|
// calc(3ch-5px) wide. One geometry everywhere, cards included.
|
||||||
"lh-card-enter w-full border-l-[6px] border-muted-foreground/30 pl-[calc(3ch-6px)] font-ui text-xs",
|
"lh-card-enter my-2 w-full border-l-[calc(3ch-5px)] border-muted-foreground/30 bg-card py-[5px] pl-[5px] font-ui text-xs",
|
||||||
banded ? "my-2 bg-card py-2.5" : "my-3",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -295,13 +295,14 @@ describe("render smoke tests", () => {
|
|||||||
// trigger row only — the thinking text is one click away, never loud.
|
// trigger row only — the thinking text is one click away, never loud.
|
||||||
assert.ok(html.includes('data-testid="your-thinking-trigger"'), "the trigger row");
|
assert.ok(html.includes('data-testid="your-thinking-trigger"'), "the trigger row");
|
||||||
assert.ok(html.includes('aria-expanded="false"'), "arrives collapsed");
|
assert.ok(html.includes('aria-expanded="false"'), "arrives collapsed");
|
||||||
// IN-CARD = RAIL ONLY (owner round two, 2026-10-07): no band on the
|
// IDENTICAL GEOMETRY (owner round three, 2026-10-07): the in-card row
|
||||||
// card's own background; geometry alone carries it there.
|
// carries the band too — collapsed thinking matches in height
|
||||||
|
// everywhere, card or chat.
|
||||||
const yourRow = html.slice(html.indexOf('data-testid="your-thinking"'));
|
const yourRow = html.slice(html.indexOf('data-testid="your-thinking"'));
|
||||||
assert.ok(!yourRow.slice(0, 400).includes("bg-card"), "no band inside the card");
|
assert.ok(yourRow.slice(0, 400).includes("bg-card"), "the band inside the card as well");
|
||||||
// THE RAIL GROUPS (owner correction): the label sits INSIDE the rail —
|
// THE RAIL GROUPS (owner correction): the label sits INSIDE the rail —
|
||||||
// ownership readable from the rail alone, collapsed or expanded.
|
// ownership readable from the rail alone, collapsed or expanded.
|
||||||
const railAt = html.indexOf("border-l-[6px]");
|
const railAt = html.indexOf("border-l-[calc(3ch-5px)]");
|
||||||
const trigAt = html.indexOf("your-thinking-trigger");
|
const trigAt = html.indexOf("your-thinking-trigger");
|
||||||
assert.ok(railAt !== -1 && railAt < trigAt, "the rail opens before the label (same quote)");
|
assert.ok(railAt !== -1 && railAt < trigAt, "the rail opens before the label (same quote)");
|
||||||
assert.ok(!html.includes("brightness isn't distance"), "the text is hidden until expanded");
|
assert.ok(!html.includes("brightness isn't distance"), "the text is hidden until expanded");
|
||||||
|
|||||||
@@ -242,24 +242,17 @@ describe("md-blocks — the restored plain blockquote (owner round, 2026-10-07)"
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ThinkingBlock — side-based authorship + ⌘T sync (owner round, 2026-10-07)", () => {
|
describe("ThinkingBlock — the document band + ⌘T sync (owner rounds, 2026-10-07)", () => {
|
||||||
it("the banded row is the document band — full-width card color, 6px rail", () => {
|
it("the row is the document band — one geometry, cards included (owner round three)", () => {
|
||||||
const html = renderToStaticMarkup(<ThinkingBlock text="banded" defaultOpen />);
|
const html = renderToStaticMarkup(<ThinkingBlock text="banded" defaultOpen />);
|
||||||
assert.ok(html.includes("bg-card"), "the card-color band");
|
assert.ok(html.includes("bg-card"), "the card-color band");
|
||||||
assert.ok(html.includes("border-l-[6px]"), "the 6px rail");
|
assert.ok(html.includes("border-l-[calc(3ch-5px)]"), "the rail fills the 3ch inset");
|
||||||
assert.ok(html.includes("pl-[calc(3ch-6px)]"), "natural gap, 3ch total inset preserved");
|
assert.ok(html.includes("py-[5px]") && html.includes("pl-[5px]"), "tight 5px padding, gap matches it");
|
||||||
assert.ok(html.includes("w-full"), "full width — no chat side");
|
assert.ok(html.includes("w-full"), "full width — no chat side");
|
||||||
assert.match(html, /aria-expanded="true"/);
|
assert.match(html, /aria-expanded="true"/);
|
||||||
assert.ok(html.includes("banded"), "text visible on arrival");
|
assert.ok(html.includes("banded"), "text visible on arrival");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("in-card rows take the rail but no band", () => {
|
|
||||||
const html = renderToStaticMarkup(<ThinkingBlock text="in card" banded={false} />);
|
|
||||||
assert.ok(!html.includes("bg-card"), "no band on the card's own background");
|
|
||||||
assert.ok(html.includes("border-l-[6px]"), "the rail stays");
|
|
||||||
assert.ok(html.includes("my-3"), "the 12px in-card rhythm");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("⌘T sets every row — explicit sets stomp individual state, late mounts honor the last set", () => {
|
it("⌘T sets every row — explicit sets stomp individual state, late mounts honor the last set", () => {
|
||||||
setAllUserThinkingOpen(false);
|
setAllUserThinkingOpen(false);
|
||||||
const collapsed = renderToStaticMarkup(<ThinkingBlock text="synced" defaultOpen />);
|
const collapsed = renderToStaticMarkup(<ThinkingBlock text="synced" defaultOpen />);
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ describe("the practice transcript cards", () => {
|
|||||||
assert.match(trace, /aria-expanded="true"/);
|
assert.match(trace, /aria-expanded="true"/);
|
||||||
assert.ok(trace.includes("stuck at step two"), "the text is visible on arrival");
|
assert.ok(trace.includes("stuck at step two"), "the text is visible on arrival");
|
||||||
assert.ok(trace.includes("bg-card"), "the card-color band");
|
assert.ok(trace.includes("bg-card"), "the card-color band");
|
||||||
assert.ok(trace.includes("border-l-[6px]"), "the 6px rail, left side");
|
assert.ok(trace.includes("border-l-[calc(3ch-5px)]"), "the rail fills the 3ch inset");
|
||||||
const legacy = renderToStaticMarkup(<ThinkingCard data={{ problemCallId: "c", commit: true, text: "It is 42." }} />);
|
const legacy = renderToStaticMarkup(<ThinkingCard data={{ problemCallId: "c", commit: true, text: "It is 42." }} />);
|
||||||
assert.match(legacy, /YOUR THINKING/);
|
assert.match(legacy, /YOUR THINKING/);
|
||||||
assert.doesNotMatch(legacy, /Your solution/);
|
assert.doesNotMatch(legacy, /Your solution/);
|
||||||
|
|||||||
Reference in New Issue
Block a user