DWSD.md

Board DSL — Groups

Groups band columns together. This page covers group blocks, the three-scope groups: semantics, and depth bands. See the reference index for the board header and the other constructs.

Group blocks (board-level groups:, lane groups:, sub-groups:)

A group block bands columns together. It carries a required name and any of columns: / lanes: / groups: (the children, by kind), plus optional agreements: / interactions: / tags: / status-category: metadata.

FieldValues / shapeDefaultDiagnostic on misuse
namestring (required)—board.invalid-group (block skipped when missing/empty)
columnslist of column entries (banded under this group)(none)per-entry board.invalid-column
laneslist of lane entries (a group hosting nested lanes)(none)per-entry board.invalid-lane
groupslist of sub-group blocks(none)per-entry board.invalid-group
agreementsstring OR string list — group working agreements(none)tolerant
interactionsstring OR string list — group meeting / interaction pointers(none)tolerant
tagsstring list (descriptive category chips, rendered visibly)(none)tolerant
status-categorysingle value, closed set to-do | in-progress | done | waiting | assess (group-band tint)(none)board.invalid-status-category

A group path nested deeper than 3 levels emits board.nested-too-deep and the deeper levels are ignored. (A lane is a container, not a group level — a depth-3 group path inside a lane is not capped by the lane wrapper.)

The three-scope groups: semantics. The same word groups: means three different things depending on where it appears — the parser dispatches on scope, not on the key name:

Where groups: appearsMeansParser behavior
Board levela group sidecarcolumns-only → flatten columns into ast.columns (carrying a group: path) + emit a metadata-only sidecar into ast.groups; lane-hosting → a first-class BoardGroup
Lane levelthe lane’s childrenmerged into the lane children array (columns-then-groups); columns-only → flatten + sidecar; lane-hosting → first-class
Group levelsub-groupsrecursive nested children, depth-capped at 3

Placing groups: at an unintended level is the construct most likely to produce the wrong shape — match the scope to the meaning above.

Depth bands & layout. Groups render as depth bands .group-bar-l1 / -l2 / -l3. A flat layout (all bands same depth) stacks the bands over one columns row, each band spanning its columns; a nested layout (any depth ≥ 2) nests .group-section containers so an L1 band visually contains its L2 bands which contain their columns. Multiple parallel sibling L1 groups (N ≥ 2) render as siblings, not nested. A group’s status-category: renders as a .group-status-lozenge on the band (a lozenge, not a full band tint — this preserves the L1/L2/L3 hierarchy color).

Pitfall — quote a name that is a YAML keyword. YAML auto-types bare scalars: a group or column named 1, true, yes, null, or on would parse as a number / boolean / null, not a string. Quote any such name: - { name: "1", columns: [a, b] }. Likewise quote an agreements: string that contains a : or starts with a special character.

Example

A focused board with two parallel board-level groups (N ≥ 2 — they render as sibling bands, not nested), each banding its own columns; one carries a status-category: lozenge on its band:

title: Roadmap
groups:
  - name: Discovery
    status-category: assess
    columns:
      - Ideas
      - Shaping
  - name: Delivery
    columns:
      - Build
      - Done

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

groups.md
# Board DSL — Groups
Groups band columns together. This page covers group blocks, the three-scope
`groups:` semantics, and depth bands. See the [reference index](./index.md) for
the board header and the other constructs.
## Group blocks (board-level `groups:`, lane `groups:`, sub-`groups:`)
A group block bands columns together. It carries a **required `name`** and any
of `columns:` / `lanes:` / `groups:` (the children, by kind), plus optional
`agreements:` / `interactions:` / `tags:` / `status-category:` metadata.
| Field | Values / shape | Default | Diagnostic on misuse |
|-------|----------------|---------|----------------------|
| `name` | string (**required**) | — | `board.invalid-group` (block skipped when missing/empty) |
| `columns` | list of column entries (banded under this group) | (none) | per-entry `board.invalid-column` |
| `lanes` | list of lane entries (a group hosting nested lanes) | (none) | per-entry `board.invalid-lane` |
| `groups` | list of sub-group blocks | (none) | per-entry `board.invalid-group` |
| `agreements` | string OR string list — group working agreements | (none) | tolerant |
| `interactions` | string OR string list — group meeting / interaction pointers | (none) | tolerant |
| `tags` | string list (descriptive category chips, rendered visibly) | (none) | tolerant |
| `status-category` | single value, closed set `to-do` \| `in-progress` \| `done` \| `waiting` \| `assess` (group-band tint) | (none) | `board.invalid-status-category` |
A group path nested deeper than **3** levels emits `board.nested-too-deep` and
the deeper levels are ignored. (A lane is a _container_, not a group level — a
depth-3 group path inside a lane is not capped by the lane wrapper.)
**The three-scope `groups:` semantics.** The same word `groups:` means three
different things depending on _where it appears_ — the parser dispatches on
scope, not on the key name:
| Where `groups:` appears | Means | Parser behavior |
|-------------------------|-------|-----------------|
| **Board level** | a group **sidecar** | columns-only → flatten columns into `ast.columns` (carrying a `group:` path) + emit a metadata-only sidecar into `ast.groups`; lane-hosting → a first-class `BoardGroup` |
| **Lane level** | the lane's **children** | merged into the lane `children` array (columns-then-groups); columns-only → flatten + sidecar; lane-hosting → first-class |
| **Group level** | **sub-groups** | recursive nested children, depth-capped at 3 |
Placing `groups:` at an unintended level is the construct most likely to produce
the wrong shape — match the scope to the meaning above.
**Depth bands & layout.** Groups render as depth bands `.group-bar-l1` / `-l2` /
`-l3`. A **flat** layout (all bands same depth) stacks the bands over one
columns row, each band spanning its columns; a **nested** layout (any depth ≥ 2)
nests `.group-section` containers so an L1 band visually contains its L2 bands
which contain their columns. Multiple parallel sibling L1 groups (N ≥ 2) render
as siblings, not nested. A group's `status-category:` renders as a
`.group-status-lozenge` on the band (a lozenge, **not** a full band tint — this
preserves the L1/L2/L3 hierarchy color).
![Group bands](../../images/group.png)
> **Pitfall — quote a name that is a YAML keyword.** YAML auto-types bare
> scalars: a group or column named `1`, `true`, `yes`, `null`, or `on` would
> parse as a number / boolean / null, not a string. Quote any such name:
> `- { name: "1", columns: [a, b] }`. Likewise quote an `agreements:` string
> that contains a `:` or starts with a special character.
## Example
A focused board with two parallel board-level groups (N ≥ 2 — they render as
sibling bands, not nested), each banding its own columns; one carries a
`status-category:` lozenge on its band:
```dwsd-board
title: Roadmap
groups:
- name: Discovery
status-category: assess
columns:
- Ideas
- Shaping
- name: Delivery
columns:
- Build
- Done
```