Whiteboard Reference
Canvas Specifications
Dimensions: 1000 Γ 563 pixels.
Coordinate system: x = 0 at the left edge, x = 1000 at the right edge. y = 0 at the top, y = 563 at the bottom. Every element has (left, top) at its top-left corner.
Safe zone: keep content within x β [20, 980] and y β [20, 543] to leave a 20px margin from the canvas edges.
Reference points:
- Centered horizontally:
x = (1000 - width) / 2 - Centered vertically:
y = (563 - height) / 2 - Two-column layout: left column
x β [20, 480], right columnx β [520, 980](40px gutter)
JSON Output Context
Whiteboard actions are {"type":"action","name":"wb_...", "params":{...}} items inside the JSON array your response is required to be. All positions are integers (or decimals accepted, but stay in pixel units).
LaTeX fields deserve special care β see the "LaTeX JSON Escape" section below.
Action Reference
For every whiteboard action, the JSON shape below is the complete, canonical form. All other prose in this file assumes these shapes.
wb_open
Open the whiteboard before drawing. Once open, wb_draw_* calls auto-render.
{"type":"action","name":"wb_open","params":{}}
No parameters. Call before any wb_draw_*. Not required before every wb_draw_* β only once at the start of a drawing phase.
wb_draw_text
Place plain text. Use for notes, steps, labels β not for math formulas (use wb_draw_latex instead).
{"type":"action","name":"wb_draw_text","params":{"content":"Step 1: identify forces","x":60,"y":60,"width":600,"height":43,"fontSize":18,"color":"#333333"}}
| Field | Type | Required | Description |
|---|---|---|---|
content |
string | yes | Plain text or HTML <p> block. No LaTeX commands. |
x |
number | yes | Left edge in pixels. |
y |
number | yes | Top edge in pixels. |
width |
number | no (default 400) | Text container width. |
height |
number | no (default 100) | Text container height. Use the Font Size Table below to pick a matching height. |
fontSize |
number | no (default 18) | Point size. Pick from the Font Size Table. |
color |
string | no (default #333333) |
Hex color. |
elementId |
string | no | Stable ID for later wb_delete. |
Common mistake: embedding LaTeX like "content":"\\frac{a}{b}" in a text element β KaTeX is NOT run on text content, so the raw backslash prints. Use wb_draw_latex for any math.
wb_draw_shape
Place a geometric shape. Use for annotations, groupings, or simple diagrams.
{"type":"action","name":"wb_draw_shape","params":{"shape":"rectangle","x":60,"y":200,"width":200,"height":100,"fillColor":"#5b9bd5"}}
| Field | Type | Required | Description |
|---|---|---|---|
shape |
"rectangle" | "circle" | "triangle" |
yes | Primitive shape. |
x, y |
number | yes | Top-left of the shape's bounding box. |
width, height |
number | yes | Bounding box size. |
fillColor |
string | no (default #5b9bd5) |
Hex fill color. |
elementId |
string | no | Stable ID. |
Common mistake: drawing a "parabola" as wb_draw_shape with shape:"triangle" or as a sequence of wb_draw_line segments. Neither renders a curve β there is no function-plot primitive. Prefer explaining algebraically or with a table of key points until this gap is closed.
wb_draw_line
Draw a straight line or arrow.
{"type":"action","name":"wb_draw_line","params":{"startX":100,"startY":300,"endX":400,"endY":300,"color":"#333333","width":2,"points":["","arrow"]}}
| Field | Type | Required | Description |
|---|---|---|---|
startX, startY |
number | yes | Start coordinates. |
endX, endY |
number | yes | End coordinates. |
color |
string | no (default #333333) |
Hex color. |
width |
number | no (default 2) | Stroke thickness, NOT line length. Keep 2β4. |
style |
"solid" | "dashed" |
no (default "solid") |
Line style. |
points |
[start, end] of "" or "arrow" |
no (default ["",""]) |
Arrow markers at each end. |
elementId |
string | no | Stable ID. |
Common mistake: setting width to the desired span (e.g., 300). width is stroke thickness; arrow markers scale with it β width:60 produces a 180Γ180 arrowhead.
wb_draw_latex
Render a math formula via KaTeX.
{"type":"action","name":"wb_draw_latex","params":{"latex":"\\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}","x":100,"y":80,"height":80}}
| Field | Type | Required | Description |
|---|---|---|---|
latex |
string | yes | LaTeX source. Every \ must be written as \\ in the JSON string β see "LaTeX JSON Escape" below. |
x, y |
number | yes | Top-left. |
height |
number | no (default 80) | Preferred rendered height. See the LaTeX Element Height Table below. |
width |
number | no (default 400) | Max horizontal space. Auto-computed from height Γ aspect ratio unless this cap kicks in. |
color |
string | no (default #000000) |
Hex color. |
elementId |
string | no | Stable ID. |
Most common mistake: single-backslash commands. If your rendered board shows literal words like ext, heta, imes, rac, ightarrow, that is the bug. Next response: rewrite with \\text, \\theta, etc.
wb_draw_chart
Render a data chart.
{"type":"action","name":"wb_draw_chart","params":{"chartType":"bar","x":100,"y":150,"width":500,"height":300,"data":{"labels":["Q1","Q2","Q3"],"legends":["Sales"],"series":[[100,120,140]]}}}
| Field | Type | Required | Description |
|---|---|---|---|
chartType |
"bar" | "column" | "line" | "pie" | "ring" | "area" | "radar" | "scatter" |
yes | Chart kind. |
x, y, width, height |
number | yes | Bounding box. |
data.labels |
string[] | yes | X-axis labels. |
data.legends |
string[] | yes | Series names (one per row in series). |
data.series |
number[][] | yes | One inner array per legend, length matches labels. |
themeColors |
string[] | no | Palette override. |
elementId |
string | no | Stable ID. |
Common mistake: placing a chart that extends past x + width = 1000 or y + height = 563 β charts silently clip at canvas edges.
wb_draw_table
Render a simple table.
{"type":"action","name":"wb_draw_table","params":{"x":100,"y":200,"width":500,"height":150,"data":[["Variable","Meaning"],["a","Coefficient of xΒ²"],["b","Coefficient of x"],["c","Constant term"]]}}
| Field | Type | Required | Description |
|---|---|---|---|
x, y, width, height |
number | yes | Bounding box. |
data |
string[][] | yes | 2D array. First row is header. All rows same length. |
outline |
{width, style, color} |
no | Border style. |
theme |
{color} |
no | Header color. |
elementId |
string | no | Stable ID. |
Common mistake: putting LaTeX into table cells ("data":[["y = \\frac{1}{2}"]]). Cell text is rendered as plain text; the backslashes stay. Put the formula in a separate wb_draw_latex adjacent to the table.
wb_draw_code
Draw a code block with syntax highlighting. Includes a ~32px header bar.
{"type":"action","name":"wb_draw_code","params":{"language":"python","code":"def greet(name):\n print(f'Hello, {name}')","x":100,"y":120,"width":500,"height":120,"fileName":"hello.py","elementId":"code1"}}
| Field | Type | Required | Description |
|---|---|---|---|
language |
string | yes | "python", "javascript", "typescript", "json", "go", "rust", "java", "c", "cpp", etc. |
code |
string | yes | Source. Use \n for newlines. |
x, y |
number | yes | Top-left. |
width |
number | no (default 500) | |
height |
number | no (default 300) | Includes ~32px header. Budget β 32 + 22 per line + 16 padding. |
fileName |
string | no | Shown in the header bar. |
elementId |
string | no | Recommended β lets you edit the block later with wb_edit_code. |
Common mistake: underestimating height β a 10-line block needs ~270px.
wb_edit_code
Modify an existing code block line-by-line. Produces smooth animations β prefer this over redrawing.
{"type":"action","name":"wb_edit_code","params":{"elementId":"code1","operation":"insert_after","lineId":"L2","content":" return name.upper()"}}
| Field | Type | Required | Description |
|---|---|---|---|
elementId |
string | yes | Target code block's ID. |
operation |
"insert_after" | "insert_before" | "delete_lines" | "replace_lines" |
yes | Edit operation. |
lineId |
string | for inserts | Reference line ID (e.g., "L2") β shown in state. |
lineIds |
string[] | for delete/replace | Lines to operate on. |
content |
string | for insert/replace | New code. Use \n for multiple lines. |
Common mistake: guessing line IDs. Read the current whiteboard state β every code line has a stable ID like L1, L2, visible in the state context.
wb_delete
Remove one element by ID.
{"type":"action","name":"wb_delete","params":{"elementId":"step1"}}
Common use: step-by-step reveals (draw step 1 with elementId:"step1", explain, delete, draw step 2).
wb_clear
Remove all elements from the whiteboard. Use sparingly β prefer wb_delete when 1-2 removals would do.
{"type":"action","name":"wb_clear","params":{}}
wb_close
Close the whiteboard to reveal the slide canvas. Do NOT call at the end of a drawing response β students need time to read. Only close when returning to slide-canvas actions (spotlight/laser).
{"type":"action","name":"wb_close","params":{}}
LaTeX JSON Escape (CRITICAL)
This is the single highest-leverage rule on the whiteboard. Read it before every math-heavy response.
The rule: in any JSON string containing LaTeX β the latex param of wb_draw_latex, or a content param that happens to contain \\text{...} β every backslash must be written as \\ (two characters) in your JSON output. When the JSON parser reads "\text" it interprets \t as an ASCII TAB control character, so by the time KaTeX receives your string it is literally <TAB>ext{...} β no \text command, just garbage.
Characters at risk (first character of the LaTeX command collides with a JSON escape):
| Control | JSON escape | LaTeX commands corrupted |
|---|---|---|
TAB (\t) |
\t |
\text, \theta, \times, \tau, \top, \tan |
CR (\r) |
\r |
\rightarrow, \Rightarrow, \rho, \right, \real |
FF (\f) |
\f |
\frac, \forall, \Phi, \phi, \flat |
BS (\b) |
\b |
\beta, \binom, \bar, \bot |
VT (\v) |
\v |
\varphi, \vec, \vdots, \vee, \varepsilon |
LF (\n) |
\n |
\neq, \ni, \not, \notin |
Correctness table (what you write in JSON β what KaTeX renders):
| LaTeX source | β Wrong in JSON | β Right in JSON |
|---|---|---|
\frac{a}{b} |
"\frac{a}{b}" |
"\\frac{a}{b}" |
\text{εθ§} |
"\text{εθ§}" |
"\\text{εθ§}" |
\theta |
"\theta" |
"\\theta" |
\times |
"\times" |
"\\times" |
\rightarrow |
"\rightarrow" |
"\\rightarrow" |
\Rightarrow |
"\Rightarrow" |
"\\Rightarrow" |
\circ |
"\circ" |
"\\circ" |
\tau |
"\tau" |
"\\tau" |
\forall |
"\forall" |
"\\forall" |
\beta |
"\beta" |
"\\beta" |
\varphi |
"\varphi" |
"\\varphi" |
\sqrt{x} |
"\sqrt{x}" |
"\\sqrt{x}" |
a^2 + b^2 = c^2 |
"a^2 + b^2 = c^2" |
"a^2 + b^2 = c^2" (no backslash β stays the same) |
Self-check heuristic: if your previous turn's rendered whiteboard shows literal tokens like ext, heta, imes, rac, irc, ightarrow, orall, eta, arphi, eq, you emitted single-backslash LaTeX. In this turn, emit the same formula again with double backslashes, via wb_delete + wb_draw_latex, or wb_clear + redraw.
Good complete example:
{"type":"action","name":"wb_draw_latex","params":{"latex":"\\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}","x":100,"y":80,"height":80}}
Renders as: the standard quadratic formula. Count the backslashes in the JSON: 4 pairs of \\. Each pair is one backslash in the actual LaTeX string, which is what KaTeX needs.
Bad example (this is what produces the ext-style garbage):
{"type":"action","name":"wb_draw_latex","params":{"latex":"\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}","x":100,"y":80,"height":80}}
The JSON parser sees \f (form feed), \p (kept as \p), \s (kept as \s). KaTeX then receives a broken string where \frac is gone. Whether KaTeX complains or silently renders wrong, the board is broken.
Bounds & Overlap
The canvas is 1000 Γ 563. Elements that extend past the edges are clipped.
Hard bounds (every element):
x β₯ 0andx + width β€ 1000y β₯ 0andy + height β€ 563
Safe zone (preferred): 20 β€ x, x + width β€ 980, 20 β€ y, y + height β€ 542.
Spacing:
- Minimum gap between adjacent elements: 20px
- Vertical stacking:
next.y = prev.y + prev.height + 30 - Side-by-side:
next.x = prev.x + prev.width + 30
Two-column layout:
- Left column:
x β [20, 480], width β€ 460 - Right column:
x β [520, 980], width β€ 460 - Gutter: 40px
Before placing every element, walk the existing elements (listed in the "Current State" section of your context). For each existing (x, y, width, height):
- Reject if the new bbox would cover > 30% of its area.
- If space is tight, choose one:
wb_deletethe existing element, shrink the new element, or pick a free region by scanning the canvas quadrants.
Worked example β adding a formula below an existing chart at (100, 80) size 500Γ200:
chart occupies x=100..600, y=80..280
next safe y = 80 + 200 + 30 = 310
formula at (100, 310, height 80) β occupies y=310..390
check: y + height = 390 β€ 563 β
check: no overlap with chart (chart ends at y=280, formula starts at y=310) β
Font Size Table
For wb_draw_text:
| Content type | fontSize |
|---|---|
| Whiteboard title | 28-32 |
| Section heading | 20-24 |
| Body / annotation | 16-18 |
| Caption / fine print | 12-14 |
Keep 2-4px between adjacent hierarchy levels. Do not use free-form sizes like 8, 11, 48, 64 β pick from this table.
For a given fontSize and 1-line text, a matching height is roughly ceil(fontSize Γ 1.5) + 20 (1.5 line-height plus 10px top/bottom padding).
Pair text and LaTeX by visual weight. A LaTeX element at height:80 visually weighs ~28px text; do NOT place 14px captions next to it. Use this table:
LaTeX height |
Companion text fontSize |
|---|---|
| 50-60 | 16-20 |
| 70-80 | 20-24 |
| 90-110 | 24-28 |
| 120+ | 28-32 |
When a formula and annotation sit on the same board, their visual weights should match. Large formula next to tiny caption looks broken.
LaTeX Element Height Table
For wb_draw_latex β use the category that best matches your formula:
| Category | Examples | height |
|---|---|---|
| Inline equations | E=mc^2, a+b=c |
50-80 |
| With fractions | \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a} |
60-100 |
| Integrals / limits | \\int_0^1 f(x)dx, \\lim_{x \\to 0} |
60-100 |
| Summations with limits | \\sum_{i=1}^{n} i^2 |
80-120 |
| Matrices | \\begin{pmatrix}a & b \\\\ c & d\\end{pmatrix} |
100-180 |
| Standalone fractions | \\frac{a}{b} |
50-80 |
| Nested fractions | \\frac{\\frac{a}{b}}{\\frac{c}{d}} |
80-120 |
Width is auto-computed from height Γ aspect_ratio; width acts as a horizontal cap only.
Multi-step derivations: give every step the same height so they render at matching vertical sizes. Widths will differ β that's correct; it reflects each step's horizontal complexity.
Pre-Output Checklist
Before emitting whiteboard actions, mentally walk through these:
- [LaTeX escape] Every
\inlatexparams or in any text with math is written as\\in the JSON. Scan for single-backslash\frac,\text,\theta,\times,\rightarrow,\circ,\beta,\varphiβ none should appear. - [Hard bounds] For each element:
x β₯ 0,y β₯ 0,x + width β€ 1000,y + height β€ 563. - [Overlap] Walk existing elements from the state; new bbox overlaps none by more than 30%. If tight,
wb_deletefirst. - [Font consistency] Every
fontSizecomes from the Font Size Table (28-32 / 20-24 / 16-18 / 12-14). No 8, 11, 48, 64. - [LaTeX height] Every
wb_draw_latexheightmatches the formula category (see the LaTeX Height Table). - [Redraw guard] The element is not already on the whiteboard β if the state lists a formula/chart/table matching your intent, reference it instead of redrawing.
- [Element type] Math expressions use
wb_draw_latex. Plain text useswb_draw_text. Never embed LaTeX commands in text. - [Safe zone] Where possible, stay within
x β [20, 980],y β [20, 543]. - [Leave whiteboard open] Do not call
wb_closeat the end of a drawing turn. Students need to read. - [Visual weight pairing] Text that sits next to a LaTeX formula uses a
fontSizematched to the LaTeXheightper the pairing table above. No tiny 12-14px text next to height-80 formulas.