DWSD.md

Units DSL — Syntax Reference

The Units DSL describes the general structural elements of an organization and their nesting: unit entities and their member-of relation. A unit is anything with a name and members — the formal structure (company, departments, teams), flow containers (a work system wears the work-system hat as one of its unit-type values), informal structures, locations. The view draws units only: no people, no names, no member count — who reports to whom is the Org Chart DSL, the flow between work systems is the Topology DSL.

The DSL is plain YAML. A standalone document is a .dwsd-units file (language id dwsd-units); inside Markdown the live-rendering fence is ```dwsd.units — the spellings dwsd-units, wsd.units and wsd-units are tolerated as well:

```dwsd.units
mode: declared
units:
- { name: Goldcrust Bakery, unit-type: company }
- { name: Bakehouse, unit-type: [team, work-system] }
member-of:
- { from: Bakehouse, to: Goldcrust Bakery }
```

A viewer that recognizes the fence (the dwsd-vscode extension preview, VS Code’s built-in Markdown preview, the website) renders the block live; the headless CLI (dwsd-dsl export) writes the same picture as SVG or PNG.

Live example · Units of the whole company — Voltfeld example → · The same people in the org chart →

Inferred from the org folder · click a box for its details, +/− to collapse, scroll to zoom, drag to pan

The parser is tolerant: it never throws on author input. Unknown or malformed fields produce a units.* diagnostic (a warning or info) and the offending value is clamped to a default or skipped — the rest of the document still parses. The diagnostic codes are listed per field below.

Fields that do not exist. The Units DSL has no people: no members list, no member-count badge, no names inside a box (the unit drawer has no members row either). There is no view: switch (units and org chart are two kinds), no webview toggle for the layout — the document key layout: alone decides — and no persisted collapse state. flightlevel: and contributes-to: on a unit entry are ignored; they are read from the workspace index for the drawer.

Design notes

Two layouts, one document key. layout: nested (the default) draws every child box inside its parent; the parent grows to contain its children plus padding, so a company is one outer box enclosing its departments enclosing their teams — the DawnSeed units view in two dimensions. layout: tree draws units as uniform boxes top-down with member-of as orthogonal edges, siblings in one row ascending by name. Both layouts show the same containment; only the document key switches between them.

Box geometry per layout. In layout: tree every box is 200 px wide and 56 px tall — fixed, like the org chart’s boxes, so a tree reads as a grid of equal boxes. The text is fitted to the box, never the box to the text: a name wider than the 176 px inner width wraps onto two lines at a word boundary, and a line that still does not fit ends in an ellipsis; a chip row wider than 176 px folds into +N. Nothing is ever clipped mid-word. The nested layout keeps its content-fitted 150–240 px boxes because every parent grows with its children — a fixed leaf width there would widen every container — and then gives the siblings of one parent one common width (see below).

Siblings at one level share one width (STRUCT-39, nested layout). Sizing every box by its own name and chips left the sub-units of one parent at different widths, so a row did not read as a band. Now the leaf-sized children of one parent — real leaves and collapsed parents — all take the width of the widest of them. That width is computed over the whole tree, not over what happens to be open, so collapsing something never changes it; a leaf may therefore widen to a collapsible sibling’s own name width. An open child container keeps its own size but is never narrower than that width — the container floor — so collapsing a narrow container can never widen its row and move the pinned picture (see the collapse note below). Heights are not equalized beyond what was already true: every leaf-sized box has the same leaf height, and a leaf beside a taller open container is not stretched to its height. The rule is per parent: root boxes, and the children of other parents, keep their own widths, and a wider box refits its chip row to its new width. layout: tree is untouched — its boxes were always a fixed 200 px.

Many siblings wrap into columns, and a tree parent stacks its leaves (STRUCT-27, UNITS-POLISH-01). A parent with more than three children does not lay them out in one strip. In layout: nested the children go into ceil(sqrt(n)) columns, so the correction lands in height — a units view is wide by nature, and the strip made the worst axis worse. The children are walked in name order, and each one goes into the column that is shortest at that moment (the leftmost one on a tie). So a small box sits under its neighbor instead of alone in a tall cell beside an open container. A column is as wide as its widest box. Children of equal height read exactly as before: left to right and then down, a short last row left-aligned. Six of them go from a 8.20 : 1 letterbox to a 3 × 2 block at 2.65 : 1; four become 2 × 2 at 1.79 : 1, nine a 3 × 3 at 1.95 : 1. The parent is never wider than that row grid would make it: when the columns would come out wider, the row grid stays. And a collapse never makes the view bigger — in the rare case where re-sorting the columns after a collapse would, the boxes keep their columns. The block of children stays centered under the parent. In layout: tree the same threshold switches a leaves-only parent to the org chart’s leaf stack — one indented column whose spine drops through the parent’s own center, each leaf 24 px to its right, 64 px apart. A parent with branch children and leaves keeps that column on its own vertical under the bus. Both views share one module, so they break at the same fan-out and draw the same spine. At or below the threshold nothing changed.

A marker tab never covers a header or the box above it (UNITS-POLISH-02). A box that carries a navigation-pin or decision-record tab gets 21 px of room above it, the height of the tab. In a nested parent the children start 21 px lower when a box in the first row, or at the top of a column, carries a tab, so the tab sits under the parent’s header instead of over its type chip. A lower box with a tab keeps the usual 12 px between its tab and the box above. A stacked tree leaf with a tab sits 21 px further down: 85 px instead of 64 from the leaf above. Documents without tabs are unchanged, and the 48 px gap between tree levels already clears a tab. Tabs come from the workspace index, so the command-line export, which has no index, draws no tabs and reserves no room.

One chip per unit-type value. A unit box shows its name and one chip per unit-type token (department, team, work-system, …). A row that does not fit the box’s inner width (176 px in a tree, 216 px in a nested box at its 240 px cap) folds its tail into one +N overflow chip — never a mid-word clip. Names are left-aligned at the same inset as the chip row, so a wide nested parent reads as a plate label. Unit types are descriptive words in your own language and show exactly as written (ART, Werk, value_stream); the one reserved value is work-system (matched without regard to case), which makes the unit a work system and reads Work System in the drawer. (A declared .dwsd-units file lower-cases the tokens it parses; an indexed unit file keeps them as written.)

Collapse and expand (+/−) in both layouts. Every unit with children carries a small +/− toggle at its top-right corner. Collapsing folds the subtree away: the parent shrinks to a leaf-sized box with a +N badge (N = the descendants hidden), nested siblings close the gap, tree siblings close ranks. The state is transient per preview session — never written to the document, never persisted; a reload or a switch to another document opens everything again. The editor preview’s Export menu is what you see: a collapsed view exports collapsed, badge included (PNG, SVG, clipboard). The command-line export and the example pictures in the published examples/ folder are always fully open and show no toggle. VS Code’s built-in Markdown preview is fully open too and draws the toggle, which does nothing there (the preview page has no channel back to the extension). The editor preview keeps the working toggle.

A toggle does not move the picture (STRUCT-26). Pan and zoom are exactly where they were: the layout extent is pinned to the fully-open view, so the <svg>’s viewBox, the zoom width and both scroll offsets are byte-unchanged across a collapse or an expand and only the boxes change. (Before, a collapse shrank the extent, the viewBox shrank with it, and the zoom percentage suddenly meant a larger scale — the picture jumped.)

Children grow out of their parent and shrink back into it (STRUCT-42). On an expand the appearing sub-units start small and transparent at their parent and grow into place; on a collapse they shrink back into the parent and fade. Their member-of edges (in layout: tree) extend from and retract into the parent’s bottom-centre, and the boxes that only change place glide. In layout: nested a resizing box carries what sits on its edge — the +/− toggle, the +N badge and the nav tab at the right, the annotation strip at the bottom, the focus ring around it — so nothing snaps to the final size while the box is still moving. Everything runs in parallel on one clock of about 200 ms, ease-out; a second toggle during the motion carries on from where the boxes are rather than jumping to the end first, and prefers-reduced-motion: reduce skips the frames entirely. The motion ships in the package and is on by default in every host that uses the package’s collapse handler (attachUnitsCollapseHandlers) — the VS Code preview, the dwsd.md site and any other host get it with no glue, and animate: false switches it off. It is never part of an export: Export, the CLI render and VS Code’s Markdown preview are static; the last frame writes exactly what an un-animated repaint writes, and an export taken while a transition is running captures the settled picture. The org chart runs the same motion from the same module. (Phase 43 moved only the boxes present before and after a toggle, over 180 ms; appearing boxes popped in and disappearing ones vanished, which in the tree layout meant a collapse did not read as motion at all.)

The document header is painted. The units view paints the shared document header — title: as a heading and tags: as chips — above the boxes when either is present, exactly as the org chart and the topology do; with neither key there is no header band.

The canvas follows the editor in the preview and stays white in the export (STRUCT-29). In the editor preview there is no canvas rectangle behind the diagram — the editor’s own background shows through — and the header title takes the editor’s foreground colour, so the view reads in a dark theme without a white slab under it. In every export (PNG, SVG, clipboard image), in the CLI render and in VS Code’s Markdown preview the canvas is still white and the title is still #333333: the renderers write those as presentation attributes and no export path carries a stylesheet, so the preview’s rules travel nowhere. Box fills, band fills and chip colours are the same in both — only the canvas and the header text differ.

The units blue. Unit boxes wear DawnSeed’s units tint in both layouts — fill #b8d4e8, stroke #6a9fc0 — so a units view reads as the same family as the 3D units view. In layout: nested an open parent (a box drawn around its children) paints a lighter plate, #d8e8f5, so the leaves keep the full tint on top of it; a collapsed parent is a leaf and paints the leaf tint; layout: tree is uniformly #b8d4e8. The org chart stays neutral grey — the tint is what tells the two structure views apart at a glance.

The chip and the name belong to the tint (STRUCT-33). A unit-type chip is filled #ecf4fb — the next step of the #b8d4e8 → #d8e8f5 units ramp — with a #6a9fc0 border, the box stroke colour: its first border, which is what carries the chip on an open parent’s lighter plate and on a delta-tinted box, where the fill alone is nearly invisible. Chip height and geometry are unchanged. The unit name is #333333 rather than #666666: on the leaf tint the old grey measured 3.72 : 1, below the 4.5 : 1 floor for normal text, and now measures 8.19 : 1.

Annotations on a unit. The eight annotation layers — Interactions · Agreements · AI Agents · Roles · Change conversation · Organizational decision records · Notes · Tags — land on a unit box the way they land on a work-system node in the topology: in the bottom marker row (on an open nested parent, below its sub-units — see the strip note below), in the drawer, under the layer filter. (The filter menu also lists Board bindings, a layer of the route view; it stays greyed on a units view.) An entity reaches a unit through a bare wikilink on one of the value-polymorphic keys: observes: "[[Deliveries]]" on a signal, interaction-of: "[[Wholesale]]" on an interaction, agreement-of: "[[Wholesale]]" on an agreement, bound-to: "[[Wholesale]]" on an AI agent — the wikilink names the unit, and every units view that draws it renders the marker. A unit that is also a work system (its unit-type includes work-system) is one entity with one anchor set in both pictures: its unit box shows the whole work-system surface — the bare interaction-of / agreement-of / bound-to / observes links and the for-domain: [[Work System]] agreements, drivers, proposals, roles and decision records — exactly as its topology node does. It does not show the topology-only buckets: no members list and no visualizations list on the unit box or in its drawer (the topology’s flow story stays in the topology). A unit without the hat is not a domain: a for-domain that names it lands nowhere, and the validator says so (units.for-domain-names-unit). In mode: infer a unit’s tags come from the unit’s frontmatter tags:; a declared tags: on the same unit in the document overlays (the union is shown). What lands where, per layer:

LayerHow it reaches a unit
Interactionsinteraction-of: "[[Unit]]" on an interaction / meeting
Agreementsagreement-of: "[[Unit]]" on an agreement; with the work-system hat also for-domain: "[[Unit]]"
AI Agentsbound-to: "[[Unit]]" on an ai-agent
Rolesfor-domain: "[[Unit]]" on a type: role file — a role marker (hat-glasses glyph and count) in the marker row, work-system units only; a role scoped to a plain team lands nowhere and gets the same info diagnostic as any other for-domain on a unit without the hat
Change conversationobserves: "[[Unit]]" on a signal / insight (a pin); with the hat also a driver / proposal via for-domain
Organizational decision recordsfor-domain: "[[Unit]]" on an organizational decision record — work-system units only
Notesdoc-level notes: - on: "[[Unit]]" in the document itself (notes)
Tagsthe unit’s frontmatter tags: (infer) ∪ the document’s declared tags:

The full contract — resolution by the target’s type, the path form for a same-named person and unit, the role bridge (which reaches people, never units) — is the locator grammar.

Focus ring, deltas. In a focus + radius cut-out the focal unit’s box wears the topology’s dashed focus ring and soft shadow — the box stroke itself stays normal, so the box looks like every other box. In the nested layout the ring surrounds the focal unit’s whole container, children included.

One delta language: one dash, the colour carries the meaning (STRUCT-28). A delta paints the box stroke and — in layout: tree — the member-of edge in the add (green) / change (orange) / remove (red) colour, with one dash pattern for all three (10 5, a 15 px period). There used to be a pattern per delta — 6 3, 4 4 and a 2 4 dotted — three rhythms distinguishing what the colour already said, at periods too fine to resolve on a short run. The box also takes a light tint of its delta colour (#ddf0ea add, #fdeee3 change, #fbe5e0 remove), which is what makes a delta visible at normal zoom; a units box’s background carries no other meaning, so it was free to take it. The org chart does the same and the topology deliberately does not — a topology node’s fill IS its Flight-Level band colour, so there the delta rides the stroke alone. A removed unit stays in place, drawn as removed.

On an open parent the strip sits below the sub-units (STRUCT-40, nested layout). An open nested parent reads top to bottom as its name and unit-type chips, then its sub-units, then its annotation strip — bottom-left, with the same divider, inset and hit zone as the strip on a leaf box, so a parent and a leaf read the same. (Before, a parent’s strip sat between its header and its children while a leaf showed its strip at the bottom.) The band below the sub-units is reserved only when that parent has entries of its own — anchors or tags; an open parent without them ends one padding below its last row of children, with no empty band. Leaf boxes keep the rule they had: in a document where any unit carries a marker, every leaf reserves the marker row. A collapsed parent is a leaf and shows its strip as a leaf does. The change-conversation marker tabs do not move — they stay at the unit’s top-right edge, on parents and leaves alike.

The hover pulse always flows child → parent (STRUCT-38). Hovering a unit sets dots travelling along its containment edges — in layout: tree the member-of edges to its parent and to its children — and on every one of them the dots run toward the root: up the edge above the hovered unit toward its parent, and up each edge below it toward the hovered unit. The direction is the edge’s own point order (every tree edge is laid out from the child to the parent’s bottom-centre), the same rule the org chart’s reporting lines follow. (Before, a branch child’s edge ran parent-first, so hovering a middle unit sent the dots on its upper edge the wrong way — toward the hovered unit.)

Modes

mode: selects how the view is assembled. Values: infer, declared, mixed. The default (when mode: is absent) is infer.

ModeMeaning
inferDerive the view from the workspace: every type: unit file the indexer knows — including the work systems, which are units wearing the work-system hat — with its first member-of as the parent. Without focus: the whole structure is drawn — roots are the units without member-of. With focus: (or an embedding file that is itself a unit) the cut-out bounded by radius: is drawn.
declaredThe author lists the units and the member-of edges explicitly. Nothing is read from the workspace, so the document renders identically in the editor preview, the Markdown preview and the headless CLI.
mixedThe author’s entries overlay the inferred view as a diff — each unit or edge carries a delta (add / change / remove) applied on top of what the indexer found. A unit listed without a delta merges with the inferred one.

An unrecognized mode: value emits units.unknown-mode (warning) and falls back to infer.

Focus and radius. focus: "[[Name]]" centers the view on one unit; radius: { up: N, down: M } bounds how many nesting hops are drawn above (containers) and below (members). Either value may be inf — unbounded: radius: { up: 1, down: inf } is the containing unit and everything inside the focal one. In infer mode the radius applies whenever a focal unit exists (the default is one hop each way); in declared and mixed mode it prunes the view only when radius: is written explicitly — a hand-authored document is never silently cut down by the default.

The embedded-focus rule. A units view embedded in another file gets that file’s basename as its default focus only when that file is a unit — an indexed unit or a name the document itself declares. Embedded in a person’s file or a tour page, the view has no focal and shows the whole structure. focus: self means the same as leaving focus: out.

Large views. A view with more than 40 units is still laid out completely; the editor preview adds a units.large-view info to its diagnostics.

Headless rendering. mode: infer needs the workspace index; the CLI has none and refuses such a standalone document with a one-line message and exit code 1. Fenced inside a Markdown file, such a block (or one with no mode: key, which defaults to infer) is skipped with that same message and the file’s other fences still export — exit 1 only when nothing in the file was exportable. Author declared or mixed for anything that must export from the command line (a mixed document renders its declared part alone there).

Syntax reference

Every field below is implemented by the parser (src/units/parser.ts) and validated by src/units/validate.ts. The Diagnostic column names the units.* code an author hits on misuse.

Top-level fields

FieldValues / shapeDefaultDiagnostic on misuse
modeinfer | declared | mixedinferunits.unknown-mode (warning; falls back to infer)
layoutnested | treenestedunits.unknown-layout (warning; falls back to nested)
titlestring — painted as the document heading(none — omitted when absent)(none — a non-string or empty value is silently ignored)
tagsstring list — painted as document chips (a scalar string is accepted as a one-element list)(none)tolerant — non-string entries dropped, empties trimmed
focus[[Name]] | [[Name|Alias]] | self(none — whole structure, or the embedding unit)units.invalid-focus (warning, parse); units.unresolved-focus (warning, validate — an explicit target that is neither indexed nor declared)
radiusa number | inf | { up, down } (each a number or inf){ up: 1, down: 1 }units.unsupported-radius (unparseable value → 1; negative → clamped to 0)
unitslist of unit objects[]units.invalid-unit (a non-object or name-less entry is skipped)
member-of (alias member_of)list of edge objects[]units.invalid-edge (a non-object or from/to-less entry is skipped); units.duplicate-edge (info; the second occurrence is ignored)
noteslist of { on, text, resolved? }(none)units.note-orphan (warning — see notes)

radius also accepts a lateral key for symmetry with the topology; it has no effect — a containment tree has no lateral relation. A bare number applies to both directions; the object form fills a missing direction with 1.

Unit fields (each entry of units)

FieldValues / shapeDefaultDiagnostic on misuse
namestring (required) — the unit’s entity name, the basename of the unit file—units.invalid-unit (entry skipped when name is missing or empty)
unit-type (alias unit_type)a token or a list of tokens (company, department, team, work-system, …), kept as written; one chip per value(none — no chip)(none — non-string entries are dropped)
deltaadd | change | remove(none)units.unknown-delta (warning; the value is omitted)
tagsstring list (a scalar string is accepted as a one-element list)(none)tolerant — non-string entries are dropped. Unioned with the unit’s frontmatter tags: when an index is present (infer / mixed).
anchorsmap of typeKey → string list — declared anchored entities, the no-index authoring form(none)units.invalid-anchors (the value is ignored when it is not a map)

Any other key on a unit — flightlevel:, contributes-to: and every other dwsd-markdown frontmatter key — is ignored without a diagnostic. The drawer reads them from the workspace index.

Edge fields (each entry of member-of)

FieldValues / shapeDefaultDiagnostic on misuse
fromstring (required) — the member (child)—units.invalid-edge (entry skipped when missing)
tostring (required) — the containing unit (parent)—units.invalid-edge (entry skipped when missing)
deltaadd | change | remove(none)units.unknown-delta (warning; the value is omitted)

A member-of edge points from the member up to its container — the same direction as the member-of: frontmatter key. An edge whose endpoint is not a drawn unit is dropped silently. Two structural diagnostics come from the layout’s tree reduction and are raised at validate time:

DiagnosticSeverityMeaning
units.multiple-parentsinfoA unit is a member of several units; the first parent in document order is kept (the DawnSeed rule — in mixed mode the inferred edge comes first) and the rest are not drawn.
units.cyclewarningThe edge closes a containment cycle (A → B → A, or a self-edge) and is not drawn. Among the edges of one cycle, the one whose from sorts last loses, so the outcome does not depend on authoring order.

One annotation diagnostic is raised at validate time when an index is present:

DiagnosticSeverityMeaning
units.for-domain-names-unitinfoA for-domain: in some entity file names a drawn unit that does not wear the work-system hat. for-domain names a domain — a work system or a role — so the entity lands nowhere. Message: `Fleet Change.md` names the unit `Deliveries` in its `for-domain:`, which is not a work system — point it at the work system or a role (the entity lands nowhere). It is a statement about the TARGET, not a quotation of the anchoring file: the spelling shown is the INDEXED one, which is not necessarily what that file wrote (Phase 43, IN-11). Placed on the mode line, one per (unit, file); never raised for a work-system unit.

Collapse

The +/− toggle sits at the top-right corner of every unit that has children, in both layouts. A click folds the subtree: the unit becomes a leaf-sized box with a +N badge counting the hidden descendants; a second click unfolds it. The toggle is not part of the drawer’s click model — folding a subtree while a drawer is open leaves that drawer open. The collapsed set lives in the preview session only (never in the document, never in the workspace state) and is reset when another document is rendered. The editor preview’s Export menu honors it; the CLI, the published example pictures and the Markdown preview always render unfolded (only the Markdown preview draws the toggle, and there it does nothing).

In the editor preview the change is animated: the folded sub-units shrink back into their parent and fade, unfolded ones grow out of it, their edges retract and extend with them, and every other box glides — one clock of about 200 ms, ease-out, with the view itself pinned (see Children grow out of their parent above). A second click during the motion carries on from where the boxes are. Reduced motion skips the frames; the animation never appears in an export, the CLI render or VS Code’s Markdown preview. A jump to a unit hidden inside a collapsed subtree does not unfold it; unfold the parent by hand. The org chart has the same toggle, badge and motion.

Drawer

A click on a unit box opens the shared drawer for that unit (a second click closes it; hovering a box pulses its containment edges — in the nested layout the parent’s own box is highlighted). The title is a link to the unit: it carries the link color and a trailing ”›” at rest, is underlined only on hover and keyboard focus, and opens the unit’s file beside the preview (the drawer stays open; titleAction: 'select' or 'none' give a host the other models). The drawer opens with the fact row — one line of small chips, no heading — then the entity rows (the Open in topology jump row among them), then the annotation bands in the topology drawer’s order and wording:

RowSource
Fact chips (first)a colored Flight Level 1 / 2 / 3 chip (the Flight Levels colors, because the chip means the flight level — work-system units only), then one chip per unit-type token side by side, exactly as written; work-system reads Work System. The fact row never follows a layer filter
member-ofthe containing unit, as a navigable wikilink (in declared mode: the parent the document names)
contributes-tothe work systems this unit contributes to — work-system units only
Open in topologya jump into the unit’s .dwsd-topology — work-system units only; its Open button carries a small arrow icon, not an emoji
Interactionsevery interaction / meeting whose interaction-of: names [[Unit]]
Agreementsevery agreement whose agreement-of: names [[Unit]]; for a work-system unit also those whose for-domain: names it
AI Agentsevery ai-agent whose bound-to: names [[Unit]]
Rolesevery type: role file whose for-domain: names the unit — work-system units only; follows the Roles layer
Change conversationthe signal / insight pins that observe: "[[Unit]]"; for a work-system unit also the driver / proposal pins that name it in for-domain:; read in chain order — Signal, Insight, Driver, Proposal — whatever order they arrive in
Organizational Decision Recordsorganizational decision records whose for-domain: names the unit — work-system units only
Notesthe inline notes: on the unit
Tagsthe unit’s tags — frontmatter (infer) ∪ declared

Every band row is the shared anchor row of the topology drawer: a navigable wikilink when the entity is indexed, a plain label when it is a name-only declared anchor, the same provenance dots. There is deliberately no members list and no child-units list — the view already shows the containment — and a work-system unit’s drawer does not repeat the topology’s members / visualizations buckets.

A row is a link exactly when its target resolves (STRUCT-34). That holds for the entity rows too, declared mode included: member-of, contributes-to and Open in topology are navigable only when the workspace can actually open what they name. A row whose target the workspace has never heard of — a parent unit declared in the document with no entity file behind it — renders as plain text: no link styling, no click handler, nothing happens. It is not a “not in workspace” hint; a dead affordance is worse than no affordance. In declared mode without an index only the rows the document itself carries (the unit-type chips, member-of from the tree, declared anchors, tags, notes) appear.

Examples

Each example is also shipped verbatim as a file under examples-src/units/ and is parsed clean (0 diagnostics) by the publish-preview drift gate. Names are from the Goldcrust Bakery example organization.

1. Declared, nested

The Goldcrust units three deep, drawn as boxes inside boxes. layout: nested is the default and could be left out.

# 1 — declared, nested: the Goldcrust Bakery units three deep, children drawn INSIDE
# their parent (layout: nested is the default). One chip per unit-type value.
mode: declared
layout: nested
units:
  - { name: Goldcrust Bakery, unit-type: company }
  - { name: Bakery Strategy, unit-type: [leadership, work-system] }
  - { name: Bakery Operations, unit-type: [department, work-system] }
  - { name: Bakehouse, unit-type: [team, work-system] }
  - { name: Deliveries, unit-type: team }
  - { name: Wholesale, unit-type: team }
member-of:
  - { from: Bakery Strategy, to: Goldcrust Bakery }
  - { from: Bakery Operations, to: Bakery Strategy }
  - { from: Bakehouse, to: Bakery Operations }
  - { from: Deliveries, to: Bakery Operations }
  - { from: Wholesale, to: Bakery Operations }

2. Declared, tree

The same containment as a top-down tree of uniform boxes with member-of drawn as edges.

# 2 — declared, tree: the same containment as a top-down tree of uniform boxes with
# member-of drawn as edges (layout: tree). Siblings sit in one row, ascending by name.
mode: declared
layout: tree
units:
  - { name: Goldcrust Bakery, unit-type: company }
  - { name: Bakery Strategy, unit-type: [leadership, work-system] }
  - { name: Bakery Operations, unit-type: [department, work-system] }
  - { name: Bakehouse, unit-type: [team, work-system] }
  - { name: Deliveries, unit-type: team }
  - { name: Wholesale, unit-type: team }
member-of:
  - { from: Bakery Strategy, to: Goldcrust Bakery }
  - { from: Bakery Operations, to: Bakery Strategy }
  - { from: Bakehouse, to: Bakery Operations }
  - { from: Deliveries, to: Bakery Operations }
  - { from: Wholesale, to: Bakery Operations }

3. Mixed with delta

A planned structural change drawn over the inferred structure: a new team appears inside Bakery Operations and an existing one is marked for removal, in place. Renders in the editor preview (it needs the index for the base).

# 3 — mixed with delta: a planned structural change drawn over the inferred units.
# A new team is added under Bakery Operations (delta: add on the unit AND its edge)
# and the Deliveries team is marked for removal, in place.
mode: mixed
layout: nested
units:
- { name: Catering, unit-type: team, delta: add }
- { name: Deliveries, delta: remove }
member-of:
- { from: Catering, to: Bakery Operations, delta: add }
- { from: Deliveries, to: Bakery Operations, delta: remove }

See also

  • Units notes — the inline notes: surface and its [[Name]] locator.
  • Org Chart DSL — the sibling structure view over reports-to.
  • Locator grammar — the bare [[Name]] wikilink as an addressable place for signals, agreements, interactions, agents and notes; a work-system unit as one entity in both pictures.
  • The document header — the title: / tags: header the units view paints.

Like what you see? → Start — build your own org folder in ~2 min

index.md
# Units DSL — Syntax Reference
The **Units DSL** describes the **general structural elements** of an
organization and their nesting: `unit` entities and their **`member-of`**
relation. A unit is anything with a name and members — the formal structure
(company, departments, teams), flow containers (a work system wears the
`work-system` hat as one of its `unit-type` values), informal structures,
locations. The view draws **units only**: no people, no names, no member count —
who reports to whom is the [Org Chart DSL](../orgchart/index.md), the flow
between work systems is the [Topology DSL](../topology/index.md).
The DSL is plain **YAML**. A standalone document is a **`.dwsd-units`** file
(language id **`dwsd-units`**); inside Markdown the live-rendering fence is
**```` ```dwsd.units ````** — the spellings `dwsd-units`, `wsd.units` and
`wsd-units` are tolerated as well:
````markdown
```dwsd.units
mode: declared
units:
- { name: Goldcrust Bakery, unit-type: company }
- { name: Bakehouse, unit-type: [team, work-system] }
member-of:
- { from: Bakehouse, to: Goldcrust Bakery }
```
````
A viewer that recognizes the fence (the `dwsd-vscode` extension preview, VS
Code's built-in Markdown preview, the website) renders the block live; the
headless CLI (`dwsd-dsl export`) writes the same picture as SVG or PNG.
The parser is **tolerant**: it never throws on author input. Unknown or
malformed fields produce a `units.*` diagnostic (a warning or info) and the
offending value is clamped to a default or skipped — the rest of the document
still parses. The diagnostic codes are listed per field below.
> **Fields that do _not_ exist.** The Units DSL has **no people**: no members
> list, no member-count badge, no names inside a box (the unit drawer has no
> members row either). There is **no `view:` switch** (units and org chart are two kinds),
> **no webview toggle** for the layout — the document key `layout:` alone decides
> — and **no persisted collapse state**. `flightlevel:` and `contributes-to:` on a
> unit entry are ignored; they are read from the workspace index for the drawer.
## Design notes
**Two layouts, one document key.** `layout: nested` (the default) draws every
child box **inside** its parent; the parent grows to contain its children plus
padding, so a company is one outer box enclosing its departments enclosing their
teams — the DawnSeed units view in two dimensions. `layout: tree` draws units as
**uniform boxes** top-down with `member-of` as orthogonal edges, siblings in one
row ascending by name. Both layouts show the same containment; only the document
key switches between them.
**Box geometry per layout.** In `layout: tree` every box is **200 px wide and
56 px tall** — fixed, like the org chart's boxes, so a tree reads as a grid of
equal boxes. The text is fitted to the box, never the box to the text: a name
wider than the 176 px inner width wraps onto two lines at a word boundary, and a
line that still does not fit ends in an ellipsis; a chip row wider than 176 px
folds into `+N`. Nothing is ever clipped mid-word. The nested layout keeps its
**content-fitted 150–240 px boxes** because every parent grows with its
children — a fixed leaf width there would widen every container — and then gives
the siblings of one parent one common width (see below).
**Siblings at one level share one width** (`STRUCT-39`, nested layout). Sizing every
box by its own name and chips left the sub-units of one parent at different widths,
so a row did not read as a band. Now the **leaf-sized** children of one parent — real
leaves and collapsed parents — all take the width of the widest of them. That width
is computed over the whole tree, not over what happens to be open, so collapsing
something never changes it; a leaf may therefore widen to a collapsible sibling's own
name width. An **open** child container keeps its own size but is **never narrower**
than that width — the container floor — so collapsing a narrow container can never
widen its row and move the pinned picture (see the collapse note below). Heights are
not equalized beyond what was already true: every leaf-sized box has the same leaf
height, and a leaf beside a taller open container is **not** stretched to its height.
The rule is per parent: root boxes, and the children of other parents, keep their own
widths, and a wider box refits its chip row to its new width. `layout: tree` is
untouched — its boxes were always a fixed 200 px.
**Many siblings wrap into columns, and a tree parent stacks its leaves**
(`STRUCT-27`, `UNITS-POLISH-01`). A parent with **more than three** children
does not lay them out in one strip. In `layout: nested` the children go into
**`ceil(sqrt(n))` columns**, so the correction lands in **height** — a units view
is wide by nature, and the strip made the worst axis worse. The children are
walked in **name order**, and each one goes into the column that is **shortest**
at that moment (the leftmost one on a tie). So a small box sits under its
neighbor instead of alone in a tall cell beside an open container. A column is as
wide as its widest box. Children of **equal height** read exactly as before:
left to right and then down, a short last row left-aligned. Six of them go from
a 8.20 : 1 letterbox to a 3 × 2 block at 2.65 : 1; four become 2 × 2 at
1.79 : 1, nine a 3 × 3 at 1.95 : 1. The parent is **never wider** than that
row grid would make it: when the columns would come out wider, the row grid
stays. And a **collapse never makes the view bigger** — in the rare case where
re-sorting the columns after a collapse would, the boxes keep their columns. The
block of children stays centered under the parent. In `layout: tree` the same
threshold switches a **leaves-only** parent to the org chart's **leaf stack** —
one indented column whose spine drops through the parent's own center, each leaf
24 px to its right, 64 px apart. A parent with branch children *and* leaves
keeps that column on its own vertical under the bus. Both views share one
module, so they break at the same fan-out and draw the same spine. At or below
the threshold nothing changed.
**A marker tab never covers a header or the box above it** (`UNITS-POLISH-02`).
A box that carries a navigation-pin or decision-record tab gets **21 px** of
room above it, the height of the tab. In a nested parent the children start
21 px lower when a box in the first row, or at the top of a column, carries a
tab, so the tab sits under the parent's header instead of over its type chip. A
lower box with a tab keeps the usual 12 px between its tab and the box above. A
stacked tree leaf with a tab sits 21 px further down: 85 px instead of 64 from
the leaf above. Documents without tabs are unchanged, and the 48 px gap between
tree levels already clears a tab. Tabs come from the workspace index, so the
command-line export, which has no index, draws no tabs and reserves no room.
**One chip per `unit-type` value.** A unit box shows its **name** and one chip
per `unit-type` token (`department`, `team`, `work-system`, …). A row that does
not fit the box's inner width (176 px in a tree, 216 px in a nested box at its
240 px cap) folds its tail into one **`+N`** overflow chip — never a mid-word
clip. Names are left-aligned at the same inset as the chip row, so a wide nested
parent reads as a plate label. Unit types are **descriptive words in your own
language** and show exactly as written (`ART`, `Werk`, `value_stream`); the one
reserved value is **`work-system`** (matched without regard to case), which makes the
unit a work system and reads **Work System** in the drawer. (A declared
`.dwsd-units` file lower-cases the tokens it parses; an indexed unit file keeps them
as written.)
**Collapse and expand (+/−) in both layouts.** Every unit with children carries
a small **+/−** toggle at its top-right corner. Collapsing folds the subtree
away: the parent shrinks to a leaf-sized box with a **`+N`** badge (N = the
descendants hidden), nested siblings close the gap, tree siblings close ranks.
The state is **transient per preview session** — never written to the document,
never persisted; a reload or a switch to another document opens everything
again. The editor preview's Export menu is **what you see**: a collapsed view
exports collapsed, badge included (PNG, SVG, clipboard). The command-line export
and the example pictures in the published `examples/` folder are always **fully
open** and show no toggle. VS Code's built-in Markdown preview is fully open too
and draws the toggle, which does nothing there (the preview page has no channel
back to the extension). The editor preview keeps the working toggle.
**A toggle does not move the picture** (`STRUCT-26`). Pan and zoom are
**exactly** where they were: the layout extent is pinned to the fully-open view,
so the `<svg>`'s `viewBox`, the zoom width and both scroll offsets are
byte-unchanged across a collapse or an expand and only the boxes change.
(Before, a collapse shrank the extent, the `viewBox` shrank with it, and the zoom
percentage suddenly meant a larger scale — the picture jumped.)
**Children grow out of their parent and shrink back into it** (`STRUCT-42`). On an
expand the appearing sub-units start small and transparent at their parent and
**grow into place**; on a collapse they **shrink back into the parent** and fade.
Their `member-of` edges (in `layout: tree`) extend from and retract into the
parent's bottom-centre, and the boxes that only change place glide. In
`layout: nested` a resizing box carries what sits on its edge — the +/− toggle,
the `+N` badge and the nav tab at the right, the annotation strip at the bottom,
the focus ring around it — so nothing snaps to the final size while the box is
still moving. Everything runs in parallel on **one clock of about 200 ms**,
ease-out; a second toggle during the motion carries on from where the boxes are
rather than jumping to the end first, and `prefers-reduced-motion: reduce` skips
the frames entirely. The motion ships in the package and is **on by default in every
host that uses the package's collapse handler** (`attachUnitsCollapseHandlers`) — the
VS Code preview, the dwsd.md site and any other host get it with no glue, and
`animate: false` switches it off. It is never part of an export: Export, the CLI
render and VS Code's Markdown preview are static; the last frame writes exactly what
an un-animated repaint writes, and an export taken while a transition is running
captures the settled picture. The
[org chart](../orgchart/index.md#collapse) runs the same motion from the same
module. (Phase 43 moved only the boxes present before and after a toggle, over
180 ms; appearing boxes popped in and disappearing ones vanished, which in the tree
layout meant a collapse did not read as motion at all.)
**The document header is painted.** The units view paints the shared
[document header](../document-header.md) — `title:` as a heading and `tags:` as
chips — above the boxes when either is present, exactly as the
[org chart](../orgchart/index.md) and the topology do; with neither key there is
no header band.
**The canvas follows the editor in the preview and stays white in the export**
(`STRUCT-29`). In the **editor preview** there is no canvas rectangle behind the
diagram — the editor's own background shows through — and the header title takes
the editor's foreground colour, so the view reads in a dark theme without a white
slab under it. In every **export** (PNG, SVG, clipboard image), in the CLI render
and in VS Code's Markdown preview the canvas is still **white** and the title is
still `#333333`: the renderers write those as presentation attributes and no
export path carries a stylesheet, so the preview's rules travel nowhere. Box
fills, band fills and chip colours are the same in both — only the canvas and the
header text differ.
**The units blue.** Unit boxes wear DawnSeed's units tint in both layouts —
fill `#b8d4e8`, stroke `#6a9fc0` — so a units view reads as the same family as
the 3D units view. In `layout: nested` an **open** parent (a box drawn around
its children) paints a lighter plate, `#d8e8f5`, so the leaves keep the full
tint on top of it; a collapsed parent is a leaf and paints the leaf tint;
`layout: tree` is uniformly `#b8d4e8`. The org chart stays neutral grey — the
tint is what tells the two structure views apart at a glance.
**The chip and the name belong to the tint** (`STRUCT-33`). A `unit-type` chip is
filled `#ecf4fb` — the next step of the `#b8d4e8` → `#d8e8f5` units ramp — with a
`#6a9fc0` border, the box stroke colour: its first border, which is what carries
the chip on an open parent's lighter plate and on a delta-tinted box, where the
fill alone is nearly invisible. Chip height and geometry are unchanged. The unit
**name** is `#333333` rather than `#666666`: on the leaf tint the old grey
measured 3.72 : 1, below the 4.5 : 1 floor for normal text, and now measures
8.19 : 1.
**Annotations on a unit.** The eight annotation layers — Interactions ·
Agreements · AI Agents · **Roles** · Change conversation · Organizational decision records ·
Notes · Tags —
land on a unit box the way they land on a work-system node in the topology: in
the bottom marker row (on an open nested parent, below its sub-units — see the
strip note below), in the drawer, under the layer filter. (The filter menu also lists
**Board bindings**, a layer of the route view; it stays greyed on a units view.) An entity reaches a unit through a **bare
wikilink** on one of the value-polymorphic keys: `observes: "[[Deliveries]]"` on
a signal, `interaction-of: "[[Wholesale]]"` on an interaction,
`agreement-of: "[[Wholesale]]"` on an agreement, `bound-to: "[[Wholesale]]"` on
an AI agent — the wikilink names the unit, and every units view that draws it
renders the marker. **A unit that is also a work system** (its `unit-type`
includes `work-system`) is **one entity with one anchor set in both pictures**:
its unit box shows the whole work-system surface — the bare `interaction-of` /
`agreement-of` / `bound-to` / `observes` links and the `for-domain: [[Work System]]`
agreements, drivers, proposals, roles and decision records — exactly as its topology
node does. It does **not** show the topology-only buckets: no members list and
no visualizations list on the unit box or in its drawer (the topology's flow
story stays in the topology). A unit **without** the hat is not a domain: a
`for-domain` that names it lands nowhere, and the validator says so
(`units.for-domain-names-unit`). In `mode: infer` a unit's **tags** come from
the unit's frontmatter `tags:`; a declared `tags:` on the same unit in the
document overlays (the union is shown). What lands where, per layer:
| Layer | How it reaches a unit |
|-------|-----------------------|
| Interactions | `interaction-of: "[[Unit]]"` on an `interaction` / `meeting` |
| Agreements | `agreement-of: "[[Unit]]"` on an `agreement`; with the work-system hat also `for-domain: "[[Unit]]"` |
| AI Agents | `bound-to: "[[Unit]]"` on an `ai-agent` |
| Roles | `for-domain: "[[Unit]]"` on a `type: role` file — a role marker (hat-glasses glyph and count) in the marker row, **work-system units only**; a role scoped to a plain team lands nowhere and gets the same info diagnostic as any other `for-domain` on a unit without the hat |
| Change conversation | `observes: "[[Unit]]"` on a `signal` / `insight` (a pin); with the hat also a `driver` / `proposal` via `for-domain` |
| Organizational decision records | `for-domain: "[[Unit]]"` on an organizational decision record — **work-system units only** |
| Notes | doc-level `notes: - on: "[[Unit]]"` in the document itself ([notes](./notes.md)) |
| Tags | the unit's frontmatter `tags:` (infer) ∪ the document's declared `tags:` |
The full contract — resolution by the target's type, the path form for a
same-named person and unit, the role bridge (which reaches people, never units)
— is the [locator grammar](../locator-grammar.md#entity-wikilinks--resolution-by-target-type-the-role-bridge-the-path-form-phase-42).
**Focus ring, deltas.** In a focus + radius cut-out the focal unit's box wears
the topology's dashed focus ring and soft shadow — the box stroke itself stays
normal, so the box looks like every other box. In the nested layout the ring surrounds
the focal unit's whole container, children included.
**One delta language: one dash, the colour carries the meaning** (`STRUCT-28`).
A `delta` paints the box stroke and — in `layout: tree` — the `member-of` edge in
the add (green) / change (orange) / remove (red) colour, with **one** dash
pattern for all three (`10 5`, a 15 px period). There used to be a pattern per
delta — `6 3`, `4 4` and a `2 4` dotted — three rhythms distinguishing what the
colour already said, at periods too fine to resolve on a short run. The box also
takes a **light tint of its delta colour** (`#ddf0ea` add, `#fdeee3` change,
`#fbe5e0` remove), which is what makes a delta visible at normal zoom; a units
box's background carries no other meaning, so it was free to take it. The org
chart does the same and the **topology deliberately does not** — a topology node's
fill IS its Flight-Level band colour, so there the delta rides the stroke alone.
A removed unit stays in place, drawn as removed.
**On an open parent the strip sits below the sub-units** (`STRUCT-40`, nested
layout). An open nested parent reads top to bottom as its name and `unit-type`
chips, then its sub-units, then its annotation strip — **bottom-left**, with the
same divider, inset and hit zone as the strip on a leaf box, so a parent and a leaf
read the same. (Before, a parent's strip sat between its header and its children
while a leaf showed its strip at the bottom.) The band below the sub-units is
reserved **only when that parent has entries of its own** — anchors or tags; an
open parent without them ends one padding below its last row of children, with no
empty band. Leaf boxes keep the rule they had: in a document where any unit carries
a marker, every leaf reserves the marker row. A collapsed parent is a leaf and shows
its strip as a leaf does. The change-conversation **marker tabs** do not move — they
stay at the unit's top-right edge, on parents and leaves alike.
**The hover pulse always flows child → parent** (`STRUCT-38`). Hovering a unit sets
dots travelling along its containment edges — in `layout: tree` the `member-of`
edges to its parent and to its children — and on **every** one of them the dots run
toward the root: up the edge above the hovered unit toward its parent, and up each
edge below it toward the hovered unit. The direction is the edge's own point order
(every tree edge is laid out from the child to the parent's bottom-centre), the
same rule the org chart's reporting lines follow. (Before, a branch child's edge ran
parent-first, so hovering a middle unit sent the dots on its upper edge the wrong
way — toward the hovered unit.)
## Modes
`mode:` selects how the view is assembled. Values: **`infer`**, **`declared`**,
**`mixed`**. The default (when `mode:` is absent) is `infer`.
| Mode | Meaning |
|------|---------|
| `infer` | Derive the view from the workspace: every `type: unit` file the indexer knows — including the work systems, which are units wearing the `work-system` hat — with its **first** `member-of` as the parent. Without `focus:` the **whole structure** is drawn — roots are the units without `member-of`. With `focus:` (or an embedding file that is itself a unit) the **cut-out** bounded by `radius:` is drawn. |
| `declared` | The author lists the `units` and the `member-of` edges explicitly. Nothing is read from the workspace, so the document renders identically in the editor preview, the Markdown preview and the headless CLI. |
| `mixed` | The author's entries **overlay** the inferred view as a diff — each unit or edge carries a `delta` (`add` / `change` / `remove`) applied on top of what the indexer found. A unit listed without a delta merges with the inferred one. |
An unrecognized `mode:` value emits **`units.unknown-mode`** (warning) and falls
back to `infer`.
**Focus and radius.** `focus: "[[Name]]"` centers the view on one unit;
`radius: { up: N, down: M }` bounds how many nesting hops are drawn above
(containers) and below (members). Either value may be **`inf`** — unbounded:
`radius: { up: 1, down: inf }` is the containing unit and everything inside the
focal one. In `infer` mode the radius applies whenever a focal unit exists (the
default is one hop each way); in `declared` and `mixed` mode it prunes the view
**only when `radius:` is written explicitly** — a hand-authored document is never
silently cut down by the default.
**The embedded-focus rule.** A units view embedded in another file gets that
file's basename as its default focus **only when that file is a unit** — an
indexed `unit` or a name the document itself declares. Embedded in a person's
file or a tour page, the view has no focal and shows the whole structure.
`focus: self` means the same as leaving `focus:` out.
**Large views.** A view with more than 40 units is still laid out completely;
the editor preview adds a `units.large-view` info to its diagnostics.
**Headless rendering.** `mode: infer` needs the workspace index; the CLI has
none and refuses such a standalone document with a one-line message and exit
code 1. Fenced inside a Markdown file, such a block (or one with no `mode:` key,
which defaults to infer) is skipped with that same message and the file's other
fences still export — exit 1 only when nothing in the file was exportable.
Author `declared` or `mixed` for anything that must export from the command
line (a `mixed` document renders its declared part alone there).
## Syntax reference
Every field below is implemented by the parser (`src/units/parser.ts`) and
validated by `src/units/validate.ts`. The **Diagnostic** column names the
`units.*` code an author hits on misuse.
### Top-level fields
| Field | Values / shape | Default | Diagnostic on misuse |
|-------|----------------|---------|----------------------|
| `mode` | `infer` \| `declared` \| `mixed` | `infer` | `units.unknown-mode` (warning; falls back to `infer`) |
| `layout` | `nested` \| `tree` | `nested` | `units.unknown-layout` (warning; falls back to `nested`) |
| `title` | string — painted as the document heading | (none — omitted when absent) | (none — a non-string or empty value is silently ignored) |
| `tags` | string list — painted as document chips (a scalar string is accepted as a one-element list) | (none) | tolerant — non-string entries dropped, empties trimmed |
| `focus` | `[[Name]]` \| `[[Name\|Alias]]` \| `self` | (none — whole structure, or the embedding unit) | `units.invalid-focus` (warning, parse); `units.unresolved-focus` (warning, validate — an explicit target that is neither indexed nor declared) |
| `radius` | a number \| `inf` \| `{ up, down }` (each a number or `inf`) | `{ up: 1, down: 1 }` | `units.unsupported-radius` (unparseable value → `1`; negative → clamped to `0`) |
| `units` | list of unit objects | `[]` | `units.invalid-unit` (a non-object or name-less entry is skipped) |
| `member-of` (alias `member_of`) | list of edge objects | `[]` | `units.invalid-edge` (a non-object or `from`/`to`-less entry is skipped); `units.duplicate-edge` (info; the second occurrence is ignored) |
| `notes` | list of `{ on, text, resolved? }` | (none) | `units.note-orphan` (warning — see [notes](./notes.md)) |
**`radius`** also accepts a `lateral` key for symmetry with the topology; it has
no effect — a containment tree has no lateral relation. A bare number applies to
both directions; the object form fills a missing direction with `1`.
### Unit fields (each entry of `units`)
| Field | Values / shape | Default | Diagnostic on misuse |
|-------|----------------|---------|----------------------|
| `name` | string (**required**) — the unit's entity name, the basename of the `unit` file | — | `units.invalid-unit` (entry skipped when `name` is missing or empty) |
| `unit-type` (alias `unit_type`) | a token or a list of tokens (`company`, `department`, `team`, `work-system`, …), kept as written; one chip per value | (none — no chip) | (none — non-string entries are dropped) |
| `delta` | `add` \| `change` \| `remove` | (none) | `units.unknown-delta` (warning; the value is omitted) |
| `tags` | string list (a scalar string is accepted as a one-element list) | (none) | tolerant — non-string entries are dropped. Unioned with the unit's frontmatter `tags:` when an index is present (`infer` / `mixed`). |
| `anchors` | map of `typeKey → string list` — declared anchored entities, the no-index authoring form | (none) | `units.invalid-anchors` (the value is ignored when it is not a map) |
Any other key on a unit — `flightlevel:`, `contributes-to:` and every other
`dwsd-markdown` frontmatter key — is **ignored without a diagnostic**. The drawer
reads them from the workspace index.
### Edge fields (each entry of `member-of`)
| Field | Values / shape | Default | Diagnostic on misuse |
|-------|----------------|---------|----------------------|
| `from` | string (**required**) — the member (child) | — | `units.invalid-edge` (entry skipped when missing) |
| `to` | string (**required**) — the containing unit (parent) | — | `units.invalid-edge` (entry skipped when missing) |
| `delta` | `add` \| `change` \| `remove` | (none) | `units.unknown-delta` (warning; the value is omitted) |
A `member-of` edge points from the member **up** to its container — the same
direction as the `member-of:` frontmatter key. An edge whose endpoint is not a
drawn unit is dropped silently. Two structural diagnostics come from the
layout's tree reduction and are raised at validate time:
| Diagnostic | Severity | Meaning |
|------------|----------|---------|
| `units.multiple-parents` | info | A unit is a member of several units; the **first parent in document order** is kept (the DawnSeed rule — in `mixed` mode the inferred edge comes first) and the rest are not drawn. |
| `units.cycle` | warning | The edge closes a containment cycle (`A → B → A`, or a self-edge) and is not drawn. Among the edges of one cycle, the one whose `from` sorts last loses, so the outcome does not depend on authoring order. |
One annotation diagnostic is raised at validate time when an index is present:
| Diagnostic | Severity | Meaning |
|------------|----------|---------|
| `units.for-domain-names-unit` | info | A `for-domain:` in some entity file names a drawn unit that does **not** wear the `work-system` hat. `for-domain` names a **domain** — a work system or a role — so the entity lands nowhere. Message: `` `Fleet Change.md` names the unit `Deliveries` in its `for-domain:`, which is not a work system — point it at the work system or a role (the entity lands nowhere) ``. It is a statement about the TARGET, not a quotation of the anchoring file: the spelling shown is the INDEXED one, which is not necessarily what that file wrote (Phase 43, IN-11). Placed on the `mode` line, one per (unit, file); never raised for a work-system unit. |
## Collapse
The **+/−** toggle sits at the top-right corner of every unit that has children,
in both layouts. A click folds the subtree: the unit becomes a leaf-sized box
with a **`+N`** badge counting the hidden descendants; a second click unfolds it.
The toggle is not part of the drawer's click model — folding a subtree while a
drawer is open leaves that drawer open. The collapsed set lives in the preview
session only (never in the document, never in the workspace state) and is reset
when another document is rendered. The editor preview's Export menu honors it;
the CLI, the published example pictures and the Markdown preview always render
unfolded (only the Markdown preview draws the toggle, and there it does nothing).
In the editor preview the change is animated: the folded sub-units shrink back into
their parent and fade, unfolded ones grow out of it, their edges retract and extend
with them, and every other box glides — one clock of about 200 ms, ease-out, with
the view itself pinned (see *Children grow out of their parent* above). A second
click during the motion carries on from where the boxes are. Reduced motion skips
the frames; the animation never appears in an export, the CLI render or VS Code's
Markdown preview. A jump to a unit hidden
inside a collapsed subtree does not unfold it; unfold the parent by hand. The
[org chart](../orgchart/index.md#collapse) has the same toggle, badge and motion.
## Drawer
A click on a unit box opens the shared drawer for that unit (a second click
closes it; hovering a box pulses its containment edges — in the nested layout the
parent's own box is highlighted). The **title** is a link to the unit: it carries the
link color and a trailing "›" at rest, is underlined only on hover and keyboard
focus, and opens the unit's file beside the preview (the drawer stays open;
`titleAction: 'select'` or `'none'` give a host the other models). The drawer opens
with the **fact row** — one line of small chips, no heading — then the entity rows
(the `Open in topology` jump row among them), then the annotation bands in the topology
drawer's order and wording:
| Row | Source |
|-----|--------|
| **Fact chips** (first) | a colored `Flight Level 1` / `2` / `3` chip (the Flight Levels colors, because the chip means the flight level — **work-system units only**), then one chip per `unit-type` token side by side, exactly as written; `work-system` reads **Work System**. The fact row never follows a layer filter |
| `member-of` | the containing unit, as a navigable wikilink (in `declared` mode: the parent the document names) |
| `contributes-to` | the work systems this unit contributes to — **work-system units only** |
| `Open in topology` | a jump into the unit's `.dwsd-topology` — **work-system units only**; its *Open* button carries a small arrow icon, not an emoji |
| **Interactions** | every `interaction` / `meeting` whose `interaction-of:` names `[[Unit]]` |
| **Agreements** | every `agreement` whose `agreement-of:` names `[[Unit]]`; for a work-system unit also those whose `for-domain:` names it |
| **AI Agents** | every `ai-agent` whose `bound-to:` names `[[Unit]]` |
| **Roles** | every `type: role` file whose `for-domain:` names the unit — **work-system units only**; follows the Roles layer |
| **Change conversation** | the `signal` / `insight` pins that `observe: "[[Unit]]"`; for a work-system unit also the `driver` / `proposal` pins that name it in `for-domain:`; read in chain order — Signal, Insight, Driver, Proposal — whatever order they arrive in |
| **Organizational Decision Records** | organizational decision records whose `for-domain:` names the unit — **work-system units only** |
| **Notes** | the inline [`notes:`](./notes.md) on the unit |
| **Tags** | the unit's tags — frontmatter (infer) ∪ declared |
Every band row is the shared anchor row of the topology drawer: a navigable
wikilink when the entity is indexed, a plain label when it is a name-only
declared anchor, the same provenance dots. There is deliberately **no members
list and no child-units list** — the view already shows the containment — and a
work-system unit's drawer does **not** repeat the topology's members /
visualizations buckets.
**A row is a link exactly when its target resolves** (`STRUCT-34`). That holds for
the entity rows too, `declared` mode included: `member-of`, `contributes-to` and
`Open in topology` are navigable only when the workspace can actually open what
they name. A row whose target the workspace has never heard of — a parent unit
declared in the document with no entity file behind it — renders as **plain text**:
no link styling, no click handler, nothing happens. It is not a "not in workspace"
hint; a dead affordance is worse than no affordance. In `declared` mode without an
index only the rows the
document itself carries (the `unit-type` chips, `member-of` from the tree, declared
`anchors`, `tags`, notes) appear.
## Examples
Each example is also shipped verbatim as a file under `examples-src/units/` and
is parsed clean (0 diagnostics) by the publish-preview drift gate. Names are
from the Goldcrust Bakery example organization.
### 1. Declared, nested
The Goldcrust units three deep, drawn as boxes inside boxes. `layout: nested` is
the default and could be left out.
```dwsd.units
# 1 — declared, nested: the Goldcrust Bakery units three deep, children drawn INSIDE
# their parent (layout: nested is the default). One chip per unit-type value.
mode: declared
layout: nested
units:
- { name: Goldcrust Bakery, unit-type: company }
- { name: Bakery Strategy, unit-type: [leadership, work-system] }
- { name: Bakery Operations, unit-type: [department, work-system] }
- { name: Bakehouse, unit-type: [team, work-system] }
- { name: Deliveries, unit-type: team }
- { name: Wholesale, unit-type: team }
member-of:
- { from: Bakery Strategy, to: Goldcrust Bakery }
- { from: Bakery Operations, to: Bakery Strategy }
- { from: Bakehouse, to: Bakery Operations }
- { from: Deliveries, to: Bakery Operations }
- { from: Wholesale, to: Bakery Operations }
```
### 2. Declared, tree
The same containment as a top-down tree of uniform boxes with `member-of` drawn
as edges.
```dwsd.units
# 2 — declared, tree: the same containment as a top-down tree of uniform boxes with
# member-of drawn as edges (layout: tree). Siblings sit in one row, ascending by name.
mode: declared
layout: tree
units:
- { name: Goldcrust Bakery, unit-type: company }
- { name: Bakery Strategy, unit-type: [leadership, work-system] }
- { name: Bakery Operations, unit-type: [department, work-system] }
- { name: Bakehouse, unit-type: [team, work-system] }
- { name: Deliveries, unit-type: team }
- { name: Wholesale, unit-type: team }
member-of:
- { from: Bakery Strategy, to: Goldcrust Bakery }
- { from: Bakery Operations, to: Bakery Strategy }
- { from: Bakehouse, to: Bakery Operations }
- { from: Deliveries, to: Bakery Operations }
- { from: Wholesale, to: Bakery Operations }
```
### 3. Mixed with delta
A planned structural change drawn over the inferred structure: a new team
appears inside Bakery Operations and an existing one is marked for removal, in
place. Renders in the editor preview (it needs the index for the base).
```dwsd.units
# 3 — mixed with delta: a planned structural change drawn over the inferred units.
# A new team is added under Bakery Operations (delta: add on the unit AND its edge)
# and the Deliveries team is marked for removal, in place.
mode: mixed
layout: nested
units:
- { name: Catering, unit-type: team, delta: add }
- { name: Deliveries, delta: remove }
member-of:
- { from: Catering, to: Bakery Operations, delta: add }
- { from: Deliveries, to: Bakery Operations, delta: remove }
```
## See also
- [Units notes](./notes.md) — the inline `notes:` surface and its `[[Name]]` locator.
- [Org Chart DSL](../orgchart/index.md) — the sibling structure view over `reports-to`.
- [Locator grammar](../locator-grammar.md) — the bare `[[Name]]` wikilink as an addressable place for signals, agreements, interactions, agents and notes; a work-system unit as one entity in both pictures.
- [The document header](../document-header.md) — the `title:` / `tags:` header the units view paints.