Skip to content

Commit cd1fbd1

Browse files
committed
feat(ux): worked numerical examples + math foundations in HelpModal
Raschka-style: every help modal now carries (1) the schematic tensor- flow SVG, (2) a concrete numerical worked example with small matrices showing real values in cells coloured by a diverging heatmap, and (3) a list of math-foundation links to 3Blue1Brown / distill.pub / Raschka / Alammar / EleutherAI / Maarten Grootendorst, each with a one-sentence gloss + a one-sentence key insight. New diagrams primitives in TensorDiagram.tsx: • MatrixGrid — N×M cells, per-cell heatmap colour, shape badge, role-coded left rail (q amber / k cyan / v violet / attn blue / out green / gate yellow / hidden grey / mask muted). • heatmapColor — diverging pink → mid → role-tinted ramp. • MathLink — chip-shaped link with gloss text. • CellRole + role tint table for canonical Raschka/3B1B/Distill palette on a dark #0e1014 background. New data modules: • worked_examples.ts — 6 concrete examples (attention, mlp, moe, rmsnorm, softmax, dot_product) with full Q/K/V/scores/probs/y tensors. RMSNorm example proves ‖y‖_RMS=1 numerically. • math_foundations.ts — 14 canonical concepts (vector_norm, rms_norm, dot_product, matmul, softmax, attention, RoPE, residual stream, layernorm, gradient descent, backprop, selective SSM, MoE, LoRA) — each verified to 200 OK. • TOPIC_FOUNDATIONS + TOPIC_WORKED_EXAMPLES — registry mapping HelpIcon topic strings to the relevant examples/foundations. New auto-rendering component: WorkedExampleDiagram lays out a WorkedExample's tensors in column-major step order, drawing arrows with operation labels between them. Handles 1-D / 2-D / 3-D tensors (3-D shows head 0 with a chip; head stacks would blow the modal width). HelpModal: three sections rendered when applicable: 1. "Tensor flow" — schematic SVG diagram (existing). 2. "Worked example" — concrete numerical matrices (new). 3. "Math foundations" — links + glosses (new). Two background research agents informed the work: Exa+Brave+Perplexity deep-research on (a) the visual idiom across Raschka / 3Blue1Brown / Distill / Jay Alammar (palette, matrix-rendering style, dimensions, notation conventions), and (b) canonical "explained-for-humans" links per concept. URLs all HEAD-verified 200. Fix: pre-existing TS error in App.tsx where `snap.spec.dim_env` was used but the wireSpecRef snapshot exposes `dimEnv` (not nested under spec). Switched to `snap.dimEnv` so inspect.histogram's brick-context path compiles. Tests: 11 new vitest (worked-example registry shape + RMSNorm=1 assertion + dot-product=11 cell render + HelpModal three-section integration + per-topic coverage). One existing TensorDiagrams test loosened from getByTestId('tensor-diagram') to getAllByTestId(...).length ≥ 1 because both schematic + worked-example now render the test-id at the same time. Regression: 522/522 vitest.
1 parent d887c2d commit cd1fbd1

9 files changed

Lines changed: 1072 additions & 10 deletions

File tree

