ui: round 5 — AGENT'S THINKING buys the 3ch rail; the standard chord; dashed model names

- THE 3CH RAIL: 'AGENT’S THINKING' (16ch, flush) − 'YOUR THINKING' (13ch)
  = 3ch of mono — the rail widens to 3ch (border + padding), keeping the
  chevrons aligned while giving the quote room to breathe (the 1ch rail
  read cramped). YOU row follows.
- THE STANDARD CHORD everywhere: Enter submits, Shift+Enter is a
  newline, ⌘/Ctrl+Enter works too — the teaching thinking field drops
  the ⌘-only Gmail convention; the hint reads '↵ submits · ⇧↵ newline'
  in both docks, shown in text mode too (it Enter-submits as well).
- The transcript-end optimistic placeholder joins the register: small
  mono caps THINKING… (was text-sm sentence case).
- Model names keep their dashes: prettyModel renders 'glm-5.3-flash'
  exactly as written (no auto-caps, no dash→space join).
This commit is contained in:
amosblomqvist
2026-10-05 04:55:59 +03:00
parent d533585bd1
commit 3e402d3c35
5 changed files with 20 additions and 19 deletions
+1 -1
View File
@@ -137,7 +137,7 @@ const defaultGetThinkingMessage = (isStreaming: boolean) => {
if (isStreaming) { if (isStreaming) {
return <Shimmer duration={1}>Thinking...</Shimmer>; return <Shimmer duration={1}>Thinking...</Shimmer>;
} }
return <p>AGENT THINKING</p>; return <p>AGENT’S THINKING</p>;
}; };
export const ReasoningTrigger = memo( export const ReasoningTrigger = memo(
+4 -4
View File
@@ -1259,7 +1259,7 @@ export function Chat({
User text renders plain — the model's LaTeX pipeline is not User text renders plain — the model's LaTeX pipeline is not
applied to learner-authored text ($ stays $, no surprises). applied to learner-authored text ($ stays $, no surprises).
Rendered WITHOUT the Message wrapper — that is the bubble. */ Rendered WITHOUT the Message wrapper — that is the bubble. */
<div data-testid="user-message" className="my-1 w-full border-l-2 border-muted-foreground/30 pl-[calc(1ch-2px)] font-ui text-xs"> <div data-testid="user-message" className="my-1 w-full border-l-2 border-muted-foreground/30 pl-[calc(3ch-2px)] 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
rail as the thinking rows — one quote geometry. */} rail as the thinking rows — one quote geometry. */}
@@ -1365,10 +1365,10 @@ export function Chat({
entered by external discovery (wake) / the poll entered by external discovery (wake) / the poll
fallback. Never collapsible itself. */} fallback. Never collapsible itself. */}
{(status === "submitted" || turnInFlight) && ( {(status === "submitted" || turnInFlight) && (
<span className="flex items-center gap-2"> <span className="flex items-center gap-2 font-ui text-xs font-medium uppercase tracking-wide text-muted-foreground">
<BrailleSpinner label="Thinking" /> <BrailleSpinner label="Thinking" />
<Shimmer duration={1} className="text-sm"> <Shimmer duration={1}>
Thinking… THINKING…
</Shimmer> </Shimmer>
</span> </span>
)} )}
+5 -6
View File
@@ -820,9 +820,10 @@ function QuestionDockForm({
onEscape(); onEscape();
return; return;
} }
// ⌘↵ on macOS, Ctrl↵ elsewhere — the Gmail convention; plain // THE STANDARD CHORD (owner round 5, 2026-10-05): Enter
// Enter stays a newline (this field is for dumping). // submits, Shift+Enter is a newline, ⌘/Ctrl+Enter works
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { // too — identical to the practice dock and the composer.
if (e.key === "Enter" && (!e.shiftKey || e.metaKey || e.ctrlKey)) {
e.preventDefault(); e.preventDefault();
submit(); submit();
} }
@@ -838,11 +839,9 @@ function QuestionDockForm({
/> />
)} )}
<div className="flex items-center justify-between gap-2 p-1"> <div className="flex items-center justify-between gap-2 p-1">
{qd.mode !== "text" && (
<span aria-hidden className="pl-1.5 text-xs text-muted-foreground"> <span aria-hidden className="pl-1.5 text-xs text-muted-foreground">
⌘/Ctrl + ↵ submits ↵ submits · ⇧↵ newline
</span> </span>
)}
<Button <Button
type="submit" type="submit"
disabled={!canSubmit} disabled={!canSubmit}
+7 -5
View File
@@ -33,16 +33,18 @@ export function ThinkingBlock({
return ( return (
/* The rail GROUPS and the rail IS THE MISSING CHARACTER (owner /* The rail GROUPS and the rail IS THE MISSING CHARACTER (owner
rounds, 2026-10-05): the heading sits INSIDE the rail, and the rounds, 2026-10-05): the heading sits INSIDE the rail, and the
rail's full width (border + padding) is exactly ONE CHARACTER of rail's full width (border + padding) is exactly THREE CHARACTERS of
the mono heading font — 'YOUR THINKING' is one char shorter than the mono heading font — 'YOUR THINKING' is three chars shorter
'AGENT THINKING', so with the user row offset by the rail and the than 'AGENT'S THINKING', so with the user row offset by the rail
agent row flush, the chevrons line up vertically. The heading and the agent row flush, the chevrons line up vertically. Three
(not one, owner round 5): the wider gap reads better than the
cramped single-char rail. The heading
register is the card eyebrows' small mono caps (text-xs = the register is the card eyebrows' small mono caps (text-xs = the
11px rung). */ 11px rung). */
<div <div
data-testid={testId} data-testid={testId}
data-commit={dataCommit === undefined ? undefined : dataCommit ? "true" : "false"} data-commit={dataCommit === undefined ? undefined : dataCommit ? "true" : "false"}
className="lh-card-enter my-1 w-full border-l-2 border-muted-foreground/30 pl-[calc(1ch-2px)] font-ui text-xs" className="lh-card-enter my-1 w-full border-l-2 border-muted-foreground/30 pl-[calc(3ch-2px)] font-ui text-xs"
> >
<button <button
type="button" type="button"
+1 -1
View File
@@ -146,7 +146,7 @@ describe("render smoke tests", () => {
assert.match(html, /Why this answer\? What are you unsure about\?/, "placeholder invites, not apologizes"); assert.match(html, /Why this answer\? What are you unsure about\?/, "placeholder invites, not apologizes");
assert.ok(!html.includes("optional"), "the field is never called optional"); assert.ok(!html.includes("optional"), "the field is never called optional");
assert.match(html, /Submit answer/, "send becomes Submit answer"); assert.match(html, /Submit answer/, "send becomes Submit answer");
assert.match(html, /⌘\/Ctrl \+ ↵ submits/, "the ⌘/Ctrl+Enter shortcut is discoverable"); assert.match(html, /↵ submits · ⇧↵ newline/, "the standard chord is discoverable (owner round 5)");
// The v1 desync bug: options are buttons with ARIA roles, not native inputs. // The v1 desync bug: options are buttons with ARIA roles, not native inputs.
assert.ok(!html.includes('type="radio"'), "no native radio inputs"); assert.ok(!html.includes('type="radio"'), "no native radio inputs");
assert.ok(html.includes('role="radio"'), "button-based radio role present"); assert.ok(html.includes('role="radio"'), "button-based radio role present");