Open the interactive page in DDR · Syntax reference · For agents (MCP)

DDR for agents: MCP, skills and strict syntax

Machine-oriented DDR guide: MCP server setup and tools, a downloadable SKILL.md, a strict grammar for every diagram type, diagnostic codes with fixes, and do/don't rules.

Generated from the DDR engine 0.0.5-alpha.5 (docs/spec, core/ddr/diag.go, internal/server, docs/examples) by go run ./cmd/ddr-docs. Do not edit by hand.

Human-friendly reference: /docs/syntax.md · This file is also served as /llms.txt and /docs/agents.md.

Rules

Workflow: get_syntax → write the source → check until ok is true (no errors, no warnings) → format → render.

Do

Don't

Diagram types

id Type: file views
flowchart Flowchart .ddr flow
graph Graph .ddr graph
architecture Graph .ddr graph
usecase UseCase .ddr overview, plus flow: <use case> per use case with a Flow
activity Activity .ddr activity
cdm CDM .ddr cdm
pdm PDM .ddr pdm
erd ERD .ddr erd
tdr - .tdr page preview

MCP server

ddr mcp runs a Model Context Protocol server over stdio (newline-delimited JSON-RPC 2.0, protocol 2025-06-18). Install the CLI from the GitHub releases (or go install github.com/Fastering18/ddr/cmd/ddr@latest), then add it to your client.

Cursor (.cursor/mcp.json or ~/.cursor/mcp.json):

{
  "mcpServers": {
    "ddr": { "command": "ddr", "args": ["mcp"] }
  }
}

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "ddr": { "command": "/usr/local/bin/ddr", "args": ["mcp"] }
  }
}

Any stdio client: start ddr mcp and write one JSON-RPC message per line:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"me","version":"1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"check","arguments":{"source":"Type: Flowchart\nTitle: T\n\nFlow {\n  Start\n  End\n}\n"}}}

HTTP clients: the server has no HTTP transport of its own. Run ddr mcp behind any stdio-to-HTTP MCP bridge, or call the same core with the CLI (ddr check, ddr render).

Tools

check

Parse and analyze a .ddr source (a .tdr document gives only E108: check its .ddr files instead). Returns JSON {ok, errors, warnings, diagnostics:[{code,severity,message,line,col}], views:[{kind,name}]}. ok is true only when there are no errors and no warnings.

Input schema:

{
  "additionalProperties": false,
  "properties": {
    "source": {
      "description": "Full text of a .ddr file.",
      "type": "string"
    }
  },
  "required": [
    "source"
  ],
  "type": "object"
}

render

Render a .ddr source to a standalone SVG document. Diagnostics, if any, follow the SVG after a line <!-- diagnostics -->.

Input schema:

{
  "additionalProperties": false,
  "properties": {
    "source": {
      "description": "Full text of a .ddr file.",
      "type": "string"
    },
    "theme": {
      "default": "light",
      "enum": [
        "light",
        "dark"
      ],
      "type": "string"
    },
    "view": {
      "description": "View name from check (default: the first view).",
      "type": "string"
    }
  },
  "required": [
    "source"
  ],
  "type": "object"
}

format

Format a .ddr source like ddr fmt (idempotent, keeps comments, never reorders). Returns the formatted text, or an error listing the syntax errors.

Input schema:

{
  "additionalProperties": false,
  "properties": {
    "source": {
      "description": "Full text of a .ddr file.",
      "type": "string"
    }
  },
  "required": [
    "source"
  ],
  "type": "object"
}

list_types

List every diagram and document type: id, the Type: header value, accepted aliases, file extension, views and a one-line summary.

Input schema:

{
  "additionalProperties": false,
  "properties": {},
  "type": "object"
}

get_syntax

Return the strict DDR syntax guide for agents (Markdown). Pass type (an id from list_types) for just that type's section.

Input schema:

{
  "additionalProperties": false,
  "properties": {
    "type": {
      "description": "Optional type id, e.g. flowchart, erd.",
      "type": "string"
    }
  },
  "type": "object"
}

Skills

Download SKILL.md and put it in your agent's skills folder (for example .cursor/skills/ddr/SKILL.md or ~/.claude/skills/ddr/SKILL.md). It tells the agent when to use DDR, the workflow, the rules and one example per type, and points to the MCP tools when they are available.

Raw Markdown of this page: /docs/agents.md and /llms.txt.

Header keys

The header is Key: value lines at the top of the file, ending at the first blank line. Keys are case-insensitive; unknown keys give W102 and are kept.

key required values default meaning
Type yes see the type list - Selects the diagram type. Missing: W106; unknown: W107.
Title no text - Diagram title. Missing: W106 (warning only).
Author no text - Metadata, not drawn.
Date no text (ISO date recommended) - Metadata, not drawn.
Version no text - Metadata.
Status no text - Metadata.
Tags no comma list - Metadata.
Theme no light dark light Preferred theme for exports.
Format no text - Metadata.
Direction no TB LR TB (LR for CDM/PDM) Main layout direction. Also accepted inside a Layout: section. Ignored in Activity (W110).
Lang no en id en Language of generated use case specification text.
Line Jumps no on off on Draw a small hop where two straight lines cross.

Lines, comments and strings

# A comment line
Type: Flowchart
Title: Lexical example

Flow {
  Start
  Read order #123
  note: "Contains; a semicolon"
  End
}

Sections

A section is Name: followed by an indented - list, or Name { … } (a block). Section names are case-insensitive.

name used in meaning
Actors UseCase Actor list.
Use Cases UseCase Use case list; alias UseCases.
Flow Flowchart, Activity, use cases The flow block.
Activity Activity Alias of Flow in activities.
Graph Graph Nodes and edges; alias Nodes.
Nodes Graph Alias of Graph.
Edges Graph Extra edges for the graph.
Entities CDM, PDM, ERD Entity list; alias Entity.
Entity CDM, PDM, ERD Alias of Entities.
Tables PDM Table list; alias Table.
Table PDM Alias of Tables.
Relations ERD Relation list; alias Relation.
Relation ERD Alias of Relations.
References CDM, PDM Reference list; also Ref { } or Ref: lines; Refs for per-segment via.
Layout all Section form of layout keys: Layout: then Direction: LR.
Numbering UseCase Header: use case codes.
Show Codes UseCase Header: print codes.
Min Use Cases UseCase Header: W120 threshold (default 6).
Lanes Activity Header: swimlanes.
Line Jumps all Header: off disables line hops.