vbgui/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1953,7 +1953,7 @@ export function App(): JSX.Element {
19531953
const params = (node.data as {
19541954
params?: Record<string, unknown> })
19551955
?.params ?? {};
1956-
const dim_env = snap.spec.dim_env;
1956+
const dim_env = snap.dimEnv;
19571957
const minimal_spec = {
19581958
graph: {
19591959
nodes: [

vbgui/src/components/HelpIcon.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
import { useState } from "react";
66
import { createPortal } from "react-dom";
7-
import { TENSOR_DIAGRAMS } from "./diagrams";
7+
import {
8+
TENSOR_DIAGRAMS, TOPIC_WORKED_EXAMPLES, WORKED_EXAMPLES,
9+
TOPIC_FOUNDATIONS, MATH_FOUNDATIONS,
10+
WorkedExampleDiagram, MathLink,
11+
} from "./diagrams";
812
import { T } from "@/theme";
913

1014
export interface HelpTopic {
@@ -1051,6 +1055,41 @@ export function HelpModal({ topic, onClose }: HelpModalProps): JSX.Element {
10511055
</Section>
10521056
) : null;
10531057
})()}
1058+
{(() => {
1059+
const exKey = TOPIC_WORKED_EXAMPLES[topic];
1060+
const ex = exKey ? WORKED_EXAMPLES[exKey] : null;
1061+
return ex ? (
1062+
<Section label="Worked example"
1063+
testid="help-modal-worked-example">
1064+
<WorkedExampleDiagram example={ex} />
1065+
</Section>
1066+
) : null;
1067+
})()}
1068+
{(() => {
1069+
const keys = TOPIC_FOUNDATIONS[topic] ?? [];
1070+
if (keys.length === 0) return null;
1071+
return (
1072+
<Section label="Math foundations"
1073+
testid="help-modal-math-foundations">
1074+
{keys.map((k) => {
1075+
const m = MATH_FOUNDATIONS[k];
1076+
if (!m) return null;
1077+
return (
1078+
<div key={k} style={{ marginBottom: 4 }}>
1079+
<MathLink topic={k}
1080+
gloss={m.gloss}
1081+
url={m.urls[0]?.url ?? "#"} />
1082+
<div style={{ color: "#94a3b8",
1083+
fontSize: 11,
1084+
padding: "2px 12px 6px" }}>
1085+
<em>{m.key_insight}</em>
1086+
</div>
1087+
</div>
1088+
);
1089+
})}
1090+
</Section>
1091+
);
1092+
})()}
10541093
<Section label="Why" testid="help-modal-why">
10551094
{entry.why}
10561095
</Section>

vbgui/src/components/diagrams/TensorDiagram.tsx

Lines changed: 197 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,75 @@ import { type ReactNode } from "react";
2121

2222

2323
export const DIAG_THEME = {
24-
bg: "rgba(15, 23, 42, 0.6)",
25-
border: "rgba(255, 255, 255, 0.05)",
24+
bg: "#0e1014", // Raschka/3B1B-style deep neutral
25+
bgPanel: "rgba(20, 23, 28, 0.85)",
26+
border: "rgba(255, 255, 255, 0.06)",
2627
tensorBg: "#1e293b",
2728
tensorFg: "#22d3ee",
2829
opBg: "#312e81",
2930
opFg: "#818cf8",
30-
arrow: "#94a3b8",
31-
residual: "#facc15",
32-
text: "#f1f5f9",
33-
textMuted: "#94a3b8",
31+
arrow: "#9aa0a6",
32+
residual: "#f5b841",
33+
text: "#e8e8e8",
34+
textMuted: "#9aa0a6",
35+
// Role-coded hues per the unified Raschka + 3B1B + Distill style:
36+
roleQ: "#f5b841", // amber — query
37+
roleK: "#4ec9b0", // cyan — key
38+
roleV: "#b48ead", // violet — value
39+
roleAttn: "#5aa9e6", // blue — attention
40+
roleAttnL: "#d96c8e", // pink low end of attn heatmap
41+
roleOut: "#7bc47f", // green — output
42+
masked: "#3a3f47",
3443
} as const;
3544

3645

46+
export type CellRole = "q" | "k" | "v" | "attn" | "out" | "gate"
47+
| "hidden" | "raw" | "mask";
48+
49+
const ROLE_TINT: Record<CellRole, string> = {
50+
q: DIAG_THEME.roleQ,
51+
k: DIAG_THEME.roleK,
52+
v: DIAG_THEME.roleV,
53+
attn: DIAG_THEME.roleAttn,
54+
out: DIAG_THEME.roleOut,
55+
gate: "#fbbf24",
56+
hidden: "#94a3b8",
57+
raw: "#64748b",
58+
mask: DIAG_THEME.masked,
59+
};
60+
61+
62+
/** Map a float (clipped to [-clip, +clip]) to a hex colour:
63+
* blue (high) → muted (zero) → pink (low). Used by MatrixGrid for
64+
* per-cell heatmap rendering. */
65+
export function heatmapColor(
66+
value: number, clip: number = 2.0, role: CellRole = "raw",
67+
): string {
68+
const v = Math.max(-clip, Math.min(clip, value));
69+
const t = (v + clip) / (2 * clip); // [0,1]
70+
// Diverging palette: pink (0) → grey (0.5) → blue/role (1).
71+
const lo = { r: 0xd9, g: 0x6c, b: 0x8e };
72+
const mid = { r: 0x24, g: 0x29, b: 0x33 };
73+
const hiHex = ROLE_TINT[role] === ROLE_TINT.raw
74+
? "#5aa9e6" : ROLE_TINT[role];
75+
const hi = {
76+
r: parseInt(hiHex.slice(1, 3), 16),
77+
g: parseInt(hiHex.slice(3, 5), 16),
78+
b: parseInt(hiHex.slice(5, 7), 16),
79+
};
80+
const lerp = (a: number, b: number, k: number): number =>
81+
Math.round(a + (b - a) * k);
82+
const c = t < 0.5
83+
? { r: lerp(lo.r, mid.r, t * 2),
84+
g: lerp(lo.g, mid.g, t * 2),
85+
b: lerp(lo.b, mid.b, t * 2) }
86+
: { r: lerp(mid.r, hi.r, (t - 0.5) * 2),
87+
g: lerp(mid.g, hi.g, (t - 0.5) * 2),
88+
b: lerp(mid.b, hi.b, (t - 0.5) * 2) };
89+
return `rgb(${c.r}, ${c.g}, ${c.b})`;
90+
}
91+
92+
3793
export interface DiagramProps {
3894
width?: number;
3995
height?: number;
@@ -227,6 +283,141 @@ export function Residual({
227283
}
228284

229285

286+
/* ===========================================================
287+
MatrixGrid + RoleTensor + dataflow worked-example primitives
288+
=========================================================== */
289+
290+
291+
export interface MatrixGridProps {
292+
/** Top-left x in SVG user-space. */
293+
x: number;
294+
y: number;
295+
/** 2-D numeric values (rows × cols). 1-D arrays are treated as
296+
* a single row. */
297+
values: number[][] | number[];
298+
/** Label rendered above the grid. */
299+
label?: string;
300+
/** Bold colour-coded role used for the highlight rail + shape
301+
* badge tint. */
302+
role?: CellRole;
303+
/** Optional shape suffix rendered top-right (e.g. "[4,4]"). When
304+
* omitted, computed from `values`. */
305+
shape?: string;
306+
/** Cell side length in user-space units. */
307+
cell?: number;
308+
/** Clip range for the heatmap colour ramp. Cells > +clip / < -clip
309+
* saturate to the role hue / pink. */
310+
clip?: number;
311+
/** Pre-rounded display floats — when omitted, uses values directly. */
312+
display?: (string | number)[][] | (string | number)[];
313+
testid?: string;
314+
}
315+
316+
317+
export function MatrixGrid({
318+
x, y, values, label, role = "raw", shape, cell = 26, clip = 2,
319+
display, testid,
320+
}: MatrixGridProps): JSX.Element {
321+
const rows: number[][] = Array.isArray(values[0])
322+
? (values as number[][])
323+
: ([values] as number[][]);
324+
const dispRows: (string | number)[][] = display
325+
? (Array.isArray((display as never[])[0])
326+
? (display as (string | number)[][])
327+
: ([display] as (string | number)[][]))
328+
: rows.map((r) => r.map((v) => v.toFixed(2)));
329+
const nRows = rows.length;
330+
const nCols = rows[0]?.length ?? 0;
331+
const totalW = nCols * cell;
332+
const totalH = nRows * cell;
333+
const shapeText = shape ??
334+
(nRows === 1 ? `[${nCols}]` : `[${nRows},${nCols}]`);
335+
const tint = ROLE_TINT[role];
336+
337+
return (
338+
<g data-testid={testid ?? (label ? `matrix-${label}` : "matrix-grid")}>
339+
{label && (
340+
<text x={x} y={y - 6}
341+
fill={tint}
342+
fontSize={11} fontWeight={700}
343+
fontFamily="system-ui, sans-serif">
344+
{label}
345+
</text>
346+
)}
347+
<text x={x + totalW} y={y - 6}
348+
textAnchor="end"
349+
fill={DIAG_THEME.textMuted}
350+
fontSize={9}
351+
fontFamily="ui-monospace, monospace">
352+
{shapeText}
353+
</text>
354+
{/* role highlight rail along the left edge */}
355+
<rect x={x - 3} y={y} width={3} height={totalH}
356+
fill={tint} opacity={0.85} rx={1} />
357+
{rows.map((row, ri) =>
358+
row.map((v, ci) => {
359+
const cx = x + ci * cell;
360+
const cy = y + ri * cell;
361+
const fill = role === "mask" ? DIAG_THEME.masked
362+
: heatmapColor(v, clip, role);
363+
return (
364+
<g key={`${ri}-${ci}`}>
365+
<rect x={cx} y={cy} width={cell} height={cell}
366+
fill={fill}
367+
stroke="rgba(255,255,255,0.06)" strokeWidth={0.6} />
368+
<text x={cx + cell / 2} y={cy + cell / 2 + 3}
369+
textAnchor="middle"
370+
fill={DIAG_THEME.text}
371+
fontSize={Math.max(8, cell * 0.32)}
372+
fontFamily="ui-monospace, monospace">
373+
{String(dispRows[ri]?.[ci] ?? v)}
374+
</text>
375+
</g>
376+
);
377+
})
378+
)}
379+
</g>
380+
);
381+
}
382+
383+
384+
export interface MathLinkProps {
385+
/** Topic key — used to render the gloss text inline. */
386+
topic: string;
387+
gloss: string;
388+
url: string;
389+
testid?: string;
390+
}
391+
392+
/** A reference chip showing a math-foundation link with a short gloss.
393+
* Rendered inside <Section label="Math foundations"> as a list. */
394+
export function MathLink({
395+
topic, gloss, url, testid,
396+
}: MathLinkProps): JSX.Element {
397+
return (
398+
<a href={url} target="_blank" rel="noopener noreferrer"
399+
data-testid={testid ?? `math-link-${topic}`}
400+
style={{
401+
display: "block",
402+
padding: "6px 10px",
403+
margin: "4px 0",
404+
background: "rgba(74, 174, 224, 0.08)",
405+
border: "1px solid rgba(74, 174, 224, 0.25)",
406+
borderRadius: 6,
407+
color: DIAG_THEME.text,
408+
textDecoration: "none",
409+
fontSize: 12,
410+
fontFamily: "system-ui, sans-serif",
411+
}}>
412+
<strong style={{ color: DIAG_THEME.roleK, marginRight: 6 }}>
413+
{topic.replace(/_/g, " ")}
414+
</strong>
415+
<span style={{ color: DIAG_THEME.textMuted }}>{gloss}</span>
416+
</a>
417+
);
418+
}
419+
420+
230421
/** A logical group/box around a sub-region of the diagram (e.g.
231422
* multi-head attention). Renders a faint outlined rectangle with a
232423
* small caption in the top-left corner. */

0 commit comments

Comments
 (0)