TikZ diagrams
You are writing LaTeX/TikZ for the diagram_write tool of this harness. The
host compiles every write with the machine's own TeX engine and returns the
compiler's diagnostics, so the loop is:
diagram_write { kind: "tikz", title: "Layered architecture", source: "..." }
-> status "ok" : compiled; the PDF/SVG is cached and rendering
-> status "error" : fix `diagram.tex:LINE: ...` and write again
-> status "unavailable" : no TeX engine on this host (or the diagram was
stored but not compiled) - say so, and offer the .tex
Rules that matter:
- Never finish on a non-
ok status. If TeX is unavailable, tell the user
plainly and hand over the source, which still exports as .tex.
- TikZ pays off for precision (alignment, arrows, annotations, plots) and
loses to Mermaid for rough, quickly-changing pictures. Choose accordingly.
- A diagram that fits one page at a readable size is the target. Prefer
≤ 40 nodes and ≤ 200 lines; split anything larger.
- Iterate with
diagram_patch (literal replace) - never re-emit a long
picture to move one arrow. diagram_read gives you the current source back.
- Compiles are cached by content: an unchanged diagram costs nothing, and a
change costs ~1-2 s.
{ recompile: true } on the panel side forces a rebuild.
Complex pictures
This file is the quick-start. For a picture that needs real layout work -
layered bands, matrices, dense edge routing, pgfplots charts, braces and
annotations, and the meaning of every compiler error this preamble can produce -
read reference/complex-diagrams.md, which sits beside this file in the same
skill folder. Every example in it was compiled by the host's own engine, and it
is the file to open before writing anything over ~20 lines.
Publishing a picture you will want again
A diagram belongs to the conversation that drew it unless you say otherwise. A
good TikZ figure is expensive to produce, so when it is worth citing later - a
reference figure, a model diagram, an architecture picture - put it in the shared
library:
diagram_write { ..., scope: "library" } writes it there directly, or
diagram_publish { id } copies one you already have.
- Its address is
dsh-resource://diagram/library/<id>: it names no conversation,
so the id resolves in ANY chat, and diagram_read { id: "jepa-model-tikz" }
finds it from a conversation that never saw it written.
- A bare id resolves library-first, and the artifact cache is
content-addressed, so the library copy shares the compiled PDF/SVG with the
conversation copy rather than costing a second compile.
The verdicts, and the two the compiler cannot give you
| Signal | Question it answers | What to do |
|---|
status: "ok" | did the engine compile this? | nothing - but keep reading |
status: "error" | no, and here is the line | fix diagnostics, write again |
status: "unavailable" | there is no TeX engine here | say so; the source still exports |
| warnings | it compiled, but is it the picture you meant? | review each one |
verification.state | did the compiled artifact actually load in a browser? | see below |
For TikZ the browser's job is smaller - the host compiled the picture, the client
fetches the artifact - but the four states are the same, and they carry the
revision they are about (revision = the current one, reported = the revision
the newest report names):
verification.state | What the browser reported | What to do |
|---|
drawn | it loaded this revision's artifact | the picture exists; you may describe it |
failed | the artifact could not be loaded, with the reason | the compile produced something the client cannot show; check the diagnostics |
stale | the newest report is about an OLDER revision | this revision has never been shown; that older report is not evidence about it |
pending | nothing at all | not a failure: no client has shown it yet, which is normal headless |
A compile that FAILED but still produced a PDF is cached on purpose and shown
flagged as errored, because the partial picture is evidence of what LaTeX did
understand - so status: "error" and a visible artifact are not a contradiction.
Two failures the compiler will never report, and the host now catches:
- A source with no picture in it. A document with no
tikzpicture/axis
and no \node/\draw/\path compiles to a blank page and reports success.
The host refuses it as status: "error" with "There is no picture in this
source", because an empty panel is a failure a person sees and pdflatex
does not.
- A picture on more than one page. A stray page break or an overflowing
figure yields a 2-page PDF; the panel shows the first page, so the second is
invisible to the user. The host warns with the page count.
Other warnings to expect: a canvas over 2000pt (the panel scales it down until
labels are unreadable - shrink with scale=, node distance= or a smaller
font), an engine font substitution, and a document past 250 non-empty lines.
Verifying without changing anything
diagram_verify { id } recompiles the stored document (through the
artifact cache, so an unchanged document costs nothing and reports
served from the artifact cache), returns the diagnostics, the warnings and
the browser line, and writes nothing - the revision does not move. Use it when
a person edited the diagram in its panel, when your context was compacted, or
before you describe a diagram's contents to the user. Do not re-write a
diagram just to re-check it: a write bumps the revision and discards the
browser's render report.
What the host already does for you
Your source may be any of these three shapes:
- a bare body of TikZ commands (
\node ...; \draw ...;) - it is wrapped in
\begin{tikzpicture} ... \end{tikzpicture};
- one or more picture environments (
\begin{tikzpicture}, \begin{axis},
...) - wrapped in the document, left as-is;
- a complete document (
\documentclass present) - used verbatim.
Leading \usepackage, \usetikzlibrary, \usepgfplotslibrary, \pgfplotsset,
\tikzset, \definecolor, \pgfkeys, \newcommand and \DeclareMathOperator
lines are hoisted into the preamble, so write them the way you normally
would: at the top, before the picture.
The supplied preamble is, in full (shown for reference - it is not a diagram, so
the example checker skips it):
\documentclass[tikz,border=4pt]{standalone}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amsmath,amssymb}
\usepackage{tikz}
\usetikzlibrary{arrows.meta,positioning,shapes.geometric,shapes.misc,shapes.callouts,
calc,fit,backgrounds,matrix,chains,automata,graphs,trees,
decorations.pathreplacing,decorations.markings,patterns,shadows.blur,quotes,angles,intersections}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\usepgfplotslibrary{fillbetween}
standalone plus border=4pt means the PDF is exactly the picture, so there
is no page-layout step: size the picture itself, and expect the app to scale it
to the panel.
Core syntax you will use constantly
\begin{tikzpicture}[
font=\small,
node distance=14mm,
box/.style={draw, rounded corners=2pt, minimum height=9mm, minimum width=24mm, align=center, fill=blue!6},
arrow/.style={-{Latex[length=2mm]}, thick},
]
\node[box] (a) {Client};
\node[box, right=of a] (b) {API};
\node[box, right=of b, fill=green!8] (c) {Store};
\draw[arrow] (a) -- node[above, font=\scriptsize]{HTTPS} (b);
\draw[arrow] (b) -- (c);
\draw[arrow, dashed] (b.south) to[out=-90, in=-90, looseness=1.2] node[below, font=\scriptsize]{async} (c.south);
\end{tikzpicture}
- Positioning:
right=of a, below left=6mm and 4mm of a, above=of a.
Requires positioning (included). Anchors: a.north east, a.30, a.south west.
- Edges:
-- straight, |-/-| orthogonal, to[out=…, in=…] curved,
edge[bend left=20], .. controls .., circle/ellipse through to paths.
- Arrow tips come from
arrows.meta: -{Latex}, -{Stealth[length=2mm]},
<->, -{Latex[length=2mm,width=1.6mm]}. A bare -> also works but is fatter.
- Styles are the maintainable unit: put
box, arrow, lbl in the options
list and reference them by name. Change one line, change every node.
- Coordinates:
(0,0), (2,1.5), ($ (a)!0.5!(b) $) (calc),
($(a)+(0,-1)$), (a |- b) (intersection of the two axes), polar (30:2).
- Loops:
\foreach \x in {1,2,3} { \node at (\x,0) {\x}; } — fine in dozens,
wasteful in thousands.
Layout recipes
Layered architecture — one row per layer, fit/backgrounds for the bands:
\begin{tikzpicture}[font=\small, layer/.style={draw, dashed, rounded corners, inner sep=6pt},
box/.style={draw, rounded corners=2pt, fill=blue!6, minimum height=8mm, minimum width=20mm, align=center}]
\matrix (m) [matrix of nodes, row sep=12mm, column sep=8mm,
nodes={box}] {
Client & CLI & IDE \\
|[fill=orange!10]| Gateway & |[fill=orange!10]| Auth \\
|[fill=green!10]| Sessions & |[fill=green!10]| Diagrams \\
};
\begin{scope}[on background layer]
\node[layer, fit=(m-1-1)(m-1-3), label={[font=\scriptsize]left:UI}] {};
\node[layer, fit=(m-3-1)(m-3-2), label={[font=\scriptsize]left:Core}] {};
\end{scope}
\draw[-{Latex[length=2mm]}] (m-1-1) -- (m-2-1);
\draw[-{Latex[length=2mm]}] (m-2-1) -- (m-3-1);
\end{tikzpicture}
Flow with decisions — shapes.geometric diamonds, explicit branches:
\node[draw, diamond, aspect=2, align=center] (c) {cache\\hit?};
\node[draw, rounded corners=2pt] (miss) [left=18mm of c] {origin};
\node[draw, rounded corners=2pt] (hit) [right=18mm of c] {cache};
\draw[-{Latex}] (c.west) -- node[above,font=\scriptsize]{no} (miss);
\draw[-{Latex}] (c.east) -- node[above,font=\scriptsize]{yes} (hit);
State machine — automata (included): state, initial, accepting,
edge[bend left], double.
Trees — trees (included):
\node {root}
child { node {left} }
child { node {right} child { node {leaf} } };
Sequence-like diagram — matrix for lanes plus decorations.markings or
plain \draw for the arrows; do not try to reproduce Mermaid's sequence
renderer.
Plots — pgfplots with compat=1.18:
\begin{axis}[width=9cm, height=5.5cm, xlabel={$t$}, ylabel={value},
grid=both, legend pos=north east, legend cell align=left]
\addplot[domain=0:10, samples=80, thick, blue] {exp(-x/3)};
\addlegendentry{decay}
\addplot+[mark=*, only marks, red] coordinates {(1,0.7) (4,0.3) (7,0.1)};
\addlegendentry{measured}
\end{axis}
\usepgfplotslibrary{fillbetween} is loaded, so \addplot fill between[...] is
available. Every \addplot inside one axis must have the same domain/samples
shape or a \closedcycle-style mismatch appears.
Text, math and labels
- Math works:
\node {$O(n\log n)$}; — but a bare _ or ^ outside $...$ is
a hard error, and % must be escaped as \%.
- Multi-line labels need
align=center (or text width=…) and \\.
- Sizes:
font=\scriptsize/\small/\footnotesize on a node, or globally in
the tikzpicture options.
- Special characters in
\node {…}: \&, \%, \$, \#, \_, \{, \}.
Sizing and export
- The PDF is the picture (
standalone), so control the size in the picture:
scale=0.9 (also transform shape when you want nodes to scale),
node distance, minimum width/height, x=…,y=… unit vectors.
- Prefer a landscape-ish aspect for the panel; a very tall picture is
scrollable but awkward.
- Export formats:
tex (the source), pdf (vector, print-ready), svg
(vector, what the panel draws), png (200 dpi raster). All are written into
the conversation folder on request.
Hard limits of this host — do not fight them
| Not available | Why | Do this instead |
|---|
\write18, \directlua shell calls | the engine runs with -no-shell-escape | compute the number in your head or in prose |
external / svg / externalization libraries | they shell out to convert | compile normally; the host converts |
minted, \lstinputlisting | they shell out | listings in a pinch, or plain \texttt |
\includegraphics{...} of a local file | no file access from the document | draw the shape, or embed a tikzpicture |
pstricks, asymptote, metapost | not TikZ and not on the engine path | TikZ/pgfplots only |
| Packages that are not installed | the engine runs with auto-install DISABLED, so it fails in ~300 ms instead of reaching the network | use the libraries listed above, or ask the user to install the package |
The engine used is the first of pdflatex, xelatex, lualatex present on the
host (normally pdflatex). PDF-specific advice above assumes pdflatex.
Reading a compile error
diagnostics returns the compiler's own lines, with the line number in your
normalized document (diagram.tex:LINE: ...), so subtract the preamble offset
when the source was wrapped - or, more usefully, read the quoted text, which
names the construct:
diagram.tex:12: Package pgf Error: No shape named `missingnode' is known.
diagram.tex:18: Undefined control sequence.
diagram.tex:9: Package pgfkeys Error: I do not know the key '/tikz/on grid'
diagram.tex:6: LaTeX Error: File `notapackage.sty' not found.
| Error | Cause | Fix |
|---|
No shape named X is known | an edge to a node that was never declared, or a typo | declare the node (or fix the name) before the \draw |
I do not know the key '/tikz/...' | the option needs a library (e.g. on grid, node distance without positioning) or it is misspelled | add the library or drop the option |
Undefined control sequence | a macro from a library/package that is not loaded, or a typo in a command | load that library in a leading \usetikzlibrary line, or fix the spelling |
Missing $ inserted | _, ^ or a math command outside math mode | wrap in $...$ |
File 'X.sty' not found | the package is not installed and auto-install is off | use a library from the list above, or tell the user which package to install |
Runaway argument / Paragraph ended before ... | an unbalanced brace or a missing ; after a \node/\draw | balance the braces, terminate the path |
Dimension too large | coordinates or minimum size far outside the page | reduce the numbers; the picture is scaled to fit anyway |
! Package pgfplots Error on an axis | a plot with mixed domain/samples, or compat mismatch | give every \addplot its own explicit domain/samples |
A failing compile still often produces a PDF, which the host caches and the
panel shows flagged as errored: use it as a hint about what LaTeX did
understand, never as proof the diagram is right.