Ids, labels and attributes

Every node is id: Label [key: value, flag]. Without an id the id is the slug of the label (Enter payment details → enter-payment-details). Ids match [A-Za-z_][A-Za-z0-9_.-]*. Several attribute groups are allowed: Login [shape: round] [x: 10, y: 20]. Unknown attributes give W105.

Node attributes:

attribute meaning
x y Pinned centre position (numbers). Without them the node gets auto layout. Ignored in Activity (W110).
w h Size override (numbers).
shape rect round pill ellipse circle diamond parallelogram actor cylinder box.
color accent purple amber orange teal pink blue green red gray, or #hex.
style Node style hint.
label Display label when the id is all you wrote.
id Alias for the node, same as an id: prefix. One word (E107 otherwise); a duplicate is E204.
system external primary secondary deprecated Flags: actor and use case kinds.
side Actors: left or right of the system boundary.
code Use case code set by hand, e.g. [code: UC07].
spec Use case specification table: own or none.
identifier pi CDM identifier (<pi> and <M>); also a key in PDM.
pk fk PDM primary / foreign key column.
unique PDM unique column (kept, not drawn).
null nullable not null nn required PDM nullability (kept, not drawn); required / [mandatory] / [M] mark CDM <M>.

Positions and bends

Type: Graph
Title: Pinned

Graph {
  a: Client [x: 100, y: 80]
  b: Server [x: 400, y: 80]
  a -> b: request [via: (250,20)]
}

Edges and chains

where syntax
Graph, Flowchart, Activity a -> b / a -> b: label [attrs]
Flow step step -> [attrs] styles the implicit edge to the next step; step -> target adds an edge
if if ok? -> [yes attrs] -> [no attrs] { … }
goto goto id [attrs] (no -> after it: W127)
Use case - Customer: Register -> Customer [attrs]
CDM / PDM Ref: a.id < b.a_id: name [attrs] with < > - <>

Chains. a -> b -> c adds one edge per consecutive pair. A label (: text) and attribute groups belong to the segment they follow; nothing is copied to other segments. Spaces around -> are optional (a->b->c). Every unknown node is E203; nodes are never created by an edge. A targetless -> [..] inside a chain or a trailing -> is ignored with W126. References chain with their own operators: Ref: a.id < b.a_id: owns < c.b_id: keeps.

Edge attributes:

attribute meaning
via Bend points in order: [via: (120,80) (240,80)].
style straight (default, polyline) or curve.
label Edge label (same as : text).
dash dashed Flag: dashed line.
arrow end (default) none both.
color As for nodes.
head Arrowhead: triangle (generalization), open, filled.
from to Explicit endpoints on a data-model reference.
Type: Graph
Title: Chain

Graph {
  a: Browser
  b: API
  c: Database [shape: cylinder]
  a -> b: HTTPS -> c: SQL [dashed]
}

Flow statements

Used in Flowchart, Activity and use case Flow { } blocks. Keywords count only at the start of a statement and are case-insensitive: if, elif, else, loop, goto, Start, End; in activities also parallel … and, stop.

stmt = step | "Start" [label] | "End" [label]
     | "if" cond block { "elif" cond block } [ "else" block ]
     | "loop" [cond] block | "goto" id | id ":" label | edge
     | "parallel" block { "and" block } | "stop" ;   (* activity only *)

Diagnostic codes

Every code the engine emits (read from core/ddr/diag.go). E codes are errors: the diagram is not drawn correctly. W codes are warnings: drawn, but fix them.

code severity meaning fix
E101 error unclosed { Add the missing } (or remove the stray {).
E102 error bad indentation Indent with spaces to match the item above; fields go two spaces under their - item.
E103 error unknown or unexpected statement (also an empty actor group, - Clerk {}) Check the statement spelling, or quote the label ("if this is text"). An actor group needs at least one case.
E104 error unexpected } Remove the extra }.
E105 error unclosed " Close the string with ", or escape a literal quote as \".
E106 error unclosed [ Close the attribute group with ].
E107 error bad attribute syntax Write attributes as [key: value, flag]; an id is one word.
E108 error a .tdr document given to check (prose with embedded .ddr diagrams): check the .ddr files it embeds instead Run check on the .ddr files the document embeds (the paths in ::diagram[...] / ::usecases[...]), not on the .tdr itself.
E201 error unknown actor Declare the actor under Actors: or fix its spelling.
E202 error unknown use case in Includes / Extends Name an existing use case in Includes / Extends.
E203 error unknown edge or goto target Declare the target node (with id: Label) before using it in an edge or goto; edges never create nodes.
E204 error duplicate id Give each node a unique id.
E205 error activity: unknown lane (not in Lanes:) Add the lane to Lanes: or fix the prefix.
E206 error activity: more than one start (start written twice or after a step) Write start at most once, as the first statement (it is implicit anyway).
E207 error data model: a relationship names an entity or table that is not declared Declare the entity or table, or fix the name in the Ref: / relation.
W101 warning tab indentation Replace tabs with spaces.
W102 warning unknown key (kept) Remove or rename the key; unknown keys are kept but unused.
W104 warning actor with no use cases Link the actor to a use case or remove it.
W105 warning unknown attribute (ignored) Remove the attribute or use one from the attribute tables.
W106 warning missing Type / Title header Add Type: and Title: header lines at the top.
W107 warning unknown Type Use one of the documented types (Architecture is Type: Graph).
W108 warning a use case includes or extends itself (the line is not drawn) Remove the self include / extend.
W109 warning include cycle (drawn; the cases keep their actor lines) Break the include cycle.
W110 warning layout adjustment (activities: [x, y] ignored, Direction ignored): a [via: ...] bend pulled inside the system boundary so an actor stays outside, a line that cannot fit without reaching an actor, or an actor pinned inside the boundary Remove the [x, y] / [via: …] the layout cannot honor (activities ignore them).
W111 warning activity: the branches of a decision meet again without a merge Let the branches meet again (the structured if always does).
W112 warning activity: a parallel branch reaches the flow after the join without passing it Let every parallel branch reach the join.
W113 warning activity: a step no flow from the start reaches Connect the step to the flow or delete it.
W114 warning activity: a lane in Lanes: with no steps Remove the empty lane from Lanes: or put a step in it.
W115 warning activity: a parallel block with an empty branch (left out) Add a step to the empty parallel branch or remove the branch.
W116 warning activity: an action with more than one incoming or outgoing flow Use a decision or merge instead of several a -> b flows on one action.
W117 warning activity: the layout did not converge; the result may overlap Simplify the activity (fewer crossing gotos); the result may overlap.
W118 warning data model: a reference that is not a.attr < b.attr, or an attribute the reference names but the entity does not have Write references as a.attr < b.attr with attributes the entities have; per-segment via goes in Refs.
W119 warning data model: a relationship from an entity to itself (not drawn) Self relationships are not drawn in CDM/PDM; model them with a link entity.
W120 warning Numbering: on and fewer main use cases than Min Use Cases: (default 6) Add main use cases or lower Min Use Cases:.
W121 warning two use cases have the same code Give each use case a unique [code: …].
W122 warning Alternative N / Alur Alternatif N.M, or a goto N inside it, names a main flow step that does not exist Number the alternative after an existing main flow step.
W123 warning a [code: ...] that does not follow the Numbering: format (UC-01 under Numbering: UC) Follow the Numbering: format, e.g. UC-01 under Numbering: UC-.
W124 warning ERD: a relation that joins fewer than two entities Make the relation name at least two entities (the same one twice for a self relation); no chains inside relations.
W125 warning ERD: an entity without a key attribute (*) Mark a key attribute with *.
W126 warning a targetless -> [..] clause or a trailing -> inside an edge chain (ignored; fmt drops a bare trailing ->) Remove the targetless -> [..] or trailing -> from the chain (ddr fmt drops a bare trailing ->).
W127 warning goto x -> y: a goto jumps to one target, the -> part is ignored Write goto x alone; add a separate edge if you need one.
W128 warning flowchart/graph: a later End with a different label joins the one End; its label is not drawn Write one End (or give every End the same label); all paths join the diagram's single End.
W129 warning unknown Lang: value (not en or id, any case); falls back to English (en) Use Lang: en or Lang: id (or drop the line for English).

