DWSD.md

Board DSL — Syntax Reference

The Board DSL describes a Flight-Levels Board: the columns a work item flows through, the lanes it flows in, and the groups that band columns together. A board carries header metadata (its title:, flight level, timebox, working agreements:, interactions:, descriptive tags:), a columns: structure, and an optional lanes: / groups: structure.

The title: + tags: half of that header is shared with the flight-route and topology DSLs — same keys, same meaning, same shape in all three. See The document header.

The DSL is plain YAML. dwsd-board is the VS Code language id for standalone .dwsd-board files. In Markdown, the fence tag that the extension’s preview renders live is dwsd.board (the dot form):

```dwsd.board
title: Team Board
columns:
- ToDo
- Doing
- Done
```

In plain Markdown / GitHub the same example reads as a YAML document either way.

Live example · Charger ART Board — Voltfeld example →

title: Charger ART
flightlevel: FL2
timebox: 10w
agreements:
  - "Features enter the PI at PI planning; mid-PI changes need the Business Owners"
lanes:
  - name: Feature
    groups:
      - name: Preparation
        columns:
          - name: Program Backlog
            status-category: to-do
          - name: Refined
            status-category: to-do
            check: "Acceptance criteria, team assignment and platform dependencies named"
      - name: In PI
        interactions:
          - "[[ART Sync]]"
        columns:
          - name: Implementing
            wip: 8
            status-category: in-progress
          - name: Integrating
            status-category: in-progress
            agreements:
              - "Integration dates are set at PI planning; a slipped date is raised at the ART Sync, not discovered on the rig"
      - name: Release
        interactions:
          - "[[System Demo]]"
        columns:
          - name: Demoed
            status-category: assess
          - name: Certified
            status-category: waiting
          - name: Released
            status-category: done
  - name: Field Issue
    type: expedite
    columns:
      - name: Triage
        status-category: assess
      - name: Fix-in-Progress
        wip: 2
        status-category: in-progress
        agreements:
          - "One hotfix in progress per team; a second one waits or becomes a feature"
      - name: Hotfix-Released
        status-category: done

