> Only need one chart type? Fetch https://diagrammo.app/llms/.txt instead — a few KB of grammar per type, universal rules included. Index of all 50 types: https://diagrammo.app/llms.txt # DGMO Language Specification > **Authoritative reference** for the DGMO diagram language. This document describes what is valid syntax. If it is not in this document, it is not valid DGMO. > **1.0 freeze:** DGMO syntax freezes at the **1.0 release tag** — after 1.0, any change to valid syntax is a major-version bump. Pre-1.0 the language was deliberately tightened toward one canonical form per construct; the last cleanups shipped through 0.44 (channel-named value ramps in 0.42, chart-type consolidation 50→44 + alias removal in 0.43 per #23–28, `chord` folded into `arc` + `layout chord` in 0.44). Retired legacy/ambiguous forms include: bare `description`, PERT `analysis`, C4 bare same-line tail, timeline positional duration (→ `duration:`), gantt legacy scheduling, and the standalone `chord` / chart-type-alias keywords; bare tag values now auto-assign a palette color. Removed forms are **not errors** since 0.43.0 (#28 deleted the dedicated `E_*_REMOVED` diagnostics): most now fall through to normal parsing silently (verified 2026-08-12 — gantt `parallel`, PERT `analysis`, venn `alias`, pyramid/ring/sitemap bare `description`, sequence removed participant types all parse without complaint), a few surface as generic diagnostics (sequence `aka` / bare `position` hit the unexpected-line warning; a C4 bare same-line tail is a hard error naming the `description:` fix). See `dgmo-language-spec-decisions.md` § "1.0 Freeze Decisions". > > **Diagnostic codes (0.43.0, decision #28):** the dedicated `E_*_REMOVED` family was **deleted** for minimal surface — removed forms fail however they naturally fail, not via a named code. Treat every `E_*_REMOVED` reference below as **historical**; re-verify behavior against the parser. Note two forms are deliberately **accepted, not errors**: data-row numeric values may use thousands-grouping commas (`1,000` → 1000) or underscores (`1_000` → 1000), and separator commas between values are tolerated (values are canonically space-separated). _(2026-07-05: the C4 bare-tail case, which regressed to a silent drop after #28, was restored to a hard error — see §"C4".)_ ## Table of Contents 1. [Universal Constructs](#1-universal-constructs) 2. [Universal Name Handling](#2-universal-name-handling) 3. [Sequence Diagrams](#3-sequence-diagrams) 4. [Infrastructure Diagrams](#4-infrastructure-diagrams) 5. [Flowchart Diagrams](#5-flowchart-diagrams) 6. [State Diagrams](#6-state-diagrams) 7. [Org Charts](#7-org-charts) 8. [C4 Architecture Diagrams](#8-c4-architecture-diagrams) 9. [Entity-Relationship Diagrams](#9-entity-relationship-diagrams) 10. [Class Diagrams](#10-class-diagrams) 11. [Kanban Boards](#11-kanban-boards) 12. [Sitemap Diagrams](#12-sitemap-diagrams) 13. [Gantt Charts](#13-gantt-charts) 14. [Boxes and Lines Diagrams](#14-boxes-and-lines-diagrams) 15. [Timeline Diagrams](#15-timeline-diagrams) 16. [Data Charts](#16-data-charts) 17. [Visualizations](#17-visualizations) 18. [Mindmap Diagrams](#18-mindmap-diagrams) 19. [Wireframe Diagrams](#19-wireframe-diagrams) 20. [Tech Radar Diagrams](#20-tech-radar-diagrams) 21. [Cycle Diagrams](#21-cycle-diagrams) 22. [Journey Map Diagrams](#22-journey-map-diagrams) 23. [Pyramid Diagrams](#23-pyramid-diagrams) 24. [Ring Diagrams](#24-ring-diagrams) - [RACI Matrices](#24a-raci-matrices-raci--rasci--daci) - [Map Diagrams](#map-diagrams) 25. [Colon Usage Summary](#25-colon-usage-summary) 26. [Authoring Rules (Generators Read This First)](#authoring-rules) 27. [Swimlane Diagrams](#27-swimlane-diagrams) 28. [Event Line Diagrams](#28-event-line-diagrams) 29. [Version-Control Diagrams](#29-version-control-diagrams) 30. [Block Diagrams](#30-block-diagrams) 31. [Sketch Diagrams](#31-sketch-diagrams) 32. [Family Diagrams](#32-family-diagrams) --- ## 1. Universal Constructs These patterns are shared across all or most diagram types. ### 1.1 Chart Type Declaration (First Line) ``` [Title] ``` - Space-separated, NO colon - Title is optional - Examples: `bar Treasure Hauls`, `sequence Auth Flow`, `gantt Product Launch` ### 1.2 Comments ``` // comment text ``` - Full-line only (no inline comments after code) - `#` is NOT a comment character - One documented carve-out: **PERT (§13A)** accepts both `#` and `//`, full-line and trailing-after-whitespace — the only chart type with inline comments. Everywhere else `#` is content. ### 1.3 Tag Declarations ``` tag GroupName as Value1 color Value2 color ``` - `tag` keyword, NO colon - **Name**: a single identifier (`[A-Za-z_][A-Za-z0-9_-]*` — letters, digits, underscore, hyphen), e.g. `Team`, `Trust-Zone`. For a multi-word display name with **spaces**, quote it: `tag "Trust Zone" as tz`. The quoted text becomes the legend label; internally it slugs to a DOM-safe key (`Trust Zone` → `trust-zone`) used for assignment, matching, and `active-tag`. Always pair a quoted name with an `as ` so values stay terse (`Node tz: client`). An *unquoted* multi-word name is not a spaced name — the whole span becomes the group name and **no alias is inferred**; when the trailing word is alias-shaped (lowercase start, e.g. `tag Status s`) the parser warns, suggesting `tag Status as s` or quoting. - Alias: optional postfix `as ` per §2A (universal alias syntax — `[A-Za-z][A-Za-z0-9_]{0,11}`) - Inline values also supported: `tag Priority as p Low green, High red` - Color follows the value as a bare trailing token (see §1.5). Capitalize the color word (`Red`, `Yellow`) to keep it as a literal value with no color. - **Color is optional — a bare value auto-assigns a palette color.** A value declared without an explicit color (`High`, not `High red`) is not an error: at parse time it is given a deterministic categorical color drawn from the recognized color names (`red`, `orange`, `yellow`, `green`, `blue`, `purple`, `teal`, `cyan` — neutrals excluded), cycling in that order. Auto-assignment **skips any color already used by an explicit entry in the same group** (including explicit entries that appear *after* the bare one), so `High` / `Low red` gives `High` a non-red color. An explicit `Value color` always wins. This lets you sketch a group as a plain value list (`tag Priority as p High, Med, Low`) and let the palette pick distinct colors. This is the language's single canonical categorical rotation — chart sections that rotate colors (e.g. ring layers, §24.6) cite this order; pyramid's primary-mix ramp (§23.6) is the one documented exception. - **First entry is the default** — applied to nodes that don't carry an explicit value for this group. Reorder the entries to change the default. Three charts opt out of first-value defaulting: **sketch** and **body** render untagged shapes neutral gray (meaning is absent until assigned — decision #33), and **swimlane** untagged nodes fall back to the lane shade (tag → symbol → lane cascade, §27.7) - Must appear before diagram content - `none` is reserved — cannot be used as a tag group name - **The first declared group is active by default** — declaring a tag group colours nodes by that group immediately; the legend renders it expanded/active with the other groups as collapsed pills. `active-tag ` is therefore **only needed when ≥2 colouring dimensions exist** (multiple tag groups, or a group plus a score ramp) and you want one *other than the first-declared* active. With a single tag group, `active-tag` is redundant. In the app a reader can click any legend pill to flip the active group (live preview only — it does not edit the source) - **Opt-out**: `active-tag none` **suppresses all tag colouring** — every node renders neutral and all groups stay collapsed. This is the *opposite* of omitting `active-tag` (which activates the first group); use it to author the quiet, no-colour default deliberately (case-insensitive: `none`, `None`, `NONE`) - **An `active-tag` naming nothing the diagram declares is a warning** (`W_ACTIVE_TAG_NO_MATCH`, §1.4.4), never an error — the diagram still draws, in flat neutral colours, and only the colouring is lost. The message lists the declared groups and offers a did-you-mean. It stays silent for `none`, for a group that is declared but has no entries, and for the app's runtime pill-click, which is not source - **Bare shorthand is not an alias** (TD-18): `tag Priority p` declares a group *named* "Priority p" — no alias registers, so `p:` assignments would silently miss. The parser **warns** on this shape (spaced name, no alias, alias-shaped trailing word), suggesting `tag Priority as p` or a quoted name. The dedicated `E_TAG_SHORTHAND_REMOVED` error was deleted with the `E_*_REMOVED` family in 0.43.0 (decision #28) **Diagram types that support tags**: sequence, infra, state, org, mindmap, c4, er, kanban, gantt, sitemap, timeline, boxes-and-lines, swimlane, pert, journey-map, treemap, map, event-line, block, sketch, family, body, bracket > This list is enforced, not hand-maintained: it must match `TAG_SUPPORTING_TYPES` in `dgmo/src/completion.ts` (derived from `STRUCTURAL_KEYWORDS`, the parser-validated source of truth) and is checked by the completion-conformance suite. #### Strict ordering: tag declarations must precede content Tag declarations (and their aliases) feed the chart's reserved-key registry (§1.4.3) — that registry must be **finalized before the parser enters content-line mode**. DGMO is a single-pass parser with no lookahead, so a tag alias declared after the first content line is unreachable: any earlier line that *could* have used the alias was already classified under the pre-declaration registry. A `tag` block appearing after the first non-tag content line emits `E_TAG_DECLARED_AFTER_CONTENT`. The declaration is still parsed (so the legend renders), but no upstream content lines retroactively pick up the alias. ``` sequence Voyage tag Concern as c // ✅ before content Alice -login-> Bob c: Auth [Backend] tag Concern as c // ❌ E_TAG_DECLARED_AFTER_CONTENT ``` This applies uniformly across all chart types that support tags. A header directive may sit on **either side** of a tag block — `active-tag Rank` above the `tag Rank` line and directly below its last entry mean the same thing, with or without a blank line between them. A tag block is a header construct, not content, so it does not close the header. Five parsers used to check their option branch while the tag block was still open and lose the line below it (fixed 2026-08-18; the silently dropped swimlane directive, #301). ### 1.4 Metadata Grammar DGMO has **one** metadata grammar that every chart type uses. Entity metadata may be written same-line after the name, or as indented lines beneath the entity. Both forms are valid wherever metadata is supported; per-chart-type preferred form is listed in §26. ``` // Same-line form (canonical for most chart types) EntityName k: v, k2: v2 API Gateway c: Auth // name = "API Gateway"; meta = { c: Auth } Alice -login-> Bob c: Auth // edge metadata, no delimiter 20bd Database Schema progress: 100 // gantt: leading-duration prefix preserved // Indented form (canonical for org, c4; available everywhere) API Gateway c: Auth // attribute — `c` is a declared tag alias description: Main gateway ``` The `|` operator that delimited metadata in pre-0.18.0 DGMO is **removed** as a metadata delimiter. Before 0.43.0 a stray `|` emitted `E_PIPE_OPERATOR_REMOVED`; as of 0.43.0 (#28) that guard is gone, so a `|` outside its surviving uses (§1.4.5) is no longer a dedicated error — it falls through to normal parsing (treated as a literal / ignored). #### 1.4.1 Same-line form The parser scans the line left-to-right and flips into **metadata mode** the first time it encounters a whitespace-delimited token whose shape is `:` (a key followed by a colon, with the key matching the chart type's reserved-key registry or a declared tag alias). Everything to the left of that token is the entity's **name region**; everything to the right is comma-separated `key: value` pairs. ``` Foo k: v // name = "Foo", meta = { k: v } Foo k: v, k2: v2 // name = "Foo", meta = { k: v, k2: v2 } Foo green k: v // name = "Foo", color = green, meta = { k: v } (color peeled from name region — §1.5) Foo as f k: v // name = "Foo", alias = f, meta = { k: v } ``` **Whitespace around `:` is tolerated** — tokenization treats `c: Auth`, `c :Auth`, `c : Auth`, and `c:Auth` identically. The value start is after `:`, with leading whitespace trimmed. **Quoted values escape the comma terminator.** Wrap a value in `"..."` when it contains commas. ``` Foo description: "My, complicated, value" // meta = { description: "My, complicated, value" } Foo description: "with spaces", color: red // meta = { description: "with spaces", color: red } ``` **No line continuation.** Same-line metadata is single-line. A trailing comma on a metadata line is a parse error. For long metadata sets, use the indented form. **Empty values are dropped.** `Foo c:` (no value) emits `W_EMPTY_METADATA_VALUE` and the key/value pair is discarded from the entity's metadata. **Quoted names tokenize as one unit before the first-`:`-token scan.** A colon inside a quoted name does not trigger the metadata cut. ``` "Auth: Service" // name = "Auth: Service" (single token) "Order | Items" k: v // name = "Order | Items", meta = { k: v } ``` #### 1.4.2 Indented form (reserved-key dispatch) An indented line of shape `key: value` attaches as metadata to the parent entity **only when `key` matches the chart type's reserved-key registry or a declared tag alias**. Otherwise, the line falls through to the chart type's own grammar (class members, RACI role assignments, ER columns, function entries, sequence notes, etc.). ``` API Gateway c: Auth // attribute — `c` is a declared tag alias latency-ms: 50 // known-schema property (colon required — see §4.3) Ship // class + name: string // member — `+` prefix; not a reserved-key match description: Capital ship // attribute — `description` is reserved [Departure] // RACI phase Plot the course // task Cap: A // role assignment — `Cap` is a declared role Nav: R ``` Indented lines without a colon remain **structural children** (sub-nodes, columns, dependencies, cards, etc.). ER columns (`id int pk`) are space-separated known-schema properties that don't carry colons; the dispatch is unambiguous. Infra node properties require colons (`latency-ms: 50`) — see §4.3. **Drift trap.** When an indented `key: value` line where `key` is in the reserved-key registry appears at the same indent level as preceding structural children, the attribute attaches to the **parent above** — but a reader could plausibly intend it for the preceding structural child. No diagnostic fires (the planned `W_ATTRIBUTE_AT_PARENT_INDENT` warning was never implemented; verified absent 2026-08-12). Indent one level further to attach to the preceding child. #### 1.4.3 Reserved-key registry Each chart type publishes a closed set of reserved attribute keys. These keys, plus any declared tag aliases (§1.3), trigger metadata dispatch on indented lines and the same-line first-`:`-token cut. Everything else falls through to chart-specific grammar. | Chart type | Reserved keys (in addition to declared tag aliases) | | --- | --- | | sequence | `color`, `description`, `role`, `collapsed`, `position` | | infra | `color`, `description`, `collapsed`, `icon` | | flowchart | *(no metadata)* | | state | *(tag-group aliases — tags newly granted, decision #48)* | | org | `color`, `description`, `role`, `location`, `email`, `phone`, plus per-chart custom keys (free-form indented attributes) | | c4 | `color`, `description`, `tech`, `type`, `collapsed` | | er | `color`, `description`, `domain` | | class | `color`, `description` | | kanban | `color`, `description`, `wip`, `assignee`, `due`, `priority` | | sitemap | `color`, `description`, `status` | | gantt | `color`, `description`, `duration`, `offset`, `progress`, `start` | | pert | `color`, `description`, `confidence`, `collapsed` | | boxes-and-lines | `color`, `description`, `heat` | | timeline | `color`, `description`, `duration` | | mindmap | `color`, `description`, `collapsed`, plus per-chart priorities/status keys declared as tag aliases | | tech-radar | `color`, `description`, `quadrant`, `ring`, `trend` | | cycle | `color`, `span`, `description` (`width` is edge-only) | | journey-map | `score`, `emotion`, `description`, `pain`, `opportunity`, `thought`, `color` (decision #13, persona long form) | | pyramid | `color`, `description` | | ring | `color`, `description` | | raci | `color`, `description`, plus declared roles (which dispatch as role assignments, not attributes) | | wireframe | *(uses trailing-keyword flag list — see §19.5)* | | treemap | `heat`, plus tag-group aliases | | swimlane | *(tag-group aliases; `note:` deferred)* | | event-line | *(tag aliases; `collapsed` via era lines)* | | version-control | `id`, `tag`, `type`, `order` | | block | `span` | | sketch | `shape`, `at` | | family | closed GEDCOM set — see §32.6 | | body | *(tag aliases)* | | bracket | *(tag aliases; seed/score keys per §35)* | | map | registry in §24B.9 (`heat`, `size`, `width`, `label`, `style`, `clock`) | | goal | *(no metadata)* | | countdown | *(no metadata)* | | clock | *(no metadata)* | Keys not in a chart type's registry are not metadata. Tag aliases declared via `tag Group as x` are added to that chart's effective registry for the duration of the parse. #### 1.4.4 Diagnostic catalog > The `*_REMOVED` rows below were **deleted in 0.43.0 (#28)** and no longer fire — see the 0.43.0 update note at the top of this spec. They are kept here, marked, for historical reference. | Code | Severity | Fires when | | --- | --- | --- | | ~~`E_PIPE_OPERATOR_REMOVED`~~ | *removed 0.43.0* | (Historical) A `|` appeared outside its surviving uses (wireframe option braces `{A | B}`, in-arrow label characters per §1.10, quoted-string content). Now: a stray `|` falls through to normal parsing (literal / ignored), no dedicated error. | | `W_EMPTY_METADATA_VALUE` | warning | A metadata pair has an empty value (`Foo c:`). The pair is dropped from the entity's metadata. | | `W_ACTIVE_TAG_NO_MATCH` | warning | `active-tag ` names neither a declared tag group nor the chart's other colouring dimension (treemap's heat label, map's value ramp, boxes-and-lines' value metric). The diagram renders in flat neutral colours, so the loss is otherwise invisible. Carries the declared names and a did-you-mean. Silent for `active-tag none`, for a group that is declared but empty, and for the app's runtime tag switcher, which is not source. Accepts a spaced group by either spelling — the raw name or its slug (§1.4 *Name*). | | ~~`W_ATTRIBUTE_AT_PARENT_INDENT`~~ | *never implemented* | (Planned, never built — verified absent 2026-08-12.) An indented attribute at the same indent as preceding structural children silently attaches to the parent above; see the drift trap note in §1.4.2. | | ~~`E_SEQUENCE_BARE_POSITION_REMOVED`~~ | *removed 0.43.0* | (Historical) A sequence participant used the retired bare-keyword `position N` ordering shorthand; use colon-keyed `position: N` (§2.2). Now: no dedicated error — falls through to normal parsing. | | ~~`E_DATA_COMMA_REMOVED`~~ | *removed 0.43.0* | (Historical) The short-lived 0.30.0 freeze hard-errored commas in data-chart VALUE position. Reversed by #28: commas are now **accepted** — thousands grouping (`Revenue 1,000` → 1000) and separator commas (`Q1 400, 700`) both parse. Values are canonically space-separated; `1_000` also works. Not an error. | #### 1.4.5 Surviving uses of `|` The `|` character is **not reserved** in DGMO 0.18.0; it survives as a literal in three specific contexts: - **Wireframe dropdown options:** `{Option A | Option B | Option C}` — inside `{...}` braces, `|` separates dropdown options (§19.3). - **In-arrow label characters:** `A -file|name-> B` — `|` inside an arrow label is preserved verbatim (§1.10). - **Quoted name characters:** `"Order | Items"` — inside `"..."` quoted strings, `|` is a literal name character. Outside these three contexts, `|` used to trigger `E_PIPE_OPERATOR_REMOVED`; as of 0.43.0 (#28) that guard is gone, so a stray `|` is simply parsed as a literal / ignored rather than flagged. `dgmo migrate` still mechanically converts legacy pipe content. ### 1.5 Color Suffixes Color is set by typing the color name at the end of a label, lowercase. Example: `Done green` colors Done green. Eleven colors exist: `red`, `orange`, `yellow`, `green`, `blue`, `purple`, `teal`, `cyan`, `gray`, `black`, `white`. To use a color word as a literal label, capitalize it: `Red` stays as the word `Red`. > **These eleven names are the ONLY valid colors. Hex (`#e6194b`), `rgb(...)`/`hsl(...)`, and CSS keyword colors (`crimson`, `pink`, …) are NOT supported anywhere in DGMO** — not on groups, series, nodes, edges, tags, or any other element. A hex or CSS value is rejected with an **error** diagnostic and the element falls back to its default / auto-assigned palette color. The only way to specify a color is one of the eleven names above. This is deliberate: a named color resolves to the right hex per active palette and light/dark theme, so the same source stays palette-portable — a hardcoded hex would not. ``` Label color // bare trailing color token Done green // value=Done, color=green Senior Engineer red // value="Senior Engineer", color=red Red // value=Red, no color (capitalized → escape hatch) ``` **The universal rule** — color trails the label: > Color is the trailing whitespace-delimited token of a label region, when that token (case-sensitive, lowercase) is one of the 11 names above. Otherwise the label region has no color. The "label region" is everything left after the parser strips off structural terminators it owns: same-line metadata (§1.4.1), `as ` postfix, numeric values, date ranges, structural brackets, arrow constructs. The color rule operates only on the label region — never on a raw line — so `Tortuga Distillery orange 3000` is `{ label: "Tortuga Distillery", color: "orange", value: 3000 }`: the parser splits off `3000` as the flow value first, then `orange` peels off as the trailing color. **Cut order is pinned.** On any line that could carry all of (a) metadata, (b) an `as ` postfix, and (c) a trailing-token color, the parser strips them in this order: 1. **Metadata cut (§1.4.1)** — the first `:` token where `key` is in the chart's reserved-key registry or a declared tag alias splits the line into name-region + metadata (left-to-right). Color tokens that appear *inside* a metadata value are NOT extracted as the entity's color — they belong to the value. 2. **Trailing-token color (§1.5)** — applied to the name region. The last whitespace-delimited token, when it is exactly a lowercase color name from the 11-name palette, is peeled as color. 3. **`as ` postfix** — stripped from the right-hand side of what remains of the name region. The alias is registered against the canonical name. Step 2 runs before step 3 because in source order the alias sits *between* the label and the trailing color (`