HTTP API

The cloud server's REST API. Paths are under /api/v1 except /healthz, /ws/… and the browser redirects of Google sign-in. JSON in and out; errors are application/problem+json ({"type","title","status","detail"}). Auth is the ddr_session cookie set by login/register; optional means signed-out callers see public projects only. State-changing requests must come from the same origin. Every route below is read from internal/server/server.go; a test fails if one is missing.

Diagrams are rendered in the browser (WebAssembly), by ddr render and by the MCP render tool; the server stores text and has no render endpoint yet.

Health

GET /healthz · auth: none
Liveness probe (outside /api/v1).

Response:

200 {"status":"ok","db":true,"realtime":{…}} (503 "degraded" when the database is down)

GET /api/v1/health · auth: none
Liveness probe.

Response:

200 {"status":"ok","db":true,"realtime":{…}} (503 "degraded" when the database is down)

Admin

GET /api/v1/admin/stats · auth: session
Site statistics (admins only).

Response:

200 {"users":12,"projects":40,"files":180,"storageBytes":123456}

GET /api/v1/admin/users · auth: session
Users with role, project count and last seen (?q= filters).

Response:

200 {"items":[]}

PATCH /api/v1/admin/users/{id} · auth: session
Set a user's site role (user or admin).

Request:

{"role":"admin"}

Response:

200 {"id":"…","role":"admin"}

GET /api/v1/admin/audit · auth: session
Audit log: sign-ins, project/file create/delete, uploads, role changes, agent use (?action=, ?before=, ?limit=).

Response:

200 {"items":[]}

Agent

GET /api/v1/agent/status · auth: session
Which AI providers budi can use and whether you saved your own key.

Response:

200 {"providers":["gemini:gemini-2.5-flash"],"byokEnabled":true,"available":true}

POST /api/v1/agent/chat · auth: session
Chat with budi. context is graph-retrieved project context; edits come back as fenced file blocks for review. Rate limited per user.

Request:

{"messages":[{"role":"user","content":"add a cancel step"}],"context":"…","projectId":"…"}

Response:

200 {"reply":"…","provider":"gemini","model":"gemini-2.5-flash"}

PUT /api/v1/me/agent-key · auth: session
Save your own AI key (encrypted at rest); budi uses it first, then the free providers.

Request:

{"provider":"openrouter","model":"","key":"sk-…"}

Response:

204 No Content

DELETE /api/v1/me/agent-key · auth: session
Remove your own AI key or disconnect OpenRouter.

Response:

204 No Content

POST /api/v1/me/agent-key/openrouter · auth: session
Finish OpenRouter OAuth PKCE: exchanges the code for a key on your OpenRouter account (your credits).

Request:

{"code":"…","codeVerifier":"…","model":"openrouter/auto"}

Response:

204 No Content

Auth

POST /api/v1/auth/register · auth: none
Create an account and sign in (sets the session cookie). Rate limited.

Request:

{"email":"ana@example.com","password":"correct horse battery","name":"Ana"}

Response:

201 {"id":"7b6c…","email":"ana@example.com","name":"Ana","avatarUrl":null,"emailVerified":true,"createdAt":"2026-10-22T10:00:00Z"}

POST /api/v1/auth/login · auth: none
Sign in with email and password (sets the session cookie). Rate limited.

Request:

{"email":"ana@example.com","password":"correct horse battery"}

Response:

200 {"id":"7b6c…","email":"ana@example.com","name":"Ana","avatarUrl":null,"emailVerified":true,"createdAt":"2026-10-22T10:00:00Z"}

POST /api/v1/auth/logout · auth: optional
End the current session and clear the cookie.

Response:

204 No Content

POST /api/v1/auth/verify · auth: none
Confirm an email address with the token from the mail.

Request:

{"token":"v_abc…"}

Response:

200 {"id":"7b6c…","email":"ana@example.com","name":"Ana","avatarUrl":null,"emailVerified":true,"createdAt":"2026-10-22T10:00:00Z"}

