diff --git a/hydra-gates/scripts/schemas/app-manifest-v2.schema.json b/hydra-gates/scripts/schemas/app-manifest-v2.schema.json index da3af5b4..426e2cc9 100644 --- a/hydra-gates/scripts/schemas/app-manifest-v2.schema.json +++ b/hydra-gates/scripts/schemas/app-manifest-v2.schema.json @@ -3,7 +3,7 @@ "$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json", "title": "Conduction App Manifest v2", "description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).", - "version": "2.138.0", + "version": "2.168.0", "type": "object", "required": ["$schema", "version"], "allOf": [ @@ -120,6 +120,8 @@ "optionsByParent": { "type": "object", "description": "choice: map of parent-choice value → options[] for a dependent (dependsOn) choice. Same option item shape as `options`.", "additionalProperties": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["value", "label"], "properties": { "value": {}, "label": { "type": "string" } } } } }, "suggestFrom": { "type": "string", "description": "choice: the configKey of an earlier choice this step pre-fills a suggestion from. When that value is set and the user has not yet picked here, the option matching suggestMap[parentValue] is auto-selected (still user-overridable)." }, "default": { "description": "choice: the option value chosen when the step opens (an array of values with `multiple`). Not applied to a step the server already completed." }, + "optionalLabels": { "type": "boolean", "description": "config-fields: a field the step's schema does not require reads \"(optional)\" after its label (PqInstellen, setup-wizard-optional-skip)." }, + "skippable": { "type": "boolean", "description": "The wizard offers \"Skip this step\": it moves on without validating or saving the step (PqInstellen, setup-wizard-optional-skip)." }, "noteStyle": { "type": "string", "enum": ["line", "card"], "default": "line", "description": "choice: how `note` is drawn: a muted line under the options (`line`) or an info note card (`card`)." }, "suggestMap": { "type": "object", "description": "choice: map of suggestFrom value → suggested option value. Used to auto-suggest a default (e.g. RGS template suggested from organisation type).", "additionalProperties": { "type": "string" } }, "multiple": { "type": "boolean", "default": false, "description": "choice: allow multi-select." }, @@ -543,12 +545,30 @@ } }, "$defs": { + "recordListFooterAction": { + "type": "object", + "additionalProperties": true, + "required": ["label"], + "properties": { + "label": { "type": "string", "minLength": 1 }, + "icon": { "type": "string", "minLength": 1 }, + "note": { "type": "string" }, + "wide": { "type": "boolean" }, + "visibleWhen": { "$ref": "#/$defs/visibleWhen" }, + "disabledWhen": { "$ref": "#/$defs/visibleWhen" }, + "disabledReason": { "type": "string" } + } + }, "fieldOverride": { "type": "object", "additionalProperties": true, "description": "One field's overrides over its schema-derived descriptor (label, order, hidden, readOnly, widget, enumLabels and more stay free-form). A `widget` that is not one of the form's own widget names mounts the registered component of that name (the v2 registry, then customComponents). 2.126.0 documents the search look of a single select.", "properties": { "widget": { "type": "string", "minLength": 1 }, + "createModal": { "type": "string", "minLength": 1, "description": "A select-or-create reference: the registry key of the `kind: 'modal'` dialog its Create mounts on top of the form, ahead of the page that lists the referenced schema. The dialog gets `initialData` with the typed term and reports the new object with `created`." }, + "createModalProps": { "type": "object", "additionalProperties": true, "description": "More props for the createModal dialog. A string `@answer.` is that form value, so a contact dialog can carry the chosen client." }, + "pickerFilter": { "type": "object", "additionalProperties": true, "description": "A select-or-create reference: filters its options are scoped by. A string `@answer.` is that form value, so a contact picker can follow the chosen client." }, + "hintWidget": { "type": "string", "minLength": 1, "description": "The registered component shown under the field, with `field`, `value` and `formData` as props, such as a hint that follows the field's value." }, "look": { "type": "string", "enum": ["search"], "description": "`search` draws a single select as CnSearchSelect: a magnifier instead of the chevron, and rows with initials or an icon, the name, a secondary line and a check on the chosen one (DqOverdragen \"Nieuw team\")." }, "optionSecondary": { "type": "string", "description": "look 'search': the row's secondary line, a template filled from each option (\"{members} leden · {focus}\")." }, "optionAvatar": { "type": "boolean", "description": "look 'search': draw each option's initials." }, @@ -597,7 +617,8 @@ "groupField": { "type": "string", "minLength": 1, "description": "Groups the options by this field, groups in the order they first appear (form-small-features)." }, "where": { "type": "object", "description": "Keep the rows whose fields equal these values; a value may be `@answer.`, another field of the form (object-list-edit-in-place)." }, "itemsPath": { "type": "string", "minLength": 1, "description": "Read the options from the list at this path in each row (object-list-edit-in-place)." }, - "hideWhenEmpty": { "type": "boolean", "description": "Leave the field out while the source gives no options (object-list-edit-in-place)." } + "hideWhenEmpty": { "type": "boolean", "description": "Leave the field out while the source gives no options (object-list-edit-in-place)." }, + "keepFields": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Values of the row each option carries along as `data`, such as a programme's terms url a create form links to (create-form-widget)." } } }, { @@ -613,7 +634,8 @@ "groupField": { "type": "string", "minLength": 1, "description": "Groups the options by this field, groups in the order they first appear (form-small-features)." }, "where": { "type": "object", "description": "Keep the rows whose fields equal these values; a value may be `@answer.`, another field of the form (object-list-edit-in-place)." }, "itemsPath": { "type": "string", "minLength": 1, "description": "Read the options from the list at this path in each row (object-list-edit-in-place)." }, - "hideWhenEmpty": { "type": "boolean", "description": "Leave the field out while the source gives no options (object-list-edit-in-place)." } + "hideWhenEmpty": { "type": "boolean", "description": "Leave the field out while the source gives no options (object-list-edit-in-place)." }, + "keepFields": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Values of the row each option carries along as `data`, such as a programme's terms url a create form links to (create-form-widget)." } } } ] @@ -631,7 +653,8 @@ "href": { "type": "string" }, "icon": { "type": "string", "minLength": 1, "description": "Optional MDI icon name (e.g. `Home`) drawn as the first crumb instead of the label, which then becomes its accessible name (zuiddrecht-pixel-gaps-2). Without it the label shows as text." }, "currentField": { "type": "string", "minLength": 1, "description": "A field of the record (dotted path, e.g. `identifier`) whose value is the current crumb instead of the display name; an empty value falls back to the display name (zuiddrecht-pixel-gaps-3)." }, - "separator": { "type": "string", "minLength": 1, "maxLength": 3, "description": "Text drawn between the crumbs (e.g. `/`) instead of the chevron (zuiddrecht-pixel-gaps-3)." } + "separator": { "type": "string", "minLength": 1, "maxLength": 3, "description": "Text drawn between the crumbs (e.g. `/`) instead of the chevron (zuiddrecht-pixel-gaps-3)." }, + "visibleWhen": { "type": "object", "description": "A local visibleWhen condition read off the record; the crumb shows only while it holds, so one page can name a different list per kind of record (detail-record-kind-variants)." } } }, "rowIndicator": { @@ -670,6 +693,14 @@ "minLength": 1, "description": "The indicator applies while the field holds a readable date before this moment: `now` or an ISO date." }, + "lt": { + "type": "number", + "description": "The indicator applies while the field holds a number below this one." + }, + "gt": { + "type": "number", + "description": "The indicator applies while the field holds a number above this one." + }, "icon": { "type": "string", "description": "CnIcon name (PascalCase). Defaults to InformationOutline." @@ -872,8 +903,8 @@ }, "sentinelFilterToken": { "type": "string", - "pattern": "^@(?:me(?:\\.(?:displayName|email))?|myGroups|now|today|monthStart|monthEnd|nextMonthStart|quarterStart|yearStart)$|^@today[+-][0-9]+d$", - "description": "Filter-value context: relative fetch-time tokens resolved by resolveFilterTokens (@me, @me.displayName, @me.email, @myGroups (the current user's group ids, an IN list), @now, @today, @today±Nd, @monthStart, @monthEnd, @nextMonthStart, @quarterStart, @yearStart). Mirrors SENTINEL_TOKEN_PATTERNS.filter in src/utils/sentinelTokens.js (byte-equality enforced by a unit test)." + "pattern": "^@(?:me(?:\\.(?:displayName|email))?|myGroups|myTeam|now|today|monthStart|monthEnd|nextMonthStart|quarterStart|yearStart)$|^@today[+-](?:[0-9]+|\\{@config\\.[A-Za-z][A-Za-z0-9_.]*\\})d$", + "description": "Filter-value context: relative fetch-time tokens resolved by resolveFilterTokens (@me, @me.displayName, @me.email, @myGroups (the current user's group ids, an IN list), @myTeam (the current user's primary team as the app answers it; dropped for a user in no team), @now, @today, @today±Nd (N may be {@config.}), @monthStart, @monthEnd, @nextMonthStart, @quarterStart, @yearStart). Mirrors SENTINEL_TOKEN_PATTERNS.filter in src/utils/sentinelTokens.js (byte-equality enforced by a unit test)." }, "sentinelConfigToken": { "type": "string", @@ -885,6 +916,11 @@ "pattern": "^@objectId$|^@object\\.[A-Za-z][A-Za-z0-9_]*$", "description": "Object context: detail-page object tokens (@objectId, @object.) resolved only when a { objectId, object } ctx is supplied. Mirrors SENTINEL_TOKEN_PATTERNS.object." }, + "sentinelLinkToken": { + "type": "string", + "pattern": "^@object\\.[A-Za-z][A-Za-z0-9_]*\\|(?:tel|mailto|geo)$", + "description": "Link context: a record field made a tel:, mailto: or geo: link in an action target (@object.|tel, |mailto, |geo), resolved by the party-actions widget. Mirrors SENTINEL_TOKEN_PATTERNS.link." + }, "sentinelWorkspaceToken": { "type": "string", "pattern": "^@workspace\\.[A-Za-z][A-Za-z0-9_]*\\??$", @@ -925,6 +961,16 @@ "pattern": "^@context\\.[A-Za-z][A-Za-z0-9_.]*$", "description": "Detail context: a record a detail page reads beside the loaded one (`config.visibilityContext`), read by a grid widget's or a tab's `visibleWhen` field as @context.. (dot path allowed). Mirrors SENTINEL_TOKEN_PATTERNS.context." }, + "sentinelSourceToken": { + "type": "string", + "pattern": "^@source\\.[A-Za-z][A-Za-z0-9_.@]*$", + "description": "Facts source: a field of the object a facts widget shows (@source., dot path allowed), read by a fact row's `route` params. Mirrors SENTINEL_TOKEN_PATTERNS.source." + }, + "sentinelInputToken": { + "type": "string", + "pattern": "^@input\\.[A-Za-z][A-Za-z0-9_]*$", + "description": "Facts input: what the user typed into a facts widget's `input` field (@input.), read by its actions' url and payload ('' when nothing was typed). Mirrors SENTINEL_TOKEN_PATTERNS.input." + }, "sentinelSubjectToken": { "type": "string", "pattern": "^@subject\\.[A-Za-z][A-Za-z0-9_.]*$", @@ -936,6 +982,7 @@ { "$ref": "#/$defs/sentinelFilterToken" }, { "$ref": "#/$defs/sentinelConfigToken" }, { "$ref": "#/$defs/sentinelObjectToken" }, + { "$ref": "#/$defs/sentinelLinkToken" }, { "$ref": "#/$defs/sentinelWorkspaceToken" }, { "$ref": "#/$defs/sentinelRouteToken" }, { "$ref": "#/$defs/sentinelDeclarativeToken" }, @@ -944,6 +991,8 @@ { "$ref": "#/$defs/sentinelRowToken" }, { "$ref": "#/$defs/sentinelSubjectToken" }, { "$ref": "#/$defs/sentinelContextToken" }, + { "$ref": "#/$defs/sentinelSourceToken" }, + { "$ref": "#/$defs/sentinelInputToken" }, { "$ref": "#/$defs/sentinelDeprecatedToken" } ] }, @@ -1204,6 +1253,24 @@ "items": { "type": "string" }, "description": "The fields a card shows. Keep it short: a card is scanned, not read." }, + "cardActions": { + "type": "array", + "items": { "type": "string", "minLength": 1 }, + "description": "pl4-board-card-actions: the ids of the page's row `actions` each card offers in its own menu (assign, priority), in this order." + }, + "cardCells": { + "type": "object", + "description": "pl4-board-card-cells: how a card field is drawn, as a table column's cell config: `{ [field]: { widget?, widgetProps?, formatter? } }` (a priority badge, a value with its currency, a close date with its due text). A field without one is text.", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "widget": { "type": "string", "minLength": 1 }, + "widgetProps": { "type": "object" }, + "formatter": { "type": "string", "minLength": 1 } + } + } + }, "swimlaneField": { "type": "string", "description": "A second field to group the board into rows by, while the columns stay the stages. Cards with no value for it land in one named row, last, rather than being dropped: grouping by handler and hiding the unassigned work turns a board into a picture of what is already somebody's problem." @@ -1249,7 +1316,7 @@ "type": "object", "description": "screens-kanban-parity: how the sum is formatted when the field's own schema does not say: `{ style, currency, decimals }`.", "properties": { - "style": { "type": "string", "enum": ["number", "currency", "percent", "duration-hours", "decimal"] }, + "style": { "type": "string", "enum": ["number", "currency", "percent", "duration-hours", "decimal", "date"] }, "currency": { "type": "string" }, "decimals": { "type": "integer", "minimum": 0 } } @@ -1287,6 +1354,9 @@ "schema": { "type": "string", "minLength": 1 }, "labelField": { "type": "string", "description": "The referenced row's field that names a column. Defaults to `name`, then `title`." }, "orderField": { "type": "string", "description": "The referenced row's numeric field the columns are ordered by." }, + "itemsPath": { "type": "string", "minLength": 1, "description": "pl4-board-list-stages: the columns are a LIST on each referenced row (a pipeline's `stages`), not the rows themselves. Each item is keyed by `keyField` and linked to its row under `_parent`, so `narrowBy: { field: \"_parent\", rowField }` shows the stages of the parent the page filters on." }, + "keyField": { "type": "string", "minLength": 1, "description": "With `itemsPath`: the item field a card stores and a drop writes (default `name`)." }, + "closedField": { "type": "string", "minLength": 1, "description": "The referenced row's (or item's) field that marks a stage closed: its column starts collapsed, with its count, until the reader opens it." }, "colorField": { "type": "string", "description": "The referenced row's field that holds the column's dot colour (a badge variant or a CSS colour)." }, "filter": { "type": "object", "description": "Equality filters on the referenced rows, e.g. `{ \"isFinal\": false }` to leave out end states.", "additionalProperties": { "type": ["string", "number", "boolean"] } }, "capacityField": { "type": "string", "minLength": 1, "description": "board-column-capacity (2.100.0): the referenced row's numeric field that holds the column's limit. The header reads \"count / capacity\" and turns full at the limit, and a move into a full row is refused before it is written (the server still guards). A column merged from several rows shows no limit, but each row's own limit still refuses a move into it. A row without a number above 0 has no limit." }, @@ -1307,7 +1377,8 @@ }, "readOnly": { "type": "boolean", "default": false, "description": "No drag and no Move to while the columns come from this field." }, "moveHandler": { "type": "string", "minLength": 1, "description": "A handler in the app's registry that makes the move: it gets `{ card, toKey, field, value, target }` (value: the referenced id to write; target: its row) and returns a promise; a rejection shows its message as the refusal. Absent, the field is written with a PATCH." }, - "moveTargetsHandler": { "type": "string", "minLength": 1, "description": "A handler in the app's registry that answers where one card may go: it gets `{ card, field }` and returns the values (referenced ids) it can reach. Columns out of reach fade during a drag, read \"not allowed\" in the card menu, and refuse a drop." } + "moveTargetsHandler": { "type": "string", "minLength": 1, "description": "A handler in the app's registry that answers where one card may go: it gets `{ card, field }` and returns the values (referenced ids) it can reach. Columns out of reach fade during a drag, read \"not allowed\" in the card menu, and refuse a drop." }, + "states": { "$ref": "#/$defs/boardDeclaredStates" } } } }, @@ -1355,7 +1426,30 @@ "type": "boolean", "default": false, "description": "screens-board-reference-columns (2.97.0): wrap the columns onto more rows, as many to a row as fit at 240px, instead of one row that scrolls sideways. For a board with more stages than fit." - } + }, + "states": { "$ref": "#/$defs/boardDeclaredStates" } + } + }, + "boardDeclaredStates": { + "type": "array", + "minItems": 1, + "description": "board-declared-states (2.150.0): the columns the board draws for a field, in order, instead of the field's enum or lifecycle states. Each entry is a value or `{ key, label?, color?, hidden?, closed? }`: `hidden` draws no column (its cards go to Elsewhere), `closed` starts the column folded, `label` is an i18n key. On `board` it applies to the `statusField`; on a `columnFields` entry to that field. Ignored on a `source` or `kind: values` column.", + "items": { + "oneOf": [ + { "type": "string", "minLength": 1 }, + { + "type": "object", + "additionalProperties": false, + "required": ["key"], + "properties": { + "key": { "type": "string", "minLength": 1 }, + "label": { "type": "string" }, + "color": { "type": "string" }, + "hidden": { "type": "boolean" }, + "closed": { "type": "boolean" } + } + } + ] } }, "headerPill": { @@ -1422,6 +1516,17 @@ } } }, + "rowClassCondition": { + "type": "object", + "additionalProperties": false, + "required": ["field", "value"], + "description": "One condition of a row-class rule: the row's `field` compared to `value` with `op`.", + "properties": { + "field": { "type": "string", "description": "Dot-path into the row." }, + "op": { "type": "string", "enum": ["eq", "neq", "gt", "gte", "lt", "lte"], "default": "eq", "description": "Comparison operator (default eq)." }, + "value": { "description": "Literal right-hand side of the comparison; tokens such as `@today` resolve." } + } + }, "dueRule": { "type": "object", "additionalProperties": false, @@ -1537,9 +1642,14 @@ "type": "string", "description": "Named vue-router route to navigate to on click." }, + "query": { + "type": "object", + "additionalProperties": { "type": "string" }, + "description": "Query for `route`, e.g. { \"action\": \"create\" } to open an index page's create flow (detail-band, 2.140.0)." + }, "href": { "type": "string", - "description": "External URL opened in a new tab on click." + "description": "External URL opened in a new tab on click. On a detail page it may name the record's fields as @object. tokens." }, "payload": { "description": "Free-form payload echoed back to the host inside the @primary-action-click event." @@ -1838,12 +1948,59 @@ "params": { "type": "object", "description": "Route params passed along with `route`.", "additionalProperties": { "type": ["string", "number"] } }, "query": { "type": "object", "description": "Query passed along with `route`.", "additionalProperties": { "type": ["string", "number", "boolean"] } }, "href": { "type": "string", "minLength": 1, "description": "External URL, opened in a new tab. Mutually exclusive with route." }, + "action": { "type": "string", "enum": ["user-settings"], "description": "Built-in action instead of a link: 'user-settings' opens the user settings dialog (board PqGeavanceerdHub, Persoonlijke instellingen). Mutually exclusive with route and href. Outside a host that offers the dialog the card is left out." }, "permission": { "type": "string", "description": "Permission the user must hold for the card to render. Same rule as menuItem.permission." }, - "visibleIf": { "$ref": "#/$defs/visibleIfCondition", "description": "Display condition, evaluated the way a menu entry's is." } + "visibleIf": { "$ref": "#/$defs/visibleIfCondition", "description": "Display condition, evaluated the way a menu entry's is." }, + "status": { + "description": "A status pill under the description (board PqGeavanceerdHub). A string, or { text, tone }. The text is translated, then {name} is filled from statusValues and {runtime.} from the manifest runtime; a status that waits on a value is not shown.", + "oneOf": [ + { "type": "string", "minLength": 1 }, + { + "type": "object", + "required": ["text"], + "additionalProperties": false, + "properties": { + "text": { "type": "string", "minLength": 1 }, + "tone": { "type": "string", "enum": ["neutral", "error", "warning", "success"] }, + "plural": { "type": "string", "minLength": 1, "description": "The text when the counted value is not 1." }, + "zero": { + "description": "The status when the counted value is 0: a string, or { text, tone }.", + "oneOf": [ + { "type": "string", "minLength": 1 }, + { "type": "object", "required": ["text"], "additionalProperties": false, "properties": { "text": { "type": "string", "minLength": 1 }, "tone": { "type": "string", "enum": ["neutral", "error", "warning", "success"] } } } + ] + }, + "countOf": { "type": "string", "description": "The statusValues name that picks zero or plural. Defaults to the first." } + } + } + ] + }, + "statusValues": { + "type": "object", + "description": "The numbers a status names, by placeholder: an aggregate descriptor { register, schema, metric?, field?, filter? } or { url, path?, params? }, a GET whose JSON holds the number at path (default total; an array counts its items).", + "additionalProperties": { + "type": "object", + "properties": { + "register": { "type": "string" }, + "schema": { "type": "string" }, + "metric": { "type": "string" }, + "field": { "type": "string" }, + "filter": { "type": "object" }, + "url": { "type": "string", "minLength": 1 }, + "path": { "type": "string" }, + "params": { "type": "object" }, + "where": { "type": "object", "description": "Count only the list items whose fields equal these values." }, + "sumOf": { "type": "string", "description": "Add up this field of each list item (an array field by its length) instead of counting the items." } + }, + "anyOf": [{ "required": ["url"] }, { "required": ["register", "schema"] }] + } + } }, "allOf": [ { "not": { "required": ["route", "href"] } }, - { "anyOf": [{ "required": ["route"] }, { "required": ["href"] }] } + { "not": { "required": ["route", "action"] } }, + { "not": { "required": ["href", "action"] } }, + { "anyOf": [{ "required": ["route"] }, { "required": ["href"] }, { "required": ["action"] }] } ] }, "navCardEntry": { @@ -1962,13 +2119,15 @@ "type": "object", "additionalProperties": false, "required": ["label"], - "description": "One text link in the widget header's actions group, before the overflow menu (zuiddrecht-pixel-gaps): \"All deadlines\", \"To the board\". A `route` (named page, optional `params` / `query`) is a router link, an `href` a plain anchor. The label is an i18n key. Rendered by CnWidgetWrapper; CnDashboardPage reads it off the layout entry or the widget definition.", + "description": "One text link in the widget header's actions group, before the overflow menu (zuiddrecht-pixel-gaps): \"All deadlines\", \"To the board\". A `route` (named page, optional `params` / `query`) is a router link, an `href` a plain anchor, a `tab` a `#` anchor that opens that tab of the same page (2.140.0, header-link-switches-tab). The label is an i18n key. Rendered by CnWidgetWrapper; CnDashboardPage reads it off the layout entry or the widget definition.", "properties": { "label": { "type": "string", "minLength": 1 }, "route": { "type": "string" }, "params": { "type": "object", "additionalProperties": true }, "query": { "type": "object", "additionalProperties": true }, - "href": { "type": "string" } + "href": { "type": "string" }, + "tab": { "type": "string", "minLength": 1 }, + "placement": { "type": "string", "enum": ["title"], "description": "`title` draws the link under the widget title instead of among the header actions (2.145.0, header-link-under-title; DqDashboard \"Alles bekijken\")." } } }, "showButtons": { @@ -1976,6 +2135,19 @@ "default": true, "description": "`false` drops the widget's footer links (its definition's `buttons`, e.g. a calendar's \"More events\"), for a placement whose card says enough without them (zuiddrecht-pixel-gaps-3)." }, + "track": { + "type": "string", + "enum": ["main", "side"], + "description": "With the dashboard's `gridSideColumn` (2.142.0): the column this placement is drawn on, the main column sharing what the fixed-width side column leaves. A placement without it keeps the twelve shares." + }, + "clear": { + "type": "boolean", + "description": "`true` starts this placement below every card that starts on an earlier row, in any column, so a row of cards after the tracks lines up under the taller one instead of rising under the shorter (DqMijnWerk, DqMijnTeam). Held in place outside edit mode." + }, + "sameHeight": { + "type": "boolean", + "description": "`true` on placements that start on one row stretches the shorter size-to-content ones to the tallest of them (a fixed-height one counts but is not stretched), so the row reads as one band and the next row starts level (DqProcesanalyse KPI tiles and charts). Not in edit mode, not on a stacked narrow grid." + }, "sizeToContent": { "type": ["boolean", "integer"], "minimum": 1, @@ -2090,15 +2262,11 @@ "required": ["when", "class"], "properties": { "when": { - "type": "object", - "additionalProperties": false, - "required": ["field", "value"], - "description": "The predicate evaluated against the row (shared visibleWhen grammar, LOCAL form).", - "properties": { - "field": { "type": "string", "description": "Dot-path into the row." }, - "op": { "type": "string", "enum": ["eq", "neq", "gt", "gte", "lt", "lte"], "default": "eq", "description": "Comparison operator (default eq)." }, - "value": { "description": "Literal right-hand side of the comparison." } - } + "description": "The predicate evaluated against the row (shared visibleWhen grammar, LOCAL form), or a list of them that must all hold.", + "oneOf": [ + { "$ref": "#/$defs/rowClassCondition" }, + { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/rowClassCondition" } } + ] }, "class": { "type": "string", "description": "CSS class added to the row when `when` holds." } } @@ -2273,16 +2441,11 @@ "type": "object", "properties": { "footerAction": { - "type": "object", - "additionalProperties": true, - "required": ["label"], - "description": "One action under the list, also under the empty text (DqBezwaar \"Beroep registreren\"). Its other keys are an action's (`type`, `route`, `form`, ...). While `disabledWhen` holds it is disabled and says `disabledReason` (\"Kan zodra de beslissing is verzonden\"). Label and reason go through the host's translate.", - "properties": { - "label": { "type": "string", "minLength": 1 }, - "icon": { "type": "string", "minLength": 1 }, - "disabledWhen": { "$ref": "#/$defs/visibleWhen" }, - "disabledReason": { "type": "string" } - } + "description": "One action under the list, also under the empty text (DqBezwaar \"Beroep registreren\"). Its other keys are an action's (`type`, `route`, `form`, ...). While `disabledWhen` holds it is disabled and says `disabledReason` (\"Kan zodra de beslissing is verzonden\"). `note` is a line above it, `wide: true` stretches it across the card. A list of actions draws the first whose `visibleWhen` (a local condition over the page's record) holds (DqZaaktype Versies). Label, reason and note go through the host's translate.", + "anyOf": [ + { "$ref": "#/$defs/recordListFooterAction" }, + { "type": "array", "items": { "$ref": "#/$defs/recordListFooterAction" } } + ] }, "dot": { "description": "A colour dot before the title: a field name, or `{ field, colorMap? }` (2.129.0).", "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "required": ["field"], "additionalProperties": false, "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string" } } } }] }, "flags": { "type": "array", "description": "The labels of the flags a row has, at its end (\"Start\", \"End\"): `[{ field, label, value? }]` (DqZaaktypeOverzicht, 2.129.0).", "items": { "type": "object", "required": ["field", "label"], "additionalProperties": false, "properties": { "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "value": {} } } } @@ -2333,7 +2496,8 @@ "type": "object", "properties": { "groupByRelation": { "type": "boolean", "description": "Split the Objects group by the relation label OpenRegister answers per direction." }, - "layout": { "type": "string", "enum": ["tabs", "list", "sections"], "description": "`sections` draws every group under its own heading in one list." }, + "sectionOrder": { "type": "array", "items": { "type": "string" }, "description": "The order of extraSources and referenceSections by key." }, + "layout": { "type": "string", "enum": ["tabs", "list", "sections", "links"], "description": "`sections` draws every group under its own heading in one list; `links` shows the host's sections as plain links with Show all." }, "groupActions": { "type": "object", "additionalProperties": { "type": "object" }, "description": "An action on a group's heading in `layout: \"sections\"`, keyed by the group's key (DqZaakGerelateerd \"Vervolg plannen\")." } } } @@ -2510,15 +2674,15 @@ "legendPosition": { "type": "string", "enum": ["top", "bottom", "left", "right"], "description": "Legend placement override (empty/omitted keeps the automatic placement)." }, "valueAxisBaseline": { "type": "string", "enum": ["auto", "zero", "fit"], "description": "Value-axis baseline: 'auto' (the default — zero for bar/area, a bounded window for line/scatter), 'zero' (force a zero baseline), or 'fit' (let ApexCharts frame the data range, for a series living far from zero such as an SLA hovering 95–99%)." }, "valueFormat": { - "description": "Named value formatter applied to the value axis + tooltip: 'currency' | 'currency-compact' | 'percent' | 'number', or the object form { name, currency?, decimals?, suffix? }. 'number' with `suffix` reads '9 d'.", + "description": "Named value formatter applied to the value axis + tooltip: 'currency' | 'currency-compact' | 'compact' | 'percent' | 'number', or the object form { name, currency?, decimals?, suffix? }. 'number' with `suffix` reads '9 d'; 'compact' writes 38000 as '38K' with no currency.", "oneOf": [ - { "type": "string", "enum": ["currency", "currency-compact", "percent", "number"] }, + { "type": "string", "enum": ["currency", "currency-compact", "compact", "percent", "number"] }, { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { - "name": { "type": "string", "enum": ["currency", "currency-compact", "percent", "number"] }, + "name": { "type": "string", "enum": ["currency", "currency-compact", "compact", "percent", "number"] }, "currency": { "type": "string", "description": "ISO-4217 code (EUR default; invalid codes fall back to EUR)." }, "decimals": { "type": "integer", "minimum": 0, "description": "Fraction digits override." }, "suffix": { "type": "string", "description": "Text after a 'number' value, e.g. ' d'." } @@ -2535,7 +2699,7 @@ "barMax": { "type": "number", "exclusiveMinimum": 0, "description": "chartKind 'bar-list': the value a full track stands for (100 for percentages). Omitted = the largest value." }, "footnote": { "type": "string", "description": "A line under the chart, through the host's translate: under the rows of a bar-list or matrix, and under the plot of a line, area or bar chart." }, "referenceLines": { "type": "array", "description": "line, area and bar charts: dashed horizontal reference lines on the value axis, such as a norm. Each `{ value, label?, color? }`; the label goes through the host's translate, the colour defaults to the theme's error colour.", "items": { "type": "object", "required": ["value"], "properties": { "value": { "type": "number" }, "label": { "type": "string" }, "color": { "type": "string" } } } }, - "categoryFormat": { "type": "string", "enum": ["", "month", "month-year"], "description": "How date categories read on the x-axis: `month` (\"jan\") or `month-year` (\"jan 2026\"), in the user's language. Categories that are not dates are left as is." }, + "categoryFormat": { "type": "string", "enum": ["", "month", "month-year", "week"], "description": "How date categories read on the x-axis: `month` (\"jan\") or `month-year` (\"jan 2026\"), in the user's language, or `week` for ISO week keys (\"2026-W40\" as \"w40\"). Categories that are not dates are left as is." }, "emptyCellLabel": { "type": "string", "description": "chartKind 'matrix': the text of a cell without a value (\"geen\"), through the host's translate. Empty leaves the cell blank. Passed through to CnChartWidget." }, "highlight": { "type": "object", @@ -2759,6 +2923,7 @@ "items": { "type": "object", "additionalProperties": true }, "description": "With `layout: \"bar\"`: the bar's controls, each a workspace-filter content (chips, a toggle, a select) with its own `writes`. `align: \"end\"` pushes a control to the right edge (2.88.0)." }, + "control": { "type": "string", "enum": ["switch", "checkbox"], "description": "style 'toggle': draw it as a switch (default) or a checkbox." }, "onValue": { "description": "style 'toggle': the value written while the switch is on. Default true (2.88.0)." }, "offValue": { "description": "style 'toggle': the value written while the switch is off. Default false (2.88.0)." }, "align": { "type": "string", "enum": ["start", "end"], "description": "In a bar: 'end' pushes this control to the right edge (2.88.0)." }, @@ -2895,12 +3060,13 @@ "register": { "type": "string" }, "schema": { "type": "string" }, "filter": { "type": "object", "additionalProperties": true }, - "limit": { "type": "integer", "minimum": 1, "description": "Cap on the records fetched for the week (default 100)." } + "limit": { "type": "integer", "minimum": 1, "description": "Cap on the records fetched for the week (default 100)." }, + "extend": { "type": "array", "items": { "type": "string" }, "description": "Relations OpenRegister inlines on each record (`_extend`), so a meta field can read a field of the related record, e.g. caseType.title." } } }, "dateField": { "type": "string", "description": "The field the items are bucketed by. Required with a source." }, "titleField": { "type": "string", "description": "The field shown as the item title. Defaults to the object's display name." }, - "metaFields": { "type": "array", "items": { "type": "string" }, "description": "Fields shown under the title." }, + "metaFields": { "type": "array", "items": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" }, "minItems": 1 }] }, "description": "Fields shown under the title, joined with a middle dot. An entry that is a list of fields shows the first one that is filled (e.g. [\"caseType.shortName\", \"caseType.title\"])." }, "itemRoute": { "type": "string", "description": "Route name an item links to; the record id goes in params.id." }, "lateWhen": { "type": "object", @@ -2931,6 +3097,7 @@ }, "emptyText": { "type": "string", "description": "Text of a day without items." }, "weekOffset": { "type": "integer", "description": "Whole weeks from the current one (1 = next week)." }, + "lang": { "type": "string", "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$", "description": "Language of the item texts (BCP 47, e.g. nl), so long words hyphenate by its rules. Without it they follow the page." }, "inset": { "type": "boolean", "default": false, "description": "Draw the strip inside the board's inset (16px above, 24px at the sides, 22px below) instead of from card edge to card edge (zuiddrecht-pixel-gaps-3). Theme hook --cn-widget-board-inset." } } } @@ -3068,6 +3235,7 @@ "sumField": { "type": "string", "description": "Numeric field summed when metric is 'sum' (required for sum)." }, "topN": { "type": "integer", "minimum": 1, "description": "Keep the N largest groups (sorted by value desc)." }, "otherBucket": { "type": "boolean", "description": "Fold the post-topN remainder into a single translated 'Other' slice (sum of the rest). The Other bucket never drilldown-navigates." }, + "mergeByLabel": { "type": "boolean", "description": "Fold the groups whose labels read the same into one bar or slice, summed, before topN is applied (2.141.0): status types declared per case type count as one 'Received'. A merged bar drills down with every key behind it." }, "labelResolve": { "type": "object", "required": ["schema"], @@ -3139,6 +3307,11 @@ "type": "string", "description": "i18n key resolved by the consuming app's t() function at render time." }, + "primaryLabel": { + "type": "string", + "minLength": 1, + "description": "What a detail page's primary button says while this action is the stage's primary action: a template filled from the record like headerMeta ({field}, {field|format}), translated first. Falls back to label when no token fills (action-label-from-record, 2.161.0)." + }, "type": { "type": "string", "enum": ["handler", "open-modal", "open-page", "navigate", "object-op", "export", "open-form", "refresh", "api-call", "agent", "run-node", "toggle"], @@ -3160,6 +3333,22 @@ "default": false, "description": "Confirm-gating (object-op + #91 Wave 3 api-call / open-form + agent): when true, the action presents a CnConfirmDialog before running (the dispatcher / CnActionButtons runs AFTER confirmation). object-op delete ALWAYS confirms regardless of this flag." }, + "confirmTitle": { + "type": "string", + "description": "confirm: true: the confirm dialog's title, through the host translate. Absent: the action's label." + }, + "confirmMessage": { + "type": "string", + "description": "confirm: true: the question the confirm dialog asks, through the host translate. Absent: 'Are you sure you want to continue?'." + }, + "confirmLabel": { + "type": "string", + "description": "confirm: true: the confirm button's label, through the host translate, e.g. 'Record it'. Absent: 'Confirm'." + }, + "confirmIcon": { + "type": "string", + "description": "confirm: true: a CnIcon name drawn on the confirm button, e.g. 'Check'. Absent: no icon." + }, "target": { "type": "string", "description": "Destination for open-modal (modal id), open-page (route name), or navigate (URL). Ignored for handler type." @@ -3205,6 +3394,10 @@ "type": "string", "description": "open-form: the sentence under the create dialog's heading (board look), e.g. which fields are required and what happens on save. Through the host translate. Absent: no sentence." }, + "formConfirmLabel": { + "type": "string", + "description": "open-form: the create dialog's confirm button label, e.g. 'Create case'. Through the host translate. Absent: 'Create' (or 'Save' when editing)." + }, "size": { "type": "string", "enum": ["small", "normal", "large", "full"], @@ -3229,6 +3422,11 @@ "required": ["url"], "additionalProperties": false }, + "exportId": { + "type": "string", + "minLength": 1, + "description": "export only: the id of the table on the page whose rows the export downloads (an object-table widget's `content.exportId`); the dispatcher emits cn:widget:export with it." + }, "formProperties": { "type": "object", "description": "open-form with `endpoint` only: the fields the form shows and sends, as JSON-schema properties (`{ \"name\": { \"type\": \"string\", \"title\": \"Name\" } }`).", @@ -3248,14 +3446,17 @@ }, "entities": { "type": "array", - "description": "export only: selectable entity types offered by the export launcher (e.g. Leads / Requests). Optional — when absent the dialog shows only the format picker. Each entry needs an id + pre-translated label.", + "description": "export only: selectable entity types offered by the export launcher (e.g. Leads / Requests). Optional — when absent the dialog shows only the format picker. Each entry needs an id + label (run through the app's translate). Without a `handler`, an entry naming `register` and `schema` downloads those OpenRegister objects, `filter` applied (pl4-export-action).", "items": { "type": "object", "additionalProperties": false, "required": ["id", "label"], "properties": { "id": { "type": "string", "description": "Entity id handed to the confirm handler." }, - "label": { "type": "string", "description": "Pre-translated display label." } + "label": { "type": "string", "description": "Display label, run through the app's translate." }, + "register": { "type": "string", "minLength": 1, "description": "OpenRegister register this entity exports from when the action has no handler." }, + "schema": { "type": "string", "minLength": 1, "description": "OpenRegister schema this entity exports from when the action has no handler." }, + "filter": { "type": "object", "description": "Filters sent with the export, as a list filter takes them." } } } }, @@ -3287,8 +3488,8 @@ }, "method": { "type": "string", - "enum": ["POST", "PUT", "GET"], - "description": "api-call: HTTP method (default POST); GET sends the resolved params as the query string, and with download: true downloads the file the endpoint answers (DqDoorlooptijd). toggle writeUrl: default PUT." + "enum": ["POST", "PUT", "PATCH", "GET"], + "description": "api-call: HTTP method (default POST); PATCH changes only the fields in the body (an OpenRegister object); GET sends the resolved params as the query string, and with download: true downloads the file the endpoint answers (DqDoorlooptijd). toggle writeUrl: default PUT." }, "payload": { "type": "object", @@ -3443,12 +3644,12 @@ "then": { "required": ["url"] } }, { - "description": "open-form actions MUST declare the create schema via `schema`.", + "description": "open-form actions MUST declare the create schema via `schema`, or edit through an `endpoint` with the `formProperties` it shows.", "if": { "properties": { "type": { "const": "open-form" } }, "required": ["type"] }, - "then": { "required": ["schema"] } + "then": { "anyOf": [{ "required": ["schema"] }, { "required": ["endpoint", "formProperties"] }] } }, { "description": "agent actions MUST declare the target agent uuid via `agent`.", @@ -3483,7 +3684,9 @@ "else": { "$ref": "#/$defs/actionFields", "properties": { - "children": { "items": { "$ref": "#/$defs/headerAction" } } + "children": { "items": { "$ref": "#/$defs/headerAction" } }, + "tabs": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "As a `primaryActionByStage` entry: the body tabs beside which its header button shows (2.155.0)." }, + "notTabs": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "As a `primaryActionByStage` entry: the body tabs beside which its header button stands aside; it stays in the Actions menu (2.155.0)." } }, "unevaluatedProperties": false } @@ -3691,6 +3894,15 @@ "additionalProperties": true, "description": "Request parameters. Values pass the shared filter-token grammar (resolveFilterTokens): @me, @now, @today, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId/@object. (detail context), @workspace./@config. with a trailing `?` for optional values (dropped when unresolved; e.g. `{ \"period\": \"@workspace.datePreset?\" }` rides the dashboard date-range pills). A required token that stays unresolved blocks the fetch until the page context provides it." }, + "loadOn": { + "type": "string", + "enum": ["mount", "request"], + "default": "mount", + "description": "When the widget reads. \"request\" reads nothing until the reader presses its button (requestLabel), and again only on the next press once the request changes; for an endpoint that records every read, so a plain visit records nothing." + }, + "requestLabel": { "type": "string", "description": "With loadOn \"request\": the button label, through the app's translate (default \"Show\")." }, + "requestHint": { "type": "string", "description": "With loadOn \"request\": a sentence above the button, through the app's translate." }, + "truncatedText": { "type": "string", "description": "The sentence shown when the answer declares itself truncated, through the app's translate; replaces the default." }, "responsePath": { "type": "string", "description": "Dot-path pluck of the response payload (e.g. 'summary', 'data.series'). Omitted = the whole response body." @@ -3718,6 +3930,15 @@ "additionalProperties": true, "description": "Request parameters — the shared filter-token grammar (see $defs/endpointSource.params). Params re-resolve and the chart refetches when the dashboard date range changes (the page publishes dateFrom / dateTo / datePreset into the workspace context)." }, + "loadOn": { + "type": "string", + "enum": ["mount", "request"], + "default": "mount", + "description": "When the widget reads. \"request\" reads nothing until the reader presses its button (requestLabel), and again only on the next press once the request changes; for an endpoint that records every read, so a plain visit records nothing." + }, + "requestLabel": { "type": "string", "description": "With loadOn \"request\": the button label, through the app's translate (default \"Show\")." }, + "requestHint": { "type": "string", "description": "With loadOn \"request\": a sentence above the button, through the app's translate." }, + "truncatedText": { "type": "string", "description": "The sentence shown when the answer declares itself truncated, through the app's translate; replaces the default." }, "responsePath": { "type": "string", "description": "Dot-path pluck of the response payload. Omitted = the whole response body." @@ -3744,6 +3965,10 @@ "type": "string", "description": "Series display name (legend / tooltip). Defaults to the path." }, + "namePath": { + "type": "string", + "description": "Path of the series name in the whole response (before responsePath), so the endpoint can say what the numbers measure. Falls back to name." + }, "path": { "type": "string", "description": "Value mapping: a per-item field path for an ARRAY payload ('value'), or the path of a parallel number array for an OBJECT payload ('totals')." @@ -3810,12 +4035,12 @@ "format": { "description": "How the value is shown, as a type:'stat' widget's content.format: a style name, or { style, currency, currencyField, decimals, prefix, suffix }. Omitted, the plain localized count is shown. Numbers are formatted with Intl in the user's locale.", "oneOf": [ - { "type": "string", "enum": ["number", "currency", "percent", "decimal", "duration-hours"] }, + { "type": "string", "enum": ["number", "currency", "percent", "decimal", "duration-hours", "date"] }, { "type": "object", "additionalProperties": false, "properties": { - "style": { "type": "string", "enum": ["number", "currency", "percent", "decimal", "duration-hours"] }, + "style": { "type": "string", "enum": ["number", "currency", "percent", "decimal", "duration-hours", "date"] }, "currency": { "type": "string", "description": "ISO-4217 code, or a token such as @config.currency or @object.currency." }, "currencyField": { "type": "string", "description": "Field of the detail page's object that holds the currency (dot-path allowed)." }, "decimals": { "type": "integer", "minimum": 0, "description": "Fraction digits." }, @@ -3930,8 +4155,8 @@ "type": { "type": "string", "enum": ["index", "detail", "dashboard", "logs", "settings", "chat", "files", "form", "map", "roadmap", "reports", - "links", "store", "search", "wiki", "flow", "flows", "flow-detail", "custom"], - "description": "Page type. Closed enum of 18 supported types. 'links' mounts CnLinkCardsPage: a page of link cards grouped under captions, declared with config.categories + config.cards ($defs/linkCard). Use it for the pages a short menu leaves out; unlike 'reports' an app may have several. 'flow' opens ONE flow in the editor; the flow LIST is an ordinary 'index' with config.entitySource='flows'. 'flows' and 'flow-detail' are DEPRECATED aliases kept for migration — 'flows' predates named index sources and 'flow-detail' is the old name for 'flow'. 'roadmap' mounts CnFeaturesAndRoadmapPage (features + GitHub-issue-backed roadmap). 'search' mounts CnSearchPage (cross-schema query + facet sidebar + results list; consumers wire the actual search via @search). 'wiki' mounts CnWikiPage (manifest-declared markdown article + optional sidebar tree; MUST declare config.register + config.schema). 'flow' and the deprecated 'flow-detail' both mount CnFlowEditorPage (the shared node/edge canvas) and take config.app to scope to the owning app. A flow lives in OpenRegister's native flow store rather than a register/schema pair, so a single flow cannot be a 'detail' page — but the LIST is a plain 'index' once a named entitySource supplies it, which is why 'flows' is deprecated (ADR-110 Decision 4). 'custom' requires a _note field explaining why a standard type was not feasible." + "links", "store", "search", "wiki", "flow", "flows", "flow-detail", "redaction", "custom"], + "description": "Page type. Closed enum of 19 supported types. 'redaction' mounts CnRedactionEditor (a record's documents redacted one by one; config.recordId, config.config, config.breadcrumbs, config.recordSource). 'links' mounts CnLinkCardsPage: a page of link cards grouped under captions, declared with config.categories + config.cards ($defs/linkCard). Use it for the pages a short menu leaves out; unlike 'reports' an app may have several. 'flow' opens ONE flow in the editor; the flow LIST is an ordinary 'index' with config.entitySource='flows'. 'flows' and 'flow-detail' are DEPRECATED aliases kept for migration — 'flows' predates named index sources and 'flow-detail' is the old name for 'flow'. 'roadmap' mounts CnFeaturesAndRoadmapPage (features + GitHub-issue-backed roadmap). 'search' mounts CnSearchPage (cross-schema query + facet sidebar + results list; consumers wire the actual search via @search). 'wiki' mounts CnWikiPage (manifest-declared markdown article + optional sidebar tree; MUST declare config.register + config.schema). 'flow' and the deprecated 'flow-detail' both mount CnFlowEditorPage (the shared node/edge canvas) and take config.app to scope to the owning app. A flow lives in OpenRegister's native flow store rather than a register/schema pair, so a single flow cannot be a 'detail' page — but the LIST is a plain 'index' once a named entitySource supplies it, which is why 'flows' is deprecated (ADR-110 Decision 4). 'custom' requires a _note field explaining why a standard type was not feasible." }, "title": { "type": "string", @@ -3993,6 +4218,11 @@ "default": false, "description": "Detail pages only. Puts the active tab in the address as ?_tab= so a link points at a tab of a record rather than at the record. The underscore prefix keeps it out of resolveQueryFilters(), which reads every other query key as a filter. Refused on any other page type." }, + "listNavigation": { + "type": "boolean", + "default": false, + "description": "Detail pages only. Opened from an index page, the record offers previous and next within that list, with its search, sort and filters (the address carries them as `_from` and the list query). CnPageRenderer reads the list from OpenRegister; a list with a source of its own offers no step. Off by default." + }, "referencePreview": { "type": "boolean", "default": false, @@ -4094,7 +4324,7 @@ "additionalProperties": false, "properties": { "key": { "type": "string", "minLength": 1 }, - "format": { "enum": ["text", "mono", "badge", "user", "date"] }, + "format": { "enum": ["text", "mono", "badge", "user", "date", "strong", "pill"] }, "labelField": { "type": "string" }, "colorField": { "type": "string" }, "warnWhenPast": { "type": "boolean" } @@ -4113,7 +4343,8 @@ "description": "Name a registered NON-OBJECT entity collection for a type='index' page, instead of register+schema. Deliberately NOT called `source`: that key is already taken on page config and is polymorphic (a URL string in some manifests, an object with `params` in others), so reusing it would give one key two meanings and break six deployed shillinq pages. A flow definition is deliberately not an OpenRegister object, so an index had nothing to bind to and such lists became bespoke type='custom' pages. With a source the index loads the list itself. Takes precedence over register/schema; non-empty `objects` still wins over both. Registered sources live in src/composables/indexSources.js. `tasks` lists the viewer's task inbox from OpenRegister's flow-tasks read (cn-tasks-entity-source). `organisations` lists the OpenRegister organisations the viewer belongs to. On a type='detail' page a source that reads one record (today `organisations`) loads the record whose id is the route's objectId, instead of register+schema, and feeds the header, pills, widgets and @objectId / @object.* tokens like a fetched object (organisations-entity-source)." }, "bulkBandAlways": { "type": "boolean", "default": false, "description": "type='index', board look: draw the bulk band (its actions and hint) before a selection too, its buttons disabled until rows are selected (DqZaken, DqWerkbord). Maps to CnIndexPage `bulkBandAlways`." }, - "quickFilterPlacement": { "type": "string", "enum": ["", "toolbar", "row"], "default": "", "description": "type='index': where the quick-filter chips sit. `toolbar` keeps them on one row with the Filter button and the view switch beside a side panel, the chips that do not fit behind the overflow chip (DqTeamwachtrij). `row` puts them on a row of their own above the toolbar, wrapping onto more lines. Maps to CnIndexPage `quickFilterPlacement`." }, + "quickFilterPlacement": { "type": "string", "enum": ["", "toolbar", "row", "active"], "default": "", "description": "type='index': where the quick-filter chips sit. `toolbar` keeps them on one row with the Filter button and the view switch beside a side panel, the chips that do not fit behind the overflow chip (DqTeamwachtrij). `row` puts them on a row of their own above the toolbar, wrapping onto more lines. `active` draws no tabs: a chosen tab with an `activeLabel` shows only on the Active line, where it can be removed (DqAanMijToegewezen). Maps to CnIndexPage `quickFilterPlacement`." }, + "live": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "type='index' (pl4-index-live): object types whose live changes the list follows; it reads itself again shortly after a change." }, "rowClass": { "type": "array", "description": "type='index': per-row CSS class rules, compiled into the table's rowClass function as on the object-table widget. Each rule { when: { field, op?, value }, class } adds `class` to a row whose `field` (a dot-path) compares to `value` with `op` (default eq; the visibleWhen operators and tokens such as `@today`, with two dates compared as moments), e.g. an overdue task `{ when: { field: \"dueAt\", op: \"lt\", value: \"@today\" }, class: \"cn-row--overdue\" }`. Maps to CnIndexPage `rowClass`.", @@ -4126,35 +4357,19 @@ ], "properties": { "when": { - "type": "object", - "additionalProperties": false, - "required": [ - "field", - "value" - ], - "description": "The predicate evaluated against the row (shared visibleWhen grammar, LOCAL form).", - "properties": { - "field": { - "type": "string", - "description": "Dot-path into the row." - }, - "op": { - "type": "string", - "enum": [ - "eq", - "neq", - "gt", - "gte", - "lt", - "lte" - ], - "default": "eq", - "description": "Comparison operator (default eq)." + "description": "The predicate evaluated against the row (shared visibleWhen grammar, LOCAL form), or a list of them that must all hold.", + "oneOf": [ + { + "$ref": "#/$defs/rowClassCondition" }, - "value": { - "description": "Literal right-hand side of the comparison." + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/rowClassCondition" + } } - } + ] }, "class": { "type": "string", @@ -4180,6 +4395,10 @@ "type": "string", "description": "type='wiki': property on the article object holding the markdown body. Defaults to 'body'. Maps to CnWikiPage `contentField` prop." }, + "titleTemplate": { + "type": "string", + "description": "type='detail': a line the header names the record by, built from its fields (\"Shift {floatAt|date}, {drawer}\", `fillTemplate` formats), in the host's translation. Wins over titleField; a template with an empty field falls back to titleField. Maps to CnDetailPage `titleTemplate`." + }, "titleField": { "type": "string", "description": "type='wiki': property on the article object holding the page title. Defaults to 'title'. Maps to CnWikiPage `titleField` prop. type='detail' (detail-title-field): the record field (a dotted path, for example `number`) the header names the record by, as an invoice page names the invoice number instead of the schema title; an empty value falls back to the display name, and the breadcrumb's current crumb and the type eyebrow follow. Maps to CnDetailPage `titleField`." @@ -4393,12 +4612,21 @@ } }, "layout": { - "type": "array", - "description": "Dashboard layout entries (for type='dashboard', legacy). Carry-forward from v1.", - "items": { - "type": "object", - "additionalProperties": true - } + "oneOf": [ + { + "type": "array", + "description": "Dashboard layout entries (for type='dashboard', legacy). Carry-forward from v1.", + "items": { + "type": "object", + "additionalProperties": true + } + }, + { + "type": "string", + "enum": ["cards", "canvas"], + "description": "type='flow' / 'flow-detail' in the board look: `canvas` draws the page as boards PqFlow and PqFlowRun do (actions under the title, floating toolbar and inspector); `cards` (default) keeps the DqFlow grid." + } + ] }, "content": { "type": "array", @@ -4435,11 +4663,22 @@ "quickActions": { "type": "array", "maxItems": 3, - "description": "type='detail': at most three always visible buttons in the header, for the actions a handler uses on every record. An item is an action in the header action shape, or the id of a `headerActions` entry, which then leaves the overflow menu.", + "description": "type='detail': at most three always visible buttons in the header, for the actions a handler uses on every record. An item is an action in the header action shape, or the id of a `headerActions` entry, which then leaves the overflow menu. `{ id, tabs?, notTabs? }` names a `headerActions` entry that shows only beside some body tabs; on the other tabs it stays in the overflow menu.", "items": { "oneOf": [ { "type": "string", "minLength": 1 }, - { "$ref": "#/$defs/headerAction" } + { "$ref": "#/$defs/headerAction" }, + { + "type": "object", + "required": ["id"], + "not": { "required": ["label"] }, + "anyOf": [{ "required": ["tabs"] }, { "required": ["notTabs"] }], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "tabs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The body tab ids this shows beside; it is hidden while another tab is open." }, + "notTabs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The body tab ids this stands aside for, such as a tab that draws the page as its own." } + } + } ] } }, @@ -4475,8 +4714,10 @@ "type": "object", "additionalProperties": false, "anyOf": [{ "required": ["stages"] }, { "required": ["stagesFrom"] }], - "description": "type='detail': the 'what now' card above the body. For the record's stage it shows a short checklist next to the primary button. A stage without an entry shows no card. Rendered by CnNextStepCard.", + "description": "type='detail': the 'what now' card above the body. For the record's stage it shows a short checklist next to the primary button. A stage without an entry shows no card. `tabs` / `notTabs` tie it to body tabs as side cards are. Rendered by CnNextStepCard.", "properties": { + "tabs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The body tab ids this shows beside; it is hidden while another tab is open." }, + "notTabs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The body tab ids this stands aside for, such as a tab that draws the page as its own." }, "field": { "type": "string", "minLength": 1, @@ -4487,6 +4728,21 @@ "minLength": 1, "description": "A field path, read like a `visibleWhen` field, whose value holds the stages in the `stages` shape, e.g. `@context.caseType.nextStep`, so each case type keeps its own stage texts and checklist. The declared `stages` apply otherwise (2.129.0)." }, + "variants": { + "type": "array", + "minItems": 1, + "description": "Per kind of record: the first variant whose `when` holds replaces the declared `title` and `stages` (a contact moment's own steps on the ticket page; detail-record-kind-variants).", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["when", "stages"], + "properties": { + "when": { "type": "object", "description": "A local visibleWhen condition read off the record." }, + "title": { "anyOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }], "description": "As `title`." }, + "stages": { "type": "object", "description": "As `stages`: keys are stage values." } + } + } + }, "title": { "anyOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }], "description": "i18n key / heading used by a stage that names none. May name record fields as `{path}` tokens; a list is candidates, the first whose fields all have a value is used." @@ -4501,6 +4757,7 @@ "properties": { "title": { "anyOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }], "description": "i18n key / heading of the card for this stage. May name record fields as `{path}` tokens (`What now? Step 2: {statusPublicLabel|lower}`); a list is candidates, the first whose fields all have a value is used." }, "after": { "anyOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }], "description": "i18n key / one line under the button saying what follows. Takes `{path}` tokens and candidates as `title` does." }, + "afterTitle": { "anyOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }], "description": "i18n key / a bold sentence above `after`, for a stage whose side states something instead of offering a button (contact-moment-parts). Takes tokens and candidates as `after` does." }, "checklist": { "type": "array", "description": "What this stage still needs. An empty list shows no card.", @@ -4530,6 +4787,44 @@ "$ref": "#/$defs/headerPill", "description": "type='detail': a pill above the title saying where the record stands." }, + "band": { + "type": "array", + "description": "type='detail': a row of widgets across the whole page, above the body and the side column (detail-band). An item is the id of a widget declared on the page, a widget definition, or { widgetId, span, chrome } where span is the share of the row in twelfths (default 12) and chrome is card or bare. The row wraps; each item follows its visibleWhen.", + "items": { + "oneOf": [ + { "type": "string", "minLength": 1 }, + { + "type": "object", + "additionalProperties": false, + "required": ["widgetId"], + "properties": { + "widgetId": { "type": "string", "minLength": 1 }, + "span": { "type": "integer", "minimum": 1, "maximum": 12 }, + "chrome": { "type": "string", "enum": ["card", "bare"], "description": "card (default) or bare, for a widget that draws its own card; a banner defaults to bare." } + } + }, + { + "type": "object", + "additionalProperties": true, + "required": ["type"], + "not": { "required": ["widgetId"] }, + "properties": { + "id": { "type": "string" }, + "type": { "type": "string", "minLength": 1 }, + "title": { "type": "string" }, + "span": { "type": "integer", "minimum": 1, "maximum": 12 }, + "chrome": { "type": "string", "enum": ["card", "bare"], "description": "card (default) or bare, for a widget that draws its own card; a banner defaults to bare." }, + "content": { "type": "object", "additionalProperties": true } + } + } + ] + } + }, + "bandDensity": { + "type": "string", + "enum": ["", "compact"], + "description": "type='detail': the band's density. compact draws 12px between tiles and 20px between rows, tiles down to 120px, and a stat figure as a small white tile (13px label over a 20px value), as boards PqKlant and PqOrganisatie draw their figures (detail-band-compact)." + }, "sideColumn": { "type": "array", "description": "type='detail': a column of cards beside the body, for the record's facts. An item is a widget definition, or the id of a widget declared on the page. The column stacks under the body on a narrow page.", @@ -4545,7 +4840,19 @@ "type": { "type": "string", "minLength": 1 }, "title": { "type": "string" }, "content": { "type": "object", "additionalProperties": true }, - "tabs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The body tab ids this card shows beside; it is hidden while another tab is open (DqZaaktypeVelden, 2.129.0)." } + "tabs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The body tab ids this card shows beside; it is hidden while another tab is open (DqZaaktypeVelden, 2.129.0)." }, + "follows": { + "type": "object", + "additionalProperties": false, + "required": ["widget"], + "description": "Host this card on the row another list on the page has selected, not on the page's record (side-card-follows-row, DqZaaktypeVelden Keuzelijst). `widget` is the id of an object-list with `content.rowSelection: true`; `register` and `schema` are those of its rows, so an editing card saves to the row. The card shows the selected row when `when` holds for it, else, while nothing is selected, the first row for which it holds, and nothing without such a row. `{field}` in its title is filled from the row.", + "properties": { + "widget": { "type": "string", "minLength": 1 }, + "register": { "type": "string" }, + "schema": { "type": "string" }, + "when": { "type": "object", "additionalProperties": true } + } + } } } ] @@ -4556,11 +4863,23 @@ "minLength": 1, "description": "type='detail': a field path, read like a `visibleWhen` field, whose value picks the tabs of the page's tabs widgets and their order for this record, e.g. `@context.caseType.tabs` (DqAanbesteding). A non-empty list of tab ids keeps those tabs in that order; otherwise the declared tabs stay, and edit mode shows them all (2.128.0)." }, + "quickActionsFrom": { + "type": "string", + "minLength": 1, + "description": "type='detail': a field path, read like a `visibleWhen` field, whose list of `headerActions` ids picks this record's quick actions in that order, e.g. `@context.caseType.quickActions` (a case type that names the actions its cases need most). Only declared header actions are taken; an id nobody declares, or an action written out in the data, is dropped. An empty or missing list keeps `quickActions` (2.142.0)." + }, "sideColumnFrom": { "type": "string", "minLength": 1, "description": "type='detail': a field path, read like a `visibleWhen` field, whose value picks the side column and its order for this record, e.g. `@context.caseType.sideCards` (a case type that names the cards its cases show). When the value is a non-empty list of widget ids, the side column is those of `sideColumn` in that order and the rest are left out; each still follows its own `visibleWhen`. Any other value, or no value, keeps `sideColumn` as declared. Maps to CnDetailPage `sideColumnFrom`." }, + "sideColumnOrder": { + "type": "string", + "enum": ["board", "declared"], + "default": "board", + "description": "type='detail', board look: the side column's order. `board` (default) puts a notice first and the History card last; `declared` keeps the order of `sideColumn`, for a board that draws History on top. Maps to CnDetailPage `sideColumnOrder`." + }, + "headerActionsViewport": { "type": "string", "enum": ["wide", "narrow"], "description": "type='dashboard': `wide` keeps the header actions and the buildiq square off a phone (under 640px), `narrow` off a wider window. Maps to CnDashboardPage `headerActionsViewport`." }, "headerActions": { "type": "array", "description": "Page-level header actions rendered inside CnActionsBar's overflow dropdown (between the built-in Refresh action and the `#action-items` slot). On a `type:'detail'` page the same entries render in the header's Actions menu, above the Request-a-feature / Report-a-bug / Documentation trio. Same shape as the row-level `actions[]` ($ref action), but handlers receive NO row context — they are page-level. Reserved ids (`refresh`, `import`, `export`, `copy`, `delete`) are dropped at render time to avoid shadowing built-ins. An entry with `href` (instead of `handler`) renders as a real link to that URL and is never dispatched ($ref headerAction). See manifest-icons-and-page-actions for the dispatch semantics.", @@ -4601,7 +4920,7 @@ }, "rowIndicators": { "type": "array", - "description": "type='index': state indicators the page declares for its rows. Each entry names a `field` (a dotted path on the row), a condition (`equals`, `in`, a date `after` or `before` a moment, or plain truthiness when none is given), an `icon` (a CnIcon name) and a `text` (the text alternative, required). An entry without `text` does not render, because an icon with no text is colour and shape alone. The page declares which indicators exist; a record cannot add one the page has not declared. Maps to CnIndexPage `rowIndicators`.", + "description": "type='index': state indicators the page declares for its rows. Each entry names a `field` (a dotted path on the row), a condition (`equals`, `in`, a date `after` or `before` a moment, a number `lt` or `gt` a bound, or plain truthiness when none is given), an `icon` (a CnIcon name) and a `text` (the text alternative, required). An entry without `text` does not render, because an icon with no text is colour and shape alone. The page declares which indicators exist; a record cannot add one the page has not declared. Maps to CnIndexPage `rowIndicators`.", "items": { "$ref": "#/$defs/rowIndicator" } @@ -4769,6 +5088,14 @@ "type": "string", "description": "One label for the control's label filter." }, + "description": { + "type": "string", + "description": "default-views-secondary (2.151.0): the line under the view in the side panel, what it shows. Translatable." + }, + "secondary": { + "type": "boolean", + "description": "default-views-secondary (2.151.0): true lists the view behind \"More default views\" in the side panel, folded until opened or applied." + }, "icon": { "type": "string", "minLength": 1, @@ -4860,6 +5187,10 @@ "minimum": 0, "description": "Same as config.quickFilterMaxVisible." }, + "bulkBandAlways": { + "type": "boolean", + "description": "Same as config.bulkBandAlways: `false` draws the bulk band only once rows are selected while the view is active (DqTeamwachtrij, DqAanMijToegewezen)." + }, "sidePanel": { "type": "array", "description": "Same as config.sidePanel." @@ -4918,6 +5249,10 @@ "type": "string", "description": "Same as config.countText." }, + "countSubtitle": { + "type": ["string", "null"], + "description": "Same as config.countSubtitle; `null` drops it, so with an empty `countText` a header note that counts (`headerMeta` with `{total}`) is the only count (DqAanMijToegewezen)." + }, "footerNote": { "type": "string", "description": "Same as config.footerNote." @@ -5152,7 +5487,8 @@ "icon": { "type": "string", "minLength": 1, "description": "Optional MDI icon name, reserved for an icon-bearing switch." }, "emptyText": { "type": "string", "minLength": 1, "description": "The sentence shown when the view has nothing to draw." }, "widgets": { "type": "array", "description": "The view's widget definitions, the same shape as the page's `widgets`.", "items": { "type": "object", "additionalProperties": true } }, - "layout": { "type": "array", "description": "The view's grid layout, the same shape as the page's `layout`.", "items": { "type": "object", "additionalProperties": true } } + "layout": { "type": "array", "description": "The view's grid layout, the same shape as the page's `layout`.", "items": { "type": "object", "additionalProperties": true } }, + "gridSideColumn": { "type": "object", "additionalProperties": false, "required": ["width"], "description": "type='dashboard': draw the grid's side column at a fixed width (2.142.0, dashboard-side-column-width). The placements with `track: side` are `width` px wide (the card without its margins) from column `from` (default 8), those with `track: main` share the rest; a placement without a track keeps the twelve shares. Only on the full twelve-column grid and not while editing.", "properties": { "width": { "type": "number", "exclusiveMinimum": 0 }, "from": { "type": "integer", "minimum": 1, "maximum": 11 } } } } } }, @@ -5167,22 +5503,47 @@ "description": "type='dashboard' | type='detail': the accessible name of the page's view switch, e.g. \"Whose work\"; defaults to \"View\" (view-switch-containers). Maps to `viewsLabel`." }, "headerMeta": { - "type": "string", - "minLength": 1, + "oneOf": [ + { "type": "string", "minLength": 1 }, + { + "type": "object", + "additionalProperties": false, + "required": ["count", "one", "other"], + "description": "header-meta-plural: the line in the singular or the plural by a `headerMetaCounts` total (\"1 lopende zaak\" / \"2 lopende zaken\"); each form is translated on its own; `zero`, when given, is used for none (an empty string leaves the line out).", + "properties": { + "count": { "type": "string", "minLength": 1, "description": "The `headerMetaCounts` name whose total picks the form." }, + "one": { "type": "string", "description": "The form for exactly one." }, + "other": { "type": "string", "description": "The form for any other number." }, + "zero": { "type": "string", "description": "The form for none; without it, none reads `other`." } + } + } + ], "description": "type='detail', board look: the meta line on row 2 of the header, after the breadcrumb and a middle dot (screens-detail-page-parity). A field template such as `\"via {channel}\"`: every `{path}` is read from the record, and a line with an empty value is dropped. Maps to CnDetailPage `headerMeta`. type='index' | 'dashboard' | 'settings' (page-header-breadcrumb): the note after the breadcrumb and a middle dot under the title; on an index page `{total}` and `{shown}` are the list's counts." }, "headerMetaCounts": { "type": "object", - "description": "type='index': the other lists headerMeta counts, by name. Each is read as one list total and fills `{count:}` in headerMeta (\"{total} contracten bij {count:suppliers} leveranciers\"); while a count loads or when it fails, the note is left out.", + "description": "type='index' or 'detail': the other lists headerMeta counts, by name. Each is read as one list total and fills `{count:}` in headerMeta (\"{total} contracten bij {count:suppliers} leveranciers\"); while a count loads or when it fails, the note is left out. A count reads a register + schema (with an optional filter) or a named entity source (`{ entitySource: \"organisations\" }`: DqTenant \"een van 6 organisaties op deze omgeving\").", "additionalProperties": { - "type": "object", - "required": ["register", "schema"], - "additionalProperties": false, - "properties": { - "register": { "type": "string", "minLength": 1 }, - "schema": { "type": "string", "minLength": 1 }, - "filter": { "type": "object" } - } + "oneOf": [ + { + "type": "object", + "required": ["register", "schema"], + "additionalProperties": false, + "properties": { + "register": { "type": "string", "minLength": 1 }, + "schema": { "type": "string", "minLength": 1 }, + "filter": { "type": "object" } + } + }, + { + "type": "object", + "required": ["entitySource"], + "additionalProperties": false, + "properties": { + "entitySource": { "type": "string", "enum": ["flows", "flowRuns", "tasks", "endpoint", "search", "users", "organisations"] } + } + } + ] } }, "tabsLabel": { @@ -5202,17 +5563,18 @@ }, "pageFilters": { "type": "array", - "description": "type='dashboard': page-level filter controls in the header. Each selection is written into the page's workspace context, so a widget reads it through a `@page.` / `@workspace.` token (an endpoint widget also in its `responsePath`). Maps to the CnDashboardPage `pageFilters` prop.", + "description": "type='dashboard' and type='detail': page-level filter controls, in a dashboard's header and in a detail page's band (detail-page-filters, 2.141.0). Each selection is written into the page's workspace context, so a widget reads it through a `@page.` / `@workspace.` token (an endpoint widget also in its `responsePath`). Maps to the CnDashboardPage `pageFilters` prop.", "items": { "type": "object", "required": ["key"], - "anyOf": [{ "required": ["options"] }, { "required": ["optionsFrom"] }], + "anyOf": [{ "required": ["options"] }, { "required": ["optionsFrom"] }, { "required": ["type"], "properties": { "type": { "enum": ["quarter", "year"] } } }], "additionalProperties": false, "properties": { "key": { "type": "string", "minLength": 1, "description": "Workspace-context key written on change (the `@page.` / `@workspace.` token name)." }, "label": { "type": "string", "description": "Control label (i18n key or plain text)." }, - "type": { "type": "string", "enum": ["select"], "default": "select", "description": "Control type, carried over from v1." }, - "control": { "type": "string", "enum": ["select", "segmented"], "default": "select", "description": "How the filter is drawn: a select, or a compact segmented group." }, + "type": { "type": "string", "enum": ["select", "quarter", "year"], "default": "select", "description": "Control type, carried over from v1. \"quarter\" and \"year\" generate their options: the current quarter or year first, then `count - 1` before it, valued YYYY-Qn or YYYY." }, + "count": { "type": "integer", "minimum": 1, "description": "With type \"quarter\" or \"year\": how many to offer (default 8 quarters, 5 years)." }, + "control": { "type": "string", "enum": ["select", "segmented", "none"], "default": "select", "description": "How the filter is drawn: a select, a compact segmented group, or none (the value is seeded for the widgets and the description, and no control is drawn)." }, "clearable": { "type": "boolean", "default": false, "description": "Lets the reader clear the select back to all; `placeholder` names that state (2.130.0)." }, "placeholder": { "type": "string", "description": "What a cleared select shows, e.g. \"All case types\", through the app's translate (2.130.0)." }, "optionsFrom": { @@ -5241,7 +5603,8 @@ "additionalProperties": false, "properties": { "value": { "type": ["string", "number"], "description": "Value written into the context when picked." }, - "label": { "type": "string", "description": "Display text (i18n key or plain text)." } + "label": { "type": "string", "description": "Display text (i18n key or plain text)." }, + "months": { "type": "integer", "minimum": 1, "description": "A rolling window of this many months ending with the current one. A dashboard description's `{filter.}` then reads the months it covers (\"November 2025 through October 2026\"); without it the token reads the label (description-period-from-filter, 2.147.0)." } } } } @@ -5254,6 +5617,14 @@ "default": [], "description": "type='dashboard': widget ids drawn as a KPI row above the grid, in the listed order, in an auto-fit grid (`repeat(auto-fit, minmax(200px, 1fr))`, gap 16). The named widgets are not GridStack items, so they do not reflow with the grid and are not part of the emitted layout; an id that names no widget is skipped with a development warning (screens-dashboard-parity). Maps to CnDashboardPage `kpiRow`. `dateRange.control` also accepts \"segmented\": the presets as one compact segmented group under the header." }, + "kpiColumns": { + "oneOf": [ + { "type": "integer", "enum": [2, 3, 4] }, + { "type": "string", "enum": ["auto"] } + ], + "default": "auto", + "description": "type='dashboard': the columns of the `kpiRow` at full width, 2, 3 or 4 (PqLoyaliteit draws eight tiles in two rows of four), or \"auto\" (the default) for as many 200px columns as fit. Maps to CnDashboardPage `kpiColumns`." + }, "greeting": { "oneOf": [ { "type": "boolean" }, @@ -5292,12 +5663,13 @@ "showActionsMenu": { "type": "boolean", "default": true, - "description": "type='dashboard': `false` removes the page Actions menu from the header, for a board page whose header carries only the buildiq square and the primary (screens-dashboard-greeting-header). Maps to CnDashboardPage `showActionsMenu`." + "description": "type='dashboard': `false` removes the page Actions menu from the header, for a board page whose header carries only the buildiq square and the primary (screens-dashboard-greeting-header). Maps to CnDashboardPage `showActionsMenu`. type='index': decides the toolbar's Actions menu; `true` keeps it beside `headerButtons` (DqAfdelingen), `false` leaves it out, and without the key it is drawn unless the header carries buttons. Maps to CnIndexPage `showActionsMenu`." }, + "gridSideColumn": { "type": "object", "additionalProperties": false, "required": ["width"], "description": "type='dashboard': draw the grid's side column at a fixed width (2.142.0, dashboard-side-column-width). The placements with `track: side` are `width` px wide (the card without its margins) from column `from` (default 8), those with `track: main` share the rest; a placement without a track keeps the twelve shares. Only on the full twelve-column grid and not while editing.", "properties": { "width": { "type": "number", "exclusiveMinimum": 0 }, "from": { "type": "integer", "minimum": 1, "maximum": 11 } } }, "gridFloat": { "type": "boolean", "default": true, - "description": "type='dashboard': `false` packs widgets upward (GridStack `float: false`), so a size-to-content widget that shrinks closes the gap under it instead of leaving an empty band (zuiddrecht-pixel-gaps-3). Maps to CnDashboardPage `gridFloat`." + "description": "type='dashboard' and type='detail': `false` packs widgets upward (GridStack `float: false`), so a size-to-content widget that shrinks closes the gap under it instead of leaving an empty band (zuiddrecht-pixel-gaps-3). Maps to CnDashboardPage and CnDetailPage `gridFloat`." }, "showTitle": { "type": "boolean", @@ -5334,6 +5706,11 @@ "minLength": 1, "description": "type='index', board look: the count line under the title, a template with `{shown}` (rows on this page), `{total}` (all rows) and free text, e.g. \"{shown} of {total} open cases in your teams\"; defaults to `countSubtitle`, else \"{shown} of {total}\" (screens-index-list-parity). Maps to CnIndexPage `countText`." }, + "countValues": { + "type": "object", + "additionalProperties": { "type": "object" }, + "description": "type='index', board look: named values the count line (`countText`) can carry, written as template tokens with a format (`{pipeline|currency:EUR}`). An entry is a sum or count over this page's register and schema under the active filters plus its own `filter` (`{ metric: 'sum', field: 'value', filter? }`), or a field of an app endpoint (`{ endpoint, path, followFilters? }`; `followFilters: true` sends the active filters as `filter[]=`). A value not read yet leaves its token empty. Maps to CnIndexPage `countValues`." + }, "footerNote": { "type": "string", "minLength": 1, @@ -5358,8 +5735,9 @@ "searchPlaceholder": { "type": "string", "minLength": 1, "description": "The search placeholder while the preset applies." }, "footerNote": { "type": "string", "minLength": 1, "description": "The footer note while the preset applies." }, "defaultSort": { "type": "array", "description": "The default sort while the preset applies; same shape as config.defaultSort." }, - "viewSwitch": { "type": "array", "items": { "type": "string", "enum": ["table", "cards", "board", "map"] }, "description": "The drawn view segments while the preset applies." }, + "viewSwitch": { "type": "array", "items": { "type": "string", "enum": ["table", "cards", "board", "map", "list"] }, "description": "The drawn view segments while the preset applies." }, "addLabel": { "type": "string", "minLength": 1, "description": "The add button label while the preset applies (index-scope-presentation)." }, + "showAdd": { "type": "boolean", "description": "Whether the Add button shows while the preset applies: false hides it on a view whose own header button adds something else (query-preset-show-add)." }, "headerButtons": { "type": "array", "description": "The header buttons while the preset applies; same shape as config.headerButtons (index-scope-presentation)." }, "breadcrumb": { "description": "The breadcrumb while the preset applies; same shape as config.breadcrumb (index-scope-presentation).", "anyOf": [{ "$ref": "#/$defs/breadcrumbCrumb" }, { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/breadcrumbCrumb" } }] }, "headerMeta": { "type": "string", "minLength": 1, "description": "The note after the breadcrumb while the preset applies (index-scope-presentation)." }, @@ -5371,8 +5749,8 @@ "type": "array", "minItems": 1, "uniqueItems": true, - "items": { "type": "string", "enum": ["table", "cards", "board", "map"] }, - "description": "type='index', board look: the segments the view switch draws, e.g. [\"table\", \"cards\", \"board\", \"map\"] as the boards do. A listed mode the page can open is an ordinary segment; one it cannot (no board columns, no map) is drawn disabled and named \": not available on this list\". Order is always table, cards, board, map. Ignored without the board look (screens-view-switch-parity). Maps to CnIndexPage `viewSwitch`." + "items": { "type": "string", "enum": ["table", "cards", "board", "map", "list"] }, + "description": "type='index', board look: the segments the view switch draws, e.g. [\"table\", \"cards\", \"board\", \"map\"] as the boards do. A listed mode the page can open is an ordinary segment; one it cannot (no board columns, no map) is drawn disabled and named \": not available on this list\". The segments are drawn in the order named, `list` included; a view the page offers that is not named is listed under More views (view-switch-order-and-more, 2.152.0). Ignored without the board look (screens-view-switch-parity). Maps to CnIndexPage `viewSwitch`." }, "filterStrip": { "type": "object", @@ -5383,7 +5761,7 @@ "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1, "description": "The strip's accessible name; default the field's title." }, "allLabel": { "type": "string", "minLength": 1, "description": "The first pill; default \"All\"." }, - "options": { "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "required": ["value"], "properties": { "value": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 } } } }, + "options": { "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "anyOf": [{ "required": ["value"] }, { "required": ["values"] }], "properties": { "value": { "type": "string", "minLength": 1 }, "values": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "Several stored values under one pill (\"phone\" and \"telefoon\" as Telefoon); picking it filters on any of them." }, "label": { "type": "string", "minLength": 1 } } } }, "showCount": { "type": "boolean", "default": false } } }, @@ -5409,11 +5787,33 @@ "minLength": 1, "description": "type='index', board look: the 13px hint after the bulk band's buttons (screens-index-list-parity). Maps to CnIndexPage `bulkHint`." }, + "bulkBandViews": { + "type": "object", + "description": "type='index', board look: another shape of the bulk band per view mode (`board`, `cards`, ...). In such a view the band shows only with a selection and without the built-in copy and delete (DqWerkbord: \"1 zaak geselecteerd · Status wijzigen · Annuleren\"). Maps to CnIndexPage `bulkBandViews`.", + "propertyNames": { "enum": ["table", "cards", "list", "map", "board", "dateAxis", "calendar"] }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "lead": { "type": "object", "additionalProperties": false, "description": "The lead counting the selection, `{count}` filled; i18n keys.", "properties": { "one": { "type": "string", "minLength": 1 }, "other": { "type": "string", "minLength": 1 } } }, + "actions": { "type": "array", "description": "The bulk actions offered, in order: an id, or `{ id, label }` to relabel one (i18n key).", "items": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["id"], "properties": { "id": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 } } }] } }, + "cancel": { "type": "boolean", "description": "A Cancel button at the end that clears the selection." }, + "hint": { "type": "boolean", "description": "`true` keeps `bulkHint` in this view." } + } + } + }, + "bulkNoun": { + "type": "string", + "minLength": 1, + "description": "type='index', board look: the plural the bulk band names (\"With the selected cases\"), an i18n key, lower-cased after translation. Wins over the schema's plural; without either the band says \"items\". Maps to CnIndexPage `bulkNoun`." + }, "cardFields": { "type": "array", "description": "type='index': the property keys the cards view shows as the facts list of each card, in order; defaults to the first four list columns under the board look (screens-card-parity). Maps to CnIndexPage `cardFields`.", "items": { "type": "string", "minLength": 1 } }, + "expandableRows": { "type": "object", "additionalProperties": false, "description": "type='index', list view: every row opens to a sub-table (CnExpandableRow, index-expandable-rows). Closed: title, a badge while `badge.when` holds, description, a list field as chips (`tones` colour each by its entry) and meta templates. Open: the list as a table (columns `as` text, order, bar, check, yesNo or pill), a note and a footer action. A button or the footer action names a row action by id. `open` first or all opens rows at the start. Maps to CnIndexPage `expandableRows`.", "required": ["title"], "properties": { "title": { "type": "string", "minLength": 1 }, "badge": { "type": "object", "additionalProperties": false, "required": ["label"], "properties": { "when": { "type": "object" }, "label": { "type": "string", "minLength": 1 } } }, "description": { "type": "string", "minLength": 1 }, "chips": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "each": { "type": "string", "minLength": 1 }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "tones": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["when", "variant"], "properties": { "when": { "type": "object" }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "text": { "type": "string" } } } } } }, "meta": { "type": "array", "items": { "type": "string", "minLength": 1 } }, "table": { "type": "object", "additionalProperties": false, "required": ["field", "columns"], "properties": { "field": { "type": "string", "minLength": 1 }, "sortable": { "type": "boolean", "description": "Every column header sorts the sub-table (ascending, descending, off)." }, "filterable": { "type": "boolean", "description": "Every column header has a filter on the values the column holds." }, "reorder": { "type": "object", "additionalProperties": false, "description": "A drag handle per entry; a move saves the row with the list renumbered in `key` (default order).", "properties": { "key": { "type": "string", "minLength": 1 } } }, "columns": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "key": { "type": "string", "minLength": 1 }, "label": { "type": "string" }, "sortable": { "type": "boolean" }, "filterable": { "type": "boolean" }, "as": { "type": "string", "enum": ["text", "order", "bar", "check", "yesNo", "pill"] }, "template": { "type": "string", "minLength": 1 }, "strong": { "type": "boolean" }, "text": { "type": "string" }, "tones": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["when", "variant"], "properties": { "when": { "type": "object" }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "text": { "type": "string" } } } } } } }, "note": { "type": "string" }, "action": { "type": "object", "additionalProperties": false, "required": ["id"], "properties": { "id": { "type": "string", "minLength": 1 }, "label": { "type": "string" } } }, "entryAction": { "type": "object", "additionalProperties": false, "required": ["id", "label"], "description": "An icon button after every entry, named \"{label} {entry}\"; it runs the row action `id`.", "properties": { "id": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "icon": { "type": "string", "minLength": 1, "description": "A CnIcon name; Pencil when left out." } } } } }, "buttons": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["action"], "properties": { "action": { "type": "string", "minLength": 1 }, "label": { "type": "string" }, "icon": { "type": "string", "minLength": 1, "description": "A CnIcon name drawn before the label." } } } }, "open": { "type": "string", "enum": ["first", "all", "none"] } } }, + "tableTitle": { "type": "string", "minLength": 1, "description": "type='index', table view: a title at the top of the table card, an i18n key; under the board look the head row then reads on white with a rule under it (index-table-title). Maps to CnIndexPage `tableTitle`." }, "cardLayout": { "type": "object", "additionalProperties": false, @@ -5422,11 +5822,13 @@ "headPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } }, "number": { "type": "string", "minLength": 1 }, "title": { "type": "string", "minLength": 1 }, - "meta": { "type": "array", "description": "The meta line's parts: a field name reads through the page column it names; `{ field, formatter?, formatterOptions? }` reads as plain text through its own formatter (card-layout-part-formatter).", "items": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" } } } ] } }, + "meta": { "type": "array", "description": "The meta line's parts: a field name reads through the page column it names; `{ field, formatter?, formatterOptions? }` reads as plain text through its own formatter (card-layout-part-formatter); `{ template }` is a line filled from the object, in the host's translation (`'{stages|count} stages'`, card-list-chips).", "items": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" } } }, { "type": "object", "additionalProperties": false, "required": ["template"], "properties": { "template": { "type": "string", "minLength": 1 } } } ] } }, "footerPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } }, + "note": { "description": "A line under the meta line, read as a meta part is (`{ field, formatter?, formatterOptions? }`), left out while empty; `tone` purple or teal inks it in that pill tone's text colour, else the soft text colour (card-layout-note, 2.151.0).", "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" }, "tone": { "type": "string", "enum": ["purple", "teal"] } } }, + "footerCell": { "description": "A part in the footer before the date, read as a meta part is: a field name draws through the page column it names (its widget, e.g. the handler's picture and name), `{ field, formatter?, formatterOptions? }` reads as text through its own formatter; left out while empty (DqZakenSorteren, 2.145.0).", "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" } } }] }, "footerDate": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "overdueVariant": { "type": "string", "enum": ["error", "warning"], "description": "The date's tone once it has passed (2.132.0)." } } }, "description": { "description": "A line under the meta line: a field, or `{ field, maxLength? }` cut at maxLength characters (140 by default) with an ellipsis (2.132.0).", "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "maxLength": { "type": "integer", "minimum": 1 } } }] }, - "chips": { "type": "array", "description": "Pills under the description, one per entry whose field holds a value; the text reads through the entry's formatter, `colorMap` and `labels` work as on a pill (2.132.0).", "items": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } } }] } } + "chips": { "type": "array", "description": "Pills under the description, one per entry whose field holds a value; the text reads through the entry's formatter, `colorMap` and `labels` work as on a pill (2.132.0). With `each`, a list field gives one pill per entry: `each` names the entry's field or is a template filled from the entry (card-list-chips).", "items": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "each": { "type": "string", "minLength": 1 }, "tones": { "type": "array", "description": "On a list chip (each): the first tone whose `when` (a visibleWhen condition read on the list entry) holds gives the chip its variant (card-list-chip-tones, 2.159.0).", "items": { "type": "object", "additionalProperties": false, "required": ["when", "variant"], "properties": { "when": { "type": "object" }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } } } }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } } }] } } } }, "afterList": { "type": "array", "description": "type='index': cards after the list, stacked full width in the listed order, the counterpart of statRow above it (index-scope-presentation). Each entry is a widget definition resolved like statRow's. A query preset or a folder scope can set its own. Maps to CnIndexPage `afterList`.", "items": { "type": "object", "required": ["id", "type"], "properties": { "id": { "type": "string", "minLength": 1 }, "type": { "type": "string", "minLength": 1 }, "title": { "type": "string" }, "subtitle": { "type": "string" }, "content": { "type": "object" }, "headerLink": { "type": "object" } } } }, @@ -5477,6 +5879,7 @@ "icon": { "type": "string", "minLength": 1, "description": "MDI icon name." }, "format": { "type": "string", "enum": ["csv", "excel"], "default": "csv", "description": "For `export`." }, "run": { "type": "string", "minLength": 1, "description": "For `export`: the id of a `headerActions` entry the button runs instead of the export dialog. On a named or endpoint source an Export without `run` (and without a mass-export listener) is not drawn (2.127.0)." }, + "placement": { "type": "string", "enum": ["header", "tabs"], "default": "header", "description": "For `actions-menu`: `tabs` draws the menu at the end of the page tabs' row instead of in the header, as PqBiExport and PqFlows draw \"Acties\" beside their tabs; a page without page tabs keeps it in the header (index-actions-menu-beside-tabs)." }, "items": { "type": "array", "minItems": 1, "items": { "anyOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["heading"], "properties": { "heading": { "type": "string", "minLength": 1, "description": "i18n key of a group heading." } } } ] }, "description": "For `actions-menu`: the menu's entries, in order. Each is `header-actions` (every header action), the id of one `headerActions` entry, or a built-in of the actions bar's own menu, which the bar drops while header buttons show: `import`, `export`, `refresh`, `copy-selected`, `delete-selected` (disabled until a row is selected) or `documentation` (a link to the page's documentationUrl, left out without one). An unknown name is left out. Absent: the header actions only (screens-index-header-actions-menu, 2.90.0). Entries can be grouped: `{ \"heading\": \"\" }` draws a group heading and `\"separator\"` a rule, each only when an entry follows it, so a group whose entries are all left out leaves no empty heading (screens-index-header-menu-headings, 2.93.0)." } } } @@ -5553,11 +5956,13 @@ "additionalProperties": false, "properties": { "value": { "type": "string", "minLength": 1 }, - "label": { "type": "string", "minLength": 1, "description": "i18n key." } + "label": { "type": "string", "minLength": 1, "description": "i18n key." }, + "hint": { "type": "string", "description": "i18n key of a line under the option: what choosing it does besides recording it." } } } }, "url": { "type": "string", "minLength": 1, "description": "App endpoint that records the purpose; {objectId} is filled in." }, + "checkUrl": { "type": "string", "minLength": 1, "description": "App endpoint asked first (GET, {objectId} filled in): an answer { required: false } opens the record without the dialog, because the app recorded the purpose itself." }, "title": { "type": "string", "description": "i18n key of the dialog's title." }, "description": { "type": "string", "description": "i18n key of the sentence under the title." } } @@ -5665,6 +6070,18 @@ "type": "boolean", "description": "Whether to show the built-in mass Import action. Maps to CnIndexPage `showMassImport`." }, + "importTemplate": { + "type": "boolean", + "description": "The import dialog offers an empty file with the schema's columns (OpenRegister's import template). Maps to CnIndexPage `importTemplate`." + }, + "importMapping": { + "type": "boolean", + "description": "The import dialog maps each column of a CSV or JSON file to a schema field, or leaves it out. Maps to CnIndexPage `importMapping`." + }, + "importCheckFirst": { + "type": "boolean", + "description": "The import dialog offers to check the file first and import nothing (OpenRegister dryRun). Maps to CnIndexPage `importCheckFirst`." + }, "showMassExport": { "type": "boolean", "description": "Whether to show the built-in mass Export action. Maps to CnIndexPage `showMassExport`." @@ -5868,7 +6285,23 @@ "modules": { "type": "array", "items": { "$ref": "#/$defs/linkModule" }, "description": "Module cards drawn above the link cards (board PqModules): per module an icon, title, in-the-menu/off badge, description, its pages, an Open button and, when off, a link to the admin settings." }, "modulesTitle": { "type": "string", "description": "i18n key / heading of the modules section. Defaults to 'Modules'." }, "footnote": { "type": "string", "description": "i18n key / a note under the module cards." }, - "groupStyle": { "type": "string", "enum": ["cards", "list"], "default": "cards", "description": "cards: a grid of link cards per group. list: one card per group with a row per link (icon, label, description, chevron or external-link icon)." } + "groupStyle": { "type": "string", "enum": ["cards", "list"], "default": "cards", "description": "cards: a grid of link cards per group. list: one card per group with a row per link (icon, label, description, chevron or external-link icon)." }, + "panel": { + "type": "object", + "additionalProperties": false, + "description": "A closing card after the groups (board PqGeavanceerdHub, the menu structure): a badge, a paragraph and a link beside a titled list of facts. Strings are translated, then {runtime.} is filled.", + "properties": { + "label": { "type": "string" }, + "badge": { "type": "object", "required": ["label"], "additionalProperties": false, "properties": { "label": { "type": "string", "minLength": 1 }, "icon": { "type": "string" }, "tone": { "type": "string", "enum": ["primary", "purple"], "description": "The pill tone: primary (default), or purple, as the boards draw an administrators-only badge." } } }, + "text": { "type": "string" }, + "link": { "type": "object", "required": ["label"], "additionalProperties": false, "properties": { "label": { "type": "string", "minLength": 1 }, "route": { "type": "string", "minLength": 1 }, "href": { "type": "string", "minLength": 1 }, "params": { "type": "object" }, "query": { "type": "object" } } }, + "title": { "type": "string" }, + "subtitle": { "type": "string" }, + "facts": { "type": "array", "items": { "type": "object", "required": ["label"], "additionalProperties": false, "properties": { "label": { "type": "string", "minLength": 1 }, "value": { "type": "string" }, "note": { "type": "string" }, "visibleIf": { "$ref": "#/$defs/visibleIfCondition" } } } }, + "permission": { "type": "string" }, + "visibleIf": { "$ref": "#/$defs/visibleIfCondition" } + } + } } } },