# JTD  -  Jarroba Tools Diagram (authoring format for AIs)

**JTD** is a **high-level JSON** format for describing a diagram in the Jarroba Tools *Diagrams*
tool. It is meant to be generated by an **AI** (reading this document and `jtd.schema.json`, served
next to this file) and **pasted** by a person into the web app: **Import Mermaid** button -> paste the
JSON -> it is drawn as a **fully editable** diagram (the same box accepts Mermaid *or* JTD).

It is "like Mermaid, but ours": more expressive (all of our shapes and **15 chart types**), and with no
coordinate wrangling  -  if you omit `x/y`, there is **auto-layout**.

> To "see" the result without opening the web app: `node scripts/render-jtd.mjs <file.jtd.json> [out.png]`
> renders the JTD with the app's own engine (useful for an AI to iterate until it looks right).
>
> **In-browser validator/preview (pure client, no server):** open `.../jtd`  -  paste the JTD and you get
> validation (errors/warnings) + the drawing (SVG) instantly. You can pass the JTD via the URL hash:
> `.../jtd#<encodeURIComponent(json)>`. There are also live warnings inside **Diagrams > Import**.

---

## Structure

```jsonc
{
  "jtd": 1,                      // format version (required)
  "layout": "flow-down",         // "flow-down" (default) | "flow-right" | "free" | "grid" | "radial"
  "title": "My diagram",         // optional; big centered heading placed above everything
  "background": "#ffffff",       // optional, canvas background
  "nodes":  [ /* JtdNode */ ],   // connectable shapes
  "edges":  [ /* JtdEdge */ ],   // connections between nodes
  "charts": [ /* JtdChart */ ]   // charts (bar, line, pie, radar...)
}
```

- `layout: "flow-down"` / `"flow-right"`: **auto-places** nodes in layers following the edges
  (top->bottom / left->right). Ignores `x/y`.
- `layout: "grid"`: square grid in order of appearance (ignores edges for placement).
- `layout: "radial"`: the **first** node at the center and the rest in a circle (ideal for hub-and-spoke).
- `layout: "free"`: honors the `x/y` you set (for manual control).
- You can mix `nodes` + `edges` (flow) with `charts` in the same document.

## `JtdNode` (a shape)

| Field | Type | Notes |
|---|---|---|
| `id` | string | **required**, unique; referenced by edges |
| `text` | string | label (use `\n` for multiple lines) |
| `shape` | enum | shape (see list); defaults to `rect` |
| `icon` | string | Material Icons name -> makes the node an icon node |
| `fill` | color | fill (`#rrggbb`) or a **theme token**; if omitted, theme color |
| `stroke` | color | border color (hex or token) |
| `x`,`y`,`w`,`h` | number | position/size. **`x`,`y` = the node's TOP-LEFT corner** (like CSS), in canvas units. Only used with `layout:"free"` (or to force placement); with a `group` parent they are relative to the parent's top-left. |

**Optional node style** (copied as-is):
`borderStyle` (`solid`,`dashed`,`dashfine`,`dashlong`,`dotted`,`dashdot`,`dashdotdot`,`double`,`frame`,`none`),
`borderWidth` (num), `corners` (`none`,`all`,`top`,`bottom`,`left`,`right`,`one`,`diag`,`three`),
`bold`, `italic`, `textColor`, `fontSize`, `font`, `align` (`left`/`center`/`right`/`justify`),
`valign` (`top`/`middle`/`bottom`), `textRotation` (0/90/-90), `opacity` (0-1), `rotation` (degrees),
`skin` (`flat`,`soft`,`glass`,`neon`,`shadow`,`ornate`,`sketch`), `group` (see below),
`runs` (**mixed formatting** in one node — see below),
`titleSide` (**`container` only**: `top` = horizontal title bar (default), `left`/`right` = vertical title with the reserved margin on that side),
`titleH` (**`container` only**: title-bar thickness; omit for AUTO-fit to the title text, or set a number to force/equalise it).

### Theme color tokens