POST /api/v1/auth/verify/resend · auth: session
Send a new verification link (valid 24h, single use). At most one per minute and 10 per day per user; otherwise 429 with Retry-After. Mail goes through Resend (RESEND_API_KEY) or SMTP (SMTP_HOST…), from MAIL_FROM; with neither, the link is written to the server log.

Response:

200 {"retryAfter":60,"remainingToday":9,"cooldown":60,"dailyLimit":10}

GET /api/v1/auth/verify/status · auth: session
Whether your email is verified, whether verification is required (EMAIL_VERIFICATION_REQUIRED; when false new emails are verified at once and resend answers 409 verification_disabled), and when Resend is allowed again.

Response:

200 {"verified":false,"required":true,"retryAfter":42,"remainingToday":8,"cooldown":60,"dailyLimit":10}

POST /api/v1/auth/forgot · auth: none
Request a password reset mail. Always 204, so it cannot probe for accounts.

Request:

{"email":"ana@example.com"}

Response:

204 No Content

POST /api/v1/auth/reset · auth: none
Set a new password with the reset token.

Request:

{"token":"r_abc…","password":"new long password"}

Response:

204 No Content

GET /api/v1/auth/oauth/{provider} · auth: none
Generic OAuth route for other providers: always 501. Google sign-in has its own routes (GET /auth/google) once they are on this server.

Response:

501 {"type":"…/not_implemented","status":501,"detail":"…"}

GET /api/v1/auth/oauth/{provider}/callback · auth: none
Callback of the generic OAuth route: always 501.

Response:

501 {"type":"…/not_implemented","status":501,"detail":"…"}

GET /api/v1/auth/providers · auth: none
Which sign-in methods this server offers.

Response:

200 {"password":true,"google":true}

GET /auth/google · auth: none
Start Google sign-in (outside /api/v1). ?next=/path is where to land afterwards (same-site paths only).

Response:

302 Location: https://accounts.google.com/o/oauth2/v2/auth?…

GET /auth/google/callback · auth: none
Google redirects here (outside /api/v1); signs in or creates the account, sets the session cookie.

Request:

`?code=…&state=…`

Response:

302 Location: /<next> (or /login?error=<code>)

Account

GET /api/v1/me · auth: session
The signed-in user.

Response:

200 {"id":"7b6c…","email":"ana@example.com","name":"Ana","avatarUrl":null,"emailVerified":true,"createdAt":"2026-10-22T10:00:00Z"}

PATCH /api/v1/me · auth: session
Change name, email, language or password. A new password, or a new email on an account with a password, needs currentPassword. A new email starts unverified and gets a verification link; email changes share the resend limits (1 per minute, 10 per day) and are capped at 5 per day (429 with Retry-After).

Request:

{"name":"Ana B.","email":"ana@new.example","currentPassword":"…"}

Response:

200 {"id":"7b6c…","email":"ana@example.com","name":"Ana","avatarUrl":null,"emailVerified":true,"createdAt":"2026-10-22T10:00:00Z"}

PUT /api/v1/me/avatar · auth: session
Upload an avatar: the raw PNG, JPEG or WebP as the body (max 2 MB). Stored as a 256x256 WebP in S3-compatible storage (S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE); the previous one is deleted. 422 for other types, 413 when too large.

Request:

<image bytes>

Response:

200 {"id":"7b6c…","email":"ana@example.com","name":"Ana","avatarUrl":null,"emailVerified":true,"createdAt":"2026-10-22T10:00:00Z"}

DELETE /api/v1/me/avatar · auth: session
Remove the uploaded avatar (a Google profile picture shows again if there is one).

Response:

200 {"id":"7b6c…","email":"ana@example.com","name":"Ana","avatarUrl":null,"emailVerified":true,"createdAt":"2026-10-22T10:00:00Z"}

GET /api/v1/users/{id}/avatar · auth: none
An uploaded avatar (image/webp). With ?v=<etag> it is cached for a year; sends an ETag and answers If-None-Match with 304.

Response:

200 image/webp

GET /api/v1/me/sessions · auth: session
List your sessions.

Response:

200 {"items":[{"id":"…","createdAt":"…","lastSeenAt":"…","expiresAt":"…","userAgent":"Firefox","ip":"203.0.113.5","current":true}]}

DELETE /api/v1/me/sessions/{id} · auth: session
Sign out one session.

Response:

204 No Content

GET /api/v1/me/invites · auth: session
Pending invites for your email.

Response:

200 {"items":[{"id":"c1e2…","projectId":"3f2a…","email":"bo@example.com","role":"editor","status":"pending","createdAt":"…","expiresAt":"…"}],"emailVerified":true}

POST /api/v1/me/invites/{id}/accept · auth: session
Accept an invite addressed to you.

Response:

200 {"projectId":"3f2a…","role":"editor"}

POST /api/v1/me/invites/{id}/decline · auth: session
Decline an invite addressed to you.

Response:

204 No Content

GET /api/v1/me/limits · auth: session
Your site role, usage and quotas.

Response:

200 {"role":"user","usage":{"projects":3,"storageBytes":10240},"limits":{"projects":30,"filesPerProject":30,"fileBytes":2097152,"storageBytes":262144000}}

DELETE /api/v1/me · auth: session
Delete your account.

Response:

204 No Content

GET /api/v1/me/identities · auth: session
Linked sign-in identities (e.g. Google).

Response:

200 {"items":[{"provider":"google","email":"ana@example.com"}]}

DELETE /api/v1/me/identities/{provider} · auth: session
Unlink an identity.

Response:

204 No Content

Projects

GET /api/v1/projects · auth: optional
List projects. ?filter= (all default; public lists public projects signed out), ?limit=, ?offset=.

Response:

200 {"items":[{"id":"3f2a…","name":"Online Store","slug":"online-store","description":"","visibility":"private","template":false,"ownerId":"7b6c…","ownerName":"Ana","role":"owner","createdAt":"…","updatedAt":"…"}],"limit":50,"offset":0}

POST /api/v1/projects · auth: session
Create a project.

Request:

{"name":"Online Store","description":"","visibility":"private"}

Response:

201 {"id":"3f2a…","name":"Online Store","slug":"online-store","description":"","visibility":"private","template":false,"ownerId":"7b6c…","ownerName":"Ana","role":"owner","createdAt":"…","updatedAt":"…"}

POST /api/v1/projects/import · auth: session
Import a .ddrproj.zip (raw application/zip body or multipart field file). ?name= overrides the name.