Fence-tag caveat — the examples on the topic pages use dwsd-board. Syntax highlighting accepts four spellings (wsd-board, dwsd-board, wsd.board, dwsd.board — the markdown-injection grammar), but the preview extractor currently accepts only dwsd.board. So a ```dwsd-board fence highlights but does not render. The topology DSL already resolved this asymmetry by accepting all four spellings in both places; board and route have not yet. Until they do, use the dot form for a fence you want to see rendered.

The parser is tolerant: it never throws on author input. Unknown or malformed fields produce a board.* 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 on each topic page and summarized under Diagnostics (board.*) below.

Note on field names that do not exist. The Board DSL has no inline sugar — there is no #status tag-suffix, no [wip] / [2..4] bracket modifier, and no bare-modifier line. Every modifier is a spelled-out YAML key (status-category:, tags:, wip:, aging:, max-returns:, agreements:, interactions:, split:).

The following are not keys — do not write them:

  • policy: / refs: — working agreements (both linked entities and inline rules) are the single agreements: key.
  • labels: — descriptive chips are tags:.
  • focus-fit: (alias focus_fit) — the header carries no FIT-type label.
  • statuses: — there is no statuses: Jira-status key (no Jira coupling is surfaced).
  • board: — retired. The document title is title: (see The document header). Unlike the other entries in this list, board: is not silently ignored: it is a hard board.retired-key error that names its own fix.

The tolerant parser silently ignores any non-key (no diagnostic, no field), so an example that writes one would parse “clean” while dropping the value — which is exactly why board: was given a real error instead of joining that list. Two repo gates hold the line on the examples below: pnpm check:dsl-corpus-sweep parses every tracked unit and live fence and requires 0 diagnostics, and pnpm check:retired-key-audit fails on any surviving retired spelling.

Topic pages

The reference is split across per-topic pages:

  • The document header — title: + tags:, the header shared by all three DSLs, the missing-value convention, and the retired keys.
  • Columns — columns + modifiers (wip, aging, max-returns, status-category tint, tags chips) and the status-tint palette.
  • Splits — a column’s split: block and per-section modifiers.
  • Lanes — lanes + lane modifiers and lane-local structure.
  • Groups — group blocks, the three-scope groups: semantics, and depth bands.
  • Anchors, agreements & interactions — the locator, the grouped bucket + shared drawer + disclosure tiers, inline markdown-lite, and overflow affordances.
  • Examples — full worked dwsd-board examples.

Design notes — YAML, humans, and machines

The Board DSL is YAML by design. Every example in this reference is also a valid YAML document — block style (columns: as an indented list) and flow style ({ name: doing, status-category: waiting }) both parse — so any YAML-aware editor, linter, or formatter can read and write it. The package ships its own tolerant parser (src/board/parser.ts) that accepts the same shape, modeled verbatim on the topology parser (src/topology/parser.ts).

The syntax optimizes for readability and round-trippability, not brevity. Field names are spelled out (max-returns, flightlevel — not abbreviations), values are explicit, and the parser never throws: unknown or malformed input yields a board.* diagnostic and a sensible default rather than a hard error. That verbosity is deliberate — the format is meant to be read and written by humans, but above all by machines.

Increasingly, that machine is an AI. The raw DSL is a good target for generation and repair by coding agents: the YAML shape, the closed and documented set of fields, and the per-field diagnostics give an agent everything it needs to author or fix a board without a UI. The board format deliberately dropped its former inline string-sugar (#status, [wip]) precisely because a mini-grammar hidden inside a string is the one thing YAML removes — a closed, explicit schema is validatable, autocompletable, and agent-repairable; string-sugar is not. The live fence preview is then the way a person checks what the machine produced.

Board header fields

Every field below is implemented by the parser (src/board/parser.ts, normalizeBoardAst) and produces the frozen BoardAst contract (src/board/types.ts). The Diagnostic column names the board.* code an author hits on misuse.

FieldValues / shapeDefaultDiagnostic on misuse
titlestring (the board name)(none — omitted when absent)(none — a non-string or empty value is silently ignored)
flightlevelFL1 | FL2 | FL3(none)board.invalid-flightlevel (warning; the value is omitted)
timeboxstring (e.g. 5d, 12w)(none)(none — a non-string or empty value is ignored)
agingstring (board-level aging duration, e.g. 3d)(none)(none — non-string ignored)
tagsstring list (descriptive category chips, rendered visibly; a scalar string is accepted as a one-element list)(none)tolerant — non-string entries dropped, empties trimmed
agreementsstring OR string list — working agreements (a [[wikilink]] entry = a linked entity; a plain string = an inline working agreement)(none)tolerant — non-string entries dropped
interactionsstring OR string list — meeting / interaction pointers(none)tolerant — non-string entries dropped
columnslist of column entries (see Columns)[]per-entry board.invalid-column
laneslist of lane entries (see Lanes)[]per-entry board.invalid-lane
groupslist of board-level group blocks (see Groups)(none)per-entry board.invalid-group

title is the optional board name — the header text. It is the same key the flight-route and topology DSLs use; when it is absent the field is omitted from the AST (never ''). The type-named board: key it replaced is retired and now errors — see The document header. flightlevel places the board in its band; only the canonical FL1/FL2/FL3 strings are accepted (anything else is dropped with board.invalid-flightlevel). tags are descriptive category chips, rendered visibly (with … ellipsis overflow when they do not fit) — the same cross-DSL convention the topology DSL uses for tags:. They carry no color tint (the tint is the separate status-category: concern, which is not a board-level key). agreements carry the working agreements: a [[Target]] / [[Target|Alias]] wikilink entry points at a linked entity (kept verbatim; bare-target extraction is a render/resolve concern), while a plain string is an inline working agreement. interactions point at meetings / interactions. Both render as a single grouped chip → shared drawer (see Anchors, agreements & interactions).

Themes

The theme is selected via data-theme on the board root; theme CSS travels as TS-string exports (board-theme-css.ts).

Theme idAestheticCharacter
jira-defaultAtlassian / Jirawhite background, the 5-color status tints (see Status tint), blue chips, uppercase column names

One theme ships today. A second one — a training-slide aesthetic from the board’s earliest days — was removed rather than parked: it had been written against a much smaller board and had no rules for most of what a board renders now, so selecting it produced a largely unstyled board. A workspace still configured to it falls back to jira-default; nothing errors and no authored file is affected, because the theme never touched authored content. The theme mechanism is unchanged and a second theme can be added again.

Zoom

A board zooms like the other diagrams. Nothing in the board text controls it; it is part of how a board is shown.

  • A board is laid out at its natural size. Every column has a minimum readable width (220 px by default), so a board with many columns is wide rather than squeezed: its cards keep their size in a narrow panel and you scroll, instead of reading text that has been crushed.
  • A board opens at Fit. Fit shows the visible board in the available space, and a board starts out that way. A collapsed lane counts with its header row only, so collapsing lanes and pressing Fit again makes the rest larger. Fit may enlarge a small board, but never beyond 1.25 times its natural size, the same limit as every other diagram.
  • + and − scale the board as a picture. + and − enlarge and shrink it step by step. The columns keep their proportions at every size. Collapsing or expanding a lane keeps the current zoom; press Fit to fit the new height.
  • The header stays the same size. The title and the chips above the board do not grow or shrink with the picture, as in every other zooming view.
  • The mouse wheel still scrolls. A board scrolls with the wheel as before; zooming is done with the toolbar’s −, Fit and +.
  • Export is the natural-size board. An exported PNG or SVG shows the board at its natural size whatever the zoom was when you exported it.

Diagnostics (board.*)

The parser is tolerant and never throws; every malformed input yields a board.* diagnostic and a clamp/skip. The full diagnostic family is:

  • board.syntax-error — the YAML itself does not parse (a YAMLParseError, including any attempt at an executable !!-tag, which is rejected, never run).
  • board.retired-key — error (the one non-tolerant code in this family): the retired type-named document-title key was authored. The message names the fix verbatim — `board:` is retired — rename it to `title:` to name this board — and the retired key’s value is dropped rather than read. Only a root-level board: fires it; a nested board: (a route’s board binding) is a different, legitimate key. See The document header.
  • board.internal-error — a belt-and-suspenders catch for an unexpected throw.
  • board.invalid-column / board.invalid-lane / board.invalid-split / board.invalid-group — a malformed entry of that kind is skipped.
  • board.invalid-wip — a wip: value that is neither a number nor a [min, max] pair; omitted.
  • board.invalid-flightlevel — a flightlevel: other than FL1/FL2/FL3; omitted.
  • board.unknown-lane-type — a lane type: other than named/assignee/expedite; defaults to named.
  • board.invalid-status-category — a status-category: value outside the closed set to-do | in-progress | done | waiting | assess; the value is dropped and the unit gets no tint (tolerant — never throws).
  • board.nested-too-deep — a group path nested deeper than 3; deeper levels are ignored.

Security: the parser uses the default core YAML schema — no executable tags (no RCE), ReDoS-safe regexes, and a hard nesting cap of 3.

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

index.md
# Board DSL — Syntax Reference
The **Board DSL** describes a Flight-Levels **Board**: the columns a work item
flows through, the lanes it flows in, and the groups that band columns together.
A board carries header metadata (its `title:`, flight level, timebox, working
`agreements:`, `interactions:`, descriptive `tags:`), a `columns:` structure,
and an optional `lanes:` / `groups:` structure.
The `title:` + `tags:` half of that header is **shared with the flight-route and
topology DSLs** — same keys, same meaning, same shape in all three. See
[The document header](../document-header.md).
The DSL is plain **YAML**. `dwsd-board` is the **VS Code language id** for
standalone `.dwsd-board` files. In Markdown, the fence tag that the extension's
**preview** renders live is **`dwsd.board`** (the dot form):
````markdown
```dwsd.board
title: Team Board
columns:
- ToDo
- Doing
- Done
```
````
In plain Markdown / GitHub the same example reads as a YAML document either way.
> **Fence-tag caveat — the examples on the topic pages use `dwsd-board`.**
> Syntax **highlighting** accepts four spellings (`wsd-board`, `dwsd-board`,
> `wsd.board`, `dwsd.board` — the markdown-injection grammar), but the
> **preview extractor** currently accepts only `dwsd.board`. So a
> ` ```dwsd-board ` fence highlights but does not render. The topology DSL already
> resolved this asymmetry by accepting all four spellings in both places; board and
> route have not yet. Until they do, use the **dot form** for a fence you want to
> see rendered.
The parser is **tolerant**: it never throws on author input. Unknown or
malformed fields produce a `board.*` 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 on each topic page and
summarized under [Diagnostics (board.*)](#diagnostics-board) below.
> **Note on field names that do _not_ exist.** The Board DSL has **no inline
> sugar** — there is no `#status` tag-suffix, no `[wip]` / `[2..4]` bracket
> modifier, and no bare-modifier line. Every modifier is a spelled-out YAML key
> (`status-category:`, `tags:`, `wip:`, `aging:`, `max-returns:`, `agreements:`,
> `interactions:`, `split:`).
>
> The following are **not** keys — do not write them:
>
> - **`policy:` / `refs:`** — working agreements (both linked entities and inline
> rules) are the single **`agreements:`** key.
> - **`labels:`** — descriptive chips are **`tags:`**.
> - **`focus-fit:`** (alias `focus_fit`) — the header carries no FIT-type label.
> - **`statuses:`** — there is **no `statuses:` Jira-status key** (no Jira
> coupling is surfaced).
> - **`board:`** — **retired.** The document title is `title:` (see
> [The document header](../document-header.md)). Unlike the other entries in this
> list, `board:` is **not** silently ignored: it is a hard `board.retired-key`
> **error** that names its own fix.
>
> The tolerant parser **silently ignores** any non-key (no diagnostic, no field),
> so an example that writes one would parse "clean" while dropping the value —
> which is exactly why `board:` was given a real error instead of joining that
> list. Two repo gates hold the line on the examples below: `pnpm
> check:dsl-corpus-sweep` parses every tracked unit and live fence and requires 0
> diagnostics, and `pnpm check:retired-key-audit` fails on any surviving retired
> spelling.
## Topic pages
The reference is split across per-topic pages:
- [The document header](../document-header.md) — `title:` + `tags:`, the header
shared by all three DSLs, the missing-value convention, and the retired keys.
- [Columns](./columns.md) — columns + modifiers (`wip`, `aging`, `max-returns`,
`status-category` tint, `tags` chips) and the status-tint palette.
- [Splits](./splits.md) — a column's `split:` block and per-section modifiers.
- [Lanes](./lanes.md) — lanes + lane modifiers and lane-local structure.
- [Groups](./groups.md) — group blocks, the three-scope `groups:` semantics, and
depth bands.
- [Anchors, agreements & interactions](./anchors.md) — the locator, the grouped
bucket + shared drawer + disclosure tiers, inline markdown-lite, and overflow
affordances.
- [Examples](./examples.md) — full worked `dwsd-board` examples.
## Design notes — YAML, humans, and machines
The Board DSL is **YAML by design**. Every example in this reference is also a
valid YAML document — block style (`columns:` as an indented list) and flow
style (`{ name: doing, status-category: waiting }`) both parse — so any YAML-aware
editor, linter, or formatter can read and write it. The package ships its own
tolerant parser (`src/board/parser.ts`) that accepts the same shape, modeled
verbatim on the topology parser (`src/topology/parser.ts`).
The syntax optimizes for **readability and round-trippability, not brevity**.
Field names are spelled out (`max-returns`, `flightlevel` — not abbreviations),
values are explicit, and the parser never throws: unknown or malformed input
yields a `board.*` diagnostic and a sensible default rather than a hard error.
That verbosity is deliberate — the format is meant to be read and written by
humans, but **above all by machines**.
Increasingly, that machine is an AI. The raw DSL is a good target for generation
and repair by coding agents: the YAML shape, the closed and documented set of
fields, and the per-field diagnostics give an agent everything it needs to
author or fix a board without a UI. The board format deliberately dropped its
former inline string-sugar (`#status`, `[wip]`) precisely because a mini-grammar
hidden _inside_ a string is the one thing YAML removes — a closed, explicit
schema is validatable, autocompletable, and agent-repairable; string-sugar is
not. The live fence preview is then the way a person checks what the machine
produced.
## Board header fields
Every field below is implemented by the parser (`src/board/parser.ts`,
`normalizeBoardAst`) and produces the frozen `BoardAst` contract
(`src/board/types.ts`). The **Diagnostic** column names the `board.*` code an
author hits on misuse.
| Field | Values / shape | Default | Diagnostic on misuse |
|-------|----------------|---------|----------------------|
| `title` | string (the board name) | (none — omitted when absent) | (none — a non-string or empty value is silently ignored) |
| `flightlevel` | `FL1` \| `FL2` \| `FL3` | (none) | `board.invalid-flightlevel` (warning; the value is omitted) |
| `timebox` | string (e.g. `5d`, `12w`) | (none) | (none — a non-string or empty value is ignored) |
| `aging` | string (board-level aging duration, e.g. `3d`) | (none) | (none — non-string ignored) |
| `tags` | string list (descriptive category chips, rendered visibly; a scalar string is accepted as a one-element list) | (none) | tolerant — non-string entries dropped, empties trimmed |
| `agreements` | string OR string list — working agreements (a `[[wikilink]]` entry = a linked entity; a plain string = an inline working agreement) | (none) | tolerant — non-string entries dropped |
| `interactions` | string OR string list — meeting / interaction pointers | (none) | tolerant — non-string entries dropped |
| `columns` | list of column entries (see [Columns](./columns.md)) | `[]` | per-entry `board.invalid-column` |
| `lanes` | list of lane entries (see [Lanes](./lanes.md)) | `[]` | per-entry `board.invalid-lane` |
| `groups` | list of board-level group blocks (see [Groups](./groups.md)) | (none) | per-entry `board.invalid-group` |
**`title`** is the optional board name — the header text. It is the **same key
the flight-route and topology DSLs use**; when it is absent the field is
**omitted** from the AST (never `''`). The type-named `board:` key it replaced is
retired and now errors — see [The document header](../document-header.md).
**`flightlevel`** places the board in its band; only the canonical
`FL1`/`FL2`/`FL3` strings are accepted (anything else is dropped with
`board.invalid-flightlevel`). **`tags`** are descriptive category chips,
rendered **visibly** (with `…` ellipsis overflow when they do not fit) — the same
cross-DSL convention the topology DSL uses for `tags:`. They carry **no** color
tint (the tint is the separate `status-category:` concern, which is not a
board-level key). **`agreements`** carry the working agreements: a `[[Target]]` /
`[[Target|Alias]]` wikilink entry points at a linked entity (kept verbatim;
bare-target extraction is a render/resolve concern), while a plain string is an
inline working agreement. **`interactions`** point at meetings / interactions.
Both render as a single grouped chip → shared drawer (see
[Anchors, agreements & interactions](./anchors.md#anchors-agreements--interactions)).
![Board header](../../images/header.png)
![Board header settings](../../images/board-settings.png)
## Themes
The theme is selected via `data-theme` on the board root; theme CSS travels as
TS-string exports (`board-theme-css.ts`).
| Theme id | Aesthetic | Character |
|----------|-----------|-----------|
| `jira-default` | Atlassian / Jira | white background, the 5-color status tints (see [Status tint](./columns.md#status-tint)), blue chips, uppercase column names |
> One theme ships today. A second one — a training-slide aesthetic from the board's
> earliest days — was removed rather than parked: it had been written against a much
> smaller board and had no rules for most of what a board renders now, so selecting
> it produced a largely unstyled board. A workspace still configured to it falls back
> to `jira-default`; nothing errors and no authored file is affected, because the
> theme never touched authored content. The theme mechanism is unchanged and a
> second theme can be added again.
## Zoom
A board zooms like the other diagrams. Nothing in the board text controls it; it is part of how a
board is shown.
- **A board is laid out at its natural size.** Every column has a minimum readable width (220 px
by default), so a board with many columns is wide rather than squeezed: its cards keep their
size in a narrow panel and you scroll, instead of reading text that has been crushed.
- **A board opens at Fit.** **Fit** shows the visible board in the available space, and a board
starts out that way. A collapsed lane counts with its header row only, so collapsing lanes and
pressing Fit again makes the rest larger. Fit may enlarge a small board, but never beyond 1.25
times its natural size, the same limit as every other diagram.
- **+ and − scale the board as a picture.** **+** and **−** enlarge and shrink it step by step.
The columns keep their proportions at every size. Collapsing or expanding a lane keeps the
current zoom; press Fit to fit the new height.
- **The header stays the same size.** The title and the chips above the board do not grow or
shrink with the picture, as in every other zooming view.
- **The mouse wheel still scrolls.** A board scrolls with the wheel as before; zooming is done with
the toolbar's −, Fit and +.
- **Export is the natural-size board.** An exported PNG or SVG shows the board at its natural size
whatever the zoom was when you exported it.
## Diagnostics (`board.*`)
The parser is tolerant and never throws; every malformed input yields a `board.*`
diagnostic and a clamp/skip. The full diagnostic family is:
- `board.syntax-error` — the YAML itself does not parse (a `YAMLParseError`,
including any attempt at an executable `!!`-tag, which is rejected, never run).
- `board.retired-key` — **error** (the one non-tolerant code in this family): the
retired type-named document-title key was authored. The message names the fix
verbatim — ``` `board:` is retired — rename it to `title:` to name this board ```
— and the retired key's value is dropped rather than read. Only a **root-level**
`board:` fires it; a nested `board:` (a route's board binding) is a different,
legitimate key. See [The document header](../document-header.md#retired-keys--board-and-route).
- `board.internal-error` — a belt-and-suspenders catch for an unexpected throw.
- `board.invalid-column` / `board.invalid-lane` / `board.invalid-split` /
`board.invalid-group` — a malformed entry of that kind is skipped.
- `board.invalid-wip` — a `wip:` value that is neither a number nor a
`[min, max]` pair; omitted.
- `board.invalid-flightlevel` — a `flightlevel:` other than `FL1`/`FL2`/`FL3`;
omitted.
- `board.unknown-lane-type` — a lane `type:` other than
`named`/`assignee`/`expedite`; defaults to `named`.
- `board.invalid-status-category` — a `status-category:` value outside the
closed set `to-do | in-progress | done | waiting | assess`; the value is dropped
and the unit gets no tint (tolerant — never throws).
- `board.nested-too-deep` — a group path nested deeper than 3; deeper levels are
ignored.
Security: the parser uses the default core YAML schema — **no executable tags**
(no RCE), ReDoS-safe regexes, and a hard nesting cap of 3.