11. Layer 8 — Persistence
Contents of DISL 0.4
The persistence layer declares exactly how the diagrams users draw are stored as DID definitions: which format, which files, how identifiers are generated, how elements and keys are ordered, which view data is kept, how precise numbers are, how old DID definitions are migrated, and how several people can edit at once. The goal is that any two conforming runtimes produce identical files for the same diagram, that files diff and merge well in version control, and that stored diagrams remain readable for decades. The structure these settings configure, and the rules a writer follows, are specified by DID (DID-specification.md).
A diagram type whose model lives in a file another tool owns (a Structurizr workspace, a Freeplane mind map, a pipeline's YAML) is not stored as a DID definition: its persistence names a binding in FBL, the Format Binding Language (FBL-specification.md), with format: "fbl" (11.2). (informative)
{
"persistence": {
"format": "json",
"files": { "mode": "split", "model": "{name}.sm.json", "view": "{name}.sm.view.json" },
"ids": { "strategy": "uuid-v7", "prefix": { "State": "st_", "Transition": "tr_" } },
"references": "id",
"ordering": { "elements": "type-then-id", "keys": "canonical" },
"omitDefaults": true,
"precision": { "canvas": 2 },
"timestamps": { "format": "rfc3339", "timezone": "utc" },
"view": { "store": ["bounds", "waypoints", "labelOffsets", "collapsed", "zIndex", "rotation", "viewport"],
"styleOverrides": ["fill", "stroke.color", "font.weight"] },
"metadata": ["languageVersion", "createdAt", "modifiedAt", "generator"],
"migrations": [],
"collaboration": { "mode": "crdt", "engine": "yjs" }
}
}
| Property | Type | Description |
|---|---|---|
format |
"json" (default), "yaml", "jsonl", "cbor", "fbl", "plugin:<name>" |
Serialization. All formats except fbl encode the same logical DID definition (11.4). jsonl writes one element per line, which merges particularly well. cbor is for large binary-efficient storage. fbl: the model is a body in another format, read and written through binding. |
binding |
URI reference or FBL Binding | Required when format is "fbl", forbidden otherwise: the binding that reads and writes the model, as <uri>#<name> of an FBL document (FBL-specification.md, section 2.3), or an inline FBL Binding object. |
typeMap |
map binding type → TypeMapEntry | (DISL 0.3) Only with format: "fbl": how the binding's types and attributes map onto the metamodel (below). |
encoding |
"utf-8" |
Only UTF-8 is allowed. |
indent |
int or "tab" |
Default 2. 0 writes minified JSON. |
newline |
"lf", "crlf" |
Default "lf". |
finalNewline |
bool | Default true. |
compression |
"none", "gzip", "zip-bundle" |
zip-bundle stores model, views and binary assets (images) in one archive with a manifest. |
mediaType |
string | Media type of DID definitions. |
YAML output MUST quote strings that would otherwise be read as other types (the "Norway problem": no, yes, on, off, ~, numeric-looking strings, dates) and MUST NOT use anchors, aliases or tags.
When format is "fbl", the stored model is the body the binding describes, not a DID definition, and it is written by the splices FBL defines, keeping the body's own conventions. files, encoding, indent, newline, finalNewline, compression, mediaType, ordering, omitDefaults, precision, timestamps, canonical, metadata and definition MUST NOT be given. ids, view, migrations, collaboration, import and export keep their meaning; view data is stored in the registration FBL defines. Such a specification needs no persistence plugin; the refusal of 13.1 for a missing required plugin applies only to a plugin the binding's reader names.
Type map (DISL 0.3). A binding may name its types and attributes as its format does rather than as the metamodel does: a hype cycle's binding reads a Unit entry, a Graph header and influences as entries with from and to. typeMap maps each binding type, by its name, to a TypeMapEntry ($defs/TypeMapEntry):
| Property | Type | Description |
|---|---|---|
as |
"diagram", "header", "unreadable" or TypeRef |
Required. What the binding's entries of this type become. |
attributes |
map binding attribute → attribute name, "source", "target", "id" or null |
The binding's attribute names mapped to the metamodel's; unlisted attributes keep their names. |
hostAttributes |
string[] | Attributes the binding reads and writes that are not attributes of the model. |
doc |
Doc |
- Without
typeMap, an FBL type and attribute MUST have the name of a metamodel type and attribute. - With it, a reader maps each FBL element to the type
asnames.as: "diagram"writes its attributes to the diagram's;"header"and"unreadable"keep the entry out of the model, and anunreadableentry is reported bystd.unreadableEntry(8.7). An attribute mapped toidis the element's id, and one mapped tonullis not in the model. - A reference attribute mapped to
sourceortargetmakes the element a relation of the typeasnames, with that end. A relation whose mapped end names nothing MUST still be created, with that end unset, and reported bystd.referenceswithdetail.endnaming the end; it is not drawn (6.1), andstd.endpointsdoes not report the missing end. hostAttributesare read and written by the binding but are not attributes of the model: they are not visible in CEL, forms or constraints, and a writer keeps their values.- A binding type that
typeMapdoes not list keeps its name, as withouttypeMap.
"persistence": { "format": "fbl", "binding": "gartner-hype-cycle-graph.fbl#ghg",
"typeMap": {
"Graph": { "as": "header" },
"Unit": { "as": "diagram", "attributes": { "value": "unit" } },
"Trend": { "as": "Trend", "hostAttributes": ["storedId", "unknownKeys"] },
"Influence": { "as": "Influence", "attributes": { "from": "source", "to": "target" }, "hostAttributes": ["storedId", "unknownKeys"] },
"Unreadable": { "as": "unreadable" } } }
The entry influences: [{ id: i1, from: steam-engine, to: railways, to-phase: peak, … }] becomes the relation Influence from steam-engine to railways, and unit: year becomes diagram.unit == 'year'.
| Property | Type | Description |
|---|---|---|
mode |
"single" (default), "split", "per-element", "bundle" |
single: one file with model and views. split: model and view data in separate files, so layout edits never touch the model file. per-element: one file per top-level element in a directory plus an index (for very large models edited by teams). bundle: see compression: "zip-bundle". |
model, view, index |
pattern | File name patterns; {name} is the diagram's name, {id} and {type} the element's id and type for per-element. |
assets |
{mode, dir} |
Binary assets: "inline" (base64), "external" (files in dir, referenced by relative URI), "bundle". |
lock |
bool | Write a lock file while open (for file-based single-user editing). |
What a stored diagram looks like, independently of format and file split, is specified by DID, the Diagram Definition Language (DID-specification.md, section 3): the element, relation and view records, and the node and edge view data. Every DID definition names the DISL specification it was made with in its language object. The settings in this section and in 11.5–11.7 are declared by a specification and configure that structure; DID specifies what a conforming writer produces from them.
persistence.ids ($defs/IdStrategy) says how the elements of a diagram are identified: a rule that applies to every type, and, in types, rules for single types that override it. Each rule is an IdRule ($defs/IdRule); types, missing and compare are allowed only at the top level.
| Property | Type | Description |
|---|---|---|
strategy |
"uuid-v4", "uuid-v7" (default), "ulid", "nanoid", "sequential", "natural", "cel", "derived" |
How ids are made (DID, section 4). uuid-v7 and ulid are time-ordered, which keeps id-sorted files in creation order and helps merges. sequential uses per-type counters (State_1) and is only suitable for single-user editing. natural derives ids from key attributes (unique by constraint). cel evaluates expression once, on creation. derived computes the id from the model with expression, whenever the model is read or changed (11.5.2). |
encoding |
"hex" (default), "base64url", "base36" |
Text form of uuid-v4 and uuid-v7 ids (11.5.1). |
prefix |
string or map TypeRef → string | Type prefixes ("st_"), making ids self-describing in diffs. A map is allowed only at the top level. Not applied to the results of cel and derived, whose expression writes any prefix itself. |
expression |
Expression | Required for cel and derived. For cel: context create, must return a string. For derived: context identity (12.3), must return a string. |
stable |
bool | Ids never change once assigned, even if natural keys change (the first derived id is kept). Default true. Has no effect on derived ids, which always follow their expression. |
pattern |
regex | Allowed id syntax; default ^[A-Za-z0-9_.:#-]{1,128}$. |
suffix |
string containing {n} |
How a generated natural or cel id is made unique. Default "-{n}", the 0.1 behaviour (-2, -3, …). |
ephemeral |
bool or Expression → bool | The id identifies the element within one reading of the model only (11.5.3). An Expression is evaluated in the identity context. Default false. |
reason |
Reason | The refusal shown when a gesture would store an ephemeral id (11.5.3); context element, with self bound to the ephemeral element. |
types |
map TypeRef → IdRule | Per-type rules (11.5.2). Top level only. |
missing |
"assign" (default), "ephemeral" |
What a runtime does with an element read without a usable id (11.5.4). Top level only. |
compare |
"exact" (default), "ignore-case" |
How ids are compared (11.5.4). Top level only. |
doc |
Doc |
- Ids MUST be treated as opaque strings. A runtime MUST NOT decode an id to recover a UUID, a time or a key.
- A hand-written id that matches
patternMUST be accepted whatever the strategy; the strategy governs only the ids the runtime makes. encodinggives the text form ofuuid-v4anduuid-v7ids.hexis the RFC 9562 hyphenated lowercase form (the 0.1 form).base64urlis the 16 bytes in RFC 9562 byte order encoded with base64url (RFC 4648, section 5) without padding: 22 characters.base36is the 128-bit value as an unsigned integer in lowercase base 36, left-padded with0to 25 characters.- A
naturalid MUST be the type'sprefixfollowed by the string forms of itskeyattributes, in declaration order after inheritance is flattened, joined by|. - When a generated
naturalorcelid is already in use, the runtime MUST appendsuffixwith{n}replaced by the smallest integer n ≥ 2 that makes the id unique. - An id generated for a new element MUST be generated once, when the transaction is first applied. Redoing the transaction MUST re-create the element with the same id, and a redo that would create a duplicate id MUST be refused.
"ids": { "strategy": "uuid-v4", "encoding": "base36", "stable": true }
"ids": { "strategy": "natural", "stable": true, "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", "suffix": "{n}", "compare": "ignore-case" }
The first is the functional decomposition graph's ShortGuid, 25 base-36 characters; the second gives new C4 elements the identifiers web, web2, web3, compared without regard to case as the DSL does.
- A rule in
typesMUST apply to the named type and to every subtype that has no rule of its own; where two inherited rules apply, the one nearest in the type's linearisation (4.7) wins. Properties a rule leaves out are taken from the top-level rule. - For the
derivedstrategy, the id of an element MUST be the value ofexpression, evaluated in theidentitycontext (12.3). It MUST be recomputed after the model is read and in the "recompute derived" step of every transaction, before invariants are checked (14.4). - A runtime MUST compute ids in this order: nodes, ancestors before descendants, then relations. An
identityexpression MUST NOT read the id of an element other than an ancestor ofselfor, for a relation, its ends and their ancestors; a validator MUST reject one that visibly does. A runtime MUST report an expression that fails, or that reads an id not yet computed, asstd.missingIdonself(8.7). - The id of an element of a derived node type or of a derived relation type (4.11) MUST be its
derived.id(for a relation without one, the default of 4.11.3), not the id rule's. A rule intypesthat applies to such a type contributes onlyephemeralandreason(11.5.3); itsstrategy,expression,prefix,encoding,suffixandstableare ignored, and a validator SHOULD warn when a rule for a derived type sets one of them. - A derived id MUST NOT be altered to make it unique; two elements with the same derived id are reported by
std.duplicateId(11.5.4). - When a derived id changes in a transaction, the runtime MUST rewrite every stored reference to it (parent, relation ends, reference attributes, view keys) in the same transaction. When an id read from storage differs from its recomputed value, the element takes the computed id, every reference follows, and the change is written with the next save; it is not an undo step.
- A rename or move that changes what a derived id is computed from changes the id, so stored view data keyed by the old id no longer applies (informative: that is intended for path ids).
Paths, IRIs, triples with a repeat counter, term forms, scope paths, relation formula ids and fixed singleton ids are all written as derived rules; they differ only in the expression:
"ids": {
"strategy": "natural", "pattern": "^(res:.+|blank:[0-9]+|edge:.+|truncation)$",
"types": {
"Resource": { "strategy": "derived", "expression": "'res:' + self.iri" },
"BlankNode": { "strategy": "derived", "expression": "'blank:' + string(self.ordinal)", "ephemeral": true,
"reason": "That is a blank node, whose identity does not survive a reparse, so a stored position could not be trusted. Name it with an IRI to arrange it." },
"Statement": { "strategy": "derived",
"expression": "cel.bind(n, self.positionIn(diagram.relationsOfType('Statement').filter(r, r.source.id == self.source.id && r.predicateIri == self.predicateIri && r.target.id == self.target.id)), 'edge:' + self.source.id + '|' + self.predicateIri + '|' + self.target.id + (n > 0 ? '|' + string(n) : ''))" },
"Truncation": { "strategy": "derived", "expression": "'truncation'" }
}
}
A relation formula id reads its ends, for example "expression": "'depends:' + self.source.id + '->' + self.target.id" for a .NET project reference, and a path id reads the attribute the reader filled, for example "expression": "'playbook:' + self.path".
An element is ephemeral when the rule that applies to its type has ephemeral true, or an ephemeral expression that evaluates to true for it. Its id identifies it within one reading of the model only: blank nodes, anonymous terms, elements numbered by their position in the file. The word ephemeral is used because stable already has its 0.1 meaning.
- A runtime MUST NOT store, for an ephemeral element, view data (11.6; DID, section 5; or a registration's layout, which is FBL's), a style override, a suppression (8.6), or a reference by its id (a parent, a relation end, or a reference attribute).
- A gesture whose effect would be one of those writes (moving, resizing or pinning an ephemeral node, connecting to it where the relation would be stored by id, suppressing one of its findings) MUST be refused before it is applied, with the rule's
reasonor, without one, a runtime sentence saying that the element's identity does not survive re-reading. - An ephemeral element MAY be selected, shown in forms, and targeted by findings and operations within the session.
- Stored view data, style overrides and suppressions keyed by an id that the current reading makes ephemeral MUST be ignored, MUST be reported by
std.ephemeralViewData(8.7), and MUST be dropped when they are next written. - A relation read from the model that ends at an ephemeral node is not a reference by id; the reader writes it back in the model's own syntax (FBL). DISL forbids only storage keyed by an ephemeral id.
- A specification whose stored elements are referred to by id (a DID definition, 11.4) SHOULD NOT declare a stored type's ids ephemeral, and a validator SHOULD warn when it does.
"Shape": { "strategy": "derived", "expression": "self.blank ? 'blank:' + string(self.ordinal) : 'res:' + self.iri",
"ephemeral": { "cel": "self.blank" } }
- Nodes, relations and views of one diagram MUST share one id space. Ports are in the id space of their element when ids are written as
element#port. - A runtime MUST open a model in which ids are missing or duplicated, and MUST report each case through
std.missingIdorstd.duplicateId(8.7). It MUST NOT refuse the model for it. - Of the elements that share an id, the first in reading order (8.6) MUST keep it: every lookup and reference resolves to it. The second and later MUST be drawn, and MUST be treated as ephemeral (11.5.3) until the duplication is resolved.
- With
missing: "assign", an element without a usable id (absent, empty, or not matchingpattern) MUST receive a new id from the rule that applies to its type. The new id MUST NOT be written when the model is opened or validated, and MUST be written with the next save that writes the model. Withmissing: "ephemeral", such an element MUST be drawn and treated as ephemeral. - A runtime MUST NOT change an id in order to resolve a duplicate, except through a user action such as a quick fix of
std.duplicateId. - With
compare: "ignore-case", ids MUST be compared using Unicode default case folding for lookups, references, duplicate detection and view keys, and the spelling of the first occurrence in reading order MUST be kept. Withcompare: "exact", ids compare as code-point sequences, as in 0.1.
"ids": { "strategy": "uuid-v4", "prefix": "ID_", "missing": "assign" }
A mind map node without an id gets ID_ and a new id in memory, written with the next save and never by opening the file or by the headless validator.
view.store lists which kinds of view data are persisted: bounds, waypoints, anchors, labelOffsets, collapsed, zIndex, rotation, params, ports, viewport, guides, pinned, settings, and (DISL 0.4) groups (the collapse states of a node's compartment groups, 6.9, by element and group key) and filters (the values of the canvas filters with persist, 6.13.1). Anything not listed is recomputed on load (by layout or defaults). A pure layout-driven language stores nothing but the model. DID, section 5, specifies the stored form.
view.styleOverrides is "none" (default), "all", or a list of style property paths users may override per element ("fill", "stroke.color", "stroke.dash", "font.size"). Overrides are stored in NodeView.style / EdgeView.style and have the highest precedence (6.1).
No view data or style override is stored for an element with an ephemeral id (11.5.3); stored data keyed by one is ignored, reported by std.ephemeralViewData and dropped on the next write.
Viewer state. Some view data belongs to the person looking, not to the diagram: which branches of a mind map are folded, which filters are set, whether the compact variant is shown. persistence.view gains:
| Property | Type | Default | Description |
|---|---|---|---|
viewer |
ViewKind[] | [] |
The view-data kinds each viewer holds for themselves. Kinds ($defs/ViewKind): those of store, plus selection, filters and viewpoint. |
initial |
map kind → value or {cel} |
the kind's default | The value a viewer starts with, evaluated in the element context when a viewer first opens the view; collapsed without one starts false. |
revealExpands |
bool | true |
Revealing or selecting an element inside a collapsed container expands its collapsed ancestors. |
Viewer state is the value of every kind in view.viewer, of every transient: "viewer" attribute (4.3), of canvas filters (6.13.1), of the variant shown (3.5), of dismissed notices and of a fitted zoom (6.13).
- A kind MUST NOT appear in both
view.storeandview.viewer. - Viewer state MUST NOT be written to a DID definition or by a format binding, MUST NOT enter the undo or redo history, MUST be changeable in read-only mode, and MUST NOT be shared with another viewer. A runtime MAY keep it for the viewer's session; it MUST NOT outlive the view unless the host keeps it as a per-user preference, which DISL does not define.
- Viewer state MUST NOT be read in the deterministic contexts of 12.5:
self.viewfor a kind inview.viewer, a viewer attribute,filterValue()anddiagram.drawnare rejected there by validators. Findings never depend on a viewer. self.view.collapsed(12.2) reads the viewer's value, and aviewaction (9.4) sets it.
"persistence": {
"view": { "store": [], "viewer": ["collapsed", "viewport"],
"initial": { "collapsed": { "cel": "self.folded" } },
"doc": "Folding belongs to each viewer: FOLDED in the file seeds it once." }
}
A mind map's folds are each viewer's own, seeded from the file's folded flags the first time a viewer opens it; folding is allowed in read-only mode and is never undone. Without viewer, kinds not in store are recomputed on load, as in 0.1.
View data in the body (DISL 0.4). With format: "fbl", view data is stored in the registration FBL defines (11.2), unless view.bind binds a stored kind onto entries of the body itself, which suits a file whose view data travels with it. bind maps a kind to a ViewBind ($defs/ViewBind):
| Property | Type | Description |
|---|---|---|
type |
binding type | Required. The binding's type whose entries hold this kind. |
element |
binding attribute | The attribute naming the element the entry is about, a reference by id. Required except for filters. |
x, y |
binding attribute | pinned: the stored position, in the coordinates of the node's placement. |
group, collapsed |
binding attribute | groups: the group key (6.9) and its stored state. |
values |
map filter id → binding attribute | filters: the attribute holding each kept filter's value. |
doc |
Doc |
- The kinds that may be bound are
pinned(an element is pinned exactly while an entry names it, and its position is the entry's),groupsandfilters. A bound kind MUST be listed inview.store. - A bound type is not part of the model: it MUST NOT be a metamodel type or a
typeMapkey withasnaming one, its entries are not elements, and CEL reads them only as view data (self.view.pinned, a group's state,filterValue()). An entry is identified by the element it names and, forgroups, the group key; where a host needs an id for it, it ispinned:<element>orgroups:<element>#<key>. Forfilters, the type MAY be a typetypeMapmaps to the diagram (as: "diagram"), whose bound attributes are then mapped tonull. - Changing bound view data is planned as the add, set or remove of such an entry (FBL section 6.4), in the same edit as the gesture, and is undone with it. An entry for an element that does not exist, or a second entry for the same key, is ignored and MUST NOT be reported; the runtime removes it with its next write of the body, and removing an element removes its entries in the same edit.
- Entries change when the body changes from outside like any other entry (FBL section 7.3): the view follows them.
"view": { "store": ["pinned", "groups", "filters"],
"bind": {
"pinned": { "type": "Placement", "element": "element", "x": "x", "y": "y" },
"groups": { "type": "GroupState", "element": "element", "group": "group", "collapsed": "collapsed" },
"filters": { "type": "Board", "values": { "archived": "showArchived" } } } }
A board's file keeps a pinned item as placements: [{ element: i7, x: 240, y: -80 }], a group opened against its default as groups: [{ element: i7, group: pending, collapsed: false }], and the archived switch as the root key showArchived.
| Property | Type | Description |
|---|---|---|
ordering.elements |
"creation", "id", "type-then-id", "tree", "cel" |
Order of elements and relations. tree writes parents before children (depth-first, siblings by order). Default "id". |
ordering.orderBy |
Expression | For cel: sort key per element. |
ordering.keys |
"canonical", "alphabetical" |
Key order within objects. canonical: the order of this specification's tables (id, type, parent, slot, order, attributes, ports; then x-); attribute keys in metamodel declaration order. |
omitDefaults |
bool | Do not write attribute values equal to their default, view sizes equal to the notation default, or empty collections. Default true. |
precision |
{canvas, numbers} |
Decimal places for canvas coordinates (default 2) and for number attributes without precision facet (default: shortest round-trip representation). |
timestamps |
{format, timezone} |
format: "rfc3339" (default) or "epoch-ms"; timezone: "utc" (normalise to Z) or "preserve" (keep the offset entered). |
canonical |
bool | Follow RFC 8785 (JSON Canonicalization Scheme) number and string serialization. Default true. |
A conforming writer applies these settings as DID, section 6, specifies, including its determinism requirement: the same logical DID definition and specification MUST produce byte-identical output. Suppressions of findings are stored as DID, section 3, describes.
metadata lists the metadata a DID definition carries in meta: languageVersion (always written in language.version), createdAt, modifiedAt, generator, authors (from env.user if the user consents), title (mirrors a diagram attribute), checksum.
definition controls how DID definitions refer to their specification: { "embed": "none" | "reference" | "inline", "uri": "https://…/statemachine-1.2.0.dis" }. reference writes language.definition with the URI of the specification and its integrity hash; inline embeds the whole specification (self-contained archives). The property names definition are kept unchanged from the earlier format.
Migrations upgrade DID definitions written with older language versions. They run on load, before validation, in version order.
{
"migrations": [
{
"from": ">=1.0.0 <1.1.0", "to": "1.1.0",
"doc": "Renamed 'label' to 'name'; transitions got explicit priorities.",
"steps": [
{ "renameAttribute": { "type": "State", "from": "label", "to": "name" } },
{ "setAttribute": { "type": "Transition", "attribute": "priority", "value": "0", "when": "!has(element.attributes.priority)" } }
]
},
{
"from": ">=1.1.0 <2.0.0", "to": "2.0.0",
"steps": [
{ "renameType": { "from": "EndState", "to": "FinalState" } },
{ "transform": { "type": "State", "cel": "element.attributes.with({'entryAction': element.attributes.?entry.orValue('')})" } },
{ "deleteAttribute": { "type": "State", "attribute": "entry" } },
{ "convertView": { "system": "canvas", "x": "value * 2.0", "y": "value * 2.0" } }
]
}
]
}
| Step | Fields | Effect |
|---|---|---|
renameType |
from, to |
Changes type of elements, relations and references. |
renameAttribute |
type, from, to |
Renames stored attribute keys (including in subtypes). |
deleteType |
type, mode ("delete", "keep-as-unknown") |
Removes elements of a type (and attached relations). |
deleteAttribute |
type, attribute |
|
setAttribute |
type, attribute, value (Expression), when |
Sets a value, for example a default for a new required attribute. |
convertAttribute |
type, attribute, cel |
Converts a value; value is the old value. |
transform |
type, cel |
Returns a replacement attributes map; element is the raw element record as a map. |
retype |
type, to, when |
Changes the type conditionally. |
convertView |
system, x, y, w, h |
Converts stored view coordinates (unit or origin changes). |
renamePort, renameSlot |
||
plugin |
name, args |
Custom migration. |
Migration expressions run in the migration context (12.3), which sees raw records as maps (element, value, document) rather than typed elements, because the old DID definition does not conform to the new metamodel. A migration that fails aborts loading; the original file is never modified until the user saves. Runtimes SHOULD tell users that a diagram was migrated and to which version, and MAY keep a backup.
DID definitions with a newer minor or patch language version than the loaded specification are opened with a warning; newer major versions are opened read-only or refused.
| Property | Type | Description |
|---|---|---|
mode |
"none" (default), "lock", "ot", "crdt" |
Single user; pessimistic element locks; operational transformation; conflict-free replicated data types. |
engine |
"yjs", "automerge", "plugin:<name>" |
CRDT or OT engine. |
granularity |
"element", "attribute" |
Conflict unit. attribute merges concurrent edits of different attributes of one element. |
text |
"replace", "merge" |
Concurrent edits of text/string attributes: last writer wins or character-level merge. |
ordering |
"fractional-index" |
Order keys for ordered children and lists that merge without renumbering. |
presence |
bool | Share cursors and selections. |
conflicts |
"last-writer-wins", "report" |
Whether semantic conflicts (both sides valid, the merge invalid) are reported as findings attributed to the merge. |
The CRDT state is a transport concern; the persisted DID definition remains the canonical form (DID, section 6). Constraints are re-evaluated after merges.
persistence.export and persistence.import declare conversions: [{ "format": "svg" | "png" | "pdf" | "did-fragment" | "plugin:<name>", "label", "doc", "options" }]. did-fragment writes a DID fragment (DID, section 7). Image exports render the notation exactly as on screen (without interaction states) at a chosen scale; pdf honours canvas.page. Constraints with timing: "export" run before export.
Illustrated by
- C4 container diagram: 9 items in this layer
- Causal loop diagram: 6 items in this layer
- Databricks job: 7 items in this layer
- Entity–relationship diagram: 6 items in this layer
- Functional decomposition graph: 5 items in this layer
- Gartner hype cycle graph: 6 items in this layer
- Mind map: 10 items in this layer
- RDF graph: 7 items in this layer
- State machine: 20 items in this layer
- Project timeline: 12 items in this layer
- Work board: 7 items in this layer
Referenced from
- 2. Foundations
- 3. Specification structure, layers and viewpoints
- 4. Layer 1 — Metamodel
- 5. Layer 2 — Coordinate systems, placement and snapping
- 6. Layer 3 — Notation (visual definition)
- 7. Layer 4 — Toolbox, context tools and forms
- 8. Layer 5 — Constraints
- 9. Layer 6 — Behavior
- 10. Layer 7 — Layout
- 12. The CEL environment
- 13. Extensions and plugins
- 14. Processing model
- 16. Security, privacy and robustness
- 17. Complete examples
- 18. Deprecated aliases
- Appendix B — Built-in catalogues
- Appendix C — Glossary
- Appendix D — Design rationale and open questions