Request:

<zip bytes>

Response:

201 {"id":"3f2a…","name":"Online Store","slug":"online-store","description":"","visibility":"private","template":false,"ownerId":"7b6c…","ownerName":"Ana","role":"owner","createdAt":"…","updatedAt":"…"}

GET /api/v1/projects/{pid} · auth: optional
One project (members, link holders, or public).

Response:

200 {"id":"3f2a…","name":"Online Store","slug":"online-store","description":"","visibility":"private","template":false,"ownerId":"7b6c…","ownerName":"Ana","role":"owner","createdAt":"…","updatedAt":"…"}

PATCH /api/v1/projects/{pid} · auth: session
Rename, describe, change visibility or template flag.

Request:

{"visibility":"public"}

Response:

200 {"id":"3f2a…","name":"Online Store","slug":"online-store","description":"","visibility":"private","template":false,"ownerId":"7b6c…","ownerName":"Ana","role":"owner","createdAt":"…","updatedAt":"…"}

DELETE /api/v1/projects/{pid} · auth: session
Delete a project (owner).

Response:

204 No Content

POST /api/v1/projects/{pid}/duplicate · auth: session
Copy a project you can view into your account.

Request:

{"name":"Online Store copy"}

Response:

201 {"id":"3f2a…","name":"Online Store","slug":"online-store","description":"","visibility":"private","template":false,"ownerId":"7b6c…","ownerName":"Ana","role":"owner","createdAt":"…","updatedAt":"…"}

GET /api/v1/projects/{pid}/export.zip · auth: optional
Download the project as .ddrproj.zip.

Response:

200 application/zip

GET /api/v1/p/{slug} · auth: none
A public project and its files by slug.

Response:

200 {"project":{"id":"3f2a…","name":"Online Store","slug":"online-store","description":"","visibility":"private","template":false,"ownerId":"7b6c…","ownerName":"Ana","role":"owner","createdAt":"…","updatedAt":"…"},"files":[{"id":"91d0…","seq":4,"path":"diagrams/flow.ddr","kind":"ddr","version":1,"etag":"\"v1\"","size":64,"createdAt":"…","updatedAt":"…"}]}

Files

POST /api/v1/projects/{pid}/assets · auth: session
Upload a file (raw body, ?path=img/logo.png): png, jpg, gif, webp, svg, pdf, txt, csv, md. Free accounts: 2 MB per file, 30 files per project, 250 MB in total (403 quota_exceeded).

Response:

201 {"id":"91d0…","seq":4,"path":"diagrams/flow.ddr","kind":"ddr","version":1,"etag":"\"v1\"","size":64,"createdAt":"…","updatedAt":"…"}

GET /api/v1/projects/{pid}/files/{fid}/raw · auth: optional
The bytes of an uploaded file (sandboxed CSP), for embedding in .tdr documents.

Response:

200 image/png

GET /api/v1/projects/{pid}/files · auth: optional
List files.

Response:

200 {"items":[{"id":"91d0…","seq":4,"path":"diagrams/flow.ddr","kind":"ddr","version":1,"etag":"\"v1\"","size":64,"createdAt":"…","updatedAt":"…"}]}

POST /api/v1/projects/{pid}/files · auth: session
Create a file (kind ddr or tdr, inferred from the path).

Request:

{"path":"diagrams/flow.ddr","content":"Type: Flowchart\nTitle: Flow\n"}

Response:

201 {"id":"91d0…","seq":4,"path":"diagrams/flow.ddr","kind":"ddr","version":1,"etag":"\"v1\"","size":64,"createdAt":"…","updatedAt":"…"}

GET /api/v1/projects/{pid}/files/{fid} · auth: optional
File metadata. Sends an ETag.

Response:

200 {"id":"91d0…","seq":4,"path":"diagrams/flow.ddr","kind":"ddr","version":1,"etag":"\"v1\"","size":64,"createdAt":"…","updatedAt":"…"}

PATCH /api/v1/projects/{pid}/files/{fid} · auth: session
Rename or replace content. Send If-Match or version; a stale version is 412.

Request:

{"path":"diagrams/checkout.ddr","version":1}

Response:

200 {"id":"91d0…","seq":4,"path":"diagrams/flow.ddr","kind":"ddr","version":1,"etag":"\"v1\"","size":64,"createdAt":"…","updatedAt":"…"}

DELETE /api/v1/projects/{pid}/files/{fid} · auth: session
Delete a file.

Response:

204 No Content

GET /api/v1/projects/{pid}/files/{fid}/text · auth: optional
The file text (text/plain, ETag; If-None-Match gives 304).

Response:

200 text/plain
Type: Flowchart
Title: Flow

PUT /api/v1/projects/{pid}/files/{fid}/text · auth: session
Replace the text (text/plain body). If-Match: "v1"; ?reset=true drops the live Yjs state.

Request:

Type: Flowchart
Title: Flow

Response:

200 {"id":"91d0…","seq":4,"path":"diagrams/flow.ddr","kind":"ddr","version":1,"etag":"\"v1\"","size":64,"createdAt":"…","updatedAt":"…"}

Sharing

GET /api/v1/projects/{pid}/members · auth: session
List members.

Response:

200 {"items":[{"userId":"…","email":"bo@example.com","name":"Bo","avatarUrl":null,"role":"editor","addedAt":"…"}]}

POST /api/v1/projects/{pid}/members · auth: session
Invite by email (same as POST invites).

Request:

{"email":"bo@example.com","role":"editor"}

Response:

201 {"id":"c1e2…","projectId":"3f2a…","email":"bo@example.com","role":"editor","status":"pending","createdAt":"…","expiresAt":"…"}

PATCH /api/v1/projects/{pid}/members/{uid} · auth: session
Change a role; owner transfers ownership.

Request:

{"role":"viewer"}

Response:

200 {"userId":"…","role":"viewer"}

DELETE /api/v1/projects/{pid}/members/{uid} · auth: session
Remove a member (or leave).

Response:

204 No Content

GET /api/v1/projects/{pid}/invites · auth: session
List invites.

Response:

200 {"items":[{"id":"c1e2…","projectId":"3f2a…","email":"bo@example.com","role":"editor","status":"pending","createdAt":"…","expiresAt":"…"}]}