`fill`, `stroke` and `textColor` accept a **hex** (`#16a34a`) **or a theme token** that resolves to the
document `theme`'s palette, so a diagram stays consistent (and works on the `dark` theme) without hardcoding
colors: `surface` (node fill), `stroke`, `text`, `accent`, `edge`, `bg`, `muted`. Example:
`{ "id": "h", "fill": "accent", "textColor": "text" }`. Omitting colors entirely also inherits the theme.

### `runs` — mixed formatting inside one node

For a label where part is bold/italic/colored and part is not, use `runs` instead of `text`: an array of
chunks `{ "t": "…", "b"?: bool, "i"?: bool, "c"?: "#hex" }` concatenated in order.

```jsonc
{ "id": "n", "shape": "rect",
  "runs": [ { "t": "Total: " }, { "t": "1.800 €", "b": true, "c": "#16a34a" }, { "t": " / año" } ] }
```

### `group` — grouping and **containers with relative coordinates**

`group` is a label: nodes sharing the same `group` are grouped together (move as one). **And if the
`group` value is the `id` of another node** (typically a `container` or `card`), that node becomes the
**parent**: this node's `x`,`y` are then **relative to the parent's top-left corner**, not to the
canvas. So you lay a card's contents out *inside the card* (`x:8, y:38`…) and the whole card can be
placed anywhere — you never compute global coordinates. Nesting works (a container inside a container).
Edges may connect nodes across container boundaries freely.

```jsonc
{ "id": "card", "shape": "container", "x": 400, "y": 60, "w": 200, "h": 160 },
{ "id": "hdr",  "shape": "rect", "text": "Header", "group": "card", "x": 8, "y": 8,  "w": 184, "h": 24 }, // → canvas 408,68
{ "id": "row",  "shape": "text", "text": "· item", "group": "card", "x": 12, "y": 40, "w": 176, "h": 18 }  // → canvas 412,100
```

**Supported `shape` values:** `rect`, `round`, `ellipse`, `diamond`, `parallelogram`, `cylinder`, `hexagon`,
`note`, `text`, `container`, `triangle`, `star`, `cloud`, `document`, `card`, `callout`, `actor`,
`cross`, `process`. (Icons: use `icon`. Images cannot be imported through JTD.)

## `JtdEdge` (a connection)

| Field | Type | Notes |
|---|---|---|
| `from`,`to` | string | **required**: node ids |
| `label` | string | text on the line |
| `arrow` | enum | `end` (default) \| `start` \| `both` \| `none` |
| `style` | enum | `orthogonal` (default) \| `straight` \| `rounded` |
| `color` | color | line color |
| `dash` | enum | `solid`,`dashed`,`dashfine`,`dashlong`,`dotted`,`dashdot`,`dashdotdot` |
| `width` | number | thickness |
| `head` | enum | arrowhead: `line`,`triangle`,`diamond`,`circle` |
| `fromPort`,`toPort` | enum | **fixed anchor side** the line leaves/enters by: `top`,`bottom`,`left`,`right`,`auto`. Omit to let the layout choose (down→`bottom`/`top`). Use it when the automatic side gives an awkward crossing. |

## `JtdChart` (a chart)

A JTD chart is a **full `ChartSpec`** + optional placement (`id`,`x`,`y`,`w`,`h`). Fields:

| Field | Type | Notes |
|---|---|---|
| `type` | enum | **required**: `bar`, `barh`, `pie`, `donut`, `line`, `area`, `scatter`, `quadrant`, `radar`, `gauge`, `voronoi`, `sankey`, `boxplot`, `heatmap`, `treemap` |
| `title` | string | chart title |
| `categories` | string[] | X-axis labels (Jan, Feb...) |
| `series` | `{ values:number[], name?, kind?, axis? }[]` | `kind`: `bar`\|`line`\|`area` (mix within one chart); `axis`: `right` for a secondary axis |
| `showValues` | bool | show the value above each mark |
| `legend` | bool | show legend |
| `grid` | bool | axes and grid |
| `xTitle`,`yTitle` | string | axis titles |
| `yMin`,`yMax` | number | **fixed range** of the Y axis (otherwise auto-scales) |
| `points` | `{ x, y, label? }[]` | for `scatter`/`quadrant`/`voronoi` |
| `max` | number | for `gauge` |
| `links` | `{ from, to, value }[]` | for `sankey` |
| `horizontal` | use `type:"barh"` | horizontal bars |

Per-group style (optional): `titleStyle`, `catStyle`, `legendStyle`, `xTitleStyle`, `yTitleStyle`
(each one `{ color?, size?, bold?, italic?, rotation? }`). The chart's **frame** is controlled with the
chart's own `fill`/`stroke`, like any shape.

---

## Examples

**Flow (auto-layout):**
```json
{ "jtd": 1, "layout": "flow-down",
  "nodes": [
    { "id": "a", "text": "Start", "shape": "round" },
    { "id": "b", "text": "Data OK?", "shape": "diamond", "fill": "#fff3cd" },
    { "id": "c", "text": "Process" },
    { "id": "d", "text": "Fix", "shape": "parallelogram" }
  ],
  "edges": [
    { "from": "a", "to": "b" },
    { "from": "b", "to": "c", "label": "yes" },
    { "from": "b", "to": "d", "label": "no" },
    { "from": "d", "to": "b" }
  ] }
```

**Line chart with a fixed axis range:**
```json
{ "jtd": 1, "charts": [
  { "type": "line", "title": "AI cost vs salary 2024-2028",
    "categories": ["2024","2025","2026","2027","2028"],
    "series": [ { "name": "AI", "values": [50,150,350,1000,2100] },
                { "name": "Salary", "values": [2000,2000,2000,2020,2050] } ],
    "yTitle": "USD/month", "yMin": 0, "yMax": 2500, "legend": true }
] }
```

**Mixed bars + line:**
```json
{ "jtd": 1, "charts": [
  { "type": "bar", "title": "Revenue vs target", "categories": ["Q1","Q2","Q3"],
    "series": [ { "name": "Revenue", "values": [30,45,38], "kind": "bar" },
                { "name": "Target", "values": [35,40,40], "kind": "line" } ],
    "showValues": true } ] }
```

---

## Making a GOOD diagram (for AIs) — workflow + design

JTD (like Excalidraw's scene JSON) is "design + writing JSON", which is what you're good at. Two things turn a
*valid* diagram into a *good* one:

### The loop: generate → render → LOOK → fix

You cannot judge a diagram from JSON alone. After writing it, **render it, look at the image, and fix what you
see — in a loop, not as a one-time final check**:

- **No install:** paste it at `.../jtd` (validator + live SVG preview), or share a link with the JTD in the
  hash (`.../jtd#<encodeURIComponent(json)>`).
- **Connected via MCP:** `diagram_validate` then `diagram_svg` (returns the SVG) — iterate until it looks right.

Feed the JTD **source** back into the model as context, never a PNG.

### Design defaults (so it looks intentional, not just valid)

- **Layout:** prefer AUTO-layout — omit `x/y` and set `layout` (`flow-down`/`flow-right` for flows and org
  charts, `radial` for hub-and-spoke/mind maps, `grid` for unrelated items). Reserve `free` (manual `x/y`) for
  dashboards/cards you place deliberately.
- **Colour = meaning, not decoration.** Use a small, consistent semantic palette — e.g. green = ok/positive,
  amber = warning, red = error/negative, gray = muted/inactive — plus one accent for emphasis. Prefer theme
  **tokens** (`surface`, `accent`, `text`, `muted`, `stroke`, `edge`) so it works in light *and* dark; use hex
  only for those status colours.
- **Type hierarchy:** title > section header > body > caption. Bigger/bold only for the few things that matter;
  don't make everything bold.
- **Whitespace:** consistent gaps; `group` related nodes and separate unrelated ones. Empty space is what makes
  the structure readable.
- **Shape = role:** `rect`/`round` = step/process, `diamond` = decision, `cylinder` = data/DB, `parallelogram`
  = I/O, `note` = annotation, `container`/`card` = grouping, `actor` = a person. Match the shape to the meaning.

### Patterns (start from these)

- **Flowchart:** `flow-down`; `round` start/end, `rect` steps, `diamond` decisions; label the yes/no edges.
- **Org chart / hierarchy:** `flow-down`; edges parent → children.
- **Architecture / system:** `free` + a `container` per zone, children with `group` = the container's `id`
  (relative coords); edges may cross zones; use `fromPort`/`toPort` to keep the routing clean.
- **Mind map / hub-and-spoke:** `radial` (the first node is the hub).
- **Cards / comparison panels:** `free`; one `container` per column (same `w`/`h`, aligned `y`), contents as
  `group` children (relative coords).
- **Data / dashboards:** add `charts` next to the shapes.

### Big diagrams

If it's large enough to risk your output limit, build it in **sections**: emit a first valid JTD with the
skeleton, then add more in further messages (the import box replaces the page each time; or keep a page per
section). A valid-but-partial JTD beats a truncated one.

### Portability (not lock-in)

The same box accepts **Mermaid** too, the result is a **fully editable** diagram, and it exports to **PNG/SVG**.
Author JTD for our shapes/charts; fall back to Mermaid for a quick throwaway flow.

---

## Governance (for whoever maintains the tool)

WARNING: **When adding or changing a shape, a chart type or a style field, you must update JTD and this
doc.** The safety net is `src/app/features/diagram/diagram-jtd.spec.ts`, which **forces** EVERY shape
(`SHAPE_SIZES`) and EVERY chart type (`CHART_TYPES`) to compile in JTD, and the pure validator's lists
(`jtd-validate.ts`) to match the real ones: if you add something and do not reflect it, the tests turn
red. Also update the enums in `public/jtd.schema.json`.

## Self-check: how an AI validates and *sees* what it made

Three ways, pick what your host allows:

1. **In-browser validator/preview page `.../jtd`** (no server, no install, nothing leaves the machine): paste
   the JTD and get validation (specific errors/warnings) **plus the drawing (SVG)** instantly. You can pass the
   JTD in the URL: `.../jtd#<encodeURIComponent(json)>` — so a link opens straight into the preview. This is the
   universal option (a person, or an AI that can open a URL / read SVG).
2. **MCP tools** (below) — for an AI whose host runs the local MCP.
3. **`node scripts/render-jtd.mjs <file.jtd.json>`** — CLI, renders a PNG with the real engine (needs the repo
   + dev server). For a coding agent with the repo checked out.

## MCP (optional, for connected AIs)

`mcp/` is a **local stdio MCP server** (`tsx src/server.ts`) — the AI's host spawns it as a subprocess and talks
over stdin/stdout; it is **not a web service**. It exposes these diagram tools (they reuse everything above):

**One-shot** (you already have the full JSON):
- **`diagram_validate(jtd)`** — validates structure/enums/edges. **Standalone: no browser, no dev server.**
  Returns `{ok, errors, warnings, summary}`. Use it to self-validate before pasting.
- **`diagram_svg(jtd)`** — renders to **SVG in pure Node (no browser, no dev server)** → the AI gets the drawing
  back and can *see* it (if it reads SVG/images) to iterate. Validate first.
- **`diagram_render(jtd, baseUrl?, out?)`** — renders to PNG with the real engine (spawns
  `scripts/render-jtd.mjs`); **this one requires the dev server running** (full fidelity: skins/effects).

**Incremental builder** — for the *generate → see → fix* loop when the diagram is big: build it in
memory piece by piece instead of writing all the JSON at once (all standalone, no server):
- **`diagram_new({layout?, title?, theme?, background?})`** — starts a fresh empty diagram in memory.
- **`diagram_add({jtd})`** — pass a JTD *fragment* (just the `nodes`/`edges`/`charts` you want to add); it
  accumulates them and returns the running counts plus the validation warnings/errors of the result so far.
- **`diagram_preview()`** — validates and renders the current in-memory diagram to SVG → look at it, then keep
  adding or correcting. This is the loop.
- **`diagram_get()`** — returns the complete JTD document you've built (to paste into the web box or save).

So everything except `diagram_render`'s PNG path needs only the MCP process itself.
