generator_annotations

auto-webclient grammar version zora.3rc2

Generator Contracts

Default

The default generator.

global-features
	'full screen planning' {
		'plannings': list non-empty feature 'planning'
	}

	'dashboard' {
		'dashboard': feature 'dashboard 2'
	}

	'layout' {
		'density': stategroup @default: 'compact' (
			'compact' { }
			'comfortable' { }
		)
		'group table columns': stategroup @default: 'no' (
			'no' { }
			'yes' { }
		)
		'show menu': stategroup @default: 'full' (
			'sparse' { }
			'full' { }
		)
		'custom colors': stategroup @default: 'no' (
			'no' { }
			'yes' {
				'primary color': text
				'secondary color': text
				'tertiary color': text
				'error color': text
				'neutral color': text
				'neutral variant color': text
			}
		)
		'advanced features': stategroup @default: 'no' (
			'no' { }
			'yes' { }
		)
	}

node-features
	'permissions' component stategroup @default: 'not set' @strategy (
		'user' binding user {
			'stategroup': binding stategroup {
				'state': binding state { }
			}
		}
		'node' binding node { }
		'not set' none
	)

	'dimension' component unbound stategroup @default: 'default' @strategy (
		'custom' {
			'width': number
			'height': number
		}
		'default' none
	)

	'margin' component unbound stategroup @default: 'default' @strategy (
		'custom' {
			'top': number
			'right': number
			'bottom': number
			'left': number
		}
		'default' none
	)

	'color' component unbound stategroup @default: 'auto' (
		'auto' { }
		'blue' { }
		'orange' { }
		'green' { }
		'red' { }
		'black' { }
		'grey' { }
		'grey light' { }
		'grey dark' { }
		'teal' { }
		'purple' { }
		'hex' text
	)

	'range' {
		'dimension': feature 'dimension'
		'margin': feature 'margin'
		'range start': stategroup (
			'static' number
			'dynamic' binding number { }
		)
		'ranges': list {
			'type': stategroup (
				'green' { }
				'blue' { }
				'orange' { }
				'red' { }
			)
			'range size': stategroup (
				'static' number
				'dynamic' binding number { }
			)
		}
		'value': binding number { }
		'style': stategroup (
			'progress bar' { }
			'gauge' { }
		)
	}

	'pie chart' {
		'dimension': feature 'dimension'
		'margin': feature 'margin'
		'query': binding nodes {
			'label': binding text { }
			'property': binding number { }
			'merge small slices': stategroup (
				'below percentage' number
				'no' { }
			)
			'sorting': stategroup (
				'yes' stategroup (
					'ascending' { }
					'descending' { }
				)
				'no' { }
			)
		}
	}

	'bar chart' {
		'dimension': feature 'dimension'
		'margin': feature 'margin'
		'query': binding nodes {
			'label': binding text { }
			'style': stategroup (
				'grouped' list {
					'property': binding number { }
					'color': feature 'color'
				}
				'stacked' list {
					'property': binding number { }
					'color': feature 'color'
				}
				'bar' binding number { }
			)
			'sorting': stategroup (
				'yes' {
					'direction': stategroup (
						'ascending' { }
						'descending' { }
					)
					'axis': stategroup (
						'single' binding number { }
						'sum' { }
					)
				}
				'no' { }
			)
		}
	}

	'line chart query' component {
		'dimension': feature 'dimension'
		'margin': feature 'margin'
		'label': binding text { }
		'lines': list {
			'property': binding number { }
			'color': feature 'color'
		}
		'style': stategroup (
			'line' { }
			'area' { }
		)
		'sort': binding number {
			'direction': stategroup (
				'ascending' { }
				'descending' { }
			)
		}
	}

	'chart value' component {
		'type': stategroup (
			'text' binding text { }
			'number' binding number { }
		// TODO 'state group' {
		// 	'property': query stategroup binding { }
		// }
		)
	}

	'custom table properties' component list binding { }

	'2d chart query' component {
		'title': text
		'x': feature 'chart value'
		'x label': stategroup @default: 'auto' (
			'auto' { }
			'text' binding text { }
		)
		'x axis title': stategroup @default: 'none' (
			'none' { }
			'set' text
		)
		'y axis title': stategroup @default: 'none' (
			'none' { }
			'set' text
		)
		'format x': stategroup @default: 'auto' (
			'auto' { }
			'day' { }
			'month' { }
			'month and year' { }
			'week' { }
			'week and year' { }
			'custom' list non-empty stategroup (
				'abbreviated weekday name' { }
				'full weekday name' { }
				'abbreviated month name' { }
				'full month name' { }
				'the locale’s date and time' { }
				'zero-padded day of the month' { }
				'space-padded day of the month' { }
				'microseconds' { }
				'ISO 8601 week-based year without century' { }
				'ISO 8601 week-based year with century' { }
				'hour (24-hour clock)' { }
				'hour (12-hour clock)' { }
				'day of the year' { }
				'month' { }
				'minute' { }
				'milliseconds' { }
				'either AM or PM' { }
				'quarter of the year' { }
				'milliseconds since UNIX epoch' { }
				'seconds since UNIX epoch' { }
				'second' { }
				'Monday-based (ISO 8601) weekday' { }
				'Sunday-based week of the year' { }
				'ISO 8601 week of the year' { }
				'Sunday-based weekday' { }
				'Monday-based week of the year' { }
				'the locale’s date' { }
				'the locale’s time' { }
				'year without century' { }
				'year with century' { }
				'time zone offset' { }
				'percent sign' { }
				'text' text
			)
		)
		'group x value': stategroup @default: 'no' (
			'no' { }
			'yes' stategroup @default: 'none' (
				'none' { }
				'week' { }
				'month' { }
				'quarter' { }
				'year' { }
			)
		)
		'size': stategroup @default: 'small' (
			'small' { }
			'medium' { }
			'large' { }
		)
		'bar mode': stategroup @default: 'grouped' (
			'grouped' { }
			'stacked' { }
		)
		'lines': list {
			'name': text @default: ""
			'y': binding number { }
			/// setting `y aggregate` switches the default `sum` function to handle multiple y
			/// values on the same x value.
			'y aggregate': stategroup @default: 'sum' (
				'sum' { }
				'minimum' { }
				'maximum' { }
				'average' { }
			)
			'color': stategroup @default: 'auto' (
				'auto' { }
				'custom' text
			)
			'style': stategroup @default: 'line' (
				'line' {
					'stack': stategroup @default: 'no' (
						'no' { }
						'yes' { }
					)
					'shape': stategroup @default: 'linear' (
						'linear' { }
						'horizontal vertical' { }
						'vertical horizontal' { }
						'horizontal vertical horizontal' { }
						'vertical horizontal vertical' { }
						'spline' { }
					)
					'dash': stategroup @default: 'solid' (
						'solid' { }
						'dot' { }
						'dash dot' { }
						'long dash dot' { }
					)
				}
				'area' { }
				'bar' { }
			)
			'partition': stategroup @default: 'no' (
				'no' { }
				'yes' {
					'value': binding text { }
					'color': stategroup @default: 'auto' (
						'auto' { }
						'custom' binding text { }
					)
				}
			)
			'show legend': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
		'drill down properties': stategroup @default: 'auto' (
			'auto' { }
			'custom' feature 'custom table properties'
		)
	}

	'line chart' binding nodes {
		'chart': feature 'line chart query'
	}

	'scatter chart' {
		'dimension': feature 'dimension'
		'margin': feature 'margin'
		'query': binding nodes {
			'label': binding text { }
			'x label': text
			'x property': binding number { }
			'y label': text
			'y property': binding number { }
			'style': stategroup (
				'dot' { }
				'connected dot' binding number {
					'sort': stategroup (
						'ascending' { }
						'descending' { }
					)
				}
				'bubble' binding number { }
			)
		}
	}

	'radar chart' {
		'dimension': feature 'dimension'
		'margin': feature 'margin'
		'query': binding nodes {
			'label': binding text { }
			'values': list binding number { }
			'style': stategroup (
				'spider' { }
				'radar' { }
			)
		}
	}

	'dashboard' {
		'cell width': number
		'cell height': number
		'grid gap': stategroup @default: 'yes' (
			'yes' number @default: 20
			'no' { }
		)
		'widgets': list {
			'x': number
			'width': number
			'y': number
			'height': number
			'widget': stategroup (
				'number' {
					'value': binding number { }
					'number size': stategroup (
						'large' { }
						'fixed' number
					)
					'unit size': stategroup @default: 'auto' (
						'auto' { }
						'fixed' number
					)
				}
				'range' feature 'range'
				'pie chart' feature 'pie chart'
				'bar chart' feature 'bar chart'
				'line chart' feature 'line chart'
				'scatter chart' feature 'scatter chart'
				'radar chart' feature 'radar chart'
				'legend' stategroup (
					'static' list {
						'label': text
						'color': feature 'color'
					}
					'dynamic' binding nodes {
						'label': binding text { }
					}
				)
			)
		}
	}

	'dashboard 2' {
		'permissions': feature 'permissions'
		'widgets': list stategroup (
			'grouped charts' feature 'grouped charts'
			'tabs' list {
				'name': text
				'widget': feature 'dashboard 2'
			}
		)
	}

	'grouped charts' component binding nodes {
		'include filters': stategroup @default: 'yes' (
			'yes' { }
			'no' { }
		)
		'filters': feature 'optional filters'
		'custom limit': feature 'optional query limit'
		'custom sorting': feature 'optional sorting'
		'charts': list stategroup (
			'2d chart' feature '2d chart query'
			'filter' stategroup @strategy (
				'text' binding text { }
				'number' binding number { }
				'stategroup' binding stategroup { }
			)
			'table' {
				'title': text
				'size': stategroup @default: 'small' (
					'small' { }
					'medium' { }
					'large' { }
				)
				'properties': feature 'custom table properties'
			}
		)
	}

	'label' component list non-empty stategroup @strategy (
		'static text' text
		'dynamic text' binding text { }
		'dynamic number' binding number { }
	)

	'optional label' component stategroup @default: 'not set' @strategy (
		'set' feature 'label'
		'not set' none
	)

	'labels' component list non-empty {
		'label': feature 'label'
	}

	'optional labels' component stategroup @default: 'not set' @strategy (
		'set' feature 'labels'
		'not set' none
	)

	'label with tooltip' component {
		'label': feature 'label'
		'tooltip': feature 'optional label'
	}

	'optional label with tooltip' component stategroup @default: 'not set' @strategy (
		'set' feature 'label with tooltip'
		'not set' none
	)

	'query property key' component stategroup @default: 'auto' @strategy (
		'auto' { }
		'set' text
	)

	/// The properties of a query are its columns: they are shown in the table and can be
	/// sorted and filtered. Every property has a label, and 'number', 'text' and
	/// 'stategroup' properties also take a filter.
	'properties' component list stategroup @strategy (
		'stategroup' binding stategroup {
			'label': stategroup @default: 'auto' @strategy (
				'auto' { }
				'set' text
			)
			/// Filter on the state of this stategroup.
			'filter': stategroup @default: 'auto' (
				/// Let the generator decide: a filter on a query or a custom query, and no
				/// filter on the query of a 'node' feature, which is a detail view of a
				/// single node.
				'auto' { }
				/// Only show entries that are in one of the 'states to include'. The user
				/// cannot change this filter.
				'yes' {
					'states to include': list binding state { }
				}
				/// The user selects the states to include.
				'simple' { }
				/// Do not filter on this property.
				'none' none
			)
		}
		'number' binding number {
			'label': feature 'query property key'
			'filter': feature 'optional number filter'
		}
		'text' binding text {
			'label': feature 'query property key'
			'filter': feature 'optional text filter'
		}
		'file' binding file {
			'label': feature 'query property key'
		}
		'collection' binding collection {
			'label': feature 'query property key'
		}
		'command' binding command {
			'label': feature 'query property key'
		}
		'action' binding action {
			'label': feature 'query property key'
		}
	)

	// Common features
	/// A number filter is added as a filter on the query column. Use 'simple' to let the
	/// user choose the operator and the value, or 'yes' to set a fixed criteria that the
	/// user cannot change.
	'number filter' component stategroup @default: 'simple' (
		/// A fixed criteria: an operator plus the value to compare against. That value is
		/// either a 'static' number, or 'now' (the current time) with an optional
		/// 'offset'. The offset is in the unit of the bound number property and can be
		/// negative to select a moment in the past.
		'yes' {
			'operator': stategroup (
				'smaller' { }
				'smaller equal' { }
				'greater' { }
				'greater equal' { }
				'equal' { }
			)
			'criteria': stategroup (
				'now' stategroup @strategy (
					'no offset' { }
					'offset' number
				)
				'static' number
			)
		}
		/// The user chooses the operator and the value.
		'simple' { }
	)

	/// A text filter is added as a filter on the query column. The default 'auto' lets the
	/// generator pick the most suitable filter for the property.
	'text filter' component stategroup @default: 'auto' (
		/// Let the generator pick the filter: a list of the referenced entries to select
		/// from when the text is a reference, a text search otherwise.
		'auto' { }
		/// A fixed search pattern that the user cannot change.
		'yes' {
			'criteria': text
			/// Use 'not in' to exclude the rows that match the criteria.
			'operator': stategroup @default: 'in' (
				'in' { }
				'not in' { }
			)
		}
		/// A text search that the user fills in.
		'simple' {
			/// Use 'not in' to exclude the rows that match what the user fills in.
			'operator': stategroup @default: 'in' (
				'in' { }
				'not in' { }
			)
		}
		/// A list of the referenced entries, to either include ('in') or exclude
		/// ('not in'). Only for texts that are references; other texts get no filter.
		'containment' {
			'operator': stategroup (
				'in' { }
				'not in' { }
			)
		}
	)

	// Optional variants of the filters above, adding the option of no filter at all. Only
	// 'properties' uses these, so they are not part of the common features.
	/// Filter on this number property, or use 'none' for a column without a filter.
	'optional number filter' component stategroup @default: 'auto' @strategy (
		/// Let the generator decide: a simple filter on a query or a custom query, and no
		/// filter on the query of a 'node' feature, which is a detail view of a single
		/// node. This is the default, so a property without a filter needs no
		/// configuration.
		'auto' { }
		'set' feature 'number filter'
		/// Do not filter on this property.
		'none' none
	)

	/// Filter on this text property, or use 'none' for a column without a filter.
	'optional text filter' component stategroup @default: 'auto' @strategy (
		/// Let the generator decide: the filter of the 'auto' state of 'text filter' on a
		/// query or a custom query, and no filter on the query of a 'node' feature, which
		/// is a detail view of a single node. This is the default, so a property without a
		/// filter needs no configuration.
		'auto' { }
		'set' feature 'text filter'
		/// Do not filter on this property.
		'none' none
	)

	// Common feature
	'filters' component list non-empty stategroup @strategy (
		'stategroup' binding stategroup {
			// A component feature cannot be referenced from a stategroup context, so the label
			// and the filter are inlined here.
			'label': stategroup @default: 'auto' @strategy (
				'auto' { }
				'set' text
			)
			'filter': stategroup @default: 'simple' (
				'yes' {
					'states to include': list binding state { }
				}
				'simple' { }
			)
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
		'number' binding number {
			'label': feature 'query property key'
			'filter': feature 'number filter'
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
		'text' binding text {
			'label': feature 'query property key'
			'filter': feature 'text filter'
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
	)

	'optional filters' component stategroup @default: 'not set' @strategy (
		'set' feature 'filters'
		'not set' none
	)

	'node' component list non-empty stategroup (
		'number' binding number { }
		'text' binding text { }
		'stategroup' binding states {
			'node': feature 'optional node'
		}
		'file' binding file { }
		'command' binding command { }
		'query' {
			'label': feature 'label'
			'query': feature 'query'
		}
		'chart' {
			'label': feature 'label'
			'type': stategroup (
				'range' feature 'range'
				'pie chart' feature 'pie chart'
				'bar chart' feature 'bar chart'
				'line chart' feature 'line chart'
				'scatter chart' feature 'scatter chart'
				'radar chart' feature 'radar chart'
			)
		}
	)

	'query' component binding nodes {
		'custom sorting': feature 'optional sorting'
		'custom limit': feature 'optional query limit'
		'filters': feature 'optional filters'
		'properties': feature 'properties'
	}

	// Common feature
	'sorting' component {
		'direction': stategroup (
			'ascending' { }
			'descending' { }
		)
		'type': stategroup (
			'number' binding number { }
			'text' binding text { }
			'stategroup' binding stategroup { }
		)
	}

	'optional sorting' component stategroup @default: 'not set' @strategy (
		'set' feature 'sorting'
		'not set' none
	)

	'query limit' component {
		'sample': stategroup @default: 'no' (
			'yes' {
				'sample size': number
				'sample limit': number
			}
			'no' { }
		)
		'absolute limit': number
	}

	'optional query limit' component stategroup @default: 'not set' @strategy (
		'set' feature 'query limit'
		'not set' none
	)

	'flowchart' {
		'orientation': stategroup (
			'horizontal' none
			'vertical' none
		)
		'collection': binding nodes {
			'label': binding text { } //todo: make label
			'background color': feature 'planning color'
			'predecessors': binding nodes {
				'reference': binding text reference { }
			}
		}
	}

	// Planning features
	'planning holidays' component binding nodes {
		'holiday': binding text { }
		'start and end': feature 'start and end'
		'highlight background': stategroup @default: 'yes' (
			'yes' { }
			'state dependent' binding stategroup {
				'state to highlight': binding state { }
			}
			'no' { }
		)
	}

	'optional planning holidays' component stategroup @default: 'not set' @strategy (
		'set' feature 'planning holidays'
		'not set' none
	)

	'global property' component stategroup @strategy (
		'number' binding number { }
		'text' binding text { }
		'command' binding command { }
		'label' feature 'label'
	)

	'planning' component {
		'label': text
		'permissions': feature 'permissions'
		'default zoom level': feature 'zoom level'
		'has more zoom levels': stategroup @default: 'no' (
			'yes' list non-empty feature 'zoom level'
			'no' { }
		)
		'type': stategroup @default: 'default' (
			'default' { }
			'specific start and end' feature 'start and end'
			'user start and end' binding user {
				'start': binding number { }
				'end': binding number { }
			}
		)
		'windows': list non-empty {
			'label': text
			'rows': feature 'rows list'
			'bottom rows': feature 'optional custom rows'
		}
		'global properties': list feature 'global property'
		'holidays': feature 'optional planning holidays'
	}

	'planning rows' component binding nodes {
		'is reference set': feature 'is reference set'
		'items': feature 'planning items'
		'background items': feature 'optional background items'
		'children': feature 'optional rows list'
		'filters': feature 'optional filters'
		'custom sorting': feature 'optional sorting'
		'custom query limit': feature 'optional simple query limit' // default: 1000
		'label': feature 'label'
		'right label': feature 'optional label with tooltip'
		'node filters': feature 'optional node filters'
		'color': feature 'planning color'
		'background color': feature 'planning color'
		'tooltip': feature 'optional label'
		'start and end filter': feature 'optional start and end'
		'popup': feature 'optional popup'
		'entry creation command': feature 'optional entry creation'
		'partitioning': feature 'optional partitioning'
		'filters dependent on children or items': feature 'filters dependent on children or items'
		'icon bar': feature 'optional icon bar'
	}

	'optional rows' component stategroup @default: 'not set' @strategy (
		'set' feature 'planning rows'
		'not set' none
	)

	'rows list' component stategroup @strategy (
		'multiple' list non-empty feature 'planning rows'
		'singular' feature 'planning rows'
	)

	'optional rows list' component stategroup @default: 'not set' @strategy (
		'multiple' list non-empty feature 'planning rows'
		'singular' feature 'planning rows'
		'not set' none
	)

	'planning items' component binding nodes {
		'is reference set': feature 'is reference set'
		'start and end': feature 'start and end'
		'label': feature 'optional label'
		'more labels': feature 'optional labels'
		'filters': feature 'optional filters'
		'node filters': feature 'optional node filters'
		'color': feature 'planning color'
		'tooltip': feature 'optional label'
		'context menu': feature 'optional context menu'
		'custom sorting': feature 'optional sorting'
		'custom query limit': feature 'optional simple query limit' // default: 1000
		'draggable': feature 'optional draggable'
		'popup': feature 'optional popup'
		'icon bar': feature 'optional icon bar'
	}

	'planning background items' component binding nodes {
		'is reference set': feature 'is reference set'
		'start and end': feature 'start and end'
		'label': feature 'optional label'
		'filters': feature 'optional filters'
		'node filters': feature 'optional node filters'
		'color': feature 'planning color'
		'tooltip': feature 'optional label'
		'context menu': feature 'optional context menu'
		'custom query limit': feature 'optional simple query limit' // default: 1000
	}

	'optional background items' component stategroup @default: 'not set' @strategy (
		'set' feature 'planning background items'
		'not set' none
	)

	'planning custom rows' component list {
		'label': feature 'label'
		'tooltip': feature 'optional label'
		'items': feature 'planning items'
		'background items': feature 'optional background items'
	}

	'optional custom rows' component stategroup @default: 'not set' @strategy (
		'set' feature 'planning custom rows'
		'not set' none
	)

	'start and end' component {
		'start': binding number { }
		'end': binding number { }
	}

	'optional start and end' component stategroup @default: 'not set' @strategy (
		'set' feature 'start and end'
		'not set' none
	)

	'optional entry creation' component stategroup @default: 'not set' @strategy (
		'set' {
			'create': feature 'create command'
			'alternative create command': feature 'optional create command'
			'draggable': stategroup (
				'yes' { }
				'no' { }
			)
		}
		'not set' none
	)

	'create command' component binding command {
		'start': binding number { }
		'end': binding number { }
		'snap': feature 'snap'
	}

	'optional create command' component stategroup @default: 'not set' @strategy (
		'set' feature 'create command'
		'not set' none
	)

	'partitioning' component stategroup (
		'static' stategroup (
			'text' binding text { }
			'stategroup' binding stategroup { }
		)
		'dynamic' list non-empty {
			'label': text
			'type': stategroup (
				'text' binding text { }
				'stategroup' binding stategroup { }
			)
		}
	)

	'optional partitioning' component stategroup @default: 'not set' @strategy (
		'set' feature 'partitioning'
		'not set' none
	)

	'node filters' component list non-empty binding node { }

	'optional node filters' component stategroup @default: 'not set' @strategy (
		'set' feature 'node filters'
		'not set' none
	)

	'planning color' component stategroup @default: 'auto' @strategy (
		'auto' none
		'property' binding text { }
		'custom' text
	)

	'filters dependent on children or items' component { // todo: universeel maken met een query die leeg kan zijn
		'hide row when it has no items': stategroup @default: 'no' (
			'yes' { }
			'no' { }
		)
		'show option to filter by items': stategroup @default: 'no' (
			'yes' {
				'hide row when it has no items label': text @default: "Hide rows without items"
			}
			'no' { }
		)
		'hide row when it has no background items': stategroup @default: 'no' (
			'yes' { }
			'no' { }
		)
		'show option to filter by background items': stategroup @default: 'no' (
			'yes' {
				'hide row when it has no background items label': text @default: "Hide rows without background items"
			}
			'no' { }
		)
		'hide row when it has no child rows': stategroup @default: 'no' (
			'yes' { }
			'no' { }
		)
		'show option to filter by children': stategroup @default: 'no' (
			'yes' {
				'hide row when it has no child rows label': text @default: "Hide rows without child rows"
			}
			'no' { }
		)
	}

	'zoom level' component unbound {
		'name': text
		'type': stategroup (
			'month' {
				'months before current month': number @default: 1
				'months after current month': number @default: 36
				'default width': number @default: 2500
				'go left button months': number @default: 4
				'go right button months': number @default: 4
			}
			'week' {
				'weeks before current week': number @default: 1
				'weeks after current week': number @default: 52
				'default width': number @default: 2500
				'go left button weeks': number @default: 4
				'go right button weeks': number @default: 4
			}
			'day' {
				'days before today': number @default: 7
				'days after today': number @default: 31
				'default width': number @default: 2000
				'go left button days': number @default: 30
				'go right button days': number @default: 30
				'show weekends': stategroup @default: 'yes' (
					'yes' { }
					'no' { }
				)
			}
			'hour' {
				'days before today': number @default: 7
				'days after today': number @default: 31
				'default width': number @default: 15000
				'go left button days': number @default: 7
				'go right button days': number @default: 7
				'show weekends': stategroup @default: 'yes' (
					'yes' { }
					'no' { }
				)
				'show nights': stategroup @default: 'yes' (
					'yes' { }
					'no' {
						'start of day': number @default: 6
						'end of day': number @default: 18
					}
				)
			}
		)
	}

	'snap' component unbound stategroup (
		'none' { }
		'minute' number
		'day' { }
		'week' { }
		'month' { }
		'year' { }
	)

	'popup' component {
		'label': feature 'label'
		'node': feature 'node'
		'has open view button': stategroup (
			'yes' {
				'label': text
			}
			'no' { }
		)
	}

	'optional popup' component stategroup @default: 'not set' @strategy (
		'set' feature 'popup'
		'not set' none
	)

	'context menu' component list non-empty {
		'icon': stategroup @default: 'not set' @strategy (
			'set' text
			'not set' none
		)
		'command': binding command { }
	}

	'optional context menu' component stategroup @default: 'not set' @strategy (
		'set' feature 'context menu'
		'not set' none
	)

	'is reference set' component stategroup @default: 'no' @strategy (
		'yes' binding text reference { }
		'no' none
	)

	'icon bar' component list non-empty binding states {
		'tooltip': stategroup @default: 'not set' @strategy (
			'set' feature 'label'
			'not set' none
		)
	}

	'optional icon bar' component stategroup @default: 'not set' @strategy (
		'set' feature 'icon bar'
		'not set' none
	)

	'move command' component binding command {
		'start': binding number { }
		'can switch row': stategroup @default: 'no' (
			'yes' binding text reference { }
			'no' { }
		)
		'snap': feature 'snap'
	}

	'optional move command' component stategroup @default: 'not set' @strategy (
		'set' feature 'move command'
		'not set' none
	)

	'draggable' component {
		'move': feature 'move command'
		'alternative move command': feature 'optional move command'
	}

	'optional draggable' component stategroup @default: 'not set' @strategy (
		'set' feature 'draggable'
		'not set' none
	)

	'optional node' component stategroup @default: 'not set' @strategy (
		'set' feature 'node'
		'not set' none
	)

	'simple query limit' component number

	'optional simple query limit' component stategroup @default: 'not set' @strategy (
		'set' feature 'simple query limit'
		'not set' none
	)

	'custom query' component {
		'name': text
		'select by default': stategroup @default: 'no' (
			'no' { }
			'yes' { }
		)
		'include dormant filter': stategroup @default: 'no' (
			'no' { }
			'yes' { }
		)
		'custom sorting': feature 'optional sorting'
		'custom limit': feature 'optional query limit'
		'filters': feature 'optional filters'
		'properties': feature 'properties'
	}

collection-features
	'comments' {
		'author': binding text { }
		'timestamp': binding number { }
		'body': binding text { }
		'attachments': stategroup @default: 'not set' @strategy (
			'not set' none
			'set' binding collection {
				'file name': binding text { }
				'file': binding file { }
			}
		)
	}

	/// Custom filters are added as extra toggle filters on the collection. They are
	/// only added on the 'compact' and 'full' views. For custom query filters are
	/// specified separately.
	'custom filters' stategroup @default: 'not set' @strategy (
		'set' feature 'filters'
		'not set' none
	)

	'custom queries' list feature 'custom query'

stategroup-features

number-features

text-features

file-features

reference-set-features
	'custom queries' list feature 'custom query'

command-features
	'include in grid' none

action-features
	'include in grid' none

Handheld

The handheld generator.

global-features

node-features
	'text label' unbound text

	'string' component list non-empty stategroup @strategy (
		// 'icon' {
		// 	'name': text
		// }
		'static text' text
		'stategroup' binding stategroup { }
		'number' binding number { }
		'text' binding text { }
		'node' {
			'italic': stategroup @default: 'no' (
				'yes' { }
				'no' { }
			)
			'bold': stategroup @default: 'no' (
				'yes' { }
				'no' { }
			)
			'subtle': stategroup @default: 'no' (
				'yes' { }
				'no' { }
			)
			'underline': stategroup @default: 'no' (
				'yes' { }
				'no' { }
			)
			'font size': stategroup @default: 'normal' (
				'normal' { }
				'small' { }
				'large' { }
			)
			'value': feature 'string'
		}
	// 'file' binding file { }
	// 'command' binding command { }
	// 'action' binding action { }
	)

	'item list' {
		'collection': binding nodes {
			'layout': stategroup (
				'item list' {
					'properties': list non-empty feature 'string'
					'properties aside': list feature 'string'
				}
				'cards' {
					'sections': list non-empty feature 'string'
				}
			)
			'filters': feature 'optional filters'
			'sorting': feature 'optional sorting'
			'open view': stategroup @default: 'none' @strategy (
				'view' view
				'none' none
			)
		}
	}

	'optional sorting' component stategroup @default: 'not set' @strategy (
		'set' feature 'sorting'
		'not set' none
	)

	// Common feature
	'sorting' component {
		'direction': stategroup (
			'ascending' { }
			'descending' { }
		)
		'type': stategroup (
			'number' binding number { }
			'text' binding text { }
			'stategroup' binding stategroup { }
		)
	}

	'optional filters' component stategroup @default: 'not set' @strategy (
		'set' feature 'filters'
		'not set' none
	)

	// Common features
	'query property key' component stategroup @default: 'auto' @strategy (
		'auto' { }
		'set' text
	)

	/// A number filter is added as a filter on the query column. Use 'simple' to let the
	/// user choose the operator and the value, or 'yes' to set a fixed criteria that the
	/// user cannot change.
	'number filter' component stategroup @default: 'simple' (
		/// A fixed criteria: an operator plus the value to compare against. That value is
		/// either a 'static' number, or 'now' (the current time) with an optional
		/// 'offset'. The offset is in the unit of the bound number property and can be
		/// negative to select a moment in the past.
		'yes' {
			'operator': stategroup (
				'smaller' { }
				'smaller equal' { }
				'greater' { }
				'greater equal' { }
				'equal' { }
			)
			'criteria': stategroup (
				'now' stategroup @strategy (
					'no offset' { }
					'offset' number
				)
				'static' number
			)
		}
		/// The user chooses the operator and the value.
		'simple' { }
	)

	/// A text filter is added as a filter on the query column. The default 'auto' lets the
	/// generator pick the most suitable filter for the property.
	'text filter' component stategroup @default: 'auto' (
		/// Let the generator pick the filter: a list of the referenced entries to select
		/// from when the text is a reference, a text search otherwise.
		'auto' { }
		/// A fixed search pattern that the user cannot change.
		'yes' {
			'criteria': text
			/// Use 'not in' to exclude the rows that match the criteria.
			'operator': stategroup @default: 'in' (
				'in' { }
				'not in' { }
			)
		}
		/// A text search that the user fills in.
		'simple' {
			/// Use 'not in' to exclude the rows that match what the user fills in.
			'operator': stategroup @default: 'in' (
				'in' { }
				'not in' { }
			)
		}
		/// A list of the referenced entries, to either include ('in') or exclude
		/// ('not in'). Only for texts that are references; other texts get no filter.
		'containment' {
			'operator': stategroup (
				'in' { }
				'not in' { }
			)
		}
	)

	'filters' component list non-empty stategroup @strategy (
		'stategroup' binding stategroup {
			'label': stategroup @default: 'auto' @strategy (
				'auto' { }
				'set' text
			)
			'filter': stategroup @default: 'simple' (
				'yes' {
					'states to include': list binding state { }
				}
				'simple' { }
			)
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
		'number' binding number {
			'label': feature 'query property key'
			'filter': feature 'number filter'
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
		'text' binding text {
			'label': feature 'query property key'
			'filter': feature 'text filter'
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
	)

	'open view' {
		'view': view
	}

collection-features
	'table layout' none

stategroup-features
	'switch' {
		'on': binding state { }
		'off': binding state { }
	}

number-features

text-features
	/// Indicates that this text can be on a bar or qr code.
	'barcode' none

file-features

reference-set-features

command-features

action-features

Production

The production generator.

global-features
	'full screen planning' {
		'plannings': list non-empty feature 'planning'
	}

node-features
	'permissions' component stategroup @default: 'not set' @strategy (
		'user' binding user {
			'stategroup': binding stategroup {
				'state': binding state { }
			}
		}
		'node' binding node { }
		'not set' none
	)

	'label' component list non-empty stategroup @strategy (
		'static text' text
		'dynamic text' binding text { }
		'dynamic number' binding number { }
	)

	'optional label' component stategroup @default: 'not set' @strategy (
		'set' feature 'label'
		'not set' none
	)

	'labels' component list non-empty {
		'label': feature 'label'
	}

	'optional labels' component stategroup @default: 'not set' @strategy (
		'set' feature 'labels'
		'not set' none
	)

	'label with tooltip' component {
		'label': feature 'label'
		'tooltip': feature 'optional label'
	}

	'optional label with tooltip' component stategroup @default: 'not set' @strategy (
		'set' feature 'label with tooltip'
		'not set' none
	)

	// Planning features
	'planning holidays' component binding nodes {
		'holiday': binding text { }
		'start and end': feature 'start and end'
		'highlight background': stategroup @default: 'yes' (
			'yes' { }
			'state dependent' binding stategroup {
				'state to highlight': binding state { }
			}
			'no' { }
		)
	}

	'optional planning holidays' component stategroup @default: 'not set' @strategy (
		'set' feature 'planning holidays'
		'not set' none
	)

	'global property' component stategroup @strategy (
		'number' binding number { }
		'text' binding text { }
		'command' binding command { }
		'label' feature 'label'
	)

	'planning' component {
		'label': text
		'permissions': feature 'permissions'
		'default zoom level': feature 'zoom level'
		'has more zoom levels': stategroup @default: 'no' (
			'yes' list non-empty feature 'zoom level'
			'no' { }
		)
		'type': stategroup @default: 'default' (
			'default' { }
			'specific start and end' feature 'start and end'
			'user start and end' binding user {
				'start': binding number { }
				'end': binding number { }
			}
		)
		'windows': list non-empty {
			'label': text
			'rows': feature 'rows list'
			'bottom rows': feature 'optional custom rows'
		}
		'global properties': list feature 'global property'
		'holidays': feature 'optional planning holidays'
	}

	'planning rows' component binding nodes {
		'is reference set': feature 'is reference set'
		'items': feature 'planning items'
		'background items': feature 'optional background items'
		'children': feature 'optional rows list'
		'filters': feature 'optional filters'
		'custom sorting': feature 'optional sorting'
		'custom query limit': feature 'optional simple query limit' // default: 1000
		'label': feature 'label'
		'right label': feature 'optional label with tooltip'
		'node filters': feature 'optional node filters'
		'color': feature 'planning color'
		'background color': feature 'planning color'
		'tooltip': feature 'optional label'
		'start and end filter': feature 'optional start and end'
		'popup': feature 'optional popup'
		'entry creation command': feature 'optional entry creation'
		'partitioning': feature 'optional partitioning'
		'filters dependent on children or items': feature 'filters dependent on children or items'
		'icon bar': feature 'optional icon bar'
	}

	'optional rows' component stategroup @default: 'not set' @strategy (
		'set' feature 'planning rows'
		'not set' none
	)

	'rows list' component stategroup @strategy (
		'multiple' list non-empty feature 'planning rows'
		'singular' feature 'planning rows'
	)

	'optional rows list' component stategroup @default: 'not set' @strategy (
		'multiple' list non-empty feature 'planning rows'
		'singular' feature 'planning rows'
		'not set' none
	)

	'planning items' component binding nodes {
		'is reference set': feature 'is reference set'
		'start and end': feature 'start and end'
		'label': feature 'optional label'
		'more labels': feature 'optional labels'
		'filters': feature 'optional filters'
		'node filters': feature 'optional node filters'
		'color': feature 'planning color'
		'tooltip': feature 'optional label'
		'context menu': feature 'optional context menu'
		'custom sorting': feature 'optional sorting'
		'custom query limit': feature 'optional simple query limit' // default: 1000
		'draggable': feature 'optional draggable'
		'popup': feature 'optional popup'
		'icon bar': feature 'optional icon bar'
	}

	'planning background items' component binding nodes {
		'is reference set': feature 'is reference set'
		'start and end': feature 'start and end'
		'label': feature 'optional label'
		'filters': feature 'optional filters'
		'node filters': feature 'optional node filters'
		'color': feature 'planning color'
		'tooltip': feature 'optional label'
		'context menu': feature 'optional context menu'
		'custom query limit': feature 'optional simple query limit' // default: 1000
	}

	'optional background items' component stategroup @default: 'not set' @strategy (
		'set' feature 'planning background items'
		'not set' none
	)

	'planning custom rows' component list {
		'label': feature 'label'
		'tooltip': feature 'optional label'
		'items': feature 'planning items'
		'background items': feature 'optional background items'
	}

	'optional custom rows' component stategroup @default: 'not set' @strategy (
		'set' feature 'planning custom rows'
		'not set' none
	)

	'start and end' component {
		'start': binding number { }
		'end': binding number { }
	}

	'optional start and end' component stategroup @default: 'not set' @strategy (
		'set' feature 'start and end'
		'not set' none
	)

	'optional entry creation' component stategroup @default: 'not set' @strategy (
		'set' {
			'create': feature 'create command'
			'alternative create command': feature 'optional create command'
			'draggable': stategroup (
				'yes' { }
				'no' { }
			)
		}
		'not set' none
	)

	'create command' component binding command {
		'start': binding number { }
		'end': binding number { }
		'snap': feature 'snap'
	}

	'optional create command' component stategroup @default: 'not set' @strategy (
		'set' feature 'create command'
		'not set' none
	)

	'partitioning' component stategroup (
		'static' stategroup (
			'text' binding text { }
			'stategroup' binding stategroup { }
		)
		'dynamic' list non-empty {
			'label': text
			'type': stategroup (
				'text' binding text { }
				'stategroup' binding stategroup { }
			)
		}
	)

	'optional partitioning' component stategroup @default: 'not set' @strategy (
		'set' feature 'partitioning'
		'not set' none
	)

	'node filters' component list non-empty binding node { }

	'optional node filters' component stategroup @default: 'not set' @strategy (
		'set' feature 'node filters'
		'not set' none
	)

	'planning color' component stategroup @default: 'auto' @strategy (
		'auto' none
		'property' binding text { }
		'custom' text
	)

	// Common features
	'query property key' component stategroup @default: 'auto' @strategy (
		'auto' { }
		'set' text
	)

	/// A number filter is added as a filter on the query column. Use 'simple' to let the
	/// user choose the operator and the value, or 'yes' to set a fixed criteria that the
	/// user cannot change.
	'number filter' component stategroup @default: 'simple' (
		/// A fixed criteria: an operator plus the value to compare against. That value is
		/// either a 'static' number, or 'now' (the current time) with an optional
		/// 'offset'. The offset is in the unit of the bound number property and can be
		/// negative to select a moment in the past.
		'yes' {
			'operator': stategroup (
				'smaller' { }
				'smaller equal' { }
				'greater' { }
				'greater equal' { }
				'equal' { }
			)
			'criteria': stategroup (
				'now' stategroup @strategy (
					'no offset' { }
					'offset' number
				)
				'static' number
			)
		}
		/// The user chooses the operator and the value.
		'simple' { }
	)

	/// A text filter is added as a filter on the query column. The default 'auto' lets the
	/// generator pick the most suitable filter for the property.
	'text filter' component stategroup @default: 'auto' (
		/// Let the generator pick the filter: a list of the referenced entries to select
		/// from when the text is a reference, a text search otherwise.
		'auto' { }
		/// A fixed search pattern that the user cannot change.
		'yes' {
			'criteria': text
			/// Use 'not in' to exclude the rows that match the criteria.
			'operator': stategroup @default: 'in' (
				'in' { }
				'not in' { }
			)
		}
		/// A text search that the user fills in.
		'simple' {
			/// Use 'not in' to exclude the rows that match what the user fills in.
			'operator': stategroup @default: 'in' (
				'in' { }
				'not in' { }
			)
		}
		/// A list of the referenced entries, to either include ('in') or exclude
		/// ('not in'). Only for texts that are references; other texts get no filter.
		'containment' {
			'operator': stategroup (
				'in' { }
				'not in' { }
			)
		}
	)

	'filters' component list non-empty stategroup @strategy (
		'stategroup' binding stategroup {
			'label': stategroup @default: 'auto' @strategy (
				'auto' { }
				'set' text
			)
			'filter': stategroup @default: 'simple' (
				'yes' {
					'states to include': list binding state { }
				}
				'simple' { }
			)
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
		'number' binding number {
			'label': feature 'query property key'
			'filter': feature 'number filter'
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
		'text' binding text {
			'label': feature 'query property key'
			'filter': feature 'text filter'
			'show': stategroup @default: 'yes' (
				'yes' { }
				'no' { }
			)
		}
	)

	'optional filters' component stategroup @default: 'not set' @strategy (
		'set' feature 'filters'
		'not set' none
	)

	'filters dependent on children or items' component { // todo: universeel maken met een query die leeg kan zijn
		'hide row when it has no items': stategroup @default: 'no' (
			'yes' { }
			'no' { }
		)
		'show option to filter by items': stategroup @default: 'no' (
			'yes' {
				'hide row when it has no items label': text @default: "Hide rows without items"
			}
			'no' { }
		)
		'hide row when it has no background items': stategroup @default: 'no' (
			'yes' { }
			'no' { }
		)
		'show option to filter by background items': stategroup @default: 'no' (
			'yes' {
				'hide row when it has no background items label': text @default: "Hide rows without background items"
			}
			'no' { }
		)
		'hide row when it has no child rows': stategroup @default: 'no' (
			'yes' { }
			'no' { }
		)
		'show option to filter by children': stategroup @default: 'no' (
			'yes' {
				'hide row when it has no child rows label': text @default: "Hide rows without child rows"
			}
			'no' { }
		)
	}

	'zoom level' component unbound {
		'name': text
		'type': stategroup (
			'month' {
				'months before current month': number @default: 1
				'months after current month': number @default: 36
				'default width': number @default: 2500
				'go left button months': number @default: 4
				'go right button months': number @default: 4
			}
			'week' {
				'weeks before current week': number @default: 1
				'weeks after current week': number @default: 52
				'default width': number @default: 2500
				'go left button weeks': number @default: 4
				'go right button weeks': number @default: 4
			}
			'day' {
				'days before today': number @default: 7
				'days after today': number @default: 31
				'default width': number @default: 2000
				'go left button days': number @default: 30
				'go right button days': number @default: 30
				'show weekends': stategroup @default: 'yes' (
					'yes' { }
					'no' { }
				)
			}
			'hour' {
				'days before today': number @default: 7
				'days after today': number @default: 31
				'default width': number @default: 15000
				'go left button days': number @default: 7
				'go right button days': number @default: 7
				'show weekends': stategroup @default: 'yes' (
					'yes' { }
					'no' { }
				)
				'show nights': stategroup @default: 'yes' (
					'yes' { }
					'no' {
						'start of day': number @default: 6
						'end of day': number @default: 18
					}
				)
			}
		)
	}

	'snap' component unbound stategroup (
		'none' { }
		'minute' number
		'day' { }
		'week' { }
		'month' { }
		'year' { }
	)

	'popup' component {
		'label': feature 'label'
		'node': feature 'node'
		'has open view button': stategroup (
			'yes' {
				'label': text
			}
			'no' { }
		)
	}

	'optional popup' component stategroup @default: 'not set' @strategy (
		'set' feature 'popup'
		'not set' none
	)

	'context menu' component list non-empty {
		'icon': stategroup @default: 'not set' @strategy (
			'set' text
			'not set' none
		)
		'command': binding command { }
	}

	'optional context menu' component stategroup @default: 'not set' @strategy (
		'set' feature 'context menu'
		'not set' none
	)

	'is reference set' component stategroup @default: 'no' @strategy (
		'yes' binding text reference { }
		'no' none
	)

	'icon bar' component list non-empty binding states {
		'tooltip': stategroup @default: 'not set' @strategy (
			'set' feature 'label'
			'not set' none
		)
	}

	'optional icon bar' component stategroup @default: 'not set' @strategy (
		'set' feature 'icon bar'
		'not set' none
	)

	'move command' component binding command {
		'start': binding number { }
		'can switch row': stategroup @default: 'no' (
			'yes' binding text reference { }
			'no' { }
		)
		'snap': feature 'snap'
	}

	'optional move command' component stategroup @default: 'not set' @strategy (
		'set' feature 'move command'
		'not set' none
	)

	'draggable' component {
		'move': feature 'move command'
		'alternative move command': feature 'optional move command'
	}

	'optional draggable' component stategroup @default: 'not set' @strategy (
		'set' feature 'draggable'
		'not set' none
	)

	'node' component list non-empty stategroup (
		'number' binding number { }
		'text' binding text { }
		'stategroup' binding states {
			'node': feature 'optional node'
		}
		'file' binding file { }
		'command' binding command { }
		'query' { }
		'chart' { }
	)

	'optional node' component stategroup @default: 'not set' @strategy (
		'set' feature 'node'
		'not set' none
	)

	// Common feature
	'sorting' component {
		'direction': stategroup (
			'ascending' { }
			'descending' { }
		)
		'type': stategroup (
			'number' binding number { }
			'text' binding text { }
			'stategroup' binding stategroup { }
		)
	}

	'optional sorting' component stategroup @default: 'not set' @strategy (
		'set' feature 'sorting'
		'not set' none
	)

	'simple query limit' component number

	'optional simple query limit' component stategroup @default: 'not set' @strategy (
		'set' feature 'simple query limit'
		'not set' none
	)

collection-features

stategroup-features

number-features

text-features

file-features

reference-set-features

command-features

action-features

Annotations

Sparse Annotation Tree

The root node of the annotations.

'annotations': component 'extended annotations'

Global Features

Configure global features provided by the selected generator.

'has global features': stategroup (
	'yes' { [ global-features: (, ) ]
		'configured features': dictionary { [ feature ]
			'has more features': stategroup = node-switch successor (
				| node = 'yes' { 'next feature' = successor }
				| none = 'no'
			)
			'feature': [ = ] component 'feature'
		}
	}
	'no' { }
)

Handheld Menu

'handheld menu': stategroup (
	'yes' { [ handheld-menu (, ) ]
		'items': dictionary {
			'icon': [ icon: ] text
			'view': component 'handheld view selector'
			'has more items': stategroup = node-switch successor (
				| node = 'yes' { 'next item' = successor }
				| none = 'no'
			)
		}
	}
	'no' { }
)
'singular path' {
	'has steps': stategroup (
		'yes' {
			'type': stategroup (
				'group' {
					'group': [ . ] reference
				}
			)
			'tail': component 'singular path'
		}
		'no' { }
	)
}
'conditional path' {
	'head': component 'singular path'
	'has steps': stategroup (
		'yes' {
			'type': stategroup (
				'state' {
					'state group': [ . ] reference
					'state': [ ? ] reference
				}
			)
			'tail': component 'conditional path'
		}
		'no' { }
	)
}
'collection path' {
	'head': component 'conditional path'
	'collection': [ ., * ] reference
	'has more steps': stategroup (
		'yes' {
			'tail': component 'collection path'
		}
		'no' { }
	)
}
'node path' {
	'has steps': stategroup (
		'yes' {
			'type': stategroup (
				'parent' { [ ^ ] }
				'property' {
					'property': [ . ] reference
					'type': stategroup (
						'group' { }
						'state' {
							'state': [ ? ] reference
						}
						'rule' {
							'rule': [ & ] reference
						}
					)
				}
				'reference' {
					'reference': [ > ] reference
				}
			)
			'tail': component 'node path'
		}
		'no' { }
	)
}

Annotation Node

'extended annotations' { [ (, ) ]
	'properties': dictionary {
		'type': [ : ] stategroup (
			'command' { [ command ]
				'annotations': component 'extended annotations'
				'has features': stategroup (
					'yes' { [ @features: ]
						'command features': [ (, ) ] component 'features matrix'
					}
					'no' { }
				)
			}
			'action' { [ action ]
				'annotations': component 'extended annotations'
				'has features': stategroup (
					'yes' { [ @features: ]
						'action features': [ (, ) ] component 'features matrix'
					}
					'no' { }
				)
			}
			'group' { [ group ]
				'annotations': component 'extended annotations'
			}
			'reference set' { [ reference-set ]
				'has custom queries': stategroup (
					'yes' {
						'queries': component 'custom queries'
					}
					'no' { }
				)
				'has features': stategroup (
					'yes' { [ @features: ]
						'collection features': [ (, ) ] component 'features matrix'
					}
					'no' { }
				)
			}
			'collection' { [ collection ]
				'entries are custom properties': stategroup (
					'yes' { [ @properties ]
						'type': stategroup (
							'value' {
								'value attribute': [ value: ] reference
							}
							'node' { [ node ] }
						)
					}
					'no' { }
				)
				'show as list': stategroup (
					'yes' { [ @list: ]
						'property': [ . ] reference
					}
					'no' { }
				)
				'has custom queries': stategroup (
					'yes' {
						'queries': component 'custom queries'
					}
					'no' { }
				)
				'has features': stategroup (
					'yes' { [ @features: ]
						'collection features': [ (, ) ] component 'features matrix'
					}
					'no' { }
				)
				'annotations': component 'extended annotations'
			}
			'stategroup' { [ stategroup ]
				'has features': stategroup (
					'yes' { [ @features: ]
						'stategroup features': [ (, ) ] component 'features matrix'
					}
					'no' { }
				)
				'states': [ (, ) ] dictionary { [ | ]
					'annotations': component 'extended annotations'
				}
			}
			'number' { [ number ]
				'has features': stategroup (
					'yes' { [ @features: ]
						'number features': [ (, ) ] component 'features matrix'
					}
					'no' { }
				)
			}
			'text' { [ text ]
				'has features': stategroup (
					'yes' { [ @features: ]
						'text features': [ (, ) ] component 'features matrix'
					}
					'no' { }
				)
			}
			'file' { [ file ]
				'has features': stategroup (
					'yes' { [ @features: ]
						'file features': [ (, ) ] component 'features matrix'
					}
					'no' { }
				)
			}
		)
	}
	'has features': stategroup (
		'yes' { [ features: (, ) ]
			'feature library': dictionary { [ component ]
				'contract': [ : feature ] reference
				'feature': [ = ] component 'feature'
			}
			'node features': component 'features matrix'
		}
		'no' { }
	)
	'has handheld frames': stategroup (
		'yes' {
			'handheld frames': dictionary { [ view: ]
				'frame': component 'handheld view frame'
			}
			'main frame': reference = first
		}
		'no' { }
	)
}
'custom queries' {
	'default custom query': stategroup (
		'none' { }
		'select' {
			'query': [ default: ] reference
		}
	)
	'custom queries': dictionary { [ query ]
		'has more queries': stategroup = node-switch successor (
			| node = 'yes' { 'next query' = successor }
			| none = 'no'
		)
		'query': component 'custom query'
	}
}
'features matrix' {
	'configured features': dictionary { [ feature ]
		'has more features': stategroup = node-switch successor (
			| node = 'yes' { 'next feature' = successor }
			| none = 'no'
		)
		'feature': [ = ] component 'feature'
	}
}
'feature' {
	'type': stategroup (
		'model binding' {
			'type': stategroup (
				'user' { [ user ] }
				'node' { [ on ]
					'path': component 'node path'
				}
				'nodes' {
					'context': component 'node path'
					'path': [ on ] component 'collection path'
				}
				'property' {
					'path': component 'node path'
					'property': [ . ] reference
				}
				'state' { [ state ]
					'state': reference
				}
			)
			'feature': component 'feature node'
		}
		'states binding' { [ stategroup ]
			'path': component 'node path'
			'property': [ . ] reference
			'states': [ (, ) ] dictionary {
				'feature': component 'feature node'
			}
		}
		'node' {
			'feature': component 'feature node'
		}
		'list' {
			'has entries': stategroup (
				'yes' {
					'list': [ [, ] ] component 'feature list'
				}
				'no' { [ empty ] }
			)
		}
		'stategroup' {
			'state': reference
			'feature': component 'feature reference'
		}
		'number' {
			'value': integer
		}
		'text' {
			'type': stategroup (
				'static' {
					'value': text
				}
				'phrase' {
					'text': [ @ ] reference
				}
			)
		}
		'view' {
			'context': component 'node path'
			'type': stategroup (
				'frame' {
					'frame': [ view: ] reference
				}
				'command' {
					'command': [ (, ) ] component 'handheld view command selector'
				}
			)
		}
		'none' { }
	)
}
'feature list' {
	'entry': component 'feature reference'
	'has tail': stategroup (
		'yes' { [ , ]
			'tail': component 'feature list'
		}
		'no' { }
	)
}
'feature reference' {
	'type': stategroup (
		'reference' {
			'feature': [ ! ] reference
		}
		'inline' {
			'feature': component 'feature'
		}
	)
}
'feature node' {
	'properties': [ (, ) ] dictionary {
		'feature': [ = ] component 'feature reference'
	}
}
'custom query' {
	'custom limit': stategroup (
		'yes' { [ limit: ]
			'sample': stategroup (
				'yes' {
					'sample size': integer
					'sample limit': [ /, / ] integer
				}
				'no' { }
			)
			'absolute limit': integer
		}
		'no' { }
	)
	'properties': [ [, ] ] dictionary {
		'has more properties': stategroup = node-switch successor (
			| node = 'yes' { 'next property' = successor }
			| none = 'no'
		)
		'context path': [ -> ] component 'node path'
		'type': [ : ] stategroup (
			'stategroup' { [ stategroup ]
				'property': reference
				'filter': stategroup (
					'yes' { [ filter ]
						'states': [ ? ] dictionary { [ | ]
							'is selected': stategroup (
								'yes' { [ selected ] }
								'no' { }
							)
						}
					}
					'simple' { [ filter simple ] }
					'no' { }
				)
			}
			'number' { [ number ]
				'property': reference
				'filter': stategroup (
					'yes' { [ filter ]
						'operator': stategroup (
							'smaller' { [ < ] }
							'smaller equal' { [ <= ] }
							'greater' { [ > ] }
							'greater equal' { [ >= ] }
							'equal' { [ == ] }
						)
						'criteria': stategroup (
							'now' { [ now ]
								'offset': [ + ] integer
							}
							'static' {
								'value': integer
							}
						)
					}
					'simple' { [ filter simple ] }
					'no' { }
				)
			}
			'text' { [ text ]
				'property': reference
				'filter': stategroup (
					'yes' { [ filter ]
						'criteria': text
					}
					'simple' { [ filter simple ] }
					'containment' { [ filter, selection ]
						'operator': stategroup (
							'in' { [ in ] }
							'not in' { [ not in ] }
						)
					}
					'no' { }
				)
			}
			'file' { [ file ]
				'property': reference
			}
			'action' { [ action ]
				'attribute': reference
			}
			'command' { [ command ]
				'attribute': reference
			}
		)
	}
	'custom sorting': stategroup (
		'yes' {
			'direction': stategroup (
				'ascending' { [ @ascending: ] }
				'descending' { [ @descending: ] }
			)
			'type': stategroup (
				'number' {
					'number': [ # ] reference
				}
				'text' {
					'text': [ . ] reference
				}
			)
		}
		'no' { }
	)
}

Handheld

'annotated collection selector' {
	'has step': stategroup (
		'yes' {
			'property': [ . ] reference
			'type': stategroup (
				'group' { }
				'collection' { [ [] ] }
				'state' {
					'state': [ ? ] reference
				}
			)
			'tail': component 'annotated collection selector'
		}
		'no' {
			'property': [ ., [] ] reference
		}
	)
}
'handheld frame selector' {
	'type': stategroup (
		'inline' {
			'frame': component 'handheld view frame'
		}
		'annotated' {
			'frame': reference
		}
	)
}
'handheld view details' {
	'title': stategroup (
		'yes' {
			'property': [ title: ] component 'handheld view descriptor'
		}
		'no' { }
	)
	'fields': dictionary {
		'has more fields': stategroup = node-switch successor (
			| node = 'yes' { 'next field' = successor }
			| none = 'no'
		)
		'emphasis': [ : ] stategroup (
			'yes' { [ @emphasis ] }
			'no' { }
		)
		'property': component 'handheld view descriptor'
	}
}
'handheld view elements' {
	'has element': stategroup (
		'yes' {
			'type': [ -> ] stategroup (
				'label' { [ label: ]
					'text': reference
				}
				'inline view' { [ inline view: ]
					'frame': component 'handheld frame selector'
				}
				'view' {
					'view': component 'handheld view selector'
				}
			)
			'tail': component 'handheld view elements'
		}
		'no' { }
	)
}
'handheld view frame' {
	'type': stategroup (
		'decision' { [ (, ) ]
			'options': dictionary {
				'has more options': stategroup = node-switch successor (
					| node = 'yes' { 'next option' = successor }
					| none = 'no'
				)
				'emphasis': [ : ] stategroup (
					'low' { }
					'medium' { [ @emphasis: medium ] }
					'high' { [ @emphasis: high ] }
				)
				'frame': component 'handheld frame selector'
			}
		}
		'collection navigation' { [ collection (, ) ]
			'collection context': [ path: ] stategroup (
				'this' { }
				'root' { [ root ] }
			)
			'path': component 'annotated collection selector'
			'annotations': group {
				'header': stategroup (
					'yes' { }
					'no' { [ @no-header ] }
				)
				'layout': stategroup (
					'set' { [ @layout: ]
						'type': stategroup (
							'tabular' { [ tabular ] }
							'cards' { [ cards ] }
							'buttons' { [ buttons ] }
							'partition' { [ partition ]
								'path': [ on ] component 'node path'
								'property': [ . ] reference
								'type': stategroup (
									'simple' { }
									'year' { [ by, year ] }
									'month' { [ by, month ] }
									'week' { [ by, week ] }
									'day' { [ by, day ] }
									'hour' { [ by, hour ] }
								)
							}
						)
					}
					'default' { }
				)
			}
			'details': stategroup (
				'yes' {
					'details': component 'handheld view details'
				}
				'no' {
					'property': [ label: ] component 'handheld view descriptor'
				}
			)
			'filters': stategroup (
				'yes' { [ filters: ]
					'fields': dictionary {
						'has more fields': stategroup = node-switch successor (
							| node = 'yes' { 'next field' = successor }
							| none = 'no'
						)
						'path': [ : ] component 'node path'
						'property': [ . ] reference
						'type': stategroup (
							'stategroup' {
								'filter': stategroup (
									'state' {
										'state': [ is ] reference
									}
									'not state' {
										'state': [ is not ] reference
									}
									'states' {
										'states': [ is (, ) ] dictionary { }
									}
									'simple' { [ stategroup ] }
								)
							}
							'number' {
								'filter': stategroup (
									'match' {
										'operator': stategroup (
											'smaller' { [ smaller ] }
											'smaller equal' { [ smaller or equal ] }
											'greater' { [ greater ] }
											'greater equal' { [ greater or equal ] }
											'equal' { [ equal ] }
										)
										'type': [ than ] stategroup (
											'now' { [ now + ] }
											'static' { }
										)
										'offset': integer
									}
									'simple' { [ number ] }
								)
							}
							'text' {
								'filter': stategroup (
									'pattern' {
										'criteria': [ match ] text
									}
									'simple' { [ text ] }
									'current node' { [ this ] }
									'containment' { [ filter, selection ]
										'operator': stategroup (
											'in' { [ in ] }
											'not in' { [ not in ] }
										)
									}
								)
							}
						)
					}
				}
				'no' { }
			)
			'sort': stategroup (
				'yes' { [ sort: ]
					'path': component 'node path'
					'property': component 'handheld view property'
					'direction': stategroup (
						'ascending' { [ ascending ] }
						'descending' { [ descending ] }
					)
				}
				'no' { }
			)
			'action': stategroup (
				'yes' { [ view: ]
					'emphasis': stategroup (
						'low' { }
						'medium' { [ @emphasis: medium ] }
						'high' { [ @emphasis: high ] }
					)
					'view': component 'handheld view selector'
				}
				'no' { }
			)
		}
		'group navigation' {
			'property': [ . ] reference
			'frame': component 'handheld frame selector'
		}
		'state navigation' {
			'property': [ . ] reference
			'state': [ ? ] reference
			'frame': component 'handheld frame selector'
		}
		'reference navigation' {
			'text': [ > ] reference
			'frame': component 'handheld frame selector'
		}
		'pivot view' { [ pivot (, ) ]
			'options': group { dynamic-order
				'show crosshair': stategroup (
					'yes' { [ @crosshair ] }
					'no' { }
				)
				'show grid': stategroup (
					'yes' { [ @grid ] }
					'no' { }
				)
			}
			'columns': [ columns: . ] reference
			'column grouping': [ : ] reference
			'column label': [ label: ] component 'handheld view descriptor'
			'cells': [ cells: . ] reference
			'row grouping': [ : ] reference
			'row label': [ label: ] component 'handheld view descriptor'
			'content': stategroup (
				'inline view' { [ inline view: ]
					'frame': component 'handheld frame selector'
				}
				'label' { [ label: ]
					'property': component 'handheld view descriptor'
				}
			)
		}
		'detail view' { [ details (, ) ]
			'details': component 'handheld view details'
			'elements': component 'handheld view elements'
		}
		'command view' { [ command (, ) ]
			'details': component 'handheld view details'
			'selection': [ view: ] stategroup (
				'button' {
					'emphasis': stategroup (
						'low' { }
						'medium' { [ @emphasis: medium ] }
						'high' { [ @emphasis: high ] }
					)
				}
				'scan' { [ @scan (, ) ]
					'referencer': group {
						'path': component 'conditional path'
						'collection': [ ., [] ] reference
					}
					'path': [ using: ] component 'conditional path'
					'search property': [ . ] reference
				}
			)
			'view': component 'handheld view selector'
		}
		'feature' {
			'definition': [ feature ] reference
			'feature': component 'feature'
		}
	)
}
'handheld view descriptor' {
	'type': stategroup (
		'dynamic' {
			'property': component 'handheld view property selector'
		}
		'static text' {
			'text': reference
		}
		'icon' {
			'icon name': [ icon: ] text
		}
	)
	'has tail': stategroup (
		'yes' { [ , ]
			'tail': component 'handheld view descriptor'
		}
		'no' { }
	)
}
'handheld view property' {
	'property': [ . ] reference
	'style': stategroup (
		'default' { }
		'barcode' { [ @barcode: ]
			'barcode type': stategroup (
				'CODE128' { [ CODE128 ] }
				'CODE128A' { [ CODE128A ] }
				'CODE128B' { [ CODE128B ] }
				'CODE128C' { [ CODE128C ] }
				'EAN13' { [ EAN13 ] }
				'EAN8' { [ EAN8 ] }
				'EAN5' { [ EAN5 ] }
				'EAN2' { [ EAN2 ] }
				'UPCA' { [ UPCA ] }
				'UPCE' { [ UPCE ] }
				'CODE39' { [ CODE39 ] }
				'ITF' { [ ITF ] }
				'ITF14' { [ ITF14 ] }
				'MSI10' { [ MSI10 ] }
				'MSI11' { [ MSI11 ] }
				'MSI1010' { [ MSI1010 ] }
				'MSI1110' { [ MSI1110 ] }
				'Pharmacode' { [ Pharmacode ] }
				'Codabar' { [ Codabar ] }
			)
		}
		'qrcode' { [ @qrcode ] }
	)
}
'handheld view property selector' {
	'type': stategroup (
		'switch' {
			'stategroup': [ . ] reference
			'states': [ (, ) ] dictionary { [ | ]
				'tail': component 'handheld view property selector'
			}
		}
		'step' {
			'type': stategroup (
				'group' {
					'group': [ . ] reference
				}
				'state' {
					'stategroup': [ . ] reference
					'state': [ ? ] reference
				}
				'reference' {
					'text': [ > ] reference
				}
				'parent' { [ ^ ] }
			)
			'tail': component 'handheld view property selector'
		}
		'property' {
			'property': component 'handheld view property'
		}
	)
}
'handheld command annotations' { [ (, ) ]
	'properties': dictionary {
		'type': [ : ] stategroup (
			'group' { [ group ]
				'parameters': component 'handheld command annotations'
			}
			'collection' { [ collection ]
				'parameters': component 'handheld command annotations'
			}
			'stategroup' { [ stategroup (, ) ]
				'states': dictionary {
					'parameters': component 'handheld command annotations'
				}
			}
			'text' { [ text ]
				'style': stategroup (
					'buttons' { [ @button ] }
					'keyboard' { [ @keyboard ] }
					'default' { }
					'scan' { [ @scan using: ]
						'path': component 'conditional path'
						'search property': [ . ] reference
					}
				)
			}
		)
	}
}
'handheld view context' {
	'has step': stategroup (
		'yes' { [ ^ ]
			'tail': component 'handheld view context'
		}
		'no' { }
	)
}
'handheld view selector' {
	'type': stategroup (
		'switch' {
			'property': [ . ] reference
			'states': [ (, ) ] dictionary { [ | ]
				'tail': component 'handheld view selector'
			}
		}
		'step' {
			'type': stategroup (
				'group' {
					'property': [ . ] reference
				}
				'state' {
					'property': [ . ] reference
					'state': [ ? ] reference
				}
				'reference' {
					'text': [ > ] reference
				}
			)
			'tail': component 'handheld view selector'
		}
		'command' {
			'annotation': component 'handheld view command selector'
		}
		'view' {
			'emphasis': [ : ] stategroup (
				'low' { }
				'medium' { [ @emphasis: medium ] }
				'high' { [ @emphasis: high ] }
			)
			'context': component 'handheld view context'
			'frame': component 'handheld frame selector'
			'title': stategroup (
				'custom' { [ title: ]
					'property': component 'handheld view descriptor'
				}
				'default' { }
			)
		}
	)
}
'handheld view command selector' {
	'command': [ command: ] reference
	'annotations': group { dynamic-order
		'auto execute': stategroup (
			'yes' { [ @auto-execute ] }
			'no' { }
		)
		'close after execute': stategroup (
			'yes' { }
			'no' { [ @keep-open ] }
		)
		'emphasis': stategroup (
			'low' { }
			'medium' { [ @emphasis: medium ] }
			'high' { [ @emphasis: high ] }
		)
	}
	'parameter annotations': stategroup (
		'yes' {
			'parameters': component 'handheld command annotations'
		}
		'no' { }
	)
	'open view': stategroup (
		'yes' { [ and ]
			'context': component 'handheld view context'
			'frame': component 'handheld frame selector'
		}
		'no' { }
	)
	'has details': stategroup (
		'no' { }
		'yes' { [ details (, ) ]
			'details': component 'handheld view details'
			'elements': component 'handheld view elements'
		}
	)
}