POST /api/v1/projects/{pid}/invites · auth: session
Invite by email.

Request:

{"email":"bo@example.com","role":"editor"}

Response:

201 {"id":"c1e2…","projectId":"3f2a…","email":"bo@example.com","role":"editor","status":"pending","createdAt":"…","expiresAt":"…"}

DELETE /api/v1/invites/{id} · auth: session
Revoke an invite.

Response:

204 No Content

POST /api/v1/invites/{id}/resend · auth: session
Resend an invite mail.

Response:

200 {"id":"c1e2…","projectId":"3f2a…","email":"bo@example.com","role":"editor","status":"pending","createdAt":"…","expiresAt":"…"}

GET /api/v1/invites/lookup · auth: none
Describe an invite by ?token=.

Response:

200 {"projectName":"Online Store","inviterName":"Ana","email":"bo@example.com","role":"editor","status":"pending"}

GET /api/v1/invites/{token} · auth: none
Same as lookup, token in the path.

Response:

200 {"projectName":"Online Store","inviterName":"Ana","email":"bo@example.com","role":"editor","status":"pending"}

POST /api/v1/invites/accept · auth: session
Accept an invite by token.

Request:

{"token":"i_abc…"}

Response:

200 {"projectId":"3f2a…","role":"editor"}

POST /api/v1/invites/decline · auth: none
Decline an invite by token.

Request:

{"token":"i_abc…"}

Response:

204 No Content

GET /api/v1/projects/{pid}/links · auth: session
List share links.

Response:

200 {"items":[{"id":"5aa1…","projectId":"3f2a…","role":"viewer","token":"s_9x…","url":"https://example.com/s/s_9x…","createdAt":"…","expiresAt":null}]}

POST /api/v1/projects/{pid}/links · auth: session
Create a share link. Editor links need confirm: true.

Request:

{"role":"viewer","expiresInHours":72}

Response:

201 {"id":"5aa1…","projectId":"3f2a…","role":"viewer","token":"s_9x…","url":"https://example.com/s/s_9x…","createdAt":"…","expiresAt":null}

DELETE /api/v1/projects/{pid}/links/{lid} · auth: session
Revoke a link.

Response:

204 No Content

DELETE /api/v1/projects/{pid}/links · auth: session
Revoke a link given as ?id=.

Response:

204 No Content

GET /api/v1/links/lookup · auth: none
Describe a share link by ?token=.

Response:

200 {"projectId":"3f2a…","projectName":"Online Store","role":"viewer","expiresAt":null}

POST /api/v1/links/accept · auth: session
Join a project through a share link.

Request:

{"token":"s_9x…"}

Response:

200 {"projectId":"3f2a…","role":"viewer"}

GET /api/v1/projects/{pid}/access-requests · auth: session
List access requests (managers).

Response:

200 {"items":[{"id":"e4f5…","projectId":"3f2a…","userId":"8c9d…","message":"May I view this?","status":"pending","createdAt":"…"}]}

POST /api/v1/projects/{pid}/access-requests · auth: session
Ask the owner for access.

Request:

{"message":"May I view this?"}

Response:

201 {"id":"e4f5…","projectId":"3f2a…","userId":"8c9d…","message":"May I view this?","status":"pending","createdAt":"…"}

PATCH /api/v1/access-requests/{id} · auth: session
Approve or deny a request.

Request:

{"status":"approved","role":"viewer"}

Response:

200 {"id":"e4f5…","projectId":"3f2a…","userId":"8c9d…","message":"May I view this?","status":"pending","createdAt":"…"}

Planned

GET /api/v1/assets/{aid} · auth: optional
Read an asset (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

GET /api/v1/projects/{pid}/versions · auth: session
Version history (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

POST /api/v1/projects/{pid}/versions · auth: session
Save a version (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

POST /api/v1/projects/{pid}/versions/{vid}/restore · auth: session
Restore a version (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

GET /api/v1/files/{fid}/threads · auth: session
Comment threads (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

POST /api/v1/files/{fid}/threads · auth: session
Start a thread (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

POST /api/v1/threads/{tid}/comments · auth: session
Reply (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

PATCH /api/v1/threads/{tid} · auth: session
Resolve a thread (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

POST /api/v1/projects/{pid}/export · auth: session
Server-side render/export job (planned). Today rendering runs in the browser (WebAssembly), the ddr render CLI and the render MCP tool.

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

GET /api/v1/exports/{id} · auth: session
Export job status (planned).

Response:

501 {"type":"…/not_implemented","title":"Not Implemented","status":501,"detail":"not available in v0.0.1"}

Realtime

GET /ws/projects/{pid} · auth: session
WebSocket for live editing (outside /api/v1). Binary frames kind u8 | docRef uvarint | payload: 0x01 subscribe to file (docRef = file seq), 0x02 unsubscribe, 0x10 y-protocols sync, 0x11 awareness, 0x12 checkpoint, 0x13 seq ack, 0x20 file tree (JSON), 0x30 presence (JSON), 0x40 events (JSON), 0x7E error, 0x7F ping.

Request:

GET with `Upgrade: websocket` and the session cookie

Response:

101 Switching Protocols

Flowchart

Classic flowchart (ISO 5807): terminators, process boxes, decision diamonds, input/output parallelograms.

Grammar:

file      = header blank "Flow" "{" { stmt } "}" ;
stmt      = step | start | end | if | loop | goto | edge ;
start     = "Start" [ label ] ;
end       = "End" [ label ] ;
step      = [ id ":" ] label { attrs } [ "->" ( attrs | target [ ":" label ] { attrs } ) ] ;
if        = "if" cond [ "->" attrs [ "->" attrs ] ] block { "elif" cond block } [ "else" block ] ;
loop      = "loop" [ cond ] block ;
goto      = "goto" id [ attrs ] ;
edge      = id "->" id [ ":" label ] { attrs } { "->" id [ ":" label ] { attrs } } ;
block     = "{" { stmt } "}" ;
key required values default meaning
Flow yes block - The one flow of the chart.

Example (docs/examples/flowchart.ddr):

Type: Flowchart
Title: Password reset
Direction: TB

# One Flow block. Steps run top to bottom; `if`, `loop` and `goto` branch.
Flow {
  Start;
  Enter email address;
  if Account exists? {
    Send reset link;
    open: Open link from email;
    if Link still valid? {
      Choose new password;
    } else {
      Show expired message;
      goto open;
    }
  } else {
    Show generic confirmation;
  }
  End;
}

Flowchart: Start/End labels and the one End

start = "Start" [ ":" ] [ label ] ;
end   = "End" [ ":" ] [ label ] ;

Example (docs/examples/pending/flowchart-start-end-labels.ddr):

Type: Flowchart
Title: Order approval

Flow {
  Start: Order received;
  if In stock? {
    Ship order;
    End: Order closed;
  } else {
    Notify customer;
    End: Order closed;
  }
}

Graph

Free boxes-and-arrows graph: node statements and explicit a -> b edges.

Grammar:

file  = header blank ( "Graph" | "Nodes" ) "{" { node | edge } "}" [ "Edges" "{" { edge } "}" ] ;
node  = [ id ":" ] label { attrs } ;
edge  = id "->" id [ ":" label ] { attrs } { "->" id [ ":" label ] { attrs } } ;
key required values default meaning
Graph yes block (alias Nodes) - Nodes and edges.
Edges no block - Extra edges added to the same graph.

Example (docs/examples/graph.ddr):

Type: Graph
Title: Order service
Direction: LR

Graph {
  user: Customer [shape: actor];
  web: Web app [shape: round];
  api: Order API;
  queue: Event queue [shape: pill, color: amber];
  db: Orders database [shape: cylinder];
  mail: Email service [shape: round, color: teal];
  user -> web: browses;
  web -> api: JSON over HTTPS;
  api -> db: SQL;
  api -> queue -> mail: order.created;
}

Architecture

Architecture diagrams are written as Type: Graph with shapes (cylinder for stores, actor for people, round for services) and colors per tier.

Grammar:

Same as Graph.

Example (docs/examples/architecture.ddr):

Type: Graph
Title: Web platform architecture
Direction: LR

# Architecture diagrams are Graphs with shapes and colors for each tier.
Graph {
  browser: Browser [shape: round];
  cdn: CDN [shape: pill, color: teal];
  app: App server;
  worker: Background worker [color: purple];
  cache: Cache [shape: cylinder, color: red];
  db: Database [shape: cylinder];
  store: File storage [shape: cylinder, color: amber];
  browser -> cdn: static files;
  browser -> app: API calls;
  app -> cache;
  app -> db: reads and writes;
  app -> worker: jobs [dashed];
  worker -> store: uploads;
}

Use case

UML use case diagram: actors outside a system boundary, use cases inside, include / extend / generalization.

Grammar:

file      = header blank "Actors:" { actor } "Use Cases:" { case | group } ;
actor     = "-" name { attrs } [ "->" name "[head: triangle]" ] ;
case      = "-" actors ":" name { attrs } [ "->" actor { attrs } ] { field } ;
group     = "-" actors [ ":" ] "{" { name { attrs } [ "{" fields "}" ] ( ";" | NL ) } "}" ;
actors    = name { "," name } ;
field     = key ":" text | ( "Flow" | "Main Flow" | "Alternative" [ N ] | "Exceptions" ) "{" { stmt } "}" ;
key       = "Description" | "Includes" | "Extends" | "Precondition" | "Postcondition" | "Actors" | ... ;
key required values default meaning
Actors yes list - One actor per - line. Flags: [system], [external], [primary], [secondary]; [side: left|right].
Use Cases yes list - - Actor, Actor: Use case with indented fields.
Numbering no prefix, e.g. UC or UC- off Gives each main case a code UC01, UC02, … (sub-cases UC03.1).
Show Codes no yes no no Print the code above the name.
Min Use Cases no number 6 With Numbering:, fewer main cases warns W120.

Example (docs/examples/usecase.ddr):

Type: UseCase
Title: Online Store
Author: Product Team

Actors:
- Customer
- Store Manager
- Payment Provider [system]

Use Cases:
- Customer: Browse catalog
  Description: The customer looks through products by category.
- Customer: Place order
  Description: The customer buys the items in the cart.
  Includes: Pay for order
- Customer, Payment Provider: Pay for order
  Description: The order total is charged to the customer.
- Customer: Track order
  Extends: Place order
- Store Manager: Manage products
  Description: The manager adds, edits and removes products.

Activity

UML activity diagram with swimlanes, decisions, merges, fork/join bars and one implicit start and end.

Grammar:

file      = header blank ( "Flow" | "Activity" ) "{" { stmt } "}" ;
stmt      = [ lane ":" ] ( step | if | loop | parallel | goto | "start" | "end" | "stop" | edge ) ;
step      = [ id ":" ] label { attrs } ;
if        = "if" cond [ "[yes:" text "," "no:" text "]" ] block { "elif" cond block } [ "else" block ] ;
loop      = "loop" [ cond ] block ;
parallel  = "parallel" block "and" block { "and" block } ;
key required values default meaning
Lanes no comma list lanes in order of first use Swimlane columns, left to right. A lane named in a step must be listed (E205) when Lanes: is given.
Flow yes block (alias Activity) - The activity.

Example (docs/examples/activity.ddr):

Type: Activity
Title: Support ticket
Lanes: Customer, Support, System

Flow {
  Customer: Submit ticket
  System: Assign ticket number
  Support: Review ticket
  if Needs more information? {
    Support: Ask customer for details
    Customer: Reply with details
  } else {
    Support: Accept ticket
  }
  parallel {
    Support: Resolve issue
  } and {
    System: Notify customer of progress
  }
  Customer: Confirm resolution
}

Language of generated labels (Lang)

lang = ( "Lang" | "Language" ) ":" ( "en" | "id" ) ;

Example (docs/examples/pending/activity-lang.ddr):

Type: Activity
Title: Leave request
Lang: en
Lanes: Employee, Manager

Flow {
  Employee: Submit leave request
  Manager: Review request
  if Approved? {
    Manager: Record approval
  } else {
    Employee: Revise request
  }
}

Conceptual data model (CDM)

PowerDesigner-style conceptual model: entities, attributes with identifiers <pi> and mandatory <M>, named relationships with crow's feet.

Grammar:

file      = header blank "Entities:" { entity } { ref } ;
entity    = "-" name ( "{" attr { ( ";" | NL ) attr } "}" | NL { INDENT attr NL } ) ;
attr      = name [ ":" type ] { attrs } ;           (* [identifier] [pi] [mandatory] [M] *)
ref       = "Ref:" end op end [ ":" name ] { attrs } { op end [ ":" name ] } ;
end       = entity_name [ "." attr_name ] ;
op        = "<" | ">" | "-" | "<>" ;
key required values default meaning
Entities yes list - One entity per - item.
Ref no line (repeatable) - A relationship. Also as a References: list or a Ref { } block.

Example (docs/examples/cdm.ddr):

Type: CDM
Title: Online Store

Entities:
- Customer {
    Customer ID: varchar(12) [identifier]
    Full Name: varchar(80) [mandatory]
    Email: varchar(120)
  }
- Order {
    Order ID: varchar(12) [identifier]
    Order Date: date [mandatory]
    Total: double
  }
- Product {
    Product ID: varchar(12) [identifier]
    Name: varchar(100) [mandatory]
    Price: double
  }

Ref: Customer < Order: places
Ref: Order <> Product: contains

Physical data model (PDM)

Physical tables with SQL types and PK/FK columns; references are written between columns.

Grammar:

file      = header blank ( "Tables:" | "Entities:" ) { table } { ref } ;
table     = "-" name ( "{" column { ( ";" | NL ) column } "}" | NL { INDENT column NL } ) ;
column    = name ":" sqltype { attrs } ;             (* [pk] [fk] [unique] [not null] [null] *)
ref       = "Ref:" table "." column op table "." column [ ":" name ] { attrs } ;
key required values default meaning
Tables yes list (alias Entities) - One table per - item.
Ref no line (repeatable) - Ref: a.col < b.col. The many side (or the right side of -) is the FK.

Example (docs/examples/pdm.ddr):

Type: PDM
Title: Online Store

Tables:
- customers {
    customer_id: bigint [pk]
    full_name: varchar(120) [not null]
    email: varchar(180) [not null]
  }
- orders {
    order_id: bigint [pk]
    customer_id: bigint [not null]
    ordered_at: timestamp [not null]
  }
- order_items {
    order_id: bigint [pk]
    product_id: bigint [pk]
    quantity: int [not null]
  }
- products {
    product_id: bigint [pk]
    name: varchar(100) [not null]
    price: numeric(10,2) [not null]
  }

Ref: customers.customer_id < orders.customer_id
Ref: orders.order_id < order_items.order_id
Ref: products.product_id < order_items.product_id

Entity relationship (ERD, Chen)

Chen notation: entities are rectangles, relations diamonds, attributes ovals; key * (bold, underlined), foreign key **, derived # (dashed), composite name { ... }.

Grammar:

file      = header blank "Entities:" { entity } "Relations:" { relation } ;
entity    = "-" name "{" attr { ";" attr } "}" ;
attr      = mark name [ "[x:" n "," "y:" n "]" ] [ "{" attr { ";" attr } "}" ]
          | name [ "*" | "**" ] [ "#" ] [ "[x:" n "," "y:" n "]" ] [ "{" attr { ";" attr } "}" ] ;
mark      = "*" | "**" ;
relation  = "-" name "{" member { ";" member } "}" ;
member    = entity_name [ ":" card ] | attr ;          (* card: 1, M, N, 0..1, ... *)
key required values default meaning
Entities yes list - - Name { Attr *; Attr; Ref ** }.
Relations yes list - - Verb { EntityA: 1; EntityB: N; attribute }.

Example (docs/examples/erd.ddr):

Type: ERD
Title: Online Store

Entities:
- Customer { Customer ID *; Name; Email }
- Order { Order ID *; Order Date; Customer ID ** }
- Product { Product ID *; Name; Price }

Relations:
- Places { Customer: 1; Order: N }
- Contains { Order: M; Product: N; Quantity }

ERD: ternary relationships

A relation joins every entity it names, so a ternary (or wider) relationship is one diamond with three member lines:

relation = "-" name "{" member ";" member ";" member { ";" ( member | attr ) } "}" ;

Example (docs/examples/erd-ternary.ddr):

Type: ERD
Title: Course enrollment

Entities:
- Student { Student ID *; Name }
- Course { Course ID *; Title }
- Teacher { Teacher ID *; Name }

Relations:
- Enrolls { Student: N; Course: M; Teacher: 1; Term }

ERD: key, derived and composite attributes

Attribute marks and nesting:

attr = name [ "*" | "**" ] [ "#" ] [ "{" attr { ";" attr } "}" ] ;

Example (docs/examples/erd-attributes.ddr):

Type: ERD
Title: Users and Orders

# key `*`, foreign key `**`, derived `#`, composite `{ }`
Entities:
- User {
    User ID *
    Name
    Age #
    Address {
      Country
      City
      District
    }
  }
- Order { *Order ID; Order Date; User ID **; Total # }

Relations:
- Places { User: 1; Order: N }

ERD: optional cardinality

The cardinality after Entity: is optional:

member = entity_name [ ":" card ] ;   (* card: 1 | M | N | 0..1 | 1..N | ... ; free text *)

Example (docs/examples/erd-optional-card.ddr):

Type: ERD
Title: Employees and parking spots

Entities:
- Employee { Employee ID *; Name }
- Parking Spot { Spot ID *; Level }
- Department { Department ID *; Name }

Relations:
- Uses { Employee: 0..1; Parking Spot: 0..1 }
- Works In { Employee; Department }

Documents (.tdr) and use case specifications

Markdown documents with a header and directives that embed live diagrams and use case specification tables.

Grammar:

file      = [ { key ":" value NL } "---" NL ] markdown ;
directive = "::diagram[" path "]" [ "{" "view=" q [ "caption=" q ] "}" ]
          | "::usecases[" path "]" [ "{" "title=" q "}" ] ;
key required values default meaning
Title no text - Title block.
Subtitle no text - Title block.
Author no text - Title block.
Date no text - Title block.

Example (docs/examples/document.tdr):

Title: Online Store requirements
Author: Product Team
---

# Introduction

This document describes the Online Store. Each figure is a live view of a
`.ddr` file, so it stays in sync with the diagram source.

::diagram[diagrams/usecase.ddr]{view="overview" caption="Use cases"}

## Use case specifications

::usecases[diagrams/usecase.ddr]{title=""}