Docs Reference
Authoring Yantra diagrams
How to write a Yantra diagram by hand or with an agent: workflow, budgets, groups, trees,
sequence views, data models, languages and validation codes. The schema is
schemas/yantra-payload.schema.json; this page
explains how to use it well.
Workflow
- Decide the story first. What question does the diagram answer? Who reads it? List the 3–6 steps of the walkthrough before listing boxes.
- Write the payload in one pass:
meta,roles,nodes,edges,scenarios. Start fromyantra new diagram.jsonor a file inexamples/. - Validate:
yantra validate diagram.json. Fix every error, then the warnings (repair order below). Repeat at most twice; if it still fails, simplify the diagram. - Build:
yantra build diagram.jsonwritesdiagram.html(single offline file). - Look at it:
yantra screenshot diagram.json --step 2and open the PNG. Never claim a diagram looks right without viewing it.
Do not place nodes
Layout and edge routing are automatic. Leave out x/y. The engine ranks nodes by the
direction of the edges, so edge direction is your layout tool:
- Point edges along the main flow (request → service → store). The main path becomes a
straight row (
LR) or column (TB). - Return / feedback edges (refund, retry, sync back) are detected and drawn in a corridor outside the drawing. Keep them; do not invert them to "help" the layout.
meta.layout.direction:LR(default) for pipelines and request flows,TBfor hierarchies and long sequential processes.node.rankforces a column when two nodes must line up; use sparingly.meta.layout.mode: "manual"+x/y(multiples of 10) only when an exact hand layout is required. You then own overlaps.meta.layout.algorithm:layered(default) for flows;treeormindmapfor hierarchies (see Trees, org charts and mind maps).
Budgets
| Thing | Budget | Why |
|---|---|---|
| Nodes | ≤ 14 (aim for 6–10) as the diagram opens; with folded groups ≤ 48 in all; trees and mind maps ≤ 40 as they open, ≤ 120 in all | Beyond that, split into an overview plus detail diagrams (see Drill-down), or fold branches and boxes |
| Edges | ≤ 20 | Merge parallel flows that say the same thing |
| Roles | 3–5 | Colour means something only when it repeats |
| Node title | ≤ 42 columns (characters; CJK count 2) | Detail goes in subtitle (card) or description (inspector) |
| Edge label | ≤ 28 columns, ideally 1–3 words | Labels name what flows: JWT, Order event, SQL write |
| Steps per scenario | 3–8 | The first step is usually an overview |
| Charts | ≤ 6 chart cards, ≤ 12 cards with a mini chart | A chart stands out only when few cards carry one |
| Groups | 2–5 in a flat diagram; nested: ≤ 24 groups, ≤ 6 levels | Deeper or larger: fold inner boxes, or drill down (see Deep hierarchies) |
Content rules
data.title: the thing's name.data.subtitle: its job, in a few words.data.tech: the technology, one line. Anything longer goes indescription.- Put facts (SLA, team size, owner) in
data.metadata; they show in the inspector, not on the card. Cards stay quiet. - Use
rolesto grouptypes into 3–5 meaningful categories with one colour each, e.g.edge,service,data,external. Give the specific label indata.badge. edge.type: "async" | "event" | "stream"(orstyle: "dashed") for asynchronous flows.- A step that highlights nothing is an overview. A focused step lists the nodes and the edges involved; every highlighted edge must have both endpoints highlighted.
step.payloadis optional and is shown in the inspector:method,endpoint,status,latency,protocolbecome chips, everything else is pretty-printed JSON.- Add
modesonly when they change something (hideNodes,hideEdges, or per-modedata.modes.<id>overrides).
Inspector tabs: include only what was asked for
The side panel builds its tabs from the payload, so add content for a tab only when the user wants that kind of information:
| Tab | Appears when | Add it when the user asks for… |
|---|---|---|
| Summary | meta.summary exists |
a summary, overview, key points, TL;DR |
| Step | any step has a payload |
request/response detail, data per step, step-by-step specifics |
| Details | any node has description, tech, metadata, ports, sourceFile, chart… |
component details, specs, owners, code references |
| your panel | listed in panels |
anything else: risks, costs, glossary, FAQ, decisions, metrics |
"panels": [
{ "id": "risks", "title": "Risks", "blocks": [
{ "type": "points", "items": [{ "title": "Provider outage", "text": "Payments queue and retry.", "tone": "warn" }] }
] }
]- Panels use the same blocks as answer pages (
points,steps,metrics,table,compare,callout,chart,code,sources,text; seedocs/pages.md). Keep the title to one or two words: it is the tab label. meta.inspector.tabs: ["summary", "risks"]picks and orders tabs explicitly;meta.inspector.labels: { "node": "Component" }renames built-in tabs.- No tabs at all hides the inspector button. Many tabs scroll sideways.
What a card click opens
Clicking a card always highlights it and its connections. What the inspector does is a
fixed rule per diagram, set with meta.inspector.onNodeClick; leave it out unless the
diagram's purpose calls for something else:
| Value | Click opens | Use when |
|---|---|---|
"node" (default) |
the card's Details tab | almost always |
"stay" |
nothing new; the open tab stays | the panel is the point (e.g. a walkthrough read alongside) and Details would distract |
a panel id, e.g. "risks" |
that panel, filtered to the card | the diagram is about that panel per component (a risk map, a cost map, an ownership map) |
For a panel opened by click, give its blocks nodes: ["card-id", …]: such blocks show
only while one of those cards is selected (with a "Show all cards" link), and blocks
without nodes always show. If the target tab does not exist, the click only highlights.
Groups
groups draws a labelled box around related nodes. Use it when the boundary itself is part
of the explanation:
- trust boundaries: public internet vs. your network, PCI scope, tenant isolation;
- cloud structure: account > region > VPC / subnet, cluster > namespace;
- ownership: teams or departments, and swimlanes for who does each step of a process;
- subsystems: "Billing", "Search", "Data platform" inside a larger architecture.
A flat diagram reads best with 2-5 groups; leave nodes that sit outside every boundary (users, third parties) ungrouped. A box with one node rarely helps; put the node's context in its subtitle instead. Groups nest up to six levels deep (24 groups at most within budget): see Deep hierarchies.
"groups": [
{ "id": "aws", "title": "AWS account (eu-west-2)", "nodes": ["cdn"], "color": "orange", "style": "solid" },
{ "id": "vpc", "title": "Private VPC", "parent": "aws", "nodes": ["worker", "db"], "color": "green",
"description": "Private subnets in two AZs; no internet gateway." }
]- List each node in its innermost group only; the parent's members include the child's.
- Group ids must not reuse node ids.
coloris a role hue name;styleisdashed(default) orsolid. - Layout keeps members together, pads each box (20px sides, 36px top for the title; a little less below the third level) and keeps boxes clear of outsiders. Edges cross borders where they enter or leave a group, but never run through a title or the box's fold button. In manual layout the box simply wraps its members' cards.
- During a step, a group whose members are all dimmed is dimmed too.
See examples/aws-serverless.json.
Trees, org charts and mind maps
When the answer is a hierarchy, pick a hierarchical layout. Edges still point from parent to child; the engine takes the hierarchy from them.
meta.layout.algorithm |
Use for | Drawing |
|---|---|---|
"tree" + direction: "TB" |
org charts, decision trees, work breakdowns | rows per level, parents centred over their children, comb connectors |
"tree" + direction: "LR" |
taxonomies, file trees, deep breakdowns | columns per level, same connectors sideways |
"mindmap" |
study or project plans, brainstorms, topic overviews | the root in the centre, branches balanced left and right, curved connectors, one colour per branch |
"meta": { "title": "Org chart", "layout": { "algorithm": "tree", "direction": "TB" } },
"nodes": [
{ "id": "ceo", "type": "exec", "data": { "title": "Amara Okafor", "subtitle": "Chief Executive Officer" } },
{ "id": "cto", "type": "exec", "data": { "title": "Daniel Reyes", "subtitle": "Chief Technology Officer" } }
],
"edges": [ { "id": "ceo-cto", "source": "ceo", "target": "cto" } ]- One parent per node. A node with two incoming edges keeps the first listed one as its
parent; the other is drawn as a routed cross-link and lint warns
layout/tree-cross-link. In a decision tree, repeat an outcome card (sev2-a,sev2-b) instead of merging branches. - Labels are optional on hierarchy edges (the hierarchy is the meaning). Label them when
the branch needs a name:
Yes/Noin a decision tree, a condition, a percentage. - People cards:
titleis the name,subtitlethe role,badgea short team or level tag;width: 180-200keeps wide org charts compact. - Wide org charts: compact stacks. In a
TBtree, a parent whose children are all leaves (no reports of their own, no edge label) can list them in one column below itself, indented on a side bus, instead of a row.meta.layout.compact:"auto"(default) does this only when the spread tree is much wider than tall (over 1600 px and 2.4 times its height),truealways,falsenever. Parents with a mix of leaf and non-leaf children keep their row. A 30-40 person org chart then fits a near-square canvas.LRtrees and mind maps are never stacked (layout/compact-ignoredwhencompact: trueis set there). - Assistants and staff:
data.assistant: trueon a leaf card (an executive assistant, a chief of staff) places it beside its manager, in the manager's row, on a short side connector instead of a level below. The first assistant goes right (TB) or below (LR), the next on the other side, more stack in those columns. A root, a card with reports or a mind map ignores the flag (layout/assistant-ignored). - Dragging a card moves its visible subtree with it; comb, stack and assistant connectors are rebuilt from where the cards now sit (mind-map curves stay curves). A child dragged out of tree order (above its parent, say) is routed like any other edge.
- Mind maps have one root (the first node nothing points at). Branch colours replace role
colours on the canvas, so the roles legend is hidden; keep
rolesfor the inspector. - Order children the way they should read: siblings keep the order of their edges.
- State shapes work here too: a self-loop sits right of its card (TB tree) or below it (LR
tree, mind map) with room kept for it; dots and diamonds carry no fold button while open,
and a
+Nbadge beside them while folded (click it to unfold). Cards withdata.drillkeep their drill mark, and each detail level opens with its owncollapsed. - Groups are not drawn in these layouts (
layout/groups-ignored); the hierarchy is the boundary. This is deliberate: a tidy tree packs subtrees by their outline, so a box around one subtree would often enclose cards of its neighbours. To show or hide a team, fold its top card (meta.layout.collapsed: ["cto"]); folding a group id in a tree warnslayout/collapse-ignored.
See examples/org-chart.json, examples/study-plan-mindmap.json and
examples/incident-triage-decision-tree.json.
Big diagrams: folding
Readers can fold parts of a diagram into one card and unfold them again (+N / − buttons,
key X on a selected card; the state is kept in the link as #collapsed=id,id):
- in
treeandmindmaplayouts, any card with children folds its subtree; - in the
layeredlayout, every group folds into a summary card that takes over the group's edges (parallel edges merge, internal ones disappear).
meta.layout.collapsed: ["id", ...] lists what is folded when the viewer opens (build,
export and screenshot take --collapsed id,id, --expand-all or --depth n to override it
for one output; MCP tools take collapsed / expandAll / depth). A folded group's card counts
the cards and boxes it holds, and folding to a depth (keys 1-6) or zooming to a group works
at any level: see Deep hierarchies. Use it for
large trees (lint counts the cards that show at first against the budget of 40) and for detail
groups in an architecture overview. A step that highlights a folded card unfolds it, so a
walkthrough can open a branch at the right moment. Validation checks the drawing both as it
opens and fully unfolded (findings for the second carry [expanded]).
Deep hierarchies
Real organisations and systems nest: a city has districts, a district has services, a service has systems, a system has sites, a site has sensors. Yantra shows such hierarchies in two ways, and they combine.
| Use | When | How |
|---|---|---|
| Nesting in one frame | the boundaries are the point and the reader should see how a card sits inside them all at once (ownership, trust zones, physical location); flows cross several levels | groups with parent, up to 6 levels (lint warns beyond, refuses beyond 8), up to 24 groups; open with inner boxes folded |
| Drill-down | each level is a different audience or a different story (board, region, store, device), or a level would blow the card budget | data.drill and diagrams, up to 8 levels below the overview |
| Both | a big estate: an overview of a few nested boxes, whose cards open detail diagrams that nest again | nested groups inside any level |
Rules of thumb: nest in one frame while the opened drawing stays under about 14 cards and a reader can name every box; drill down when a level needs its own walkthrough. Six boxes deep is the limit of what reads at a glance.
"groups": [
{ "id": "city", "title": "Harbourside City", "nodes": [], "style": "solid" },
{ "id": "north", "title": "North district", "parent": "city", "nodes": [] },
{ "id": "transport", "title": "Transport", "parent": "north", "nodes": [] },
{ "id": "traffic", "title": "Traffic management", "parent": "transport", "nodes": ["utc"] },
{ "id": "junction", "title": "Junction 14 cabinet", "parent": "traffic", "nodes": ["signals"] },
{ "id": "sensors", "title": "Roadside sensors", "parent": "junction", "nodes": ["loops", "camera"] }
],
"meta": { "layout": { "collapsed": ["junction", "sensors"] } }- A group may hold only child groups (
"nodes": []); list each card in its innermost group. - Layout. Up to three levels, one layered pass keeps every edge in the main flow. From four
levels (or whenever one pass cannot keep the boxes apart) each group is laid out on its own and
placed as one block in its parent, so boxes never overlap and every edge enters and leaves
through the borders. Boxes are wide enough for their whole title and the fold button; padding
tightens a little below the third level (
groupPad), and tints and outlines get calmer with each level (dashed, then dotted from the fourth), in both themes. - Folding. Folding a box hides everything inside it and shows one summary card that counts
them ("9 cards · 3 groups"); unfolding it brings back the inner boxes as they were, folded or
not. List inner ids in
meta.layout.collapsedtoo, so a reader who opens the outer box finds the next level still tidy. - Fold to depth. Keys
1-6keep that many levels of boxes open and fold the rest (0opens everything; with several scenarios the digits switch scenario, so holdShift), or the toolbar's depth menu. The link keeps it (#depth=2). CLI--depth n(build,export,screenshot), MCPdepth, Pythondepth=. In tree and mind-map layouts the same control folds by card level. - Focus a group. Double-click a group title (or select a card and press
G) to zoom to its box: folded boxes around it open, and a path of its ancestors appears under the toolbar (Whole diagram › … › Transport › Traffic management; the middle of a long path is a menu).Esczooms out one box at a time. The link keeps it (#focus=<id>);screenshot --focus <id>. - Walkthrough. A step's
focusGroup: "<group id>"zooms to that box and unfolds it and its ancestors, so a story can open the deep part at the right moment; the next step withoutfocusGrouplets go. Highlight the cards too (highlightNodes), as in any focused step. - Validation. Lint checks the drawing as it opens, fully unfolded and, for three or more
levels, at every depth the viewer folds to (
[depth n]in the message): boxes inside their parents and below their titles, titles clear of cards, edges and labels, nothing cut. - Drill-down breadcrumbs. Deep paths show the overview, the parent and the current level:
Global retail group › … › Payment terminal T-07 › EMV kernel; the…opens a menu of the levels in between.
See examples/smart-city-operations.json (six levels in one frame),
examples/global-retail-group.json (six-level drill-down with nested boxes inside levels) and
examples/hospital-trust-monitoring.json (both).
Sequence view
The same payload can be drawn as a sequence diagram. Use it when time order matters more than topology: request/response flows, protocols and handshakes (OAuth, TLS, payments), "who calls whom" during one user action. The steps drive it, so no extra data is needed:
- Every id in a step's
highlightEdgesbecomes one arrow, source -> target, in step order. An edge used in two steps appears twice. Labels come from the edge (fallback: the step title);style: "dashed"or an async/eventtypedraws a dashed arrow; an edge from a node to itself draws a self-loop. - Participants are the nodes that send or receive a message, left to right in order of first
appearance (a step without messages contributes its
highlightNodes, so an opening "the user clicks" step puts the user on the left).meta.sequence.allParticipants: trueadds every node. Group members are kept adjacent and their group becomes a box around their columns. - Each step is a numbered row; a step without
highlightEdgesbecomes a note across its highlighted nodes. Column gaps widen automatically until every label fits between its lifelines.
"meta": { "view": "sequence" } opens in the sequence view. Whenever the active scenario has a
step with highlightEdges, the viewer shows a Flow / Sequence toggle (key V, deep link
#view=sequence). CLI: yantra export file.json -f svg --view sequence [--step n] and
yantra screenshot file.json --view sequence --step n. Write highlightEdges for every step
that sends something, and keep the flow view readable too: both views come from one payload.
See examples/oauth-login.json.
meta.views lists the views a reader may switch between. ["flow"] hides the toggle (a
diagram whose steps highlight relationships rather than messages); ["sequence"] shows only the
sequence view. Without it both are offered, except that a diagram made only of table or class
cards (an ERD or a class diagram: every node has fields or methods) has no sequence view and
no toggle; list ["flow", "sequence"] to offer it anyway.
Drill-down: an overview plus detail diagrams
A big system does not fit in 14 cards, and should not. Draw the overview with one card per
subsystem, then give a subsystem its own detail diagram: the card gets a drill mark, and
opening it (click the mark, double-click the card, key D, or the inspector button) shows the
detail with a breadcrumb (Overview › Payments). The breadcrumb, Esc (once nothing is
selected) and the browser's Back button return; the URL carries the level (#level=payments).
Each level is a full diagram: its own steps, inspector tabs, modes and sequence view.
{
"meta": { "title": "Shop platform" },
"nodes": [
{ "id": "payments", "type": "service", "data": { "title": "Payments", "drill": "payments" } }
],
"diagrams": {
"payments": { "meta": { "title": "Payments service" }, "nodes": [ ... ], "edges": [ ... ], "scenarios": [ ... ] },
"search": { "$ref": "./search.json" }
}
}data.drillnames a key of the top-leveldiagramsmap. A detail diagram may drill further (at most 8 levels below the overview, no cycles); every id lives in one namespace, so a detail diagram's owndiagramsmap is merged in. Any level may use nested groups. Deep breadcrumbs keep the overview, the parent and the current level, with a menu (…) for the rest.{ "$ref": "./search.json" }keeps a level in its own file. The CLI and MCP server load it relative to the file that contains it; it must be a relative.jsonpath that stays inside the input folder (the input file's folder for the CLI,YANTRA_INPUT_ROOTfor MCP). The built HTML always contains every level, so it still works offline. The dev server (npm run dev) inlines them too when a gallery example is imported (relative to the example, inside the project only), and the Python SDK loads them for a page's diagramsrc(relative to that file, insidebase_dir).data.modes.<mode id>.drillopens another level while that mode is shown (null: no detail in that mode). Lint checks these targets likedata.drill, and the Markdown export lists them asMode: detail: Titleafter the card.meta.startLevel: "payments"opens the viewer on a detail diagram.- Exports follow the level on screen (SVG, PNG, Markdown); Export → Markdown, all levels
(CLI
export -f md --all-levels) appends every level under its breadcrumb. CLI and MCP take--level <id>/levelforexportandscreenshot. - Answer pages: a diagram block with
level: "payments"shows that level; withinteractive: truethe viewer opens there and can go back up. One drillable embed is still one viewer for the two-per-page budget.
See examples/platform-overview.json.
Links
data.url on a node, url on an edge or a step adds an Open link to the inspector (node
Details, the edge in its card's connections, the Step tab), opened in a new tab with
rel="noopener noreferrer". Only http:, https: and mailto: links are allowed; Markdown
exports keep them as links. Use links for runbooks, dashboards, specs and source, not for
content that belongs on the card.
State machines and lifecycles
Use a state diagram when the question is what states a thing can be in and which event moves it (an order, a ticket, a document, a connection). Nodes are states, edges are transitions labelled with their event:
shape: "state"draws a rounder card for a state.shape: "initial"(filled dot),"final"(ringed dot) and"choice"(diamond) are small, textless pseudo-states; theirdata.titlestill names them in tooltips, the inspector and exports. Give them their own role (e.g.pseudo, slate) so the legend explains the dots.- A transition out of an initial state or into a final state needs no label; every other transition should name its event.
- When several transitions meet a dot or diamond, the best-aligned one keeps its side and each other one enters from the free side that gives it the shortest route, so lines reach the dot directly instead of going round it.
- Self-loops (
sourceequal totarget) draw as a small loop on a free side of the card (above or below it inLR, to its right inTB), with the label on the loop. Layout makes room for it; steps may highlight it like any edge. Ports of other edges on that side stay clear of the loop's legs. A loop on a dot or diamond takes a side no other edge uses, starts on the outline and reaches well past the shape, with its label beyond the loop. - Back-edges (reopen, retry, decline) are fine: point them the way the event goes and layout routes them back. Two edges between the same pair of states get a wider gap so both labels fit side by side.
edge.arrow:"forward"(default),"both"for one two-way edge (ask / reply), or"none"for an undirected link. Layout still ranks bysource→target.
{ "id": "start", "type": "pseudo", "shape": "initial", "data": { "title": "Start" } },
{ "id": "open", "type": "state", "shape": "state", "data": { "title": "Open" } },
{ "id": "open-note", "source": "open", "target": "open", "label": "add note" },
{ "id": "open-waiting", "source": "open", "target": "waiting", "label": "ask / reply", "arrow": "both" }See examples/ticket-lifecycle.json; Mermaid stateDiagram imports to the same shapes.
Fork, join and concurrent regions
shape: "fork"draws a fork or join bar: a thick bar across the flow (vertical inLR, horizontal inTB). Edges attach along its length on the two long sides, lined up with the cards they join, and end on the bar's face. The bar grows with its edges (and stretches to span its neighbours when there is room);widthsets a length. Use the same shape for a join. Transitions into or out of a bar need no label; a bar should split into or merge two or more transitions (story/fork-needs-branches).- Concurrent regions: make the composite state a group, and each region a child group with
"kind": "region"and"parent"set to it. Regions stack across the flow (rows inLR, columns inTB); the parent draws its box and title, each region only its title, with a dashed line between neighbouring regions. Put every node of the composite state inside one of its regions, and use two or more regions. - In
treeandmindmaplayouts a bar is drawn like the other pseudo-states (folded, it shows a+Nbadge); hierarchy edges there are tree connectors and ignoresourceField/targetField, which apply to cross-links and self-loops only.
{ "id": "split", "type": "pseudo", "shape": "fork", "data": { "title": "Fork" } },
"groups": [
{ "id": "processing", "title": "Processing", "nodes": [] },
{ "id": "payment", "title": "Payment", "kind": "region", "parent": "processing", "nodes": ["authorise", "capture"] },
{ "id": "stock", "title": "Stock", "kind": "region", "parent": "processing", "nodes": ["reserve", "pack"] }
]See examples/parallel-checkout-states.json.
Data models and class diagrams
A card becomes an entity card when its data has fields and/or methods: the rows
appear under the title and the card grows to fit them (and widens, up to 320px, for long
types). Use it for database schemas (ERDs) and UML class diagrams.
{ "id": "orders", "type": "table", "data": { "title": "orders", "fields": [
{ "name": "id", "type": "uuid", "pk": true },
{ "name": "customer_id", "type": "uuid", "fk": true, "ref": "customers.id" },
{ "name": "shipped_at", "type": "timestamptz", "nullable": true }
] } }- Fields:
name(required),type, flagspk/fk/unique(shown as PK / FK / UK in a key column, PK names in bold),nullable(type ends in?),ref("node"or"node.field", validated),note(inspector and Markdown only). - Class cards:
fieldsare attributes andmethodsare operations ({ name, params?, type?, visibility?, static?, abstract? }, drawn asname(params)with the return type on the right).visibilityis+ - # ~; static members are underlined, abstract ones italic.data.stereotype: "interface"shows«interface»in place of the kicker. - Keep cards small: a card shows 12 rows (fields + methods); the rest become
+ N moreand lint warnscontent/too-many-fields. Show the keys and the columns the story needs; list the rest indescription. - Layout:
LRsuits ERDs (tall cards side by side),TBsuits class hierarchies.
Relationship notation
Edges take a marker at each end, independent of style:
| Field | Values |
|---|---|
sourceEnd, targetEnd |
ER cardinality: one (two bars, exactly one), zero-or-one, many, one-or-many, zero-or-many; UML heads: triangle, diamond, open-diamond, open-arrow, arrow; none |
relation |
inheritance, realization (dashed), composition, aggregation, association, dependency (dashed) |
{ "id": "places", "source": "customers", "target": "orders", "label": "places", "sourceEnd": "one", "targetEnd": "zero-or-many" },
{ "id": "is-a", "source": "entity", "target": "order", "relation": "inheritance" }relationputs triangles and diamonds on the source (the parent, interface or whole) and open arrows on the target. Point edges parent → child and whole → part, so the layout draws parents first;sourceEnd/targetEndoverride either end.- An edge with any notation drops the default arrowhead (and ignores
arrow); an end without a marker is plain. Usearrowfor plain flows and state transitions, notation for data models. - Inheritance, realization, composition and aggregation need no label (the symbol says it);
ER relationships still want a verb (
places,contains). Markers fit in the straight stub at each port, and labels are kept clear of them. - Field rows:
sourceField/targetFieldname a field of the source / target card; the edge then attaches at that row, on the left or right side facing the other card (both on the right when the cards stand one above the other). Point foreign keys at the key they reference ("sourceField": "id", "targetField": "customer_id"); several edges from one key share its port and leave on one trunk before branching. Two edges between the same pair of rows run on separate tracks, and a self-relationship whose row another edge also leaves nests inside it. An unknown name is an error (ref/edge-unknown-field). Without them, edges attach to card sides. - End labels:
sourceLabel/targetLabelput short text beside each end, past its marker: multiplicities ("1","0..*","1..*") or role names ("owner"), up to 16 characters. The Markdown export adds them to the Relationship column (1 → 0..*). - Self-relationships (
employees.manager_id→employees.id) are self-loops: with field names they join the two rows on the less busy left or right side, with their markers and the label beside the loop.
{ "id": "places", "source": "customers", "target": "orders", "label": "places", "sourceEnd": "one", "targetEnd": "zero-or-many", "sourceField": "id", "targetField": "customer_id" },
{ "id": "lines", "source": "order", "target": "line", "relation": "composition", "label": "lines", "sourceLabel": "1", "targetLabel": "1..*" }See examples/ecommerce-schema.json and examples/payment-classes.json.
Charts in diagrams
A card can carry a chart, drawn inside it under the subtitle. The card grows to fit it (the layout uses the real height), both themes and every zoom level look the same, and the SVG / PNG export draws it too. Clicking the card shows the chart full size, with its data table, in the Details tab; the Markdown export lists the numbers under Chart.
Mini charts (data.chart on an ordinary card) show one reading at a glance:
{ "id": "api", "type": "service", "data": { "title": "Orders API", "subtitle": "Takes orders",
"chart": { "kind": "line", "label": "p95 latency", "unit": " ms",
"values": [180, 172, 165, 190, 158, 150, 146, 142], "target": 160 } } }kind |
Draws | Data |
|---|---|---|
line (default with values), area |
A sparkline; the latest value at the right of the caption | values (2-48 numbers, null for a gap), optional labels |
column |
Mini columns from zero | values (up to 16), optional labels |
bar |
Labelled bars with their values | data: [{ label, value, tone? }] (up to 5) |
donut |
A small share donut with a legend | data: [{ label, value, tone? }] (up to 5, values ≥ 0) |
ring (default with only value) |
Progress: value of max (default 100) |
value, max, optional target |
bullet |
One bar: value against a target tick |
value, target, max (default: a round number above both) |
labelis the short caption drawn on the card ("p95 latency", "Uptime").unitgoes after numbers (%,ms,k),prefixbefore (£).targetdraws a dashed line on line, area and column charts, a tick on rings and bullets and a dashed mark across mini bars. The full-size chart in the Details tab draws it as a labelled target line ("Target 160 ms"), also for bullets (the value as one bar against the line).- The chart takes the card's role colour;
tone(good,warn,bad,info,neutralor a hue) overrides it, anddataitems take their owntone. - A ring states its scale ("47 / 52") unless it is a plain percentage.
- Per-mode readings: put a different
chartindata.modes.<id>.
Chart cards ("shape": "chart") are wider cards (360px; width 240-480) whose data.chart
is a page chart block (see docs/pages.md, Charts): kind column, bar, line, area,
pie or donut, with data or categories + series, stacked, unit and prefix. They
draw axes, gridlines and a legend at card scale, connect like any card and can be highlighted in
steps. The card title is drawn; the chart's own title is what a screen reader hears.
A chart card takes target too (column, bar, line, area: a dashed target line).
{ "id": "latency", "type": "dashboard", "shape": "chart", "width": 400,
"data": { "title": "p95 latency by service", "subtitle": "Today against last Friday",
"chart": { "kind": "column", "unit": " ms", "categories": ["Checkout", "Payments", "Search"],
"series": [{ "name": "Last Friday", "values": [190, 210, 170] },
{ "name": "Today", "values": [240, 260, 340] }] } } }- Keep a chart card to 4 series, 12 bars or columns, 60 line points or 6 slices; a single series takes the card's colour, several take the page chart colours.
- Use chart cards for the 1-3 numbers the diagram is about (a dashboard, a funnel); use mini charts for a reading per component; put anything bigger in a panel.
- Accessibility: the card's accessible name adds the chart's kind, caption and values; chart
cards draw a
<title>, and the inspector adds the hidden data table. - Custom
panelstakechart,ganttandmatrixblocks too; they are drawn at the panel's width (a gantt puts task names above their bars there, so they are not cut short).
See examples/live-operations-dashboard.json (chart cards and mini charts),
examples/cloud-architecture.json, examples/sales-pipeline.json and
examples/smart-city-operations.json.
Languages and right to left
"meta": { "title": "…", "lang": "ne" }meta.langis the BCP 47 tag of the content (es,pt-BR,ne,zh,ja,ar…). It sets<html lang>(built HTML) andxml:lang(SVG), the number format of metrics, charts, counters and zoom (Intl.NumberFormat), and the language of every fixed label: toolbar, tooltips, walkthrough, inspector, shortcuts panel, toasts, export menu, the badge, and the SVG and Markdown exports. Built-in tables:en es fr de pt it hi bn ne zh zh-Hant ja ko id ar he fa ur. A region tag uses its language (pt-BR→pt); Chinese uses Traditional (zh-Hant) forzh-Hant*,zh-TW,zh-HKandzh-MO(unlessHansis given) and Simplified otherwise. Any other language keeps English labels and the validator warnsi18n/unknown-lang; the content still gets itslang.- Write the payload text (titles, labels, steps) in that language yourself; Yantra only translates its own interface. Keyboard keys, Yantra and GitHub are never translated.
meta.stringsoverrides labels by key, e.g.{ "story.next": "Continue" }; placeholders are written{n},{total},{title},{role},{name}. The keys areSTRING_KEYSinsrc/core/i18n.js(listed below).meta.inspector.labelsstill renames the built-in tabs.- Plurals. Counted words (
chart.day1/chart.days,chart.point1/chart.points) are chosen withIntl.PluralRules: the CLDR categoryoneuses the first key, anything else the second. A table ormeta.stringsmay addzero,two,fewormanyforms as<key>.<category>, e.g."chart.days.two": "יומיים"; Arabic and Hebrew ship them. - File size. A built HTML file carries the English table plus only the tables its
payload uses (its
meta.langand those of detail diagrams; an answer page: those of its interactive diagrams). The tables live insrc/core/locales/<lang>.js; the dev viewer and gallery load them all. - Right to left.
ar,he,fa,ur(andyi ps sd ckb dv ug), ormeta.dir: "rtl", setdir="rtl": the toolbar, walkthrough, step chips, inspector (docks left), legend and badge (bottom-left) mirror, arrow keys follow the reading direction, and the text inside cards, group titles, the SVG title, legend and badge are right-aligned.meta.dir: "ltr"forces left to right. - The diagram keeps its layout direction.
LRstays left to right in an RTL document: arrows, ranks, edge routes and sequence-view columns are the same as in any language, so one payload translated into Arabic has the same geometry, the validator checks the same scene, and technical flows read the way their source code and protocols are written. If a process should run right to left for its readers, reverse the order of the nodes yourself (e.g. withrank); a mirrored layout is not generated automatically. - Text is measured per script: Han, kana and Hangul count as full width, Devanagari and
Arabic letters have their own widths and combining marks count as zero, so cards are
sized without clipping. Chinese and Japanese wrap between characters without spaces
(no line starts with
、。」or ends with「). - Budgets measure width.
content/title-too-long(42),content/label-too-long(28) andpage/headline-too-long(220) count display columns, as terminals do: a full-width Han, kana or Hangul character counts 2, a combining mark (Devanagari vowel signs, Arabic harakat) 0, anything else 1. A 20-character Chinese title (40 columns) passes, a 25-character one does not; for text without wide characters the count equals its length. The codes are unchanged; the message says "columns wide" when the two differ. - Validation messages in a language. Codes and English messages are the interface for
tooling. For people,
yantra validate file.json --lang ja(or--lang autofor the payload'smeta.lang; MCPyantra_validatetakeslang; Pythonvalidate(lang=...)) translates thecontent/,story/,ref/andi18n/messages into any built-in language and keeps the English text inmessageEn. Other messages (geometry, schema, pages) stay in English: they are rarer and name JSON fields and layout terms that read best as written in the schema.
| Code | Fix |
|---|---|
i18n/unknown-lang (warning) |
No built-in labels for that language: English labels are used. Translate them with meta.strings, or use a built-in language. |
i18n/bad-lang (warning) |
Use a BCP 47 tag such as en, pt-BR, zh-Hans. |
i18n/bad-dir (warning) |
meta.dir is ltr or rtl. |
i18n/unknown-string, i18n/bad-strings (warnings) |
Use a key from the list below, with a string value. |
Examples: examples/health-referral-nepali.json, examples/order-fulfilment-arabic.json.
| Key | English |
|---|---|
app.untitled |
Untitled diagram |
app.examples |
Example diagram |
app.mode |
Mode |
app.view |
View |
view.flow |
Flow |
view.flowTip |
Flow: the system as a graph |
view.sequence |
Sequence |
view.sequenceTip |
Sequence: messages in time order |
view.flowToast |
Flow view |
view.sequenceToast |
Sequence view |
zoom.in |
Zoom in |
zoom.out |
Zoom out |
zoom.fit |
Fit to screen |
inspector.title |
Inspector |
inspector.toggle |
Toggle inspector |
inspector.sections |
Inspector sections |
inspector.close |
Close inspector |
theme.title |
Light / dark |
theme.toggle |
Toggle theme |
export.title |
Export |
export.png |
PNG image |
export.svg |
SVG vector |
export.html |
Standalone HTML |
export.md |
Markdown spec |
export.json |
Copy JSON with positions |
export.reset |
Reset layout |
export.diagram |
Diagram |
export.mdAll |
Markdown, all levels |
help.title |
Keyboard shortcuts |
help.close |
Close keyboard shortcuts |
help.next |
Next step |
help.prev |
Previous step |
help.play |
Play / pause |
help.ends |
First / last step |
help.follow |
Camera follows steps |
help.view |
Flow / sequence view |
help.scenario |
Switch scenario |
help.mode |
Next mode |
help.inspector |
Inspector |
help.legend |
Roles legend |
help.story |
Collapse walkthrough |
help.fit |
Fit to screen |
help.zoom |
Zoom |
help.theme |
Light / dark |
help.fullscreen |
Fullscreen |
help.clear |
Clear selection, then back up a level |
help.drill |
Open the card's detail diagram |
help.fold |
Fold / unfold selected branch |
common.close |
Close |
canvas.label |
Diagram canvas |
legend.title |
Roles |
legend.label |
Roles legend |
legend.filter |
Filter by role |
legend.all |
All roles |
legend.showing |
Showing: {role} |
legend.labelShowing |
Roles legend, showing {role} |
story.label |
Walkthrough |
story.steps |
Steps |
story.scenario |
Scenario |
story.scenarioN |
Scenario {n} |
story.follow |
Camera follows each step |
story.followOff |
Camera stays where you put it |
story.present |
Present full screen |
story.collapse |
Collapse |
story.collapseLabel |
Collapse walkthrough |
story.expand |
Expand |
story.more |
Show more |
story.less |
Show less |
story.restart |
Back to the first step |
story.play |
Play |
story.pause |
Pause |
story.replay |
Replay |
story.autoplay |
Autoplay |
story.speed |
Autoplay speed |
story.back |
Back |
story.next |
Next |
story.prevStep |
Previous step |
story.nextStep |
Next step |
story.step |
Step {n} |
story.stepOf |
Step {n} of {total}: {title} |
story.empty |
No steps in this scenario |
tab.summary |
Summary |
tab.step |
Step |
tab.node |
Details |
summary.components |
Components |
summary.connections |
Connections |
summary.steps |
Steps |
summary.modes |
Modes |
summary.points |
Key points |
summary.facts |
Facts |
summary.roles |
Roles |
summary.scenarios |
Scenarios |
step.none |
This diagram has no walkthrough steps. |
step.payload |
Payload |
step.copy |
Copy |
node.none |
Select a card to see its details and connections. |
node.tech |
Technology |
node.runtime |
Runtime |
node.ports |
Ports |
node.status |
Status |
node.source |
Source |
node.connections |
Connections |
node.noConnections |
No connections. |
panel.for |
For {name} |
panel.showAll |
Show all cards |
panel.hint |
Select a card to see the items for that component. |
panel.empty |
Nothing recorded for this component. |
toast.payloadCopied |
Payload copied |
toast.jsonCopied |
JSON copied with current positions |
toast.layoutReset |
Layout reset |
toast.buildHint |
Run 'yantra build diagram.json' to produce a standalone file |
toast.pngFailed |
PNG export failed; exported SVG instead |
toast.noFullscreen |
Full screen is not available here |
watermark.text |
Made with Yantra |
watermark.title |
Yantra on GitHub |
watermark.label |
{text} – Yantra on GitHub (opens in a new tab) |
page.answer |
Answer |
page.question |
Question |
page.contents |
Contents |
page.updated |
Updated {date} |
page.theme |
Toggle light or dark theme |
page.recommended |
Recommended |
page.done |
Done: |
page.todo |
To do: |
page.diagram |
Diagram |
page.interactive |
{title} (interactive) |
page.diagramMissing |
Diagram missing. |
page.imageOmitted |
Image omitted: only https: and data:image/ sources are allowed. |
page.diagramNote |
Diagram: {title} |
page.seeHtml |
see the HTML page |
page.image |
Image: {alt} |
md.component |
Component |
md.role |
Role |
md.description |
Description |
md.groups |
Groups |
md.group |
Group |
md.inside |
Inside |
md.members |
Members |
md.groupSuffix |
{name} (group) |
md.from |
From |
md.to |
To |
md.label |
Label |
md.hides |
hides: {list} |
md.version |
Version {v} |
md.fields |
Fields |
md.methods |
Methods |
md.field |
Field |
md.method |
Method |
md.type |
Type |
md.returns |
Returns |
md.key |
Key |
md.visibility |
Visibility |
md.notes |
Notes |
md.nullable |
nullable |
md.static |
static |
md.abstract |
abstract |
md.direction |
Direction |
md.relationship |
Relationship |
md.both |
both ways |
md.undirected |
undirected |
md.selfLoop |
self-loop |
md.link |
link |
md.more |
More: {url} |
md.detail |
detail: {title} |
drill.crumbs |
Diagram levels |
drill.back |
Back to {title} |
drill.open |
Open detail: {title} |
drill.cardHint |
opens a detail diagram |
drill.opened |
Opened {title} |
drill.details |
Detail diagrams |
drill.link |
Open link |
drill.newTab |
(opens in a new tab) |
drill.edgeLink |
Open link for {label} |
drill.overview |
Overview |
drill.hasDetail |
Has a detail diagram |
card.more |
+ {n} more |
vis.public |
public |
vis.private |
private |
vis.protected |
protected |
vis.package |
package |
end.one |
exactly one |
end.zero-or-one |
zero or one |
end.many |
many |
end.one-or-many |
one or more |
end.zero-or-many |
zero or more |
end.any |
any |
end.between |
{a} to {b} |
chart.total |
Total |
chart.today |
Today |
chart.day |
Day {n} |
chart.day1 |
{n} day |
chart.days |
{n} days |
chart.trend |
trend |
chart.value |
Value |
chart.share |
Share |
chart.task |
Task |
chart.group |
Group |
chart.start |
Start |
chart.end |
End |
chart.after |
After |
chart.milestone |
Milestone |
chart.item |
Item |
chart.noData |
No data to chart. |
chart.series |
Series {n} |
chart.more |
+{n} more |
chart.topLeft |
Top left |
chart.topRight |
Top right |
chart.bottomLeft |
Bottom left |
chart.bottomRight |
Bottom right |
chart.kind.bar |
Bar chart |
chart.kind.column |
Column chart |
chart.kind.line |
Line chart |
chart.kind.area |
Area chart |
chart.kind.pie |
Pie chart |
chart.kind.donut |
Donut chart |
chart.kind.gantt |
Gantt chart |
chart.kind.matrix |
Matrix |
chart.kind.heat |
Heat map |
chart.kind.trend |
Trend |
chart.point1 |
{n} point |
chart.points |
{n} points |
chart.seriesCount |
{n} series |
chart.summary |
{kind}, {points}; values in the table that follows. |
chart.kind.ring |
Progress ring |
chart.kind.bullet |
Bullet chart |
chart.target |
Target |
chart.remaining |
Remaining |
node.chart |
Chart |
fold.expand |
Expand {title} ({n} hidden) |
fold.collapse |
Collapse {title} |
fold.folded |
Folded |
fold.expanded |
Expanded |
fold.badge |
Group |
fold.card |
{n} card folded |
fold.cards |
{n} cards folded |
fold.cardCount1 |
{n} card |
fold.cardCount |
{n} cards |
fold.groupCount1 |
{n} group |
fold.groupCount |
{n} groups |
depth.title |
Fold to depth |
depth.level1 |
{n} level open |
depth.level |
{n} levels open |
depth.none |
Everything folded |
depth.all |
Everything open |
help.depth |
Fold to depth (0 opens everything) |
focus.crumbs |
Group path |
focus.whole |
Whole diagram |
focus.hint |
Double-click to zoom to {title} |
focus.focused |
Zoomed to {title} |
help.focus |
Zoom to the selected card's group (Esc zooms out) |
help.gWalk |
Walkthrough |
help.gNav |
Navigate |
help.gView |
View |
help.gFold |
Fold & depth |
help.gDrill |
Drill-down & focus |
help.gPointer |
Mouse & touch |
help.panel |
Show or hide this panel |
help.footer |
Press ? to show or hide this panel. Toolbar buttons show their key in the tooltip. |
help.click |
Click a card |
help.clickDo |
Select it |
help.dragCard |
Drag a card |
help.dragCardDo |
Move it; edges re-route live |
help.dragBg |
Drag the background |
help.dragBgDo |
Pan |
help.wheel |
Scroll or pinch |
help.wheelDo |
Zoom |
help.dblGroup |
Double-click a group title |
help.dblGroupDo |
Zoom to the group |
help.dblCard |
Double-click a card |
help.dblCardDo |
Open its detail diagram |
drill.more |
Show {n} hidden levels |
Limits
- Card titles, subtitles and edge labels over 200 characters are cut with an ellipsis;
lint warns
content/text-too-long. - Graphs over 60 nodes or 120 edges switch to a faster windowed router
(
budget/large-graph-fast-routing); it may leave the odd label overlap the careful router avoids. Split big diagrams instead where you can.
Validation codes and how to fix them
Fix in this order; each fix can clear later errors.
| Code | Fix |
|---|---|
schema/* |
Add the missing field (meta.title, data.title, step title). |
ref/duplicate-*, ref/*-unknown-* |
Make ids unique; fix the referenced id (copy-paste ids, don't retype). |
ref/default-mode-unknown |
Point meta.defaultMode at an existing mode id. |
layout/manual-missing-xy, layout/off-grid |
Manual layout only: give every node x/y on the 10px grid, or remove layout.mode. |
geometry/node-overlap |
Manual layout: move nodes apart. Auto layout never overlaps. |
geometry/route-failed, geometry/edge-through-node |
Usually manual layout or extreme density: spread nodes, drop redundant edges, or split. |
geometry/label-overlaps-node, geometry/label-overlaps-label |
Shorten labels; remove labels that repeat the target's name. |
geometry/edges-stacked (warning) |
Two edges share a track; merge them or remove one. |
group/unknown-node, group/unknown-parent, group/duplicate-id |
Fix the id (group ids must differ from node ids and from each other). |
group/node-in-two-groups |
List the node only in its innermost group. |
group/cycle, group/too-deep |
Fix parent so groups form a tree at most 8 levels deep (aim for 6). |
budget/deep-nesting, budget/too-many-groups (warnings) |
Over 6 levels or 24 groups: merge a level, fold inner boxes, or move a branch into a detail diagram. |
ref/step-unknown-group |
A step's focusGroup must name a group id. |
layout/focus-group-ignored (warning) |
Tree and mind-map layouts draw no groups: highlight the cards instead of focusGroup. |
geometry/group-outside-parent, geometry/group-title-overlap |
Should not occur in auto layout; in manual layout move cards so each box sits inside its parent, below its title. |
content/group-title-cut (warning) |
The title is cut to fit its box (manual layout): shorten it; detail goes in description. |
geometry/edge-through-group-title (warning) |
Should not occur in auto layout; in manual layout move cards so the edge runs clear of the title. |
group/empty, group/missing-title (warnings) |
Add members and a title, or remove the group. |
geometry/group-overlap, geometry/node-in-foreign-group |
Manual layout: move nodes so boxes separate, or add the node to the group. Auto layout avoids both. |
geometry/label-overlaps-group-title (warning) |
Shorten the label or the group title. |
ref/inspector-unknown-click-target, ref/panel-unknown-node |
Point onNodeClick at node, stay or a panel id; list only real card ids in nodes. |
panel/*, ref/inspector-unknown-tab |
Give each panel a unique id, a title and blocks; list only existing tab ids. |
page/block-invalid (inside a panel) |
Supply the block fields listed in the message. |
schema/node-shape-invalid, schema/edge-arrow-invalid |
Use a listed shape (card, state, initial, final, choice, fork, chart) or arrow (forward, both, none). |
story/fork-needs-branches (warning) |
A fork splits into two or more transitions and a join merges two or more: add the missing branch, or drop the bar. |
schema/group-kind-invalid |
kind on a group is "region" or absent. |
group/region-needs-parent |
A region divides a composite state: set its parent to that group. |
group/single-region, group/region-mixed (warnings) |
Give the composite state two or more regions and put all of its nodes inside them. |
story/initial-has-incoming, story/final-has-outgoing (warnings) |
Nothing enters an initial state or leaves a final one: retarget the edge at a real state. |
story/choice-needs-branches (warning) |
Give the choice two or more outgoing, labelled branches, or make it a state. |
story/edge-endpoint-dimmed (warning) |
Add the edge's source and target to that step's highlightNodes. |
story/no-steps (warning) |
Add a scenario with an overview step and 2+ focused steps. |
sequence/no-messages (warning) |
meta.view is sequence but no step lists highlightEdges: add them, or drop meta.view. |
schema/unknown-view |
meta.view must be flow or sequence; meta.views a non-empty list of them. |
sequence/not-offered (warning) |
meta.view names a view the payload does not offer (meta.views, or sequence on a diagram of only table / class cards): change one of them. |
schema/unknown-layout-algorithm, schema/layout-collapsed-invalid |
meta.layout.algorithm is layered, tree or mindmap; collapsed is an array of ids. |
ref/collapsed-unknown |
List only node or group ids in meta.layout.collapsed. |
layout/collapse-ignored (warning) |
Tree layouts fold cards that have children (not groups); the layered layout folds groups only. |
schema/layout-compact-invalid |
meta.layout.compact is true, false or "auto". |
layout/compact-ignored (warning) |
Compact stacks apply to tree layouts with direction: "TB" only. |
layout/assistant-ignored (warning) |
data.assistant: true needs a tree layout and a leaf card with a manager. |
layout/tree-cross-link (warning) |
A node has a second parent: repeat the shared card or drop the edge. |
layout/groups-ignored (warning) |
Remove groups from a tree or mind map, or use the layered layout. |
layout/mindmap-roots (warning) |
Connect every branch to one central root. |
geometry/sequence-label-* |
Should not occur (gaps widen to fit); report it, and shorten the label meanwhile. |
schema/members-invalid |
data.fields / data.methods must be arrays of objects with a name; stereotype a string. |
ref/field-unknown-ref |
A field's ref must name a node ("orders") or one of its fields ("orders.id"). |
schema/unknown-relation, schema/unknown-end |
Use a listed relation or sourceEnd / targetEnd value. |
ref/edge-unknown-field |
sourceField / targetField must name a field (data.fields[].name) of that end's card. |
schema/end-label-invalid, content/end-label-too-long (warning) |
sourceLabel / targetLabel are short strings (a multiplicity or role name, under 16 chars). |
geometry/end-label-overlap (warning) |
An end label touches a card or another label: widen the gap, or shorten the label. |
content/too-many-fields (warning) |
Over 12 rows on one card: keep the keys and the columns the story needs. |
content/duplicate-field (warning) |
Remove or rename the repeated field. |
chart/missing |
A chart card (shape: "chart") needs data.chart. |
chart/invalid |
data.chart is an object with numbers to draw: values (2+ for line and area), data: [{ label, value }], or value for a ring or bullet; a chart card needs data or categories + series. |
chart/unknown-kind |
Mini charts: line, area, column, bar, donut, ring, bullet. Chart cards: column, bar, line, area, pie, donut. |
chart/mismatch |
Give one label per value, and one value per category in every series. |
chart/negative |
Ring, donut and pie values must be 0 or more (and not all 0). |
chart/out-of-range (warning) |
A ring or bullet value or target is above max: raise max. |
chart/too-many-points, chart/too-many-series (warnings) |
Over the mini chart or chart card limits: shorten the series, group the rest as "Other", or move the chart to a panel. |
budget/too-many-chart-cards, budget/too-many-mini-charts (warnings) |
Over 6 chart cards or 12 mini charts: keep the charts that carry the story. |
drill/unknown-target |
data.drill must name a key of diagrams (ids are case-sensitive). |
drill/cycle |
Levels drill into each other (A > B > A), or $ref files load each other: remove one data.drill or ref so levels form a tree. |
drill/too-deep |
More than 8 levels below the overview: merge a level, or link the deepest one as a separate diagram (url). |
drill/ref-missing |
The $ref file is missing, unreadable or not valid JSON (or nothing loaded it: validate with the CLI or MCP). |
drill/ref-unsafe |
Use a relative .json path inside the input folder: no absolute paths, URLs or .. that leaves it. |
drill/invalid-diagram, drill/diagrams-invalid, drill/duplicate-id |
diagrams is an object of diagram payloads (or $refs) with ids unique across all levels. |
drill/unknown-start-level |
meta.startLevel must be a key of diagrams. |
drill/unused-diagram (warning) |
No card drills into this level (in any mode): add data.drill or remove it. |
drill/unknown-mode (warning) |
data.modes.<id>.drill names a mode the diagram does not have. |
diagrams.<id>: … |
A finding inside a detail diagram; fix it there like any other code. |
link/unsafe-url |
url must start with http://, https:// or mailto:. |
budget/* (warning) |
Split the diagram or merge nodes. |
content/* (warning) |
Shorten titles/labels; add missing edge labels. |
Minimal example
{
"meta": { "title": "Checkout", "description": "How an order is paid and stored." },
"roles": {
"client": { "label": "Client", "color": "amber" },
"service": { "label": "Service", "color": "blue" },
"data": { "label": "Data", "color": "violet" }
},
"nodes": [
{ "id": "web", "type": "client", "data": { "title": "Web app", "subtitle": "Customer checkout", "icon": "monitor" } },
{ "id": "api", "type": "service", "data": { "title": "Orders API", "subtitle": "Validates and charges", "tech": "Go" } },
{ "id": "db", "type": "data", "data": { "title": "Orders DB", "subtitle": "Source of truth", "tech": "PostgreSQL", "icon": "database" } }
],
"edges": [
{ "id": "web-api", "source": "web", "target": "api", "label": "POST /orders" },
{ "id": "api-db", "source": "api", "target": "db", "label": "INSERT order" }
],
"scenarios": [
{
"id": "main",
"name": "Place an order",
"steps": [
{ "id": "s1", "title": "Overview", "description": "Three parts: client, API, database." },
{ "id": "s2", "title": "Submit", "description": "The browser posts the cart.", "highlightNodes": ["web", "api"], "highlightEdges": ["web-api"] },
{ "id": "s3", "title": "Persist", "description": "The API stores the order.", "highlightNodes": ["api", "db"], "highlightEdges": ["api-db"] }
]
}
]
}