@meltemi/diagrams-cli 1.9.0 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/agent-setup/prompt.md +83 -0
  2. package/dist/assets-command.d.ts.map +1 -1
  3. package/dist/assets-command.js +15 -0
  4. package/dist/assets-command.js.map +1 -1
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +11 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/export-command.d.ts.map +1 -1
  10. package/dist/export-command.js +16 -3
  11. package/dist/export-command.js.map +1 -1
  12. package/dist/init-command.d.ts.map +1 -1
  13. package/dist/init-command.js +6 -3
  14. package/dist/init-command.js.map +1 -1
  15. package/dist/messages/en.d.ts +45 -7
  16. package/dist/messages/en.d.ts.map +1 -1
  17. package/dist/messages/en.js +82 -9
  18. package/dist/messages/en.js.map +1 -1
  19. package/dist/messages/es.d.ts.map +1 -1
  20. package/dist/messages/es.js +79 -8
  21. package/dist/messages/es.js.map +1 -1
  22. package/dist/messages/index.d.ts +41 -6
  23. package/dist/messages/index.d.ts.map +1 -1
  24. package/dist/skill-command.d.ts +48 -0
  25. package/dist/skill-command.d.ts.map +1 -0
  26. package/dist/skill-command.js +148 -0
  27. package/dist/skill-command.js.map +1 -0
  28. package/package.json +25 -18
  29. package/skills/.skills-sync.json +5 -0
  30. package/skills/meltemi-diagrams/SKILL.md +232 -0
  31. package/skills/meltemi-diagrams/examples/architecture.meltemi +73 -0
  32. package/skills/meltemi-diagrams/examples/platform.meltemi +139 -0
  33. package/skills/meltemi-diagrams/examples/sequence.meltemi +52 -0
  34. package/skills/meltemi-diagrams/examples/slide.meltemi +60 -0
  35. package/skills/meltemi-diagrams/examples/story.meltemi +53 -0
  36. package/skills/meltemi-diagrams/reference/c4.md +92 -0
  37. package/skills/meltemi-diagrams/reference/cloud.md +19 -0
  38. package/skills/meltemi-diagrams/reference/converting.md +101 -0
  39. package/skills/meltemi-diagrams/reference/free.md +327 -0
  40. package/skills/meltemi-diagrams/reference/language.md +245 -0
  41. package/skills/meltemi-diagrams/reference/layout.md +165 -0
  42. package/skills/meltemi-diagrams/reference/sequence.md +143 -0
  43. package/skills/meltemi-diagrams/reference/stories.md +94 -0
  44. package/skills/meltemi-diagrams/reference/themes.md +148 -0
  45. package/templates/kanban/diagrams/board.meltemi +57 -0
  46. package/templates/whiteboard/diagrams/whiteboard.meltemi +59 -0
@@ -0,0 +1,327 @@
1
+ # Free compositions
2
+
3
+ Use `diagram <id>: free { }` for a slide, an executive picture, a whiteboard, a kanban board or an
4
+ explanation that does not fit a flow notation. Objects are placed by hand (`layout manual` is the
5
+ default); relations, `placement`, constraints, `style`, `class`, `rule`, `animate`, themes,
6
+ `preset` and `canvas` work as in flow diagrams. A whiteboard writes `preset board`: the canvas
7
+ grows with its content and never reports `layout.too-large`.
8
+
9
+ ## Objects
10
+
11
+ | Declaration | Object | Default size |
12
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------- |
13
+ | `box <id> "Label"`, `id["L"]` | a shape with a label and an optional `description` | its shape's size, widened to its text |
14
+ | `id[("Label")]` | a box with the `cylinder` shape | 160 × 64 |
15
+ | `text <id> "Text\nmore"` | measured text without a frame | its extent |
16
+ | `icon <id> "tabler:server"` | an icon of the packs; find names with `pnpm mdg assets icons <w>` | 48 × 48 |
17
+ | `image <id> "img/logo.png"` | a PNG, JPEG, GIF, WebP or SVG file of the project, relative to the file | its pixel size, at most 640 wide |
18
+ | `note <id> "Text" [on <element>]` | an annotation: text with no frame until styled, and a dotted leader to the element it is attached to (below) | its text's extent |
19
+ | `sticky <id> "Text"` | a sticky note: a square card whose text wraps and shrinks to fit (below) | 160 × 160, never grown |
20
+ | `group <id> "Label" { ... }` | a band holding objects and relations; `group <id> "" { }` is plain; `layout column` makes it a list (below) | written, or fitted to children |
21
+ | `line <id> from (x, y) to (x, y)` | a line that joins no element: a divider, a bracket, an underline (below) | the bounding box of its points |
22
+ | `pen <id> [(x, y), (x, y), ...]` | a freehand stroke: handwriting, a drawn arrow, a highlighter mark (below) | its points' box plus half its width |
23
+
24
+ `node`, `decision`, `data` and `tag` are flow kinds and are refused here; `note`, `sticky` and
25
+ `pen` are free kinds. Relations take the lines without heads `a -- b` and `a --- b` too.
26
+
27
+ ## Notes
28
+
29
+ `note remark "Checked at the gateway" on gateway { layer front }` is text at the theme's note size in
30
+ the muted colour, left-aligned and without a frame. `style remark { fill "#fff7d6"; stroke warning;
31
+ line dashed }` gives it an outline (any of `fill`, `stroke` or `pattern` does); `font`, `fontSize`,
32
+ `fontWeight`, `textAlign`, `radius`, `color`, `icon` and `rotation` apply, `shape` does not. The
33
+ block takes `description`, `class`, `layer`, `link` and `url`. `on <element>` attaches the note to
34
+ any other element of the diagram: a dotted leader joins them, `layout auto` keeps them near each
35
+ other, renaming the element rewrites the clause, and deleting the element leaves the note. An
36
+ unknown target is `ref.unknown-element`; a note on itself, `prop.invalid-value`. A note with an
37
+ outline can take `animate remark { effect dash }`; without one, only a pulse.
38
+
39
+ ## Stickies
40
+
41
+ ```text
42
+ sticky cache "Cache the catalogue at the edge before the sale"
43
+ sticky retry "Retry payments\nonce"
44
+ style retry { fill "#f8bbd0"; rotation 4 }
45
+ ```
46
+
47
+ - A 160 × 160 card (scaled with the theme's `labelSize`, or its `size`) that never grows: the
48
+ text wraps at spaces inside the theme's `padding`, at `fontSize` or 1.2 × the label size
49
+ (15.6 px), and shrinks 1 px at a time down to 8 px until it fits. Text that still does not fit
50
+ is `layout.text-overflow` (warning) and clipped: shorten it or use the `size` in the hint.
51
+ - Colours: the theme's `sticky` card (`#fff59d` on light themes) and `stickyText` text; `fill`
52
+ and `color` override them. Common fills: yellow `#fff59d`, pink `#f8bbd0`, green `#c8e6c9`,
53
+ blue `#bbdefb`, orange `#ffe0b2`, purple `#e1bee7`. A soft shadow, `radius` 2, an outline only
54
+ with `stroke`. Text is centred (`textAlign`, `verticalAlign`); an `icon` sits top left.
55
+ - Takes `fill`, `stroke`, `color`, `line`, `width`, `fontSize`, `fontWeight`, `textAlign`,
56
+ `verticalAlign`, `radius`, `rotation`, `opacity`, `icon`, `iconColor`, `iconSize`; `shape`,
57
+ `pattern`, `iconPosition` and `labelPosition` are `prop.invalid-value`. `rule sticky { }` styles
58
+ them all.
59
+ - A sticky is a box for everything else: `place`, `size`, ports, constraints, relation endpoints
60
+ (`cache -> retry`), stories and effects. `sticky s` without text is `prop.invalid-value`.
61
+
62
+ ## Shapes and corners
63
+
64
+ `style a { shape hexagon }` draws a box (or a flow `node`) as one of twenty-four shapes. Each has a
65
+ default size (technical themes; it scales with the label size), keeps its text inside its outline
66
+ and grows until the text fits, and connectors end on its outline:
67
+
68
+ | Size | Shapes |
69
+ | --------- | ------------------------------------------------------------------------------------ |
70
+ | 160 × 56 | `rounded` (default), `rectangle`, `pill`, `parallelogram`, `arrow`, `framed`, `flag` |
71
+ | 160 × 64 | `cylinder`, `trapezoid`, `hexagon` |
72
+ | 160 × 72 | `ellipse`, `octagon`, `document`, `callout` |
73
+ | 160 × 80 | `diamond` |
74
+ | 160 × 96 | `triangle`, `pentagon` |
75
+ | 160 × 104 | `person` |
76
+ | 160 × 120 | `agent` |
77
+ | 120 × 120 | `square`, `circle`, `star` |
78
+ | 180 × 100 | `cloud` |
79
+ | 64 × 64 | `icon` (or its `iconSize`), the label outside (below) |
80
+
81
+ - `square` and `circle` are a rectangle and an ellipse with a square default size; `arrow` points
82
+ right; `framed` has two inner lines (a subroutine); `callout` has a tail at the bottom left.
83
+ - `person` is a round head over a body; `agent` is an AI agent, a robot head (eyes, mouth and an
84
+ antenna) over a body. Both keep the text in the body; an end on the north side meets the head.
85
+ - `\n` breaks a box's or a node's label into lines, as in a `text`.
86
+ - A `size` too narrow for the text inside a shape is `layout.text-overflow`; use the hint's width.
87
+ - A pinned end (`route r from a.east to d.north 30%`) is drawn where the side's normal meets the
88
+ outline, so arrows touch diamonds, ellipses and stars.
89
+ - `radius 0..64` sets the corner radius of boxes, nodes, notes and groups (`radius 0` squares
90
+ them); `rectangle`, `square` and `framed` are always square, the other shapes ignore it. The
91
+ theme's `radius` (8) and `groupRadius` (14) are the defaults.
92
+ - Decisions, data stores, notes, tags, groups, texts, icons and images refuse `shape`, in `style`
93
+ statements as in rules: their kind is their drawing. Only `rule box` and `rule node` may set it.
94
+
95
+ Text in every profile: `font "<Family>"`, `fontSize 6..160` and `fontWeight 400|500|600|700` on
96
+ whatever draws text (text defaults to 1.6 × the label size, stickies to 1.2 × at 400). Free-only
97
+ text properties (refused in flow diagrams):
98
+
99
+ - `textAlign left|center|right` on boxes, stickies and text (text is left aligned by default);
100
+ - `verticalAlign top|middle|bottom` on boxes (`middle` by default) moves the label, description
101
+ and icon to the top or bottom of the shape's text band, the theme's padding from its edge:
102
+ `style card { shape rectangle; textAlign left; verticalAlign top }` is draw.io's top-left text.
103
+ Ellipses and other narrowing shapes move a wide block back towards the middle until it fits;
104
+ cylinders keep it between the rims, documents above the wave, callouts above the tail. On
105
+ `triangle`, `pentagon`, `star`, `person`, `agent` and `icon` shapes, with `iconPosition center`,
106
+ and on texts, icons, images, groups and lines it moves nothing (`layout.vertical-align-ignored`);
107
+ - `opacity 0..1` fades an element, a group, a line or a pen (`style draft { opacity 0.5 }`);
108
+ relations refuse it;
109
+ - a text, icon or image with `fill`, `stroke` or `pattern` gets a frame.
110
+
111
+ ## Lines
112
+
113
+ ```text
114
+ line divider "Phase 2" from (40, 160) to (520, 160) // a labelled divider
115
+ line bracket from (40, 220) to (520, 280) via [(280, 220)] // bends in a via list
116
+ group notes "Notes" {
117
+ text remark "Read first"
118
+ line underline from (0, 22) to (120, 22) // in the group's interior
119
+ }
120
+ style divider { line dashed; stroke muted }
121
+ style bracket { routing orthogonal; corners rounded; arrowhead triangle }
122
+ ```
123
+
124
+ - A `line` joins nothing: use it for dividers, brackets, underlines and strokes; between two
125
+ boxes write a relation (`a -- b`) so it follows them.
126
+ - Its points are written in the statement, in the parent group's interior like `place`; it takes
127
+ no `place`, `size` or `rotation` (`prop.invalid-value`).
128
+ - It takes a relation's styles: `stroke`, `width`, `line`, `arrowhead` and `arrowtail` (none by
129
+ default), `routing` (`polyline` by default, `orthogonal`, `bezier`), `corners`, and `color`,
130
+ `fill`, `icon` for its label pill; a box's properties (`shape`, `radius`, the text properties)
131
+ are `prop.invalid-value`. `rule line { }` styles every line.
132
+ - The label is the optional second argument, on a pill at 50 %;
133
+ `label divider at 20% offset (0, -14)` in `placement` moves it. `flow`, `dash`, `pulse` and
134
+ `trace` run along a line.
135
+ - A line whose points coincide is drawn as a dot: `layout.degenerate-line`. Flow, sequence and C4
136
+ diagrams refuse `line` (`prop.unknown`).
137
+
138
+ ## Pen strokes
139
+
140
+ ```text
141
+ pen marker [(40, 70), (280, 70)] // declared first: under the text
142
+ text heading "Why the cache helps"
143
+ pen arrow [(216, 140), (250, 128.5), (290, 130), (336, 140)]
144
+ pen dot [(400, 60)] // one point is a dot
145
+ style marker { stroke highlight; width 20; opacity 0.35 } // a highlighter mark
146
+ style arrow { width 4; arrowhead triangle }
147
+ ```
148
+
149
+ - A freehand stroke: one smooth path through the midpoints of its points (round caps and joins).
150
+ Use it for handwriting, drawn arrows, circles and underlines; use a `line` for a straight
151
+ divider and a relation between two boxes.
152
+ - `pen <id> [(x, y), ...]`: at least one tuple of plain numbers, decimals kept, in the parent
153
+ group's interior like `place`; no label, no `place`, `size` or `rotation`
154
+ (`prop.invalid-value`). `pen p [10, 20]`, `pen p []` and `pen p` are `prop.invalid-value` with
155
+ the form `pen <id> [(x, y), (x, y), ...]` in the hint.
156
+ - Styles: `stroke` (the theme's `ink`), `width` 1..64 (3; `width 0` is refused), `line`,
157
+ `opacity`, `arrowhead`, `arrowtail` (none). Anything else (`fill`, `shape`, `routing`, text
158
+ properties) is `prop.invalid-value`. `rule pen { }` styles every pen.
159
+ - The highlighter is `stroke highlight; width 20; opacity 0.35`; declare it before the text it
160
+ marks so the text is painted over it.
161
+ - Pens take no part in `layout.overlap` or the label checks, are never moved by the engine or a
162
+ list, are not jittered by sketch themes, and run `flow`, `dash`, `pulse` and `trace` like a line.
163
+ A story's `draw a, b over 2s` replays them ([stories.md](stories.md)).
164
+ - `pnpm mdg format` prints a long point list filled to 100 columns, not one point per line.
165
+
166
+ ## Icons in boxes and as shapes
167
+
168
+ Flow diagrams take these too. The `icon` of a node, decision, data store or box goes beside the
169
+ text (`iconPosition left`, the default, or `right`), above or below it (`top`, `bottom`), or at the
170
+ centre as the picture of the box (`center`), with the label in a band at the `labelPosition` edge
171
+ inside (`bottom` by default). `shape icon` draws the element as its icon alone, with the label
172
+ outside at `labelPosition`, as provider icons are drawn in architecture diagrams:
173
+
174
+ ```text
175
+ box gateway "API Gateway"
176
+ box fn "Checkout"
177
+ box orders "Orders"
178
+ box tile "Orders DB"
179
+ gateway -> fn
180
+ fn -> orders
181
+
182
+ style gateway { icon "aws:api-gateway"; shape icon } // label below the icon
183
+ style fn { icon "aws:lambda"; shape icon; labelPosition top } // label above
184
+ style orders { icon "aws:dynamodb"; shape icon; fill surface; stroke line } // framed
185
+ style tile { icon "tabler:database"; iconPosition center } // 120 × 120 tile
186
+ ```
187
+
188
+ - `iconSize` is the icon's side: 8 to 128 (px or unitless) or `auto`. Beside the text the theme's
189
+ 18 px is the default; a number grows a box without `size`, and is clamped (never cut) in a box
190
+ with a written `size`, where `auto` fills the room. A centred icon is as large as fits; a
191
+ written `size` without room for an 8 px icon beside the label band is `layout.text-overflow`.
192
+ - An icon shape is a 64 × 64 square (or `iconSize`; a written `size` wins, the icon fitted
193
+ inside), with nothing behind the icon unless `fill`, `stroke` or `pattern` frames it; a frame
194
+ adds half the padding on each side (an 80 × 80 square around a 64 px icon). Arrows, ports and
195
+ anchors end on the square (`to fn.south` is the icon's bottom edge); groups and the canvas
196
+ count the label too, and a box over the label is `layout.overlap`, so leave room for it. A
197
+ connector on the label's side is drawn from beyond the label; other routes go around it.
198
+ - `labelPosition` does nothing unless the icon is the centre piece: `layout.label-position-ignored`.
199
+ `shape icon` without an `icon` is `layout.icon-missing` and draws a dashed placeholder.
200
+ - A free `icon <id> "pack:name"` object is an icon without text; for a labelled icon use a box
201
+ with `shape icon`.
202
+
203
+ ## Coordinates
204
+
205
+ - `place` is the centre, as in flow diagrams.
206
+ - **Inside a group, `place` is relative to the group's interior**: its top-left corner plus
207
+ `groupPadding` (16) across and the header down: `groupHeader` (40) in the technical themes, or
208
+ `groupPadding` (16) for an untitled group (`""`, no description, no icon). A child at
209
+ `(w/2, h/2)` touches the interior's top-left corner. Moving the group moves its children.
210
+ - Every group needs a `place`. Without `size` it fits its children: padding + rightmost child edge
211
+ - padding wide, header + lowest child edge + padding tall.
212
+ - Route waypoints are always diagram coordinates.
213
+
214
+ ## Containers, stacks and bands
215
+
216
+ These are the pictures of a visual editor's containers and backdrops; flow diagrams take them too.
217
+
218
+ ```text
219
+ group outer "" { // nested plain rectangles: untitled groups
220
+ group inner "" {
221
+ box core "Core"
222
+ }
223
+ }
224
+ box plate "" // a stack: the outer box first, empty label
225
+ box card "Card"
226
+ group band "" { layer back } // a band behind arranged boxes (layers back, main)
227
+
228
+ style outer { fill "#dae8fc"; stroke "#6c8ebf"; radius 0 }
229
+ style inner { fill "#fff2cc"; stroke "#d6b656"; radius 0 }
230
+ style band { fill "#f5f5f5"; radius 0 }
231
+ ```
232
+
233
+ - **Untitled groups** draw only their outline, have no title band and cut no gap in connectors;
234
+ children keep half the padding (8 px) from every edge (`layout.container-margin`).
235
+ - **Stacks**: a box lying strictly inside a box declared before it on the same layer is not
236
+ `layout.overlap`; a partial overlap still is, so are two boxes that coincide, and so is a stack
237
+ declared inner first (its hint says to declare the outer box first). A
238
+ container's own label may lie over its children, but two labels at the same point are
239
+ `layout.label-overlap`: leave the outer labels empty.
240
+ - **Bands**: a group with `place` and `size` is neither arranged by the engine nor an obstacle, so
241
+ on a back layer it stays behind the boxes `layout assisted` arranges (pin one box with `place`
242
+ so the others arrange around it). Boxes and groups on different layers are not checked against
243
+ each other, and connectors cross them straight. In `layout auto` a group's `place` is ignored:
244
+ make the band a group holding its boxes.
245
+
246
+ ## Lists and kanban boards
247
+
248
+ ```text
249
+ diagram board: free {
250
+ preset board
251
+ group kanban "" {
252
+ layout row // the columns side by side
253
+ spacing 24
254
+ align stretch // every column as tall as the tallest
255
+ group todo "To do" {
256
+ layout column // the cards top to bottom, in source order
257
+ spacing 12
258
+ sticky cache "Cache the catalogue"
259
+ sticky retry "Retry payments once"
260
+ }
261
+ group done "Done" {
262
+ layout column
263
+ sticky login "Sign in with passkeys"
264
+ }
265
+ }
266
+ placement {
267
+ place kanban at (360, 300) // only the outer list needs a place
268
+ }
269
+ }
270
+ ```
271
+
272
+ - `layout column|row` in a free group block makes the group a list: its elements and groups go one
273
+ after the other in source order from the interior's corner, `spacing <n>` apart (0..400; half
274
+ `groupPadding`, 8, by default). To reorder cards, reorder their declarations.
275
+ - `align start|center|end|stretch` places children across the axis (`start` by default);
276
+ `stretch` gives a child without a written `size` the interior's full width (column) or height
277
+ (row).
278
+ - Children keep their size; the list is derived from them, and a written `size` on the list is
279
+ only its least size. A list inside a list needs no `place`.
280
+ - A `place` on a child of a list is `layout.place-ignored` (warning): remove it. Lines and pens in a
281
+ list keep their written points. A group that is not a list, inside a list, still places its own
282
+ children with `place`.
283
+ - The engine treats a list as one box in every mode; `pnpm mdg layout` writes a `place` for it and
284
+ nothing for its children.
285
+ - `spacing` or `align` without `layout` is `prop.invalid-value` (add `layout column`); `layout grid`
286
+ lists `column, row`; `layout auto` belongs to the diagram, not to a group. Flow diagrams refuse
287
+ the three.
288
+
289
+ ## Layers, order and links
290
+
291
+ - `layers back, main, front` lists layers back to front; `main` is the default and comes last
292
+ when unlisted. Put `layer front` in an object's or relation's block.
293
+ - Within a layer, groups paint first, then objects (lines included) in source order, then
294
+ relations: declare what must be on top later.
295
+ - `link checkout.api` in a block records that the object stands for element `api` of `diagram
296
+ checkout` (same file), `link co.api` through `import checkout as co from "..."`, or `link
297
+ views.checkout.api` through `import "..." as views`. Identity only; nothing is kept in step.
298
+
299
+ ## From a flow diagram
300
+
301
+ `pnpm mdg convert sheet.meltemi --to free [--diagram <id>] [--id <new>]` adds a free copy of the
302
+ flow diagram as drawn: every box with `place` and `size`, children inside their copied groups,
303
+ layers, routes, labels, styles and animations, a `link` from each object to its source, and
304
+ classes `from_decision`, `from_data`, `from_tag` for the drawing of the flow kinds; notes stay notes,
305
+ with their `on` clause.
306
+ Edit the copy freely; the flow diagram is unchanged.
307
+
308
+ ## Diagnostics
309
+
310
+ | Code | Fix |
311
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
312
+ | `ref.unknown-layer` | add the layer to `layers` (the hint gives the statement) or fix its name |
313
+ | `ref.unknown-link` | the diagram or element does not exist; fix the name or remove the `link` |
314
+ | `asset.outside-project` | image paths are relative, use `/`, no URLs, and stay inside the project |
315
+ | `asset.unknown-image` | the file does not exist next to the path written |
316
+ | `asset.unsupported-image` | not a PNG, JPEG, GIF, WebP or SVG image, broken, or over 8 MB |
317
+ | `layout.image-unavailable` | the host could not read the image; a placeholder is drawn |
318
+ | `layout.missing-placement` | an object or a group has no `place` (children of a list need none; a group that is not a list, inside a list, places its own children) |
319
+ | `layout.place-ignored` | a child of a list (`layout column\|row`) has a `place`, which the list overrides: remove the `place`, or the group's `layout` |
320
+ | `layout.text-overflow` | a `size` too narrow for a box's text, or a sticky whose text does not fit even at 8 px: shorten the text or use the size in the hint |
321
+ | `layout.too-large` | the content exceeds `preset document` or `preset slide`: split it into views, or write `preset board` for a whiteboard |
322
+ | `layout.overlap` | boxes overlap partly, or a stack is declared inner first: declare the outer box first |
323
+ | `layout.icon-missing` | `shape icon` without an `icon` style: add `icon "pack:name"` or choose another shape |
324
+ | `layout.label-position-ignored` | `labelPosition` only moves the label of `iconPosition center` and `shape icon` elements |
325
+ | `layout.vertical-align-ignored` | `verticalAlign` moves nothing on this shape or kind: choose a shape with a text band (`rectangle`, `ellipse`, `cylinder`), set `labelPosition` with a centred icon, or remove it |
326
+ | `layout.degenerate-line` | a line's points coincide: move an end or add a bend with `via` |
327
+ | `prop.invalid-value` | `textAlign`, `verticalAlign` and `opacity` are free-only; `font`, `fontSize` and `fontWeight` are refused on icons, images and pens; `shape` is for `node` and `box` only; a line or a pen takes no `place`, `size` or `rotation`; a pen needs `[(x, y), ...]` and `width` 1..64; a sticky needs its text; `spacing` and `align` need `layout column\|row`; `opacity` is refused on relations |
@@ -0,0 +1,245 @@
1
+ # The Meltemi language (0.2), flow profile
2
+
3
+ A `.meltemi` file is UTF-8 text with LF or CRLF line breaks. Statements end at a line break or
4
+ `;`; `//` and `/* */` are comments and are preserved by every tool. Identifiers match
5
+ `[A-Za-z_][A-Za-z0-9_]*`; strings are double-quoted with `\"`, `\\`, `\n` and `\t` escapes;
6
+ numbers may carry a unit (`1200ms`, `2s`, `50%`).
7
+
8
+ ## File structure
9
+
10
+ ```text
11
+ language "0.2" // optional header, must come first
12
+
13
+ import "../themes/company.meltemi" as company // imports go before the blocks
14
+ import corporate from "../themes/company.meltemi"
15
+
16
+ theme <id> { <token> <value> ... } // optional, see themes.md
17
+
18
+ diagram <id>: flow { ... } // one or more diagrams
19
+ ```
20
+
21
+ ## Projects and imports
22
+
23
+ A folder with `project.json` (`{ "name": "...", "language": "0.2" }`) is a project; files in it
24
+ import each other with relative paths (`./`, `../`, ending in `.meltemi`, never URLs).
25
+ `import "<path>" as <ns>` makes `<ns>.<id>` available; `import <id> [as <alias>] from "<path>"`
26
+ binds ids directly. Use imported themes by name: `theme "corporate"`, `theme "company.base"`,
27
+ `extends "company.base"`. Import cycles are errors (`module.cycle`). Validate a whole project with
28
+ `pnpm mdg validate <project folder>`; the full rules are in the repository's
29
+ `docs/language/projects.md`.
30
+
31
+ Every block compiles: `flow`, `free`, `sequence` and `c4` diagrams render, `story` blocks play
32
+ ([stories.md](stories.md)) and `export` profiles write deliveries (below); free compositions are described in [free.md](free.md), sequence
33
+ diagrams in [sequence.md](sequence.md), and models with C4 views in [c4.md](c4.md). Any element block may hold `link <diagram>.<element>`, which ties
34
+ it to an element of another diagram.
35
+
36
+ ## Inside `diagram <id>: flow { }`
37
+
38
+ | Statement | Meaning |
39
+ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
40
+ | `title "..."`, `description "..."` | SVG `<title>` and `<desc>`; the title also appears in the brand frame |
41
+ | `layout auto`, `layout assisted`, `layout manual` | ELK arranges everything, the unplaced boxes only, or nothing (layout.md) |
42
+ | `routing orthogonal` | connector shape: `auto` (default), `orthogonal`, `polyline`, `bezier` |
43
+ | `theme "<name>"` | built-in or file theme; default the project theme or `technical.light` |
44
+ | `preset document`, `preset slide`, `preset board`, `canvas (w, h)` | canvas size and limits; `board` grows with a whiteboard (see themes.md) |
45
+ | `class id { ... }`, `rule kind { ... }` | reusable styles and styles for every element of a kind (themes.md) |
46
+ | `direction LR` | flow of the automatic layout (`LR`, `RL`, `TB`, `BT`) |
47
+ | `layers back, main, front`; `layer back` in a block | paint order, back to front; `main` always exists; elements, groups and relations take `layer` |
48
+ | `id["Label"]` | node (rounded box) |
49
+ | `id[("Label")]` | data store (cylinder) |
50
+ | `node id "Label"`, `decision id "Label?"`, `data id "Label"` | typed declarations; label defaults to the id |
51
+ | `group id "Label" { ... }` | band holding declarations, relations and nested groups |
52
+ | `group id "" { ... }` | a plain container: no title band, padding 16 px all round (free.md) |
53
+ | `note id "Line one\nLine two" [on element]` | free text, left-aligned, no border; `on` attaches it to an element with a dotted leader |
54
+ | `tag id "PKCE"` | small pill with mono text |
55
+ | `a -> b`, `a --> b`, `a <-> b`, `a <--> b` | solid or dashed relation, one way or two-way; undeclared ids become nodes |
56
+ | `a -- b`, `a --- b` | a solid or dashed line without heads between two elements |
57
+ | `a -> b: "Label" as id` | relation with label and id |
58
+ | `{ description "..." }` after an element or relation | second text line of a box; description of a relation |
59
+ | `{ assumption "why" }` after a relation | an inferred relation, drawn dashed and muted until someone confirms it |
60
+ | `{ class a, b }` after an element, group or relation | apply classes |
61
+ | `{ port in west 30% }` after an element | a named port on a side, at a percentage along it (50 % by default) |
62
+ | `placement { ... }` | positions, sizes, routes, label anchors (below) |
63
+ | `style <t>[, <t>...] { ... }` | visual properties for elements or named relations |
64
+ | `animate <t>[, <t>...] { ... }` | motion effects: flow particles, a moving dash, a pulse, a trace (below) |
65
+
66
+ Elements and relations share one namespace per diagram. Anonymous relations get the key
67
+ `<from>-><to>#<n>` in tools and JSON; name a relation (`as id`) before routing or styling it.
68
+
69
+ ## Placement
70
+
71
+ ```text
72
+ placement {
73
+ place api at (360, 180) // centre of the box
74
+ size api (200, 56) // width, height
75
+ route create from api.east to orders.west via [(520, 180)]
76
+ label create at 50% offset (0, -16) // position of the relation label
77
+ row api, orders // constraints: align, row, column,
78
+ distance orders, db 80 // order and distance
79
+ }
80
+ ```
81
+
82
+ Ports: `north`, `south`, `east`, `west`, `auto` (`from api` means `api.auto`) and the ports an
83
+ element declares with `port`; an undeclared name is `placement.unknown-port`. A route's endpoints
84
+ must be the relation's source and target, in that order. Each element has at most one `place` and
85
+ one `size`; each relation at most one `route` and one `label`. Several `placement` blocks may
86
+ exist; order does not matter.
87
+
88
+ ## Styles
89
+
90
+ ```text
91
+ style api, db { fill "#ebe5fd"; stroke "#6a4fd0"; color "#33247a" }
92
+ style obo, ciba, proxy { line dashed }
93
+ style r1, r2 { fill "#0B1830"; color "#FFFFFF" } // dark label pills
94
+ style legendBox { fill "none" }
95
+ ```
96
+
97
+ | Property | Element | Relation |
98
+ | --------------- | ------------------------------------------------------------------------------ | ------------------------------------------ |
99
+ | `fill` | background (`"none"` for no fill) | label pill background |
100
+ | `stroke` | outline colour | connector and arrowhead colour |
101
+ | `color` | label and description text | label pill text |
102
+ | `line` | `solid`, `dashed` or `dotted` outline | overrides the arrow style |
103
+ | `width` | outline width (0 to 64; a pen's stroke 1 to 64) | connector width |
104
+ | `radius` | corner radius (0 to 64) of boxes, groups | not allowed |
105
+ | `shape` | `node` only: `hexagon`, `icon`… (free.md) | not allowed |
106
+ | `pattern` | `hatch`, `dots`, `cross` or `none` | not allowed |
107
+ | `arrowhead` | not allowed | head at the target (below) |
108
+ | `arrowtail` | not allowed | head at the source (below) |
109
+ | `routing` | not allowed | `auto`, `orthogonal`, `polyline`, `bezier` |
110
+ | `corners` | not allowed | `sharp` or `rounded` bends |
111
+ | `class` | apply classes | apply classes |
112
+ | `icon` | `"pack:name"`, placed by `iconPosition` | inside the label pill |
113
+ | `iconColor` | colour of a monochrome icon | same |
114
+ | `iconPosition` | `left`, `right`, `top`, `bottom`, `center` | not allowed |
115
+ | `labelPosition` | label edge of a centred icon or `shape icon`: `top`, `bottom`, `left`, `right` | not allowed |
116
+ | `iconSize` | `auto` or 8 to 128: the icon's side | not allowed |
117
+
118
+ Colours are quoted (`"#6a4fd0"`) or colour token names that follow the theme (`danger`, `accent`,
119
+ `muted`, `success`, `warning`, `info`, …).
120
+
121
+ Heads (`arrowhead` at the target, `arrowtail` at the source): `triangle`, `open`, `none`,
122
+ `triangleOutline` (UML generalisation), `diamond` and `diamondOutline` (composition and
123
+ aggregation), `circle`, `circleOutline`, `bar`, `cross`, and the ER crow's feet `many`,
124
+ `oneOrMany`, `onlyOne`, `zeroOrOne`, `zeroOrMany`. A relation has no start head unless it is
125
+ written `<->`/`<-->` (the end's head at both ends) or has `arrowtail`. A line `--` (dashed `---`)
126
+ has no head at either end unless `arrowhead` or `arrowtail` is written; write it for an
127
+ association, a link to a note or a plain connection, instead of `arrowhead none`:
128
+
129
+ ```text
130
+ customer -> order as places
131
+ style places { arrowtail onlyOne; arrowhead zeroOrMany } // one customer places zero or more orders
132
+ api <-> cache // two-way
133
+ api -- db // a line, no heads
134
+ note1 --- api // a dashed line, no heads
135
+ style slow { line dotted; corners rounded } // dotted, rounded bends
136
+ ```
137
+
138
+ The six tokens are `->`, `-->`, `--`, `---`, `<->`, `<-->`; `--->` is a syntax error. Sequence
139
+ diagrams take the four arrows and C4 views `->` and `-->`: another token is `prop.invalid-value`.
140
+
141
+ Setting the same property twice for a target across statements is `prop.duplicate`; different
142
+ properties in different statements are fine.
143
+
144
+ ## Animate
145
+
146
+ ```text
147
+ animate r1, r2 { effect flow; particles 2 } // particles along named relations
148
+ animate save { effect dash; direction reverse } // a dash moving along a line or outline
149
+ animate db { effect pulse; duration 1500ms; repeat 3; delay 300ms; color accent }
150
+ animate r1 { effect trace } // a highlight that fills the relation, then restarts
151
+ ```
152
+
153
+ `effect` is required. `duration` 100 ms to 60 s (the theme's `flowDuration`, `dashDuration`,
154
+ `pulseDuration` or `traceDuration`, else 1200 ms for flow, 1500 ms for trace and 1 s otherwise),
155
+ `repeat` `forever` (default) or 1 to 1000, `delay`, `direction forward|reverse` (flow, dash and
156
+ trace), `particles 1..8` (flow), `color` (flow, pulse and trace; a trace defaults to `accent`).
157
+ Flow and trace run on relations (and free lines and pens) only; dash on relations, free lines,
158
+ pens, boxes with an outline and notes or texts that style an outline (`fill`, `stroke` or
159
+ `pattern`); pulse on elements, relations, notes and fragments. One effect of each kind per target
160
+ (`animate.conflict`). Exports are static unless you ask:
161
+ `pnpm mdg export sheet.meltemi --motion animated` writes an animated SVG (CSS keyframes, static
162
+ fallback), `--at 1.5s` the frame at that time. Use the static export for documents and slides.
163
+
164
+ ## Stories
165
+
166
+ `story <id> for <diagram> { scene ... }` walks through a diagram: `reveal`, `hide`, `highlight`,
167
+ `focus`, `camera`, `trace`, `draw` (pens, lines and relations drawn again) and `hold` on one
168
+ clock, `parallel` blocks, and `view` with a `transition` to change diagram.
169
+ `pnpm mdg export sheet.meltemi --story <id> --at 2s` writes a frame. See [stories.md](stories.md).
170
+
171
+ ## Export profiles and links
172
+
173
+ ```text
174
+ export slide for checkout {
175
+ format png // svg, png, pdf, html (player), mp4, webm, gif
176
+ scale 2 // png pixels per unit
177
+ size (1920, 1080) // png, pdf, video: fitted and centred
178
+ background transparent // or a quoted colour or a token
179
+ file "slides/checkout.png"
180
+ }
181
+ ```
182
+
183
+ Plugins installed in `project.json` may declare diagram types (`diagram s: erd { entity a "A" }`):
184
+ their kinds are boxes with the type's style and properties; `pnpm mdg plugins <project>` lists them.
185
+ `pnpm mdg convert <file> --to json` writes the file's model JSON for other tools.
186
+ `pnpm mdg init <folder> --template flow|c4|sequence|free|purchase` starts a project;
187
+ `pnpm mdg preview <project>` serves its catalog and reloads it as files change;
188
+ `pnpm mdg review <project> --base main` lists what changed by id (renames included).
189
+ `pnpm mdg build catalog <project>` writes one offline HTML catalog of the project (settings in
190
+ `project.json` `catalog`; comments and source stay out unless `--source`). In the catalog and in
191
+ `--format html` pages, a double click (or the plus sign) on a system opens its container view, on
192
+ a container its component view, on an element with `link` the diagram it names: write those
193
+ views (`level` + `scope`) and links so readers can drill down; descriptions show on hover.
194
+ `pnpm mdg export sheet.meltemi --profiles` writes every profile; `--profile slide` one.
195
+ `--format mp4|webm|gif` records a story (`--story`) or effects with the installed browser and
196
+ FFmpeg: 1080p at 30 fps, GIF 960 × 540 at 15 fps. Effects that repeat forever last one loop of their
197
+ cycles (6 s with the defaults) unless `--duration` or a profile's `duration` says otherwise, and the
198
+ export notes the length it chose; a transparent MP4 takes the theme's background. Options of
199
+ another format are `export.invalid-option`. Elements, groups and model entities take
200
+ `url "https://…"`: SVG, PDF and HTML exports keep the link.
201
+ `pnpm mdg login`, `push`, `pull`, `publish` and `projects` keep a project in step with its copy in
202
+ the optional cloud service, which `project.json` records in `cloud`, and
203
+ `pnpm mdg review <project> --base cloud` compares with it; run them only when asked (see SKILL.md).
204
+
205
+ ## Diagnostics and fixes
206
+
207
+ | Code | Cause and fix |
208
+ | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
209
+ | `syntax.expected-token`, `syntax.unexpected-token` | malformed statement; check quotes, `->`, parentheses, one statement per line |
210
+ | `syntax.unterminated-string` | a string reached the end of the line; close it, use `\n` for line breaks |
211
+ | `syntax.unexpected-character` | characters such as `=>`, `--->` or `#` outside strings; the relation tokens are `->`, `-->`, `--`, `---`, `<->`, `<-->` |
212
+ | `ref.duplicate-id` | the id is already an element, relation or top-level block; rename one |
213
+ | `ref.unknown-element` | target of `place`, `size`, `style` or a qualified endpoint does not exist; declare it |
214
+ | `ref.unknown-relation` | `route`/`label` on an unnamed relation; add `as <id>` |
215
+ | `placement.route-mismatch` | `route` endpoints are reversed or wrong; use the relation's source then target |
216
+ | `placement.duplicate` | second `place`/`size` for an element or `route`/`label` for a relation; delete one |
217
+ | `placement.unknown-port` | the route names a port the element does not declare; the hint lists its ports |
218
+ | `prop.unknown` | unknown word; the hint lists what the block accepts |
219
+ | `prop.invalid-value` | wrong value shape; the message names the expected form; `shape` only on `node` and `box`; a relation token the profile refuses (`--` in a sequence) |
220
+ | `prop.duplicate` | property set twice for the same target; keep one |
221
+ | `layout.missing-placement` | manual layout and an element without `place`, or a group with only one of `place`/`size` |
222
+ | `layout.mode-unsupported` | a host without a layout engine met unplaced boxes; the command line always has ELK |
223
+ | `layout.placement-ignored` | `place` or waypoints in `layout auto`; write `layout assisted` or `manual` |
224
+ | `layout.constraint-conflict` | placed boxes break a constraint; move a `place` or change the constraint |
225
+ | `layout.overlap` | two boxes overlap, or a box crosses a group edge; move it or add `distance`; a stack goes outer first |
226
+ | `layout.label-under-connector` | a label sits on another arrow; move it with `label r at <n>% offset (dx, dy)` |
227
+ | `layout.container-margin` | a box touches the edge or title band of its sized group; enlarge the group or move it |
228
+ | `layout.connector-overlap` | two arrows run along each other; use other ports, a waypoint or `routing bezier` |
229
+ | `layout.connector-crossing` | an arrow runs through a box it does not join; move the box or add a waypoint |
230
+ | `layout.unknown-theme` | theme name is neither built in nor in the file; warning, renders with the default |
231
+ | `theme.unknown-base`, `theme.cycle` | `extends` names a missing theme or loops; point it at a built-in theme |
232
+ | `theme.unknown-class` | a class is not in the diagram, its theme or the built-in ones; declare it or fix the name |
233
+ | `asset.unknown-icon`, `asset.unknown-pack` | the icon name or pack does not exist; search with `pnpm mdg assets icons <words>` |
234
+ | `layout.too-large` | the sheet exceeds its `preset` or `canvas`; split it into views instead of shrinking, or write `preset board` for a whiteboard |
235
+ | `layout.text-overflow` | an explicit `size` is too narrow for its text; use the width in the hint |
236
+ | `layout.unmeasured-font` | a theme's or a style's `font` names no bundled or project family, or characters no font has: name a bundled family |
237
+ | `layout.icon-missing` | `shape icon` without an `icon`: a dashed placeholder is drawn; add the icon or change the shape |
238
+ | `layout.label-position-ignored` | `labelPosition` without `iconPosition center` or `shape icon`; add one of them or remove it |
239
+ | `animate.missing-effect`, `animate.invalid-target` | write `effect`; flow and trace on named relations, free lines and pens, dash on lines, pens and boxes, pulse anywhere |
240
+ | `animate.conflict` | one effect of each kind per target; merge the two statements |
241
+ | `story.*` | stories; see [stories.md](stories.md) |
242
+ | `export.*` | export profiles: a known target, a `format`, options of that format, a video `duration` |
243
+ | `ref.unknown-link` | a `link` names a diagram or element that does not exist; fix it or remove the link |
244
+ | `ref.unknown-layer`, `asset.*-image`, `layout.image-unavailable` | free compositions; see [free.md](free.md) |
245
+ | `export.no-diagram` | no flow or free diagram in the file |