@conduction/nextcloud-vue 2.144.0 → 2.146.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +1 -0
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js.map +1 -1
  7. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  8. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +55 -2
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  10. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  12. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +16 -0
  13. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -1
  15. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +1 -1
  17. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -1
  18. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +12 -2
  20. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +42 -30
  22. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  23. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +22 -0
  24. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  25. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -1
  26. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +1 -1
  28. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +1 -1
  29. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +3 -3
  31. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -1
  32. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  33. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +46 -8
  34. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +3 -3
  36. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  37. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  38. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +2 -2
  39. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +6 -3
  42. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  44. package/dist/esm/components/CnMailTab/CnMailTab.vue.js +1 -1
  45. package/dist/esm/components/CnMailTab/CnMailTab.vue2.js.map +1 -1
  46. package/dist/esm/components/CnMailTab/CnMailTab.vue3.js +2 -2
  47. package/dist/esm/components/CnMailTab/CnMailTab.vue3.js.map +1 -1
  48. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  49. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  50. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js +1 -0
  51. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  52. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue.js +1 -1
  53. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue2.js +21 -2
  54. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue2.js.map +1 -1
  55. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue3.js.map +1 -1
  56. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  57. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +73 -12
  58. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +165 -126
  60. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
  62. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
  65. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js +14 -0
  66. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
  67. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +2 -1
  68. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
  69. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  70. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  71. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  72. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  73. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -8
  74. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  75. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  76. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue.js +1 -1
  77. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +10 -2
  78. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  79. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  80. package/dist/esm/composables/indexSources.js +35 -9
  81. package/dist/esm/composables/indexSources.js.map +1 -1
  82. package/dist/esm/l10n/nl.json.js +2 -0
  83. package/dist/esm/l10n/nl.json.js.map +1 -1
  84. package/dist/esm/library-style.js +1 -1
  85. package/dist/esm/nextcloud-vue.css +1204 -467
  86. package/dist/esm/utils/builtInFormatters.js +18 -1
  87. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  88. package/dist/esm/utils/detailObjectContext.js +7 -2
  89. package/dist/esm/utils/detailObjectContext.js.map +1 -1
  90. package/dist/esm/utils/localHeaderRows.js +95 -0
  91. package/dist/esm/utils/localHeaderRows.js.map +1 -0
  92. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  93. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  94. package/dist/esm/utils/visibleWhen.js +6 -0
  95. package/dist/esm/utils/visibleWhen.js.map +1 -1
  96. package/dist/nextcloud-vue.cjs.js +686 -239
  97. package/dist/nextcloud-vue.cjs.js.map +2 -2
  98. package/dist/nextcloud-vue.css +1204 -467
  99. package/l10n/nl.json +2 -0
  100. package/package.json +1 -1
  101. package/src/components/CnActionDialog/CnActionDialog.vue +8 -1
  102. package/src/components/CnAiCompanion/CnAiChatPanel.vue +3 -0
  103. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +55 -2
  104. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +18 -0
  105. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +4 -2
  106. package/src/components/CnDataTable/CnDataTable.vue +19 -2
  107. package/src/components/CnDetailPage/CnDetailPage.md +1 -0
  108. package/src/components/CnDetailPage/CnDetailPage.vue +23 -0
  109. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +3 -1
  110. package/src/components/CnFactsWidget/CnFactsWidget.vue +74 -9
  111. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +2 -2
  112. package/src/components/CnIndexPage/CnIndexPage.vue +6 -3
  113. package/src/components/CnMailTab/CnMailTab.vue +6 -4
  114. package/src/components/CnNotesCard/CnNotesCard.vue +7 -2
  115. package/src/components/CnNotificationMatrix/CnNotificationMatrix.vue +25 -3
  116. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +132 -20
  117. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +2 -0
  118. package/src/components/CnSettingsField/CnSettingsField.vue +15 -0
  119. package/src/components/CnTabsWidget/CnTabsWidget.vue +6 -1
  120. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -8
  121. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +10 -2
  122. package/src/composables/indexSources.js +35 -9
  123. package/src/css/dialog.css +25 -9
  124. package/src/css/look-board-detail.css +370 -30
  125. package/src/css/look-board-index.css +116 -4
  126. package/src/css/look-board.css +168 -8
  127. package/src/css/table.css +17 -1
  128. package/src/schemas/app-manifest-v2.schema.json +2 -2
  129. package/src/utils/builtInFormatters.js +17 -0
  130. package/src/utils/detailObjectContext.js +7 -2
  131. package/src/utils/localHeaderRows.js +91 -0
  132. package/src/utils/validateManifestV2.compiled.js +2 -2
  133. package/src/utils/visibleWhen.js +6 -0
@@ -1,7 +1,7 @@
1
1
  import { getDefaultExportFromCjs } from '../_virtual/_commonjsHelpers.js';
2
2
  import { __module as validateManifestV2_compiled } from '../_virtual/validateManifestV2.compiled.js';
3
3
 
4
- validateManifestV2_compiled.exports = validate20;validateManifestV2_compiled.exports.default = validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$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.155.0","type":"object","required":["$schema","version"],"allOf":[{"$comment":"UI manifests: a manifest that declares pages MUST also declare menu (unchanged v2 coupling).","if":{"required":["pages"]},"then":{"required":["menu"]}},{"$comment":"Observability-only profile: a manifest with no pages MUST carry an observability block. ADR-040 Tier-0 adopters (e.g. nldesign, planix) ship src/manifest.json solely as AppHost engine config — the app renders no manifest-driven UI, so forcing menu/pages on them produced permanently-failing gate-22 manifests (2026-07-06 fleet audit, item 3b).","if":{"not":{"required":["pages"]}},"then":{"required":["observability"]}}],"additionalProperties":false,"properties":{"$schema":{"type":"string","format":"uri","description":"REQUIRED in v2. Must point to the v2 schema URL: https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json. Presence of this field enables the validator to dispatch to the v2 Ajv instance rather than the v1 hand-rolled validator."},"version":{"type":"string","pattern":"^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$","description":"Semver of the manifest content. Bump when the manifest changes meaningfully. Used for cache busting and app-builder migration tracking. Distinct from the schema's own version."},"look":{"type":"string","enum":["nextcloud","board"],"default":"nextcloud","description":"screens-chrome-parity: the look the whole app is drawn in. `nextcloud` (the default) renders exactly as before. `board` moves the app to the look of the screens on identity.conduction.nl: CnAppRoot puts `cn-look-board` on its root and provides `cnLook` to dialogs and other teleported components. A page can override it with `config.look`."},"i18n":{"type":"object","required":["sourceLanguage","languages","labels"],"additionalProperties":false,"description":"manifest-i18n-labels: translations of the app's labels. Labels are written once, in `sourceLanguage`, and `labels` maps a language to a map of the text as written to its translation. CnAppRoot looks a label up here for the user's language (then its base language, `en_GB` falls back to `en`), then in the host's translate, then shows the text as written; the fallback is per label. `languages` lists the languages translated into (not the source language); every key of `labels` must be one of them. Language codes are Nextcloud language codes (nl, en_GB, pt_BR).","properties":{"sourceLanguage":{"type":"string","minLength":2},"languages":{"type":"array","uniqueItems":true,"items":{"type":"string","minLength":2}},"labels":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"string"}}}}},"openbuildEditable":{"type":"boolean","default":true,"description":"Whether the Buildiq in-app edit button (ADR-041) is offered on this app's pages. Default true. Set false to suppress it — e.g. Buildiq's own UI, which does not edit itself with itself. Read by CnAppRoot to gate `cnOpenBuildAvailable`. The property name and the inject key keep their `openbuild` spelling: both are data/runtime contracts that shipped manifests and consumer apps already carry, so the 2026-08-21 OpenBuild → Buildiq rename deliberately left them untouched."},"dependencies":{"type":"array","default":[],"items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","description":"Nextcloud app id."},"required":{"type":"boolean","default":true,"description":"true (default) = HARD: absence blocks the app shell behind CnDependencyMissing. false = SOFT: an optional integration whose absence surfaces a dismissible in-shell notice and never blocks."},"name":{"type":"string","description":"Human-readable display label; falls back to id."}}}]},"description":"Nextcloud app dependencies. Each entry is either a string (a HARD dependency — the app cannot run without it, absence blocks the shell) or an object { id, required?, name? } where required:false marks a SOFT (optional) dependency (required defaults to true). CnAppRoot checks each via useAppStatus(id); unresolved HARD deps block behind CnDependencyMissing, unresolved SOFT deps show a dismissible NcNoteCard banner. Sentinels (@resolve:*) are NOT allowed here — resolved at build time against known app IDs."},"setup":{"type":"object","additionalProperties":false,"required":["steps"],"description":"First-time setup wizard (ADR-042). Rendered by CnSetupWizard; required steps gate the app via CnAppRoot's setup phase. Steps run privileged server-side actions via /api/setup/action — the wizard never writes OpenRegister objects from the browser.","properties":{"enabled":{"type":"boolean","default":true,"description":"Whether the setup wizard is active for this app."},"version":{"type":"integer","default":1,"description":"Setup contract version; bump to re-trigger setup after a breaking change."},"completionConfigKey":{"type":"string","description":"App-config key storing the completed setup version."},"dismissAction":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"Setup action that records a closed or finished setup wizard on the server, so it stays closed in every browser. CnAppRoot posts `/apps/{appId}/api/setup/action/{dismissAction}` with `{ finished }` once when the user closes or finishes the wizard. The app either answers the outstanding optional steps or reports `dismissed` (true, or the setup version it was closed at) in `GET /api/setup/status`. Without it, the close is remembered only in the browser (localStorage), as before."},"steps":{"type":"array","description":"Ordered setup steps rendered by CnSetupWizard.","items":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"type":"string","description":"Unique step id; also the #step-<id> slot name."},"type":{"type":"string","enum":["info","config-fields","choice","run-action","summary","component"],"description":"Built-in step renderer."},"title":{"type":"string"},"body":{"type":"string","description":"info: explanatory copy."},"note":{"type":"string","minLength":1,"description":"choice: a footnote under the options, translated like body."},"visibleWhen":{"description":"Offer the step only when the setup status says so: a dotted path into the setup status document whose value must be truthy, or { path, equals } for an exact value. Hidden while the status loads.","oneOf":[{"type":"string","minLength":1},{"type":"object","required":["path"],"additionalProperties":false,"properties":{"path":{"type":"string","minLength":1},"equals":{}}}]},"footnote":{"type":"string","minLength":1,"description":"choice: the same footnote under another name; note wins when both are set."},"required":{"type":"boolean","default":false,"description":"When true the step gates the app until done."},"schema":{"description":"config-fields: a JSON Schema ref (string) or inline object rendered via fieldsFromSchema."},"configKey":{"type":"string","description":"choice: app-config key the selection is written to."},"configKeys":{"type":"array","items":{"type":"string"},"description":"config-fields: app-config keys the fields write."},"options":{"type":"array","description":"choice: selectable options.","items":{"type":"object","additionalProperties":false,"required":["value","label"],"properties":{"value":{},"label":{"type":"string"}}}},"optionsSource":{"type":"string","description":"choice: where to read the option list from at runtime, instead of declaring it here. Names a key in the app's own GET /api/setup/status document (dots walk into nested objects, e.g. `profiles`). Entries accept `id` for `value` and `name` for `label`, and a numeric `objectCount` becomes the stat a card shows. Use it when the SERVER owns what is on offer, so the manifest cannot drift from it."},"display":{"type":"string","enum":["select","cards","rows"],"default":"select","description":"choice: how the options are rendered. `select` is a dropdown; `cards` is a grid of CnChoiceCards, which shows each option's description and stats; `rows` is CnChoiceCards' rows layout, one radio row per option with its description below the label. Pick cards or rows when the choice needs explaining."},"dependsOn":{"type":"string","description":"choice: the configKey of a previous choice step this one cascades from. Disabled until the parent is set; its option list is taken from optionsByParent[parentValue] (e.g. organisation-type depends on country)."},"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."},"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."},"loadAction":{"type":"string","minLength":1,"description":"choice with display cards: an actionId each option card loads itself with. Every card except the one whose value is `none` gets its own Load button, which POSTs { dataset: <value> } to /api/setup/action/{loadAction}, spins while it runs and shows the result on that card. A successful load also selects the card. With loadAction the separate run-action step that loaded the picked dataset is no longer needed; manifests that keep it still work."},"requires":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true,"description":"App ids this step needs. When any of them is not installed and enabled, the wizard skips the step and the summary shows it as skipped, naming the missing apps. Setup status then counts the step as not applicable, neither met nor unmet, so it never reopens the wizard or gates the app. This holds for a required step too."},"action":{"type":"string","description":"run-action: actionId POSTed to /api/setup/action/{action}."},"onDemand":{"type":"boolean","default":false,"description":"run-action: the step runs only when the user clicks Run, never automatically, and is never outstanding work (it does not open the wizard or decide where a resumed wizard starts). The summary ticks it only when its action succeeded in the current session and shows it as not run otherwise, whatever the server reports. For destructive steps an app reports as done on purpose so they never auto-run, such as removing example data."},"healthCheck":{"type":"boolean","default":false,"description":"summary: render a health recap."},"component":{"type":"string","description":"component: app-registered component name (or use the #step-<id> slot)."}}}}}},"walkthrough":{"type":"object","additionalProperties":false,"description":"Abstract product walkthrough (ADR-043). One or more spotlighted, versioned tours rendered by CnWalkthrough over the live shell.","required":["tours"],"properties":{"enabled":{"type":"boolean","default":true,"description":"Master switch; when false no tour auto-starts."},"version":{"type":"integer","description":"Walkthrough contract version."},"completionConfigKey":{"type":"string","description":"Per-user app-config key holding the last app version whose tour the user has seen."},"tours":{"type":"array","description":"The app's tours.","items":{"type":"object","additionalProperties":false,"required":["id","steps"],"properties":{"id":{"type":"string","description":"Unique tour id (globally unique for cross-app hand-off, e.g. 'pipelinq:lead-to-bill')."},"title":{"type":"string","description":"i18n key / text for the tour name (restart menu)."},"trigger":{"type":"string","enum":["first-visit","version-bump","empty-index","manual"],"default":"manual","description":"When the tour auto-starts."},"minAppVersion":{"type":"string","description":"Semver; the tour is inert below this app version."},"startOn":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"uniqueItems":true,"description":"Route names the tour may auto-start on. A user who first lands anywhere else, such as on a case opened from a link, does not get the tour there: it waits until they reach one of these routes. Without it a tour whose first step is centered starts wherever the user lands. Has no effect on a tour started by hand."},"steps":{"type":"array","description":"Ordered steps.","items":{"type":"object","additionalProperties":false,"required":["id","target","advanceOn"],"properties":{"id":{"type":"string","description":"Unique step id within the tour."},"title":{"type":"string","description":"i18n key / text for the coachmark title."},"body":{"type":"string","description":"i18n key / text for the coachmark body; supports {{contextVar}} interpolation."},"task":{"type":"string","description":"Optional 'go do this' instruction; presence + a non-manual advanceOn makes this an enforced action step."},"sinceVersion":{"type":"string","description":"Semver the step was introduced in; drives the 'what's new' composition."},"placement":{"type":"string","enum":["auto","top","bottom","left","right","center"],"default":"auto","description":"Coachmark placement; 'center' renders anchorless with no cutout."},"optional":{"type":"boolean","default":false,"description":"Skip gracefully when the target/condition is absent."},"allowManualNext":{"type":"boolean","default":false,"description":"Show a manual Next escape hatch on an enforced action step."},"handoff":{"type":"object","additionalProperties":false,"required":["url"],"description":"Cross-app hand-off (ADR-043). When present, the coachmark primary action becomes 'Continue in {app}' and navigates to `url` carrying a cn_resume_tour/cn_resume_step token so the destination app resumes the named tour.","properties":{"app":{"type":"string","description":"Destination Conduction app id (label + intent)."},"url":{"type":"string","description":"Destination URL to navigate to."},"tour":{"type":"string","description":"Tour id to resume in the destination (defaults to this tour's id)."},"step":{"type":"string","description":"Step id to resume at (defaults to the destination tour's first step)."}}},"target":{"type":"object","additionalProperties":false,"required":["kind"],"description":"What the step spotlights.","properties":{"kind":{"type":"string","enum":["nav-item","widget","action","page","element","selector"],"description":"Resolution strategy."},"ref":{"type":"string","description":"Stable id: route name (nav-item/page), widgetKey (widget), action id (action), or data-walkthrough-id / data-testid (element)."},"app":{"type":"string","description":"Optional other Conduction app id for a cross-app target (page kind)."},"selector":{"type":"string","description":"Raw CSS selector for kind:selector (last resort)."}}},"advanceOn":{"type":"object","additionalProperties":false,"required":["type"],"description":"Condition that advances the step.","properties":{"type":{"type":"string","enum":["manual","click-target","route-match","element-appears","object-created","delay"],"description":"Advance trigger."},"route":{"type":"string","description":"route-match: the route name to await."},"register":{"type":"string","description":"object-created: OR register slug."},"schema":{"type":"string","description":"object-created: OR schema slug."},"selector":{"type":"string","description":"element-appears: selector to await."},"ms":{"type":"integer","minimum":0,"description":"delay: milliseconds before auto-advance."},"capture":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of contextVar -> ':param' (route-match) or ':id' (object-created) captured into the tour context for {{var}} interpolation."}}}}}}}}}}},"support":{"type":"object","additionalProperties":false,"description":"First-open support and donation note. CnAppRoot mounts CnSupportDialog once per user on first open and reads this block; CnEditSupportModal edits it. Omitting the block keeps the default-on behaviour with the shell's own copy, so an app only declares what it wants to override. The block was read by CnAppRoot and written by CnEditSupportModal before it was declared here, which made `additionalProperties: false` reject every manifest that set it.","properties":{"enabled":{"type":"boolean","description":"Whether the note is shown on first open. Omit for the default (true); set false to suppress it for this app."},"title":{"type":"string","description":"Dialog heading. Blank keeps the shell default."},"appName":{"type":"string","description":"Display name interpolated into the body copy. Defaults to the host app's name."},"bodyParagraphs":{"type":"array","description":"Body copy, one entry per paragraph, replacing the shell's default paragraphs.","items":{"type":"string"}},"founderName":{"type":"string","description":"Signature name."},"founderTitle":{"type":"string","description":"Signature role, shown under the name."},"founderAvatarUrl":{"type":"string","description":"Signature avatar image URL."},"founderProfileUrl":{"type":"string","description":"Profile the signature avatar links to."},"buttons":{"type":"object","additionalProperties":false,"description":"Per-button overrides, keyed by the four built-in button ids. Each may be hidden, relabelled, restyled or re-pointed; omit a key to keep its default.","properties":{"donate":{"$ref":"#/$defs/supportButton"},"support":{"$ref":"#/$defs/supportButton"},"feature-request":{"$ref":"#/$defs/supportButton"},"app-store":{"$ref":"#/$defs/supportButton"}}}}},"personalisation":{"$ref":"#/$defs/personalisation","description":"App defaults for the per-user options CnPersonalPreferences renders, and the switch that turns the personal layer off instance wide."},"nav":{"type":"object","additionalProperties":false,"description":"Navigation-level configuration consumed by CnAppNav.","properties":{"includePersonalSettings":{"type":"boolean","default":true,"description":"Auto-prepend a 'Personal settings' entry at the top of the settings foldout (opens the host's NcAppSettingsDialog via cnOpenUserSettings). Set false for apps with no per-user settings."},"settingsLabel":{"type":"string","description":"Override label for the settings foldout's gear button. Defaults to 'Advanced'."},"card":{"type":"object","additionalProperties":false,"required":["title"],"description":"A card above the footer entries, rendered by CnAppNav (zuiddrecht-pixel-gaps): a title, a line of text and one link. Title, text and link label are i18n keys. Omitted, nothing renders there.","properties":{"title":{"type":"string","minLength":1},"text":{"type":"string"},"link":{"type":"object","additionalProperties":false,"required":["label"],"description":"The card's link: a `route` (named page, optional `params`) is a router link, an `href` a plain anchor, an `action` id a button that emits `card-action`.","properties":{"label":{"type":"string","minLength":1},"route":{"type":"string"},"params":{"type":"object","additionalProperties":true},"href":{"type":"string"},"action":{"type":"string"}}}}},"help":{"type":"object","additionalProperties":false,"required":["label"],"description":"A help entry with a help icon in the navigation footer, above the settings foldout (zuiddrecht-pixel-gaps): `route` (named page) or `href` (opens in a new tab).","properties":{"label":{"type":"string","minLength":1},"route":{"type":"string"},"href":{"type":"string"}}},"footer":{"type":"array","items":{"type":"string","minLength":1},"description":"Declare the navigation footer (zuiddrecht-pixel-gaps-2): an ordered list of `section: \"footer\"` menu entry ids, plus the reserved ids `help` (the `nav.help` entry) and `settings` (the settings foldout). Only the named footer entries render there, in that order; footer entries left out move into the settings foldout so their pages stay reachable. `settings` first puts the foldout above the list (the board's `[\"settings\", \"help\"]`). Omitted, the footer renders as before: help, every footer entry, then the foldout."},"primaryAction":{"$ref":"#/$defs/primaryAction","description":"App-wide default primary action rendered as an NcAppNavigationNew button above the main menu list. A page-scoped pages[].primaryAction for the current route wins over this default."},"brand":{"type":"object","additionalProperties":false,"description":"A brand block at the very top of the app navigation, above the primary action: a logo, a name and a caption. Rendered by CnAppNav. Omitted, nothing renders there.","properties":{"logo":{"type":"string","description":"URL of the logo image. Decorative when `name` is set."},"emblem":{"type":["string","boolean"],"description":"An emblem drawn in place of the logo (zuiddrecht-pixel-gaps): the municipality's mark beside the app name, distinct from the wordmark in the top bar. A URL, or `true` to draw the theme's emblem from the `--nldesign-emblem-url` custom property. Decorative when `name` is set."},"name":{"type":"string","description":"i18n key / text for the app or organisation name."},"caption":{"type":"string","description":"i18n key / text for the line under the name, for example the organisation."},"alt":{"type":"string","description":"i18n key / alternative text for the logo. Used only when no `name` is set."},"placement":{"type":"string","enum":["header","nav"],"description":"Where the brand block is drawn (screens-brand-block-top-bar). Under `look: \"board\"` the default is `header`: a 237px block at the start of Nextcloud's own header (#header), before the app menu, drawn by CnAppRoot; an app without CnAppNav has it too. Nextcloud offers no extension point there, so when `#header` is absent the block falls back to the navigation. `nav` keeps the block at the top of the app navigation. Ignored without the board look, where the navigation draws the block."}}},"searchPlaceholder":{"type":"string","minLength":1,"description":"i18n key / text for the line on the search field in Nextcloud's header (screens-board-top-bar-search, 2.118.0), naming what the app's search finds, for example \"Search cases, documents or contacts\". Applied by CnAppRoot under `look: \"board\"` on a page with `#header`; it replaces only the text of the resting field. Omitted, the field keeps Nextcloud's own line."},"featureRequestRepo":{"type":"string","description":"`<owner>/<repo>` slug on the forge that the in-product feature-request deep-link targets (provided to descendants as `cnFeatureRequestRepo`). Falls back to `Conduction/<appId>` when omitted."},"forge":{"type":"object","additionalProperties":false,"description":"Forge that the in-product feature-request deep-link targets. Switching the whole fleet's forge (onto a self-hosted Forgejo/Gitea, or back to Codeberg) is just this one field. Defaults to GitHub, the only host the fleet publishes to.","properties":{"type":{"type":"string","enum":["codeberg","forgejo","gitea","github"],"default":"github","description":"Forge type. Selects how the 'new issue' form is pre-filled: `github` uses per-field Issue-Form query params; `codeberg`/`forgejo`/`gitea` assemble a Markdown body (only title + body are supported there)."},"baseUrl":{"type":"string","description":"Override the forge host. Required for self-hosted `forgejo`/`gitea`; optional for `codeberg`/`github` (their canonical public hosts are used when omitted)."}}}}},"runtime":{"type":"object","description":"Runtime context data injected into the manifest by the backend (e.g. OpenRegister) at serve time. Carries per-user fields that visibleIf context-path predicates resolve against. The canonical sub-object is 'user', which exposes user-specific flags and role information.","additionalProperties":true,"properties":{"user":{"type":"object","description":"Per-user runtime context. Populated by the backend for authenticated requests. All fields are optional; the FE predicate evaluator treats missing fields as undefined.","additionalProperties":true},"theme":{"$ref":"#/$defs/runtimeTheme","description":"NL Design System scoped theme selection (scoped-theme-applier). Consumed by CnAppRoot's useScopedTheme wiring, not by the backend."}}},"menu":{"type":"array","items":{"$ref":"#/$defs/menuItem"},"description":"Top-level navigation entries rendered by CnAppNav."},"pages":{"type":"array","items":{"$ref":"#/$defs/page"},"description":"Page definitions dispatched by CnPageRenderer. Each page's id is also its vue-router route name. Ids MUST be unique across the array; uniqueness is enforced by validateManifestV2 as a post-schema check."},"adminSettings":{"type":"array","items":{"$ref":"#/$defs/adminSettingsEntry"},"description":"Admin-only settings sections rendered by CnAppRoot's generic admin NcAppSettingsDialog, gated on app-owner-group membership (not OC.isUserAdmin()). An absent key or an empty array yields no admin nav entry and no admin dialog. See the manifest-admin-settings and admin-settings-owner-gating capabilities."},"credentials":{"type":"array","description":"External-provider credentials this app can use via the OpenRegister credential broker (github, gitlab, …). Each entry declares which provider, why, and at what scope; the app never receives the secret — the broker performs the outbound call on the user's behalf. See the credential-broker capability.","items":{"type":"object","required":["provider"],"additionalProperties":false,"properties":{"provider":{"type":"string","description":"Catalogue provider identifier (e.g. \"github\", \"gitlab\") — a key in OpenRegister's credential-providers catalogue."},"reason":{"type":"string","description":"Human-readable reason shown to the user in credential settings (why the app wants this provider)."},"scopes":{"type":"array","items":{"type":"string"},"description":"Advisory scopes the app needs (e.g. [\"repo\"])."}}}},"schedules":{"type":"array","description":"Declarative scheduled tasks (apphost-scheduling capability). Each entry declares a recurring task the OpenRegister AppHost schedule-reconciler turns into an Integriq job that runs on the existing background-job path — so a manifest-driven app (including a pure-virtual Buildiq app with no PHP on disk) can own its ingestion/maintenance cadence without shipping a TimedJob. The temporal peer of `credentials`/`observability`: consumed by the OpenRegister engine, NOT by the Vue renderer. Optional and additive.","items":{"type":"object","required":["id","action"],"additionalProperties":false,"oneOf":[{"required":["interval"],"not":{"required":["cron"]}},{"required":["cron"],"not":{"required":["interval"]}}],"properties":{"id":{"type":"string","description":"Stable identifier for this schedule, unique within the manifest. The reconciled job is keyed on applicationId + this id, so renaming it creates a new job and orphans the old one (which is then garbage-collected)."},"interval":{"type":"integer","minimum":1,"description":"Run cadence in seconds (e.g. 604800 = weekly). Exactly one of `interval` or `cron` must be set."},"cron":{"type":"string","description":"Standard 5-field cron expression (e.g. \"0 3 * * 1\"). The reconciler computes the job's nextRun from it. Exactly one of `interval` or `cron` must be set."},"action":{"type":"string","pattern":"^[a-z0-9]+:[a-z0-9-]+$","description":"A server-allow-listed generic action type, NOT a PHP class name. Only vetted types are accepted (v1: \"openconnector:synchronization\"); the reconciler maps the type to a trusted jobClass — a manifest-supplied class name is never executed. A non-allow-listed action is rejected and logged."},"arguments":{"type":"object","description":"Free-form arguments passed to the action (e.g. the synchronization id/ref). Interpreted by the vetted action, not by the reconciler.","additionalProperties":true},"enabled":{"type":"boolean","description":"Whether this schedule is active. Defaults to true. Setting false disables the reconciled job (preserving its run history) rather than deleting it."}}}},"observability":{"$ref":"#/$defs/observability","description":"Declarative observability block (ADR-040). Consumed at request time by the OpenRegister AppHost observability engine (GenericHealthController + GenericMetricsController), NOT by the Vue renderer. Declares the app's health checks and Prometheus metric descriptors so adopting apps ship zero hand-written observability PHP while keeping their /api/health + /api/metrics URLs and the ADR-006 contract."},"store":{"type":"object","description":"ADR-080 / ADR-114 Decision 4. The declarative store plane. An app that declares this block gets a working Store surface with NO backend of its own: OpenRegister's AppHost aliases /api/store/items and /api/store/items/{slug}/install at its GenericStoreController, which reads this block at request time. Discovery stays in the engine (the SSRF guard, the redirect refusal and the registry token never leave it) and the app builds no registry URL.","additionalProperties":false,"anyOf":[{"required":["schema"],"title":"An objects store names the remote schema its registry serves."},{"required":["types"],"title":"A configuration store names the shareable types it surfaces."}],"properties":{"schema":{"type":"string","description":"Remote schema slug the registry serves (e.g. \"case-type-template\", \"application-template\")."},"register":{"type":"string","description":"Remote register segment, used when the instance's registry_register config is unset or empty."},"localRegister":{"type":"string","description":"The register an install WRITES into. Defaults to the app id. A schema slug is NOT unique across the fleet (`case`, `task` and `document` exist in several apps at once), so an unscoped write can land in another app's register: an app whose OpenRegister register slug differs from its app id must set this."},"installable":{"type":"array","items":{"type":"string"},"description":"Schema slugs an install may write. THIS IS A SECURITY BOUNDARY, not a convenience: a registry is somebody else's server, and without the allowlist an item could name any schema the app owns. An absent or empty list refuses EVERY install rather than permitting every install."},"kinds":{"type":"array","items":{"type":"string"},"description":"Kind quick-filters (ADR-080 Decision 5). Declared ONCE here, beside the allowlist: the engine returns them in the search response and CnStorePage prefers them over its page config. Defaults to the shared vocabulary when omitted."},"types":{"type":"array","items":{"type":"string"},"description":"Shareable configuration type ids this store surfaces (store-over-federated-config). Declaring these makes the store exchange CONFIGURATION rather than rows: an item is a configuration set (`openregister.configset`), a flow (`openregister.flows`), a register with its schemas (`openregister.registers`), or any schema that marked itself `x-openregister-shareable`. Cards come from federated discovery and an install runs through the owning type, so a set arrives as registers, schemas, objects, views, flows, sources and mappings. An app that declares none keeps the remote objects API described by `schema`/`register`, and makes no discovery call at all."},"cardFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Card field to remote property map. Defaults to the field names the fleet's registries already publish."},"builtIn":{"type":"array","description":"The items this app ships itself, rendered when no registry is configured or the registry does not answer. ADR-080 Decision 4 makes this the price of the word Store: a surface that goes blank without a registry was never a store.","items":{"type":"object","additionalProperties":false,"required":["slug","title"],"properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string"}}}},"_note":{"type":"string","description":"Free-text rationale, ignored at runtime."}}},"deepLinks":{"type":"array","items":{"$ref":"#/$defs/deepLink"},"description":"ADR-040: object deep-link descriptors consumed by the integration registry / cross-app linking, NOT by the Vue renderer. Optional and additive."},"pageTemplates":{"type":"array","items":{"$ref":"#/$defs/pageTemplate"},"description":"Entity-scaffold page templates (manifest-entity-scaffold-templating). A template declares one reusable index/detail page shape ONCE, with `{{param}}` placeholders for the parts that vary per entity (register/schema binding, label, field/column subset) and a declared parameter list. The expander (utils/expandPageTemplates) materialises `pageInstances[]` into concrete `pages[]` at build/boot time, so CnPageRenderer only ever sees concrete pages and is unchanged by templating. Optional and additive: a manifest without pageTemplates is unaffected."},"pageInstances":{"type":"array","items":{"$ref":"#/$defs/pageInstance"},"description":"Per-entity template instantiations. Each entry references a `pageTemplates[]` template by `templateRef` and supplies only the values that vary. The expander substitutes them into the referenced template and appends the resulting concrete pages to `pages[]`. Expansion FAILS (named error) on an unknown templateRef or a missing required template parameter. Optional and additive."},"sets":{"type":"object","description":"Named, reusable field/column/sidebar sets referenced from templates via a `{{set:NAME}}` placeholder, so a repeated field/column/sidebar block is declared ONCE and shared across templates and pages instead of being re-listed. Each value is arbitrary JSON (typically an array of field/column defs or a sidebar object). Optional and additive.","additionalProperties":true},"mcp":{"type":"object","title":"MCP tool visibility hints","description":"OPTIONAL. Advisory visibility/UX hints for MCP tool surfaces (Hermiq agent tool picker, openbuild tool browser). Purely presentational: per ADR-063, OpenRegister's register (x-openregister-mcp dialect + #[McpTool] attributes) is the single source of CRUD-tool truth and OpenRegister RBAC is the authoritative invoke-time gate. This block grants no access and gates nothing; nothing in nextcloud-vue consumes it.","additionalProperties":false,"properties":{"expose":{"type":"boolean","title":"App-level exposure hint","description":"Advisory app-wide default for whether this app's register-derived MCP tools should be shown in agent / tool-picker surfaces. Advisory only — the authoritative gate is OpenRegister RBAC plus per-agent whitelists. Defaults to false (opt-in), matching ADR-063's default-OFF exposure model.","default":false},"pageTools":{"type":"object","title":"Per-page tool hints","description":"Maps a pages[].id to the ordered list of MCP tool ids that are contextually relevant on that page. Advisory grouping for UX surfaces only; it does not grant access. Keys are expected to match an existing pages[].id; the register remains authoritative for whether each listed tool actually exists.","additionalProperties":{"type":"array","title":"Relevant tool ids for a page","description":"Ordered MCP tool ids relevant to the page keyed above. Advisory ordering only.","items":{"type":"string","title":"MCP tool id","description":"A fully-qualified MCP tool id in the register's derived-CRUD shape {appId}.{schemaSlug}.{verb}, verb one of search|get|create|update|delete (ADR-063). Example only: \"pipelinq.lead.search\". The register is authoritative for existence; this is a hint.","pattern":"^[a-z0-9_-]+\\.[a-zA-Z0-9_-]+\\.(search|get|create|update|delete)$"}}},"agentHints":{"type":"object","title":"Agent-facing hints","description":"Advisory metadata for agent surfaces (Hermiq). All values are non-authoritative UX hints. Additional properties are permitted so consuming surfaces can introduce new advisory hints without a schema release (forward compatibility).","additionalProperties":true,"properties":{"summary":{"type":"string","title":"App tool summary","description":"One-line description of what this app's tools help an agent accomplish. Shown in agent / tool-picker surfaces."},"defaultTools":{"type":"array","title":"Default tool ids","description":"MCP tool ids to surface first / preselect when an agent has no page context. Advisory ordering hint only; not a grant.","items":{"type":"string","title":"MCP tool id","description":"Fully-qualified MCP tool id {appId}.{schemaSlug}.{verb}. Example only: \"pipelinq.lead.search\"."}},"keywords":{"type":"array","title":"Discovery keywords","description":"Keywords helping progressive-disclosure / tool-search surfaces (Hermiq) rank this app's tools. Advisory only.","items":{"type":"string","title":"Keyword","description":"A single discovery keyword."}}}}}}},"$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.<key>` 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.<key>` 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."},"optionIcon":{"type":"string","minLength":1,"description":"look 'search': one icon for every row."},"itemOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"widget 'sub-objects': field overrides for the row form (sub-objects-editor)."},"newItem":{"type":"object","description":"widget 'sub-objects': the values a new row starts with."},"itemLabel":{"type":"string","minLength":1,"description":"widget 'sub-objects': what one row is called (\"Checklist item\"), for its accessible name and dialog title."},"emptyText":{"type":"string","description":"widget 'sub-objects': shown while there are no rows."},"pruneWhen":{"type":"object","additionalProperties":false,"description":"widget 'sub-objects': tidy the rows on save: trim strings, omit empty optional keys, drop rows with a `blank` key, and with `invalid: true` rows missing a required key.","properties":{"blank":{"type":"array","items":{"type":"string","minLength":1}},"invalid":{"type":"boolean"}}},"placeholderFrom":{"type":"string","minLength":1,"description":"A text field's placeholder is this other field's value (form-small-features)."},"requiredWhen":{"type":"object","description":"Required while this condition holds: a `visibleWhen` predicate, or `{ all }` / `{ any }` of them (form-small-features)."},"warnWhen":{"description":"A note under the field that does not block the save, shown while `when` holds (form-small-features).","anyOf":[{"$ref":"#/$defs/fieldWarning"},{"type":"array","items":{"$ref":"#/$defs/fieldWarning"}}]},"clearWhenHidden":{"type":"boolean","description":"Write the field empty when its visibleWhen hides it (form-small-features)."},"stored":{"type":"boolean","description":"`false`: the field steers the form only and is not saved; it may be a key the schema does not have, with `type` and `label` (form-small-features)."},"initialWhen":{"type":"object","description":"A field that is not stored starts true when this condition holds on the loaded values (form-small-features)."},"sectionCollapsed":{"type":"boolean","description":"Makes the field's section foldable from its heading; true opens it folded (form-small-features)."},"optionSwatches":{"type":"object","additionalProperties":{"type":"string"},"description":"A select option's colour by value, drawn before its label (form-small-features)."},"optionIcons":{"type":"object","additionalProperties":{"type":"string"},"description":"A select option's icon by value, drawn before its label (form-small-features)."},"unique":{"description":"Refuse a value another stored object of this schema already has, checked before the save: `true`, or `{ filter }` to compare only among the objects the filter matches (`{ \"caseType\": \"@objectId\" }`) (list-reorder-order-field).","anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object"}}}]},"defaults":{"type":"object","description":"widget 'object-switch': the object the switch fills in when turned on; off writes null. Its keys are edited as fields at dotted paths."},"optionsFrom":{"$ref":"#/$defs/optionsFrom","description":"Draw the field as a select (a multiselect on an array field) over options read from a source; the option's value is stored and its label shown. A register schema property can say the same with `x-options-from` (form-field-options-from, 2.133.0)."}}},"fieldWarning":{"type":"object","additionalProperties":false,"required":["when","text"],"properties":{"when":{"type":"object","description":"The condition, as visibleWhen takes it, or `{ all }` / `{ any }`."},"text":{"type":"string","minLength":1,"description":"The note, through the app's translate."}}},"optionsFrom":{"description":"Select options read from a source: rows from an app endpoint (`url`, `params`, `responsePath`) or OpenRegister objects (`register`, `schema`, `filter`; tokens resolve). `valueField` and `labelField` are dot-paths into a row: by default the value is `id` (else the object's uuid) and the label `title` (else `name`, else the value).","oneOf":[{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","minLength":1,"description":"An app path, resolved with generateUrl."},"params":{"type":"object","description":"Query parameters sent with the request."},"responsePath":{"type":"string","minLength":1,"description":"Where the rows sit in the answer."},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"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.<key>`, 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)."}}},{"type":"object","additionalProperties":false,"required":["register","schema"],"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","description":"Filters on the objects, as a list filter takes them."},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"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.<key>`, 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)."}}}]},"breadcrumbCrumb":{"type":"object","additionalProperties":false,"required":["label"],"description":"One breadcrumb crumb (config.breadcrumb).","properties":{"label":{"type":"string","minLength":1},"route":{"type":"string"},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true,"description":"Query of the `route` link, such as `{ savedView: 'default:<slug>' }` to land on a view (saved-view-overrides)."},"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)."}}},"rowIndicator":{"type":"object","additionalProperties":false,"required":["field","text"],"description":"One declared state indicator on an index page's rows. Renders as an icon with a text alternative and a tooltip, never as colour alone.","properties":{"id":{"type":"string","minLength":1,"description":"Stable id for this indicator, used as its test id and its render key. Defaults to `field`."},"field":{"type":"string","minLength":1,"description":"Dotted path on the row the condition reads."},"equals":{"description":"The indicator applies when the field equals this value exactly."},"in":{"type":"array","description":"The indicator applies when the field's value is one of these."},"after":{"type":"string","minLength":1,"description":"The indicator applies while the field holds a readable date after this moment: `now` or an ISO date (\"held until a later date\")."},"before":{"type":"string","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."},"text":{"type":"string","minLength":1,"description":"The text alternative, read out beside the icon. Required: an indicator without one does not render."},"tooltip":{"type":"string","description":"Hover text. Defaults to `text`."}}},"folderSidebarScope":{"type":"object","additionalProperties":true,"description":"One folder in `config.folderSidebar.folders[]`, which doubles as a SCOPE: the three optional list-layout keys below let one index page show each folder the way that folder needs. Every other key (id, name, icon, ...) stays free-form, so an existing folder list validates unchanged. A scope (or the `x-index` block of the row a register-derived folder comes from) may also carry the presentation keys a query preset sets, under the same names: title, quickFilters, cardFields, countText, searchPlaceholder, footerNote, viewSwitch, addLabel, headerButtons, breadcrumb, headerMeta and afterList (index-scope-presentation); a scope's value wins over a preset's. The page's `config.columns` still decides which columns this page HAS; a scope only decides which of them are shown, in what order, sorted by what and searched over what. A scope naming a column the page does not declare is refused by validateManifestV2, not silently rendered.","properties":{"columns":{"type":"array","description":"The columns this scope shows, in order. Same shapes as `config.columns`: a bare key string or a column object. Every key must be one the page's `config.columns` declares. Omit to show the page's own columns.","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]}},"defaultSort":{"description":"The sort the fetch carries while this scope is active. One `{ key, order }` entry or a list of them; `order` is 'asc' (default) or 'desc'. `field` is accepted in place of `key`, matching the CnIndexPage `defaultSort` prop.","oneOf":[{"$ref":"#/$defs/folderSidebarScopeSort"},{"type":"array","items":{"$ref":"#/$defs/folderSidebarScopeSort"}}]},"searchFields":{"type":"array","items":{"type":"string","minLength":1},"description":"The fields the search box searches over while this scope is active. Omit to search whatever the page searches by default."}}},"folderSidebarScopeSort":{"type":"object","additionalProperties":false,"description":"One sort entry for a `folderSidebar` scope. Name the column with `key` (manifest spelling) or `field` (the CnIndexPage prop spelling); an entry carrying neither is dropped rather than sent as an empty sort key.","properties":{"key":{"type":"string","minLength":1},"field":{"type":"string","minLength":1},"order":{"type":"string","enum":["asc","desc"],"default":"asc"}}},"supportButton":{"type":"object","additionalProperties":false,"description":"Override for one of the four built-in support-note buttons (donate, support, feature-request, app-store). Every field is optional; omit the whole entry to keep the shell's default for that button.","properties":{"enabled":{"type":"boolean","description":"Show this button. Set false to hide it."},"label":{"type":"string","description":"Button text. Blank keeps the default label."},"url":{"type":"string","description":"Where the button goes. The feature-request and app-store buttons have no useful default, so an app that wants them must set this."},"variant":{"type":"string","enum":["primary","secondary","tertiary","error","warning","success"],"description":"NcButton variant."},"icon":{"type":"string","description":"Material Design Icon component name, e.g. HeartOutline."}}},"runtimeTheme":{"type":"object","additionalProperties":false,"required":["source","tokenSet"],"description":"A leaf app's NL Design token-set selection, as returned by nldesign's GET /api/token-sets catalogue (app-token-set-selection). Applied by CnAppRoot via useScopedTheme — the ONLY consumer; the backend does not read this field.","properties":{"source":{"type":"string","enum":["nldesign"],"description":"Theme provider. Closed to 'nldesign' — the only scoped-theme source this schema (and useScopedTheme) currently supports."},"tokenSet":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"The nldesign token-set id (a GET /api/token-sets entry's `id`), kebab-case. Resolves the token CSS at css/tokens/<tokenSet>.css."},"tokenSetName":{"type":"string","description":"Optional human-readable name (the matching catalogue entry's `name`), cached at selection time so a picker can display it without a second catalogue fetch."},"preview":{"type":"object","additionalProperties":false,"description":"Optional cached swatch preview (the matching catalogue entry's theming colors), for a picker to render a preview chip without re-fetching the catalogue.","properties":{"primaryColor":{"type":"string","description":"Cached theming.primary_color from the catalogue entry."},"backgroundColor":{"type":"string","description":"Cached theming.background_color from the catalogue entry."}}}}},"pageTemplate":{"type":"object","additionalProperties":false,"required":["id","page"],"description":"A reusable page shape declared once and instantiated per entity. `page` is an ordinary v2 page object whose string values may contain `{{param}}` placeholders (and `{{set:NAME}}` set references); `params` declares which placeholders exist and which are required. Fixed structure lives in `page`; only declared params vary per instantiation.","properties":{"id":{"type":"string","description":"Unique template identifier — the `templateRef` target. Uniqueness across pageTemplates[] is enforced by validateManifestV2 as a post-schema check."},"_note":{"type":"string","description":"Free-form maintainer note; ignored by the expander."},"params":{"type":"array","description":"Declared parameters. A placeholder `{{name}}` in `page` is substituted with the instantiation's value for `name`. A `required` param absent from an instantiation is an expansion error; an optional param absent from an instantiation causes its exact-match placeholder key to be omitted from the expanded page.","items":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","description":"Parameter name, referenced as `{{name}}` in the template `page`."},"required":{"type":"boolean","default":false,"description":"When true, an instantiation omitting this parameter is an expansion error."},"description":{"type":"string","description":"Human-readable description of the parameter."}}}},"page":{"type":"object","description":"The parameterised page shape. An ordinary v2 page whose string leaves may embed `{{param}}` placeholders and `{{set:NAME}}` set references. After substitution the result MUST be a valid concrete page (it is validated as part of the expanded pages[])."}}},"pageInstance":{"type":"object","additionalProperties":false,"required":["templateRef"],"description":"A per-entity instantiation of a pageTemplate. Supplies only the values that vary. `register`, `schema` and `label` are first-class shortcuts folded into the parameter map (equivalent to listing them under `params`); `params` may carry any additional declared parameters (e.g. a field/column subset).","properties":{"templateRef":{"type":"string","description":"Id of the `pageTemplates[]` entry to instantiate. An unknown templateRef is an expansion error."},"register":{"type":"string","description":"Shortcut for the `register` parameter."},"schema":{"type":"string","description":"Shortcut for the `schema` parameter."},"label":{"type":"string","description":"Shortcut for the `label` parameter."},"params":{"type":"object","description":"Parameter values keyed by the template's declared parameter names. Values may be any JSON type (a field/column subset is an array value). Overrides the register/schema/label shortcuts on conflict.","additionalProperties":true},"override":{"type":"object","description":"Optional structural delta applied over the substituted page via the SAME base+delta merge semantics as mergeManifestDelta (layered-versioned-app-deltas alignment). Lets an instantiation patch a single widget/field without re-listing the page, and is where a per-user/app override sits on top of the template base. No second merge model is introduced.","additionalProperties":true}}},"sentinelFilterToken":{"type":"string","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.<key>}), @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","pattern":"^@resolve:[a-z][a-z0-9_-]*$|^@config\\.[A-Za-z][A-Za-z0-9_.]*\\??$","description":"Config context: IAppConfig-sourced values. @resolve:<key> resolved at manifest-load time; @config.<key> (trailing ? optional) at fetch time. Mirrors SENTINEL_TOKEN_PATTERNS.config."},"sentinelObjectToken":{"type":"string","pattern":"^@objectId$|^@object\\.[A-Za-z][A-Za-z0-9_]*$","description":"Object context: detail-page object tokens (@objectId, @object.<field>) 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.<field>|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_]*\\??$","description":"Workspace context: page-level workspace state (@workspace.<key>; trailing ? marks optional). Mirrors SENTINEL_TOKEN_PATTERNS.workspace."},"sentinelRouteToken":{"type":"string","pattern":"^@route\\.[A-Za-z][A-Za-z0-9_-]*$","description":"Route context: vue-router param substitution (@route.<param>) resolved by resolveRouteSentinels. Mirrors SENTINEL_TOKEN_PATTERNS.route."},"sentinelDeclarativeToken":{"type":"string","pattern":"^@self\\.[A-Za-z][A-Za-z0-9_]*$|^@ref:[A-Za-z][A-Za-z0-9_./-]*$|^@aggregate:.+$","description":"Declarative context (OpenRegister direction, resolved server-side): @self.<field> (the object's own field in a cross-object calc), @ref:<path>, @aggregate:<expr>. @ref/@aggregate are first-class members even though currently unused fleet-wide. Mirrors SENTINEL_TOKEN_PATTERNS.declarative."},"sentinelDeprecatedToken":{"type":"string","pattern":"^@currentFiscalYear$|^@page\\.[A-Za-z][A-Za-z0-9_]*$|^@runtime(?:\\.[A-Za-z][A-Za-z0-9_]*)?$","description":"Deprecated single-app inventions kept in the union so deployed manifests still validate during the migration window. The hydra token-vocabulary gate downgrades these to a WARN (see SENTINEL_DEPRECATIONS): @currentFiscalYear → @config.fiscalYear, @page.<key> → @workspace.<key>, @runtime → removal. Mirrors SENTINEL_TOKEN_PATTERNS.deprecated."},"sentinelVisibleWhenToken":{"type":"string","pattern":"^@total$","description":"manifest-form-logic: the shared visibleWhen predicate's source-mode collection-total marker, resolved client-side by evaluateVisibleWhen (utils/visibleWhen.js) — NOT one of the four IAppConfig/OpenRegister resolvers, but a pre-existing convention (already documented on $defs.visibleWhen and the banner widget) reachable through the sentinelGuardedValue-guarded pages[].config subtree for the first time via config.fields[].visibleWhen. Mirrors SENTINEL_TOKEN_PATTERNS.visibleWhen."},"sentinelAnswerToken":{"type":"string","pattern":"^@answer\\.[A-Za-z][A-Za-z0-9_]*$","description":"form-live-values: another answer in the same form (@answer.<field>), read by a form field's assign rule and resolved client-side by CnFormPage / CnFormDialog. Mirrors SENTINEL_TOKEN_PATTERNS.answer."},"sentinelRowToken":{"type":"string","pattern":"^@row\\.[A-Za-z][A-Za-z0-9_.@]*$","description":"Row context: a field of the row a row action runs on (@row.<field>, dot path allowed), read by the field-list widget's rowActions request url and payload (payload values resolve at any depth). Mirrors SENTINEL_TOKEN_PATTERNS.row."},"sentinelContextToken":{"type":"string","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.<name>.<field> (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.<field>, dot path allowed), read by a fact row's `route` params. Mirrors SENTINEL_TOKEN_PATTERNS.source."},"sentinelSubjectToken":{"type":"string","pattern":"^@subject\\.[A-Za-z][A-Za-z0-9_.]*$","description":"Subject context: a field of the record an attention card names (the first row of its ordered `visibleWhen` source query), read by the banner widget's action route params and query (@subject.<field>, dot path allowed). Mirrors SENTINEL_TOKEN_PATTERNS.subject."},"sentinelTokenAny":{"$comment":"Union of every canonical context plus the deprecated-during-window overlay. Single source the shared resolver + gate import.","anyOf":[{"$ref":"#/$defs/sentinelFilterToken"},{"$ref":"#/$defs/sentinelConfigToken"},{"$ref":"#/$defs/sentinelObjectToken"},{"$ref":"#/$defs/sentinelLinkToken"},{"$ref":"#/$defs/sentinelWorkspaceToken"},{"$ref":"#/$defs/sentinelRouteToken"},{"$ref":"#/$defs/sentinelDeclarativeToken"},{"$ref":"#/$defs/sentinelVisibleWhenToken"},{"$ref":"#/$defs/sentinelAnswerToken"},{"$ref":"#/$defs/sentinelRowToken"},{"$ref":"#/$defs/sentinelSubjectToken"},{"$ref":"#/$defs/sentinelContextToken"},{"$ref":"#/$defs/sentinelSourceToken"},{"$ref":"#/$defs/sentinelDeprecatedToken"}]},"sentinelTokenLeaf":{"$comment":"A string leaf: either NOT a sentinel (does not start with @) or a member of the closed vocabulary. An @-prefixed out-of-vocabulary string FAILS here instead of passing through additionalProperties:true.","anyOf":[{"not":{"pattern":"^@"}},{"$ref":"#/$defs/sentinelTokenAny"}]},"sentinelGuardedValue":{"$comment":"Recursive guard applied (via allOf) to pages[].config and widgetEntry so every string leaf anywhere beneath is checked against the closed vocabulary. Additive + backward-compatible: only @-prefixed out-of-vocabulary strings newly fail; all other shapes are untouched.","if":{"type":"string"},"then":{"$ref":"#/$defs/sentinelTokenLeaf"},"else":{"if":{"type":"array"},"then":{"items":{"$ref":"#/$defs/sentinelGuardedValue"}},"else":{"if":{"type":"object"},"then":{"additionalProperties":{"$ref":"#/$defs/sentinelGuardedValue"}}}}},"observability":{"type":"object","additionalProperties":false,"description":"Health checks + metric descriptors executed by the AppHost engine.","properties":{"_note":{"type":"string","description":"Free-form note for maintainers; ignored by the engine."},"health":{"type":"object","additionalProperties":false,"description":"Declarative health checks rendered as the ADR-006 {status, app, version, checks} shape.","properties":{"statusCodePolicy":{"type":"string","enum":["adr006","always200"],"default":"adr006","description":"HTTP status-code policy. 'adr006' returns 503 on a failed critical check; 'always200' always returns 200."},"checks":{"type":"array","description":"Health check descriptors.","items":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"type":"string","description":"Check id, surfaced as the key in the response's checks map."},"type":{"type":"string","enum":["database","filesystem","appEnabled","appConfig","orAvailable"],"description":"Engine check kind."},"severity":{"type":"string","enum":["critical","degraded"],"default":"critical","description":"critical → a failure flips status to error (503 under adr006); degraded → status degraded (200)."},"app":{"type":"string","description":"App id, for the appEnabled check."},"key":{"type":"string","description":"Config key, for the appConfig check."}}}},"cors":{"type":"boolean","default":false,"description":"ADR-040: when true the engine emits permissive CORS headers on the /api/health response."}}},"metrics":{"type":"array","description":"Prometheus metric descriptors. The {app}_ prefix, exposition format, and implicit {app}_info / {app}_up gauges are engine-owned.","items":{"type":"object","additionalProperties":false,"required":["name","type","source"],"properties":{"name":{"type":"string","description":"Metric name (without the {app}_ prefix the engine adds)."},"type":{"type":"string","enum":["gauge","counter"],"description":"Prometheus metric type."},"help":{"type":"string","description":"HELP text for the metric family."},"cacheTtl":{"type":"integer","minimum":0,"description":"ADR-040: optional per-metric cache TTL in seconds."},"source":{"type":"object","additionalProperties":false,"required":["kind"],"description":"Where the engine sources the value.","properties":{"kind":{"type":"string","enum":["tableCount","objectCount","objectSum","appConfig","provider"],"description":"Source kind. tableCount = COUNT over an allowlisted own-table; objectCount = OR objects by register/schema; appConfig = a numeric app-config value; provider = an IMetricsProvider implementation."},"table":{"type":"string","description":"tableCount: allowlisted [a-z0-9_] table name."},"register":{"type":"string","description":"objectCount: OR register slug."},"schema":{"type":"string","description":"objectCount / objectSum: OR schema slug."},"field":{"type":"string","description":"objectSum: the numeric object property to SUM."},"groupBy":{"type":"array","items":{"type":"string"},"description":"Columns to GROUP BY (one label per column)."},"labelMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Rename a groupBy column to a different Prometheus label name."},"labelDefaults":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Default label value for NULL/empty group columns."},"filter":{"type":"object","additionalProperties":true,"description":"Per-column operator filters (eq/neq/lt/lte/gt/gte/like)."},"key":{"type":"string","description":"appConfig: the config key whose numeric value is the sample."},"id":{"type":"string","description":"provider: the IMetricsProvider id to invoke."}}}}}}}},"splitView":{"type":"object","additionalProperties":false,"description":"The list and one open record, side by side. Declared on an index page; buildManifestRoutes() then emits a second route <route>/split/:id under the name <pageId>__split, carrying meta.cnSplitOf so CnPageRenderer still resolves it to this page and the list never unmounts.","properties":{"enabled":{"type":"boolean","default":false,"description":"Whether opening a row opens it beside the list. False renders exactly as today: a full page list and a full page detail."},"breakpoint":{"type":"integer","minimum":320,"maximum":2560,"default":1024,"description":"Viewport width in pixels below which the split address renders the full detail page instead of the pane, so a link sent from a laptop opens on a phone."},"paneWidth":{"type":"string","description":"CSS width of the detail pane, e.g. '38%' or '520px'. Defaults to '42%'."}}},"savedViewTree":{"type":"object","additionalProperties":false,"description":"Saved views render as a tree rather than a flat list, carry labels, and may ship with the app. Two hundred personal views in one dropdown is the problem this solves. Index pages only, and only meaningful alongside allowSavedViews: without the dropdown there is no view to put in a tree. Absent, the page renders the flat dropdown it renders today. The view entity itself, its parent, its labels and its slug are OpenRegister's (saved-search-views); this key declares how this page presents them.","properties":{"enabled":{"type":"boolean","default":false,"description":"Whether this page renders its saved views as a tree. False reads as a page that considered it and declined, and renders exactly as a page that never named the key."},"maxDepth":{"type":"integer","minimum":1,"maximum":5,"default":3,"description":"How deep a parent chain may go. A bound rather than a taste: inheritance resolves by walking the chain, so an unbounded depth turns one render into an unbounded walk, and a person who can nest for ever will. Past the bound a save is refused naming the view, which is louder than a tree nobody can read."},"templates":{"type":"array","description":"What a new view may start from. A template presets the columns, the sort and the export field set, and the person may change each. Declare none and Save current view means what it has always meant: the list as it stands.","items":{"type":"object","additionalProperties":false,"required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"name":{"type":"string"},"columns":{"type":"array","items":{"type":"string"}},"defaultSort":{"type":"string"},"exportFields":{"type":"array","items":{"type":"string"}}}}},"landingView":{"type":"object","additionalProperties":false,"description":"Which view a role opens this page on, as { role: slug }. A personal choice always wins and Reset returns here, so this is where somebody starts rather than where they are kept. A role's landing view changing mid-session applies on the next arrival: moving a reader's list out from under them is worse than a stale default.","patternProperties":{"^[A-Za-z0-9_.-]+$":{"type":"string"}}},"columnsPerRole":{"type":"object","additionalProperties":false,"description":"Which columns a role sees, as { role: [column] }. NARROWS index-columns-per-scope rather than replacing it: a role naming a column the scope does not offer gets nothing extra, because a per-role list that could add one would be a second, quieter way to put a field on screen the scope deliberately left off.","patternProperties":{"^[A-Za-z0-9_.-]+$":{"type":"array","items":{"type":"string"}}}},"groupBy":{"type":"string","description":"A field the list groups on, with a count per group. The counts are of the rows the list HOLDS, so on a paged list they are of the page and the surface says so; a second fetch per group is the cost this grouping exists to avoid."},"seeded":{"type":"array","deprecated":true,"description":"DEPRECATED since 2.129.0: declare default views with the index page's `config.defaultSavedViews`, which carries the criteria this key never had and is what the page actually lists. Nothing ever read this key, so moving off it loses nothing. Views that ship with the app. They render as their own group above the user's own, and a user may copy one but not delete it: a view the product promises is not a view one person can remove from everybody's install.","items":{"type":"object","additionalProperties":false,"required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The stable name a dashboard widget, an export action or an API caller cites this view by. Lowercase and dash only, because it appears in an address and in a manifest somebody else writes. Required: a seeded view with no slug is a view nothing can call, which is most of the reason to seed it."},"name":{"type":"string","description":"What the view is called in the control. Translatable."},"label":{"type":"string","description":"One label this view carries, for the label filter. A view may carry none and stays reachable when no label filter is active."},"parent":{"type":"string","description":"The slug of the view this one hangs under. Omitted, it sits at the root of the seeded group."},"actions":{"type":"array","description":"The action ids this view offers, so a triage view offers three and not thirty. INTERSECTED with what the reader may run and never added to: a view declaring an action the reader has no right to does not grant it. Declare none and the view offers the page's actions, as today.","items":{"type":"string"},"uniqueItems":true},"inherits":{"type":"array","description":"Which parts this view takes from its parent. Each part resolves independently, so a child may narrow the criteria and keep the parent's columns. Declared on a view with no parent it is a contradiction and the validator refuses it: there is nothing to inherit from.","items":{"type":"string","enum":["criteria","columns","sorting","defaultSort","exportFields"]},"uniqueItems":true}},"allOf":[{"description":"inherits without a parent is a declaration that reads as configured and resolves to nothing. Refused here rather than silently ignored at render, which is the shape this whole change exists to stop.","if":{"required":["inherits"]},"then":{"required":["parent"]}}]}}}},"boardView":{"type":"object","additionalProperties":false,"required":["statusField"],"description":"The board view of this list: one column per stage of a status field. Index pages only, and only offered when `viewModes` also lists `board`. The COLUMNS COME FROM THE SCHEMA, not from the values present in the loaded rows: a board built from the data would lose the stage nothing is in, which is the one you drag a card into, and would reorder itself as work moved. A move goes through the host's transition and never writes the status field, so a board can never move a case past a rule the case page enforces.","properties":{"statusField":{"type":"string","description":"The property the columns come from. Required: a board with no status field has nothing to be a board of. Its enum or lifecycle states are read from the register schema at RUNTIME, so a field with neither cannot be caught here; the view says so on screen instead of drawing empty columns."},"cardFields":{"type":"array","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."},"card":{"type":"object","additionalProperties":false,"description":"screens-kanban-parity: the board look's card, by role instead of by list. Each value is a dot-path to a field on the row. Missing, the board look takes the first of `cardFields` as the title and the rest as the sub line. The Nextcloud look ignores it.","properties":{"title":{"type":"string","description":"The field that is the card's title, and the link that opens it."},"sub":{"description":"The field, or fields, of the sub line; several are joined by a middle dot and an empty one drops out with its separator.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"pill":{"type":"string","description":"The field shown as a status pill."},"resolve":{"type":"object","additionalProperties":false,"description":"pill-color-field: the `pill` 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}}},"pillColors":{"type":"object","description":"Pill value to badge variant. A value without an entry takes the default variant.","additionalProperties":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"]}},"due":{"type":"string","description":"The date field of the footer's due text, marked by `dueRule`."},"owner":{"type":"string","description":"The field of the owner shown as an avatar (initials) at the footer's end."},"formatters":{"type":"object","description":"screens-board-reference-columns (2.97.0): a card field (one of the role fields above) to the id of a formatter in the app's `cnFormatters` registry, the one table columns name in `formatter`. The card shows the formatted text, so a reference reads as its name (`{ \"caseType\": \"caseTypeName\" }`).","additionalProperties":{"type":"string","minLength":1}}}},"colorField":{"type":"string","description":"screens-kanban-parity: the key on a column's state that holds its dot colour. Without it the lifecycle state's declared `color` is used, else the secondary text colour."},"sumField":{"type":"string","description":"screens-kanban-parity: a numeric field whose sum each column shows under its heading, formatted as the field's format declares."},"sumFormat":{"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"]},"currency":{"type":"string"},"decimals":{"type":"integer","minimum":0}}},"columnLimit":{"type":"integer","minimum":0,"default":0,"description":"screens-kanban-parity: how many cards a column draws before a dashed \"Show N more\" button. 0 draws every card. The count badge keeps showing the column's total."},"dueRule":{"$ref":"#/$defs/dueRule","description":"Marks late cards. A card whose date is before today gets an error edge and the label 'Overdue'; one due within `soonDays` gets 'Due soon'. Omitted, no card is marked."},"columnFields":{"type":"array","minItems":1,"description":"screens-board-reference-columns (2.97.0): the fields the columns can come from, offered in a \"Columns:\" switch after the search when there are two or more (\"status | type | assignee\"). The entry on `statusField` is the default. An entry with a `source` takes its columns from the rows the field REFERENCES (a case's status pointing at statusType rows), fetched, not from the values on the cards; `kind: \"values\"` (a user id, which references no schema) takes the values present plus a column for no value. Without a `source` or `kind` the field's enum or lifecycle states are read as before. A drop writes the reference: through `moveHandler` when named, else a PATCH of the field on the row.","items":{"type":"object","additionalProperties":false,"required":["field"],"properties":{"field":{"type":"string","minLength":1,"description":"The row field the columns come from."},"label":{"type":"string","description":"Its name in the column switch (an i18n key). Defaults to the field."},"kind":{"type":"string","enum":["values"],"description":"`values`: the field references no schema (a Nextcloud user id); the columns are the values present on the rows, by `valueLabels` or the value itself, then one column for no value (`emptyLabel`)."},"valueLabels":{"type":"object","additionalProperties":{"type":"string"},"description":"`kind: values`: raw value to the column label."},"emptyLabel":{"type":"string","description":"`kind: values`: the label of the column for no value (an i18n key). Defaults to \"Nobody\"."},"source":{"type":"object","additionalProperties":false,"description":"The referenced rows that are the columns: one list request to `register`/`schema` (defaults: the page's register and the property's `$ref`).","properties":{"register":{"type":"string","minLength":1},"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."},"neutralColor":{"type":"string","minLength":1,"description":"board-neutral-column-colour (2.101.0): the colour a referenced row holds when nobody chose one (the schema default, e.g. \"grey\"). A column merged from several rows (`mergeBy`) then takes the first row colour that is not this one, and this one only when every row has it. Compared as written: \"Grey\" is not \"grey\", and a hex value is not a name. Unset, a merged column takes its first row's colour."},"colorMap":{"type":"object","description":"board-column-colour-map (2.102.0): a stored row colour to the CSS colour its column is drawn in, e.g. `{ \"orange\": \"var(--nl-color-orange, #e17000)\" }`, so a palette name draws a theme token. Applied after the merge, so `neutralColor` still compares the stored values. A colour it does not map draws as before.","additionalProperties":{"type":"string","minLength":1}},"mergeBy":{"type":"string","description":"Merge referenced rows with the same value of this field into one column (statuses of every case type by `name`). A drop then writes the row of the card's own parent (`narrowBy`), and is refused when that parent has none."},"narrowBy":{"type":"object","additionalProperties":false,"required":["field","rowField"],"description":"The parent link: the referenced row's `field` equals the card's `rowField` (statusType.caseType = case.caseType). While the page filters on `rowField` with one value, the columns are that parent's rows only.","properties":{"field":{"type":"string","minLength":1},"rowField":{"type":"string","minLength":1}}}}},"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."},"states":{"$ref":"#/$defs/boardDeclaredStates"}}}},"moveHandler":{"type":"string","minLength":1,"description":"screens-board-reference-columns (2.97.0): a handler in the app's registry that moves a card on `statusField` (the app's transition), with the `columnFields[].moveHandler` call shape. Lets a manifest-only page run the host's transition, which the `runTransition` prop could not."},"moveTargetsHandler":{"type":"string","minLength":1,"description":"screens-board-reference-columns (2.97.0): where a card on `statusField` may go, with the `columnFields[].moveTargetsHandler` call shape."},"narrow":{"type":"object","additionalProperties":false,"required":["field"],"description":"screens-board-reference-columns (2.97.0): a select after the search (\"Case type: All case types | Woo-verzoek | ...\") that sets the page filter on `field`. With a reference column's `narrowBy.rowField` on the same field, the columns narrow to that parent's rows.","properties":{"field":{"type":"string","minLength":1,"description":"The row field the select filters on."},"label":{"type":"string","description":"The select's label (an i18n key), shown with a colon. Defaults to the property's title."},"allLabel":{"type":"string","description":"The option for no narrowing (an i18n key). Defaults to \"All\"."},"source":{"type":"object","additionalProperties":false,"description":"Where the options come from (defaults: the page's register and the property's `$ref`).","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"labelField":{"type":"string","description":"The option row's label field. Defaults to `title`."},"filter":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}}}}}},"columnSwitchLabel":{"type":"string","description":"screens-board-reference-columns (2.97.0): the column switch's label (an i18n key), shown with a colon. Defaults to \"Columns\"."},"hint":{"type":"string","description":"screens-board-reference-columns (2.97.0): one sentence after the board's controls, in the secondary colour (an i18n key), e.g. how a move works."},"wrap":{"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":{"type":"object","additionalProperties":false,"anyOf":[{"required":["field"]},{"required":["text"]},{"required":["countFrom"]}],"description":"A pill in a detail page header, rendered through CnStatusBadge: from one field of the record (`field`; nothing renders while it is empty), a constant (`text`), or a count of a related list (`countFrom` with `countLabel`; nothing renders at 0, while counting or after a failed count). screens-relation-pages, 2.85.0.","properties":{"field":{"type":"string","minLength":1,"description":"Dot-path to the value on the record."},"values":{"type":"array","items":{"type":"string"},"description":"With `field`: draw the pill only when the raw value is one of these (a priority pill for high and urgent only)."},"text":{"type":"string","minLength":1,"description":"A constant label (an i18n key), such as \"Inwoner\" or \"Klant\". Used when `field` is not set."},"countFrom":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"The list the pill counts with one list request (`_limit=1`, the response's `total`). `filter` values take the `@objectId` and `@object.<field>` tokens. Used when `field` is not set.","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true}}},"resolve":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"detail-pill-resolve: the field holds the id of a record of this register and schema; the pill shows that record's `labelField` (else its title or name) and keys `colorMap` by it. The raw value shows while the lookup runs and when it finds nothing.","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"colorField":{"type":"string","minLength":1,"description":"pill-color-field: the referenced record's property holding a colour name (an NL Design System hue such as `red` or `blue-light`, or a badge role); the pill takes the tone it maps onto."},"iconField":{"type":"string","minLength":1,"description":"pill-color-field: the referenced record's property holding a registered icon name, shown before the label."}}},"countLabel":{"type":"object","additionalProperties":false,"description":"The count pill's label for one and for more, i18n keys with `{count}`: `{ \"one\": \"{count} lopende zaak\", \"other\": \"{count} lopende zaken\" }`.","properties":{"one":{"type":"string","minLength":1},"other":{"type":"string","minLength":1}}},"colorMap":{"type":"object","description":"Raw value to badge variant. Keys are matched ignoring case.","additionalProperties":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"]}},"labels":{"type":"object","description":"Raw value to the i18n key / text to show. A value without an entry is shown as is.","additionalProperties":{"type":"string"}},"variant":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"],"default":"default","description":"Badge variant for a value `colorMap` does not name."}}},"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,"required":["field"],"description":"How a surface decides that a dated item is late. One shape for boards and lists, so a list and its board agree. A date before today is overdue. Days are whole calendar days in the reader's own timezone.","properties":{"field":{"type":"string","minLength":1,"description":"Dot-path to the due date on the row."},"soonDays":{"type":"integer","minimum":0,"default":3,"description":"How many days ahead still counts as due soon. 0 marks only today."},"variantWhen":{"type":"array","description":"The full rule list, in the shape a table's date cell uses, for a board that needs more than `soonDays`. Compared against the calendar days until the date (0 is today, negative is past); first match wins. `error` marks the card overdue, `warning` due soon. When set, `soonDays` is ignored.","items":{"type":"object","additionalProperties":false,"required":["op","value","variant"],"properties":{"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte"]},"value":{"type":"number"},"variant":{"type":"string","enum":["default","success","warning","error","danger"]}}}}}},"dateAxisView":{"type":"object","additionalProperties":false,"required":["startField","endField"],"description":"The date-axis view of this list: rows on a time scale, overlaps visible. Index pages only, and only offered when `viewModes` also lists `dateAxis`. IT HIDES NOTHING AND RESCHEDULES NOTHING: overlapping bars get their own track so the lane grows taller rather than losing a bar, rows with no usable dates go to a visible unplanned lane, and the only gesture is opening a row. A view that moved a bar on drag would be changing statutory dates from a picture.","properties":{"startField":{"type":"string","description":"Where a row's start is. Required: a bar needs both ends, and a row missing one is unplanned rather than drawn."},"endField":{"type":"string","description":"Where a row's end is. Required, for the same reason."},"laneField":{"type":"string","description":"What to put in a lane together, usually the person the work is on. Omitted, everything shares one lane."},"labelField":{"type":"string","description":"The field a bar is named by, on screen and in its accessible name. A bar positioned by percentage says nothing at all to a reader who cannot see it."}}},"personalisation":{"type":"object","additionalProperties":false,"description":"The app's own defaults for the per-user options CnPersonalPreferences renders. The values a person chooses are stored in their Nextcloud preferences, never here; this block is only the bottom layer of the app default, administered value, personal value order.","properties":{"enabled":{"type":"boolean","default":true,"description":"Whether the personal layer applies at all. An administrator setting this false collapses it: the options render as unavailable with the reason, rather than being silently ignored."},"disabledReason":{"type":"string","description":"What to tell a person when the personal layer is off. Shown beside the unavailable options."},"landingPage":{"type":"string","description":"Page id the app opens on when a person has chosen nothing."},"dateDisplay":{"type":"string","enum":["absolute","relative"],"default":"absolute","description":"How dates read by default. A relative date always carries its absolute date in the accessible name and the tooltip, because a term read as '3 dagen geleden' hides the date the term runs to."},"rememberLastView":{"type":"boolean","default":false,"description":"Whether each list reopens in the view mode that person last used on it."},"pinnedMenu":{"type":"array","items":{"type":"string"},"description":"Menu ids pinned to the top of the navigation by default. A person may reorder and repin their own."}}},"primaryAction":{"type":"object","required":["label"],"additionalProperties":false,"description":"A primary action declared on either a pages[] entry (active-page scoped) or nav.primaryAction (app-wide default). Rendered above the menu list as an NcAppNavigationNew button.","properties":{"id":{"type":"string","description":"Stable identifier for this action."},"label":{"type":"string","description":"i18n translation key / text for the button."},"icon":{"type":"string","description":"MDI icon name (e.g. 'Plus'). Defaults to 'Plus' when omitted."},"route":{"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. On a detail page it may name the record's fields as @object.<field> tokens."},"payload":{"description":"Free-form payload echoed back to the host inside the @primary-action-click event."},"action":{"$ref":"#/$defs/action","description":"A page action to run on click (zuiddrecht-pixel-gaps): CnAppNav then renders the button through CnActionButtons, so `type: \"open-form\"` (with `register` + `schema`) opens the create dialog without a create page, `navigate` pushes a route, `api-call` calls an endpoint. Drawn as one solid full-width primary button. `route` / `href` on the primary action itself are ignored while `action` is set."},"solid":{"type":"boolean","default":false,"description":"Draw the primary action as a solid, full-width primary button whatever its form (zuiddrecht-pixel-gaps). Default: a link action is already a primary button; a plain one is NcAppNavigationNew."},"permission":{"type":"string","description":"Permission the reader must hold for the action to render, checked like `menuItem.permission` (zuiddrecht-pixel-gaps)."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Display condition evaluated at render time, like `menuItem.visibleIf` (zuiddrecht-pixel-gaps). A gated-out page-scoped action falls back to `nav.primaryAction`."}}},"menuItem":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"A top-level navigation entry. May contain one level of nested children.","properties":{"id":{"type":"string","description":"Unique identifier for this menu entry. Sentinels (@resolve:*) are NOT allowed — this is a router invariant."},"label":{"type":"string","description":"i18n translation key resolved by the consuming app's t() function at render time."},"translateLabel":{"type":"boolean","default":true,"description":"Whether CnAppNav runs `label` through the translate function. Set false for a user-authored title (e.g. a catalog name) that must render as written even when it equals a translation key such as 'Search'. Rendered as text either way, never as HTML."},"icon":{"type":"string","description":"CSS class for the icon (e.g. 'icon-checkmark') or MDI component name."},"route":{"type":"string","description":"Vue-router route name (matches a pages[].id) that this entry navigates to. Sentinels (@resolve:*) are NOT allowed — this is a router invariant."},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Optional vue-router query parameters merged into the target route link (e.g. a filter like { caseType: '<uuid>' }). Deep-links a nav entry to a pre-filtered index page."},"params":{"type":"object","additionalProperties":{"type":["string","number"],"minLength":1},"description":"Optional vue-router route params for a parameterised route (e.g. { catalogSlug: 'news' } for a page at '/catalogs/:catalogSlug'). String values must be non-empty. CnAppNav links to { name: route, params, query }. The entry is active only while every declared param equals the current route's param (compared as strings), so sibling entries on one route that differ in params are never marked together. On pages whose path sits below its page path with these params filled in, it is active too: by the same params check when those routes carry the same param names, otherwise as the one entry marked below its route."},"order":{"type":"integer","description":"Display order in the menu. Items without an order render last."},"permission":{"type":"string","description":"Permission string the user must hold for this entry to render."},"section":{"type":"string","enum":["main","footer","settings","integrations"],"default":"main","description":"Where the entry renders inside CnAppNav. 'main' (default) places it in the top list. 'footer' pins the entry to the bottom as a flat item above the settings foldout. 'settings' places it inside the NcAppNavigationSettings gear-icon foldout. 'integrations' renders the entry NOT in the navigation at all but in the Integrations section at the bottom of the per-user settings modal — the destination for links that leave this app for another (ADR-110). An 'integrations' entry MUST be clickable (href); a group with children is meaningless there."},"type":{"type":"string","enum":["item","caption"],"default":"item","description":"Render kind. 'item' (default) renders a clickable NcAppNavigationItem. 'caption' renders an NcAppNavigationCaption section divider — only label, id, order, and section are honoured."},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]},{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"A FILTERED count (zuiddrecht-pixel-gaps): the total of this register and schema with `filter` applied (filter-value tokens such as `@me` and `@today` resolve at fetch time), fetched by CnAppRoot under the entry's own key so an index page's whole-schema total is untouched. Renders \"My work 6\" beside \"All cases 48\".","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true}}}],"description":"Counter badge in the entry's #counter slot. Positive integer renders as-is; 'auto' resolves from cnMenuCounts (populated by CnAppRoot from useObjectStore totals) for the entry's resolved index-type page (register + schema); an object `{ register, schema, filter? }` is the total of that filtered list."},"counterVariant":{"type":"string","enum":["default","attention"],"default":"default","description":"screens-chrome-parity: how the entry's `count` reads. `attention` asks for the red attention pill under the board look (unread or overdue work) and maps to the counter bubble's highlighted state without it. `default` is the neutral pill."},"pinned":{"type":"boolean","default":false,"description":"Forwarded to NcAppNavigationItem's pinned prop. NC bottom-pins pinned items inside the parent list region."},"open":{"type":"boolean","default":false,"description":"Initial expansion state for a parent entry with children. When true and children[] is non-empty, the parent renders with :open=\"true\" so children are visible on mount."},"href":{"type":"string","description":"External URL. When set, the entry opens the URL in a new tab instead of dispatching a vue-router navigation."},"action":{"type":"string","enum":["user-settings","admin-settings","replay-walkthrough"],"description":"Built-in action to invoke when the entry is clicked. Closed enum. 'admin-settings' opens the app-level admin-settings dialog (distinct from the per-user settings dialog). 'replay-walkthrough' re-runs the product walkthrough (ADR-043), optionally for a specific tourId."},"tourId":{"type":"string","description":"Tour id for the 'replay-walkthrough' action (ADR-043); replays that tour from its first step. Omit to replay the default tour."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Optional display condition evaluated at render time."},"dynamicSource":{"$ref":"#/$defs/dynamicSource","description":"Optional dynamic source for populating nested menu items from a data endpoint. Carry-forward from v1.3.0."},"children":{"type":"array","items":{"$ref":"#/$defs/menuItemLeaf"},"description":"Nested entries. Only one level of nesting is supported."}}},"menuItemLeaf":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"A nested menu entry. Has no further children.","properties":{"id":{"type":"string"},"label":{"type":"string"},"translateLabel":{"type":"boolean","default":true,"description":"Same as menuItem.translateLabel: false renders `label` as written instead of translating it."},"icon":{"type":"string"},"route":{"type":"string"},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Optional vue-router query parameters merged into the target route link (e.g. a filter like { caseType: '<uuid>' }). Deep-links a nav entry to a pre-filtered index page."},"params":{"type":"object","additionalProperties":{"type":["string","number"],"minLength":1},"description":"Optional vue-router route params for a parameterised route; string values must be non-empty. Same contract as menuItem.params: part of the link, and the entry is active only while every declared param equals the current route's param."},"order":{"type":"integer"},"permission":{"type":"string"},"section":{"type":"string","enum":["main","footer","settings","integrations"],"default":"main"},"type":{"type":"string","enum":["item","caption"],"default":"item"},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]}]},"counterVariant":{"type":"string","enum":["default","attention"],"default":"default"},"pinned":{"type":"boolean","default":false},"href":{"type":"string"},"action":{"type":"string","enum":["user-settings","admin-settings","replay-walkthrough"]},"tourId":{"type":"string","description":"Tour id for the 'replay-walkthrough' action (ADR-043)."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition"}}},"dynamicSource":{"type":"object","description":"Dynamic source configuration for populating menu items from a backend endpoint. Carry-forward from v1.3.0 — identical shape to v1.","additionalProperties":true,"properties":{"url":{"type":"string","description":"URL to fetch menu items from."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method for the fetch."},"labelField":{"type":"string","description":"Property name in the response items to use as the menu entry label."},"idField":{"type":"string","description":"Property name in the response items to use as the menu entry id."},"routeField":{"type":"string","description":"Property name in the response items to use as the menu entry route."}}},"linkModule":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"One module on a type='links' page. The app decides whether it is in the menu (`enabled`, or `enabledIf` against runtime). Every card opens; a module that is off links to the admin settings (`settings`).","properties":{"id":{"type":"string","minLength":1,"description":"Unique within the page's modules."},"label":{"type":"string","minLength":1,"description":"i18n key / the module's title."},"description":{"type":"string","description":"i18n key / one or two lines under the title."},"icon":{"type":"string","description":"MDI icon name."},"enabled":{"type":"boolean","description":"Whether the module is in the menu. Wins over enabledIf."},"enabledIf":{"$ref":"#/$defs/visibleIfCondition","description":"Condition, evaluated like visibleIf, that says the module is in the menu."},"route":{"type":"string","minLength":1,"description":"Where Open goes: a route name or a path starting with '/'. Defaults to the first page."},"params":{"type":"object","additionalProperties":{"type":["string","number"]}},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}},"pages":{"type":"array","description":"The module's pages, drawn as links and named in the 'N pages' line.","items":{"type":"object","required":["label"],"additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"label":{"type":"string","minLength":1},"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":{"type":["string","number"]}},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}},"href":{"type":"string","minLength":1},"highlight":{"type":"boolean","description":"Named in the line of a module with more than four pages ('15 pages, including ...')."}}}},"settings":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"description":"Where an admin puts a module that is off in the menu (the admin settings), shown on its card as '<prefix> <label>'.","properties":{"label":{"type":"string","description":"i18n key / link text. Defaults to 'Admin settings'."},"prefix":{"type":"string","description":"i18n key / text before the link. Defaults to 'Put it in the menu:'."},"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":{"type":["string","number"]}},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}},"href":{"type":"string","minLength":1},"permission":{"type":"string"},"visibleIf":{"$ref":"#/$defs/visibleIfCondition"}}},"permission":{"type":"string","description":"Permission the user must hold for the module to render."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Display condition, evaluated the way a menu entry's is."}}},"linkCard":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"One card on a type='links' page: a link to another page of the app, or to a URL. Same vocabulary as menuItem and navCardEntry.","properties":{"id":{"type":"string","minLength":1,"description":"Unique within the page's cards."},"label":{"type":"string","minLength":1,"description":"i18n key / text of the card's title."},"description":{"type":"string","description":"i18n key / one line under the title."},"icon":{"type":"string","description":"MDI icon name (e.g. 'CashMultiple')."},"category":{"type":"string","description":"Key in config.categories naming the group this card sits in. A card without one, or with an unknown one, renders first under no caption."},"route":{"type":"string","minLength":1,"description":"Route name (a pages[].id), or a router path when it starts with '/'. Resolved through the router, so it works with and without /index.php. Mutually exclusive with href."},"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."},"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."}},"allOf":[{"not":{"required":["route","href"]}},{"anyOf":[{"required":["route"]},{"required":["href"]}]}]},"navCardEntry":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"One card in a nav-card-grid built-in widget (ADR-044 §4 cards-collapse) — an arbitrary navigation link, not an OpenRegister object. Mirrors the menuItem/primaryAction vocabulary: id + label required, route/href are alternative navigation targets and mutually exclusive, count supports the same integer-or-\"auto\" shape as menuItem.count.","properties":{"id":{"type":"string","description":"Unique identifier for this card within its widget's entries[] array."},"label":{"type":"string","description":"i18n translation key / text rendered as the card's title."},"description":{"type":"string","description":"Optional explanatory text rendered below the label, associated to the card via aria-describedby (never aria-label)."},"icon":{"type":"string","description":"MDI icon name (e.g. 'ChartLine'), resolved the same way as menuItem.icon."},"route":{"type":"string","description":"Vue-router route name (matches a pages[].id) this card navigates to. Mutually exclusive with href."},"href":{"type":"string","description":"External URL opened per the rendering component's target handling. Mutually exclusive with route."},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]}],"description":"Count badge on the card. Positive integer renders as-is; 'auto' resolves from cnMenuCounts (populated by CnAppRoot) for the card's resolved route page (register + schema), the same mechanism menuItem.count:\"auto\" uses."},"order":{"type":"integer","description":"Display order among sibling cards. Cards without an order render last."},"permission":{"type":"string","description":"Permission string the user must hold for this card to render."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Optional display condition evaluated at render time."}},"allOf":[{"description":"route and href are mutually exclusive navigation targets — a card links either in-app or externally, never both.","not":{"required":["route","href"]}}]},"widgetEntry":{"type":"object","required":["widgetKey","slot","gridX","gridY","gridWidth","gridHeight"],"additionalProperties":false,"description":"A uniform widget placement entry used on every page type in v2. Replaces the v1 widgetDef + layoutItem pair with a single shape. Its props/dataSource/filter string leaves are guarded against the closed sentinel vocabulary via the sentinelGuardedValue allOf. The cross-field constraint gridX + gridWidth <= 12 CANNOT be expressed in JSON Schema (arithmetic over sibling fields) — it is documented here and enforced at runtime by validateManifestV2() as a post-schema check. Failure message: \"Widget '{widgetKey}' in slot '{slot}': gridX ({gridX}) + gridWidth ({gridWidth}) exceeds 12\".","properties":{"roles":{"type":"array","items":{"type":"string","minLength":1},"description":"Group ids that may see this widget. A DECLARATION THE HOST APP ENFORCES, not something this library evaluates: the check belongs on the read, where the figures are, because a widget hidden in the browser still had its data in the page payload (ADR-004). Pair it with `visibleWhen` pointing at an endpoint of the app's own, which IS evaluated here and collapses the cell when the fetch or the shape fails, so the declaration and the layout cannot disagree. Omitting it means every reader of the page sees the widget. Added because the key was expressible in the legacy `config.widgets[]` array, whose items are open, and refused here, so the same declaration validated on one page and was rejected on another.","examples":[["controllers","beheerders","admin"]]},"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"Render this widget only while the predicate holds. The same shape every other surface takes it in. It was already honoured at runtime for widgets (CnDashboardPage reads `def.visibleWhen`) and could not be declared in this shape, so a widget in the uniform entry had no way to express a visibility condition at all while one in the legacy array did."},"requiredApp":{"type":"string","minLength":1,"description":"Nextcloud app id this widget leans on — its data lives in that app's register, or its component comes from that app's integration leaf. When the app is absent the widget renders its normal chrome and a set-up state and issues NO request. Declared rather than inferred from content.register, because a register slug is not always the app id and a widget can depend on an app without querying it."},"id":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"Optional stable, kebab-case identity for this widget entry. Used as the merge key under the `delta` merge mode (mergeManifestDelta keys widgets[] by id) so a stored delta can patch a single widget without resending the array. Omitting it keeps the entry valid; a delta simply cannot target it by key. Must be unique within a page's widgets[]."},"widgetKey":{"type":"string","description":"Registry key identifying the widget component. Resolved against the customComponents map passed to CnAppRoot at boot."},"slot":{"$ref":"#/$defs/slotValue","description":"Placement slot. Valid values: body, sidebar, header-actions, footer, modal (literals) or tab:<id> / section:<id> (pattern). Per-slot grid constraints are enforced via allOf/if-then below."},"gridX":{"type":"integer","minimum":0,"description":"0-based column start position. gridX + gridWidth MUST be <= 12 (enforced by post-schema check, not JSON Schema)."},"gridY":{"type":"integer","minimum":0,"description":"0-based row start position. For header-actions slot, MUST be 0 (enforced by allOf/if-then)."},"gridWidth":{"type":"integer","minimum":1,"maximum":24,"description":"Column span. For the default 12-column slots, 1-12. A page MAY widen a slot via config.slotColumns (up to the schema cap of 24); the real per-slot bound gridX + gridWidth <= resolvedColumns is enforced by the post-schema check, not JSON Schema. For sidebar slot, MUST be exactly 1 (enforced by allOf/if-then)."},"gridHeight":{"type":"integer","minimum":1,"description":"Row span (>= 1)."},"headerLink":{"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, a `tab` a `#<tab>` 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"},"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":{"type":"boolean","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."},"sizeToContent":{"type":["boolean","integer"],"minimum":1,"description":"Opt-in: hand this placement's height to whatever it renders, so a widget that decides AT RUNTIME it has nothing to show costs no row. `true` sizes purely to content; an integer sizes to content too but caps the height at that many rows (GridStack's maximum, NOT a minimum: content shorter than N rows is as short as it is, content taller is clipped at N). It makes gridHeight advisory for this widget — right for anything whose height is whatever it turned out to be (a banner whose condition is false, a list that came back empty), wrong for anything with a deliberate size. Maps to CnDashboardGrid's per-item `sizeToContent`."},"props":{"type":"object","description":"Optional widget-specific props passed at render time. Shape is widget-specific; not constrained here.","additionalProperties":true},"tabGroup":{"type":"string","description":"Optional: groups this widget into a named tab within the slot. Only meaningful for tab:* and section:* slots."},"dataSource":{"$ref":"#/$defs/dataSource","description":"Optional declarative data binding. Carry-forward from v1 widgetDef.dataSource — same shape."},"dateChip":{"type":"boolean","default":false,"description":"Opt-in (custom widgets only): when true AND the page's `dateRange.enabled` is true, the shared date-range chip renders in this widget's title bar (chart widgets with a `dataSource.bucket` get it automatically). Carry-forward from v1 layoutItem.dateChip — same semantics."},"_note":{"type":"string","description":"Human-readable note. Optional on most widgets. REQUIRED when the containing page has type 'custom' — in that case this field must document why a standard page type was not feasible."},"ncDashboard":{"type":"object","additionalProperties":false,"description":"Opt-in: also publish this placement as a widget on the native Nextcloud dashboard, alongside the app's own pages. An object rather than a boolean because a Nextcloud panel is a single narrow column with its own title, icon and link, none of which the grid placement carries. Presence of this key REQUIRES an `id` on the same entry (enforced in allOf below), and that id MUST be unique across the WHOLE manifest, not merely within its own page's widgets[] — Nextcloud's widget namespace is per app, so two pages both declaring id 'recent' derive one shared panel. Manifest-wide uniqueness is beyond JSON Schema; validateManifestV2 enforces it post-schema. Omitting the key leaves the widget visible only inside the app.","properties":{"title":{"type":"string","description":"Panel title on the Nextcloud dashboard. Falls back to the widget's own title when omitted."},"icon":{"type":"string","description":"MDI icon name (PascalCase), e.g. ViewDashboardOutline."},"order":{"type":"integer","description":"Initial ordering hint, passed through as IWidget::getOrder(). It decides where the panel first appears; once a user rearranges their own dashboard their arrangement wins, so this is a hint and never a guarantee."},"link":{"type":"string","pattern":"^(?![a-zA-Z][a-zA-Z0-9+.-]*:)(?!//)\\S+$","description":"In-app route the panel header links to, so a reader can open the page where the widget's full context lives. Written the way `pages[].route` is, e.g. '/reporting' — the host resolves it against its own app root with generateUrl() and never renders it bare. A scheme ('https:', and 'javascript:' above all) and a protocol-relative '//host' are both refused: the panel is rendered by Nextcloud's dashboard, outside this app's own pages, so a manifest is not a place to hand it an offsite destination."}}}},"allOf":[{"$ref":"#/$defs/sentinelGuardedValue"},{"description":"ncDashboard requires a stable id. The host derives a permanent Nextcloud widget id from `id`, and Nextcloud stores each user's chosen widgets by that id in its own namespace, which no migration of this app can reach. A widget identified by array position would move onto a different widget whenever the array is reordered, silently changing what a user sees. `id`'s own pattern already refuses an empty string, so requiring the key is the whole of the rule expressible here; the manifest-wide uniqueness half lives in validateManifestV2.","if":{"required":["ncDashboard"]},"then":{"required":["id"]}},{"description":"sidebar slot: gridWidth MUST equal 1. The sidebar is a fixed-width panel; widgets always span the full width.","if":{"properties":{"slot":{"const":"sidebar"}},"required":["slot"]},"then":{"properties":{"gridWidth":{"const":1}}}},{"description":"header-actions slot: gridY MUST equal 0. All header action widgets live in a single row.","if":{"properties":{"slot":{"const":"header-actions"}},"required":["slot"]},"then":{"properties":{"gridY":{"const":0}}}},{"description":"object-table built-in widget: when props declares a `source` it must match the declarative self-fetch shape, an `endpointSource` (Wave 2) must match the shared endpoint binding (exactly one of source | endpointSource — post-schema check), and `actions` must be typed manifest actions (incl. type:'object-op'). Other props stay free-form (pass-through to CnDataTable).","if":{"properties":{"widgetKey":{"const":"object-table"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"source":{"$ref":"#/$defs/objectTableSource"},"endpointSource":{"$ref":"#/$defs/endpointSource","description":"Wave 2 (#91): endpoint-bound rows — the payload at responsePath must be an array; columns / formatters / rowRoute / actions apply unchanged on top. Exactly one of source | endpointSource (post-schema check)."},"actions":{"type":"array","items":{"$ref":"#/$defs/action"},"description":"Declarative row/widget actions rendered by CnWidgetObjectTable. object-op patch/delete render per row; object-op create renders as a widget-scoped footer affordance."},"pageSize":{"type":"integer","minimum":1,"description":"Rows per page: the table shows one page with the board pager under it (\"4 of 7\", DqCatalogus). Under a pager `source.limit` is how many rows are read (2.127.0)."},"rowClass":{"type":"array","description":"#91: declarative per-row CSS class rules compiled into CnDataTable's rowClass function. Each rule { when: { field, op?, value }, class } adds `class` to a row when the shared visibleWhen predicate holds against that row (field = dot-path into the row, op = eq|neq|gt|gte|lt|lte default eq, value = literal). Rules evaluate in order and every matching class is space-joined — so overdue / at-risk rows can be highlighted from the manifest. A host-supplied rowClass FUNCTION (runtime-only) still passes straight through.","items":{"type":"object","additionalProperties":false,"required":["when","class"],"properties":{"when":{"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."}}}}}}}}},{"description":"nav-card-grid built-in widget (ADR-044 §4 cards-collapse): props.entries is REQUIRED and each entry must match the navCardEntry shape. Unlike stats-block's optional entries[], nav-card-grid has no dataSource alternative — it renders arbitrary navigation links, not fetched data.","if":{"properties":{"widgetKey":{"const":"nav-card-grid"}},"required":["widgetKey"]},"then":{"required":["props"],"properties":{"props":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","items":{"$ref":"#/$defs/navCardEntry"},"description":"One card per array entry, rendered by CnNavCardGrid."}}}}}},{"description":"stats-block built-in widget: when props declares `entries[]` each entry must match the statsBlockEntry shape. The exactly-one-of dataSource | props.entries constraint is a cross-field rule enforced by validateManifestV2() as a post-schema check (clear error message), not by JSON Schema.","if":{"properties":{"widgetKey":{"const":"stats-block"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/$defs/statsBlockEntry"},"description":"Multi-entry KPI sources — each entry renders one CnStatsBlock within the widget card. Mutually exclusive with a single dataSource."}}}}}},{"description":"banner built-in widget (CnBannerWidget, Wave 1): a declarative notice banner. props { variant, text, visibleWhen?, route? } — visibleWhen is a simple {endpoint|source, field?, op?, value} predicate evaluated at mount (fail-safe: hidden on fetch failure).","if":{"properties":{"widgetKey":{"const":"banner"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"variant":{"type":"string","enum":["info","warning","error","success"],"description":"Banner severity variant (NcNoteCard type). Default info."},"text":{"type":"string","description":"Pre-translated banner text (required for the banner to render)."},"visibleWhen":{"type":"object","additionalProperties":false,"description":"Optional visibility condition: exactly one of `endpoint` (same-origin JSON URL) or `source` (OpenRegister object query). `field` is a dot-path into the response (source: into the first result; omit or use '@total' for the collection total). Fail-safe: the banner stays hidden until the predicate evaluates true.","properties":{"endpoint":{"type":"string","description":"Same-origin URL returning JSON."},"source":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"OpenRegister object query; `filter` supports the shared @-token grammar.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"filter":{"type":"object","additionalProperties":true},"order":{"type":"object","additionalProperties":{"type":"string","enum":["asc","desc"]},"description":"Order of the query (`{ field: \"asc\" | \"desc\" }`), so the first result is the first of an ordered list. With it an attention card names that row: `{subject.<field>}` in its title, reason and kicker and `@subject.<field>` in an action route read it (screens-start-here-banner, 2.89.0)."}}},"field":{"type":"string","description":"Dot-path into the response ('@total' or omitted = collection total for a source)."},"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte","empty","notEmpty"],"default":"eq","description":"Comparison operator. `notEmpty` on `id` is the \"there is a first row\" test of a card that names one."},"value":{"description":"Literal right-hand side of the comparison."}}},"route":{"description":"Optional click-through: a vue-router route NAME (string) or a full location object. Makes the banner text an accessible button.","oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]},"layout":{"type":"string","enum":["banner","attention"],"default":"banner","description":"How the banner renders. `banner` (default) is the note card. `attention` is a card with a severity edge, a kicker, a title, a reason line and up to two actions."},"stacked":{"type":"boolean","default":false,"description":"Attention card: put the actions under the text instead of beside it, with a 16px title (PqMijnWerk \"Begin hier\"; screens-start-here-banner, 2.89.0)."},"kicker":{"type":"string","description":"Attention card: the small label above the title."},"title":{"type":"string","description":"Attention card: the title. Falls back to `text`."},"reason":{"type":"string","description":"Attention card: the line that says why this needs attention. `{value}` is replaced by the value visibleWhen read."},"actions":{"type":"array","maxItems":2,"description":"Attention card: at most two actions. The first is the primary one unless an action sets `primary: true`.","items":{"type":"object","required":["label"],"additionalProperties":false,"properties":{"id":{"type":"string","description":"Identifier passed with the `action` event of an action that has no route or href."},"label":{"type":"string","description":"The text on the action."},"route":{"description":"A vue-router route NAME (string) or a full location object.","oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]},"href":{"type":"string","description":"An URL, used when there is no route."},"primary":{"type":"boolean","description":"Render this action as the primary one."}}}}}}}}},{"description":"record-list built-in widget (CnRecordListWidget): the action under the list, `props.content.footerAction` (2.126.0), and a row's colour `dot` and `flags` (2.129.0). Other content keys stay free-form.","if":{"properties":{"widgetKey":{"const":"record-list"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"footerAction":{"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":{}}}}}}}}}}},{"description":"notice built-in widget (CnNoticeWidget): one toned box with an icon, a bold title and a text, through the host's translate, in the side column or the grid (DqSociaalDomein, 2.128.0).","if":{"properties":{"widgetKey":{"const":"notice"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"variant":{"type":"string","enum":["info","warning","error","success"],"default":"info"},"title":{"type":"string"},"text":{"type":"string"},"icon":{"type":"string","minLength":1}}}}}}}},{"description":"related built-in widget (CnRelatedObjectsWidget): grouping by relation (2.129.0). Other content keys stay free-form.","if":{"properties":{"widgetKey":{"const":"related"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"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."},"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\")."}}}}}}}},{"description":"nc-widget built-in widget (CnNcWidgetWidget): leaving items out (2.129.0). Other content keys stay free-form.","if":{"properties":{"widgetKey":{"const":"nc-widget"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"excludeApps":{"type":"array","items":{"type":"string","minLength":1},"description":"Apps whose items are left out (an item's app is its own app field, else the /apps/<app>/ part of its link or icon), e.g. [\"systemtags\"]."},"excludeTypes":{"type":"array","items":{"type":"string","minLength":1},"description":"Item types that are left out."}}}}}}}},{"description":"decision-list built-in widget (CnDecisionListWidget): per-row sign-offs, each with an answer, its fields and a reason, sent to an app endpoint (2.133.0). Texts go through the host translate.","if":{"properties":{"widgetKey":{"const":"decision-list"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"source":{"type":"object","additionalProperties":true,"description":"An endpointSource: `url`, `params?`, `responsePath?`."},"itemsPath":{"type":"string","minLength":1},"idField":{"type":"string","minLength":1},"title":{"type":"string","description":"A template filled from a row."},"meta":{"type":"string","description":"A template filled from a row."},"answers":{"type":"array","items":{"type":"object","required":["value","label"],"additionalProperties":true,"properties":{"value":{},"label":{"type":"string","minLength":1},"fields":{"type":"array","items":{"type":"object","required":["field"],"additionalProperties":true,"properties":{"field":{"type":"string","minLength":1},"type":{"type":"string","enum":["date","text"]},"label":{"type":"string"},"required":{"type":"boolean"}}}}}}},"reason":{"description":"`{ label?, required?, field? }` (required by default, sent as `field`, default `reason`); `false` drops it.","anyOf":[{"type":"boolean","const":false},{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string"},"required":{"type":"boolean"},"field":{"type":"string","minLength":1}}}]},"submit":{"type":"object","required":["request"],"additionalProperties":true,"properties":{"label":{"type":"string"},"request":{"type":"object","required":["url"],"additionalProperties":true,"properties":{"method":{"type":"string","minLength":1},"url":{"type":"string","minLength":1},"body":{"type":"object","additionalProperties":true}}}}},"answerField":{"type":"string","minLength":1},"emptyText":{"type":"string"},"errorTitle":{"type":"string"},"answerLabel":{"type":"string"}}}}}}}},{"description":"retention built-in widget (CnRetentionWidget): a record's stored retention, and a recompute for archivists (2.133.0). Texts go through the host translate.","if":{"properties":{"widgetKey":{"const":"retention"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"title":{"type":"string"},"subtitle":{"type":"string"},"statusField":{"type":"string","minLength":1},"archivedValues":{"type":"array","items":{"type":"string"}},"archivistGroups":{"type":"array","items":{"type":"string","minLength":1},"description":"Groups that may recompute (default [\"archivaris\"]; admins always may)."},"recompute":{"type":"boolean","description":"false hides the recompute control."}}}}}}}},{"description":"plan built-in widget (CnPlanWidget): a record's planned steps from OpenRegister's case layer (2.133.0).","if":{"properties":{"widgetKey":{"const":"plan"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"emptyText":{"type":"string","description":"Through the host translate."}}}}}}}},{"description":"work-queue built-in widget (CnWorkQueue): one queue of everything waiting on the reader, in groups that can be hidden until tomorrow (2.133.0).","if":{"properties":{"widgetKey":{"const":"work-queue"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"source":{"type":"object","additionalProperties":true,"description":"An endpointSource answering `{ groups, hiddenGroups?, unavailable? }`."},"hide":{"type":"string","minLength":1,"description":"A url template with `{group}` that hides a group until tomorrow."},"show":{"type":"string","minLength":1,"description":"A url template with `{group}` that shows a hidden group again."},"itemHref":{"type":"string","minLength":1,"description":"A template filled from an item, for items without `route` or `href`."},"waitingParties":{"type":"object","additionalProperties":{"type":"string"}},"emptyText":{"type":"string"}}}}}}}},{"description":"audit-trail built-in widget (CnAuditTrailWidget, Wave 1): the object change-log card. All props are optional — register/schema/objectId normally arrive via the detail-page object-context merge; explicit props win.","if":{"properties":{"widgetKey":{"const":"audit-trail"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"register":{"type":"string","description":"OpenRegister register slug/id (context merge fallback when omitted)."},"schema":{"type":"string","description":"OpenRegister schema slug (context merge fallback when omitted)."},"objectId":{"type":"string","description":"The audited object's id (context merge fallback when omitted)."},"title":{"type":"string","description":"Optional card title override."},"maxDisplay":{"type":"integer","minimum":1,"description":"Maximum audit rows to render (default 5)."}}}}}},{"description":"chart widget display passthrough (Wave 1): additive presentation keys forwarded verbatim to CnChartWidget by CnDashboardPage's dispatcher and the v2 grid. Other chart props stay free-form.","if":{"properties":{"widgetKey":{"const":"chart"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"horizontal":{"type":"boolean","description":"Render type:'bar' charts horizontally (row bars)."},"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'.","oneOf":[{"type":"string","enum":["currency","currency-compact","percent","number"]},{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","enum":["currency","currency-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'."}}}]},"colorMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Per-category colour map ({ categoryLabel: cssColor }) for pie/donut/radialBar slices and (distributed) bar categories."},"emptyLabel":{"type":"string","description":"Empty-state message rendered instead of the chart when the resolved series contain no data points."},"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","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","additionalProperties":false,"required":["row","column"],"description":"chartKind 'matrix': the cell to outline, usually the record's on a detail page. `row` matches a series key, name or 1-based position; `column` an entry of columnKeys, a category label or a 1-based position. Both take tokens such as '@object.behaviour'.","properties":{"row":{"type":["string","number"]},"column":{"type":["string","number"]},"columnKeys":{"type":"array","items":{"type":["string","number"]}}}},"cellSource":{"type":"object","additionalProperties":false,"required":["register","schema","rowField","columnField","valueField"],"description":"chartKind 'matrix': read the cells from records that each hold one cell. The records are listed from register/schema with `filter` (tokens such as '@object.matrixVersion' resolved; the list waits while one is unresolved), then laid out by rowField / columnField with valueField as the cell. Labels come from rowLabels / columnLabels maps or rowLabelField / columnLabelField; order from rowOrder / columnOrder lists, else rowOrderField / columnOrderField, else natural order. valueLabels maps a stored value to the text shown; colorMap may key on either. With cellsPath the matrix sits inside ONE record: the first record filter finds (else fallbackFilter), its array at cellsPath narrowed by cellFilter (tokens allowed), and the axis order from rowOrderPath / columnOrderPath on that record. Wins over series / categories.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"filter":{"type":"object","additionalProperties":true},"rowField":{"type":"string"},"columnField":{"type":"string"},"valueField":{"type":"string"},"rowLabelField":{"type":"string"},"columnLabelField":{"type":"string"},"rowLabels":{"type":"object","additionalProperties":{"type":"string"}},"columnLabels":{"type":"object","additionalProperties":{"type":"string"}},"valueLabels":{"type":"object","additionalProperties":{"type":"string"}},"rowOrder":{"type":"array","items":{"type":["string","number"]}},"columnOrder":{"type":"array","items":{"type":["string","number"]}},"rowOrderField":{"type":"string"},"columnOrderField":{"type":"string"},"limit":{"type":"integer","minimum":1},"cellsPath":{"type":"string","description":"Path of the cells array inside the one record the source finds."},"cellFilter":{"type":"object","additionalProperties":true,"description":"Equality filter on the cells of that record, e.g. { actorType: '@object.actorType' }."},"fallbackFilter":{"type":"object","additionalProperties":true,"description":"Filter used when `filter` finds no record, e.g. { active: true }."},"rowOrderPath":{"type":"string","description":"Path of an array on that record giving the row order."},"columnOrderPath":{"type":"string","description":"Path of an array on that record giving the column order."}}},"endpointSource":{"$ref":"#/$defs/chartEndpointSource","description":"Wave 2 (#91): endpoint-bound series/labels — the shared endpoint transport plus the labelsPath / series[] {name, path} response mapping. Exactly one of dataSource | endpointSource (post-schema check)."}}}}}},{"description":"tabs built-in widget (CnTabsWidget): `props.content.tabs[]` entries are a widget id or an object { id?, widgetId, label?, icon?, count?, countField?, unreadKey?, overflow?, visibleWhen? }. `visibleWhen` reuses the shared predicate: a tab whose condition is false is absent from the strip, not empty. LOCAL form reads a field of the page object; the `source` form counts related rows (`@total`) once on mount and after a reported write, and shows the tab disabled while it is pending. A tab without the key always renders.","if":{"properties":{"widgetKey":{"const":"tabs"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"tabs":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true,"properties":{"visibleWhen":{"$ref":"#/$defs/visibleWhen"}}}]}}}}}}}}},{"description":"header / text / divider on the v2 grid (Wave 1): the dashboard catalog's content-only presentation widgets reused as built-ins. Each takes a single `props.content` object with the same shape as its dashboard `content` blob.","if":{"properties":{"widgetKey":{"enum":["header","text","divider"]}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"description":"The widget's content blob — header: { title, subtitle, backgroundImageUrl, overlayMode, height, cta, … }; text: { text, contentMode, fontSize, … }; divider: { style, lineColor, headingText, … }."}}}}}},{"description":"stat / delta catalog widgets on the v2 grid (Wave 2, #91): the KPI tiles take a `props.content` blob (same shape as their dashboard `content`). The endpoint-binding keys are typed here; the rest of the blob stays free-form (label, icon, format, source, …). Exactly one of content.source | content.endpointSource — a cross-field rule enforced by validateManifestV2() as a post-schema check (stats-block precedent), which also covers the legacy pages[].config.widgets[] placement.","if":{"properties":{"widgetKey":{"enum":["stat","delta"]}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"endpointSource":{"$ref":"#/$defs/endpointSource","description":"Wave 2 (#91): endpoint-bound KPI value(s) — exactly one of source | endpointSource (post-schema check)."},"valueField":{"type":"string","description":"Dot-path into the endpoint payload for the displayed (current) value. Omitted = the payload itself."},"previousField":{"type":"string","description":"Dot-path into the endpoint payload for the previous-period value — renders the trend sublabel (arrow + percent-vs-previous; the pipelinq previousPeriod contract)."},"deltaField":{"type":"string","description":"Dot-path into the endpoint payload for a SERVER-computed delta percent — wins over the client-side previousField computation."},"goodDirection":{"type":"string","enum":["up","down"],"default":"up","description":"Which trend direction tints green ('up' default; 'down' for cost-like KPIs). The delta widget's source.goodDirection still wins for the OpenRegister form."},"variantWhen":{"type":"array","description":"First-match threshold styling rules for the stat tile — the matched rule's variant re-tints the value + icon circle, and its optional icon overrides content.icon.","items":{"type":"object","required":["op","value","variant"],"additionalProperties":false,"properties":{"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte"],"description":"Comparison operator against the resolved display value (numeric when both sides coerce; eq/neq fall back to string equality)."},"value":{"description":"Literal right-hand side of the comparison."},"variant":{"type":"string","enum":["default","primary","success","warning","error"],"description":"Colour variant applied on match ('default' keeps the configured colours). The component also accepts 'danger' as an alias of 'error' for doriath-migrated content."},"icon":{"type":"string","description":"Optional MDI icon name overriding content.icon while this rule matches."}}}},"clickRoute":{"description":"Whole-tile click-through: a vue-router route NAME (string) or full location object. Alias of content.route (route wins when both are set).","oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]}}}}}}}},{"description":"workspace-filter catalog widget (#91 Wave 3): a choice list whose selection writes into the page workspace context so sibling widgets refetch. The endpoint/OR-source binding keys of `props.content` are typed here; the rest stays free-form.","if":{"properties":{"widgetKey":{"const":"workspace-filter"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"writes":{"type":"string","description":"The workspace context key the selection writes (`@workspace.<key>` or a bare `<key>`). Sibling widgets whose declarative source interpolates `@workspace.<key>` refetch on change."},"style":{"type":"string","enum":["radio","select","chips","toggle"],"default":"radio","description":"Choice style: 'radio' (default) list, 'select' dropdown, 'chips' (one compact segmented group with a count after each label; screens-page-lenses, 2.87.0) or 'toggle' (a switch writing `onValue` / `offValue`; screens-page-controls, 2.88.0)."},"layout":{"type":"string","enum":["start","bar"],"description":"'start' draws the start card; 'bar' draws `controls` side by side in one row (screens-page-controls, 2.88.0)."},"controls":{"type":"array","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)."},"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)."},"urlKey":{"type":"string","minLength":1,"description":"Keep the choice in the address as `?<urlKey>=<value>` (replacing the history entry, keeping other query keys). The address wins over `default` on load, so a chip is linkable (screens-page-lenses, 2.87.0)."},"allLabel":{"type":"string","description":"Optional leading 'All' option — writes the empty string so an optional sibling token (`@workspace.<key>?`) drops (unfiltered set)."},"showCounts":{"type":"boolean","default":true,"description":"Render per-option counts."},"options":{"type":"array","description":"Static options — each { value, label, count?, countFrom? } (or { id, name } / bare strings, normalised). `countFrom` is `{ register, schema, filter? }`, or a list of those that are added up; the option shows that list's total (one list request each; none when it fails). screens-page-lenses, 2.87.0.","items":{"type":["object","string","number"]}},"source":{"type":"object","additionalProperties":true,"description":"OpenRegister /grouped facet source { register, schema, groupBy, filter? } — each distinct value becomes an option with its object count.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"groupBy":{"type":"string"},"filter":{"type":"object","additionalProperties":true}}},"endpointSource":{"$ref":"#/$defs/endpointSource","description":"App endpoint returning an array of { value, label, count? } options at responsePath."}}}}}}}},{"description":"kb-search catalog widget (#91 Wave 3): summary-driven knowledge-base search through a PLUGGABLE PROVIDER. The provider seam keys of `props.content` are typed here; the rest stays free-form.","if":{"properties":{"widgetKey":{"const":"kb-search"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"provider":{"type":"string","description":"Provider registry key (default 'default' — the built-in endpoint search). An app registers bespoke providers (the xwiki proxy) on CnAppRoot's kbSearchProviders; the xwiki client stays app-side."},"space":{"type":"string","description":"Provider-specific KB space filter (passed to provider.search)."},"tags":{"description":"Provider-specific tag filter — a single tag or an array.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"endpoint":{"type":"string","description":"The default provider's search endpoint (OpenRegister xWiki leaf by default)."},"queryParam":{"type":"string","description":"The default provider's query parameter name (default 'q')."},"bindTo":{"type":"string","description":"Workspace context key to auto-search on (default 'activeSummary')."},"minChars":{"type":"integer","minimum":0,"description":"Minimum characters before a search fires (default 3)."},"limit":{"type":"integer","minimum":1,"description":"Result cap passed to the provider (default 8)."},"externalOpen":{"type":"boolean","description":"Whether result links open in a new tab (defaults to the provider's own externalOpen flag)."},"unavailableFallback":{"type":"string","description":"Text shown when the provider rejects / is unavailable (replaces the default 'Knowledge base unavailable')."}}}}}}}},{"description":"week-strip catalog widget (CnWeekStripWidget): the current week as day columns with the dated items under each day. Takes a `props.content` blob. Items come from an OpenRegister `source` (one request for the week, bucketed by `dateField`) or from static `items`.","if":{"properties":{"widgetKey":{"const":"week-strip"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"days":{"type":"integer","enum":[5,7],"default":5,"description":"5 working days (default) or the whole week. The week starts on Monday."},"source":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"OpenRegister object query; `filter` supports the shared @-token grammar.","properties":{"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)."}}},"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."},"itemRoute":{"type":"string","description":"Route name an item links to; the record id goes in params.id."},"lateWhen":{"type":"object","additionalProperties":false,"description":"Rule on the number of days until the date that marks an item late. Default: before today ({ op: 'lt', value: 0 }).","properties":{"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte"]},"value":{"type":"number"}}},"lateField":{"type":"string","description":"A boolean field that marks a record late, whatever its date."},"items":{"type":"array","description":"Static items, used when there is no source.","items":{"type":"object","required":["title","date"],"additionalProperties":false,"properties":{"title":{"type":"string"},"meta":{"type":"string"},"date":{"type":"string","description":"ISO date or date-time."},"route":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]},"href":{"type":"string"},"late":{"type":"boolean"}}}},"emptyText":{"type":"string","description":"Text of a day without items."},"weekOffset":{"type":"integer","description":"Whole weeks from the current one (1 = next week)."},"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."}}}}}}}},{"description":"stacked-bar catalog widget (CnStackedBarWidget): one segmented bar with a legend that carries the label and count of every segment. Takes a `props.content` blob. Segments come from one grouped count request (`source.groupBy`) or from static `segments`.","if":{"properties":{"widgetKey":{"const":"stacked-bar"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"source":{"type":"object","additionalProperties":false,"required":["register","schema","groupBy"],"description":"OpenRegister grouped count; `filter` supports the shared @-token grammar.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"groupBy":{"type":"string","description":"The field the records are counted per."},"filter":{"type":"object","additionalProperties":true}}},"segments":{"type":"array","description":"Static segments, used when there is no source.","items":{"type":"object","required":["label","value"],"additionalProperties":false,"properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":"number","minimum":0}}}},"order":{"type":"array","items":{"type":"string"},"description":"Group keys in the order they render. Unlisted groups follow; a listed key without records shows 0."},"labels":{"type":"object","additionalProperties":{"type":"string"},"description":"The label of a group key."},"emptyText":{"type":"string","description":"Shown when the total is 0."},"inset":{"type":"boolean","default":false,"description":"Draw the bar and legend 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."}}}}}}}}]},"fieldWidget":{"type":"object","required":["id","component"],"additionalProperties":false,"description":"A declarative widget-component slot used inside `form` / `detail` page config. Distinct from the top-level pages[].widgets[] grid placement (which carries widgetKey + grid coordinates). fieldWidget mounts a lib `Cn*` SFC inline as a form field or detail data slot; the renderer resolves `component` against the lib's exported component map, NOT the host-app customComponents registry. Carries serialisable `props` only — handler / function props must be wired via the host App's manifest-level mounts. Replaces the untyped `fieldWidgets[]` key relied on by pipelinq#415 (which rode through on additionalProperties: true). See ConductionNL/nextcloud-vue#314.","properties":{"id":{"type":"string","pattern":"^[a-z][a-zA-Z0-9_-]*$","description":"Unique identifier within the fieldWidgets[] array. Lower-camel-case-ish: must start with a lowercase letter and contain only letters, digits, underscores, or hyphens. Used as the form field key / data binding key by CnFormPage / CnDetailPage."},"component":{"type":"string","pattern":"^Cn[A-Z]\\w+$","description":"Name of a lib component (e.g. CnMarkdownEditor, CnSchemaSelect). MUST be Cn-prefixed and resolved against the @conduction/nextcloud-vue exported component map. Host-app SFCs are not allowed here — those go through pages[].component on a type:'custom' page."},"props":{"type":"object","description":"Serialisable props passed to the resolved component at render time. Shape is component-specific; not constrained here. Handler / function values are not supported through the manifest (the manifest is JSON).","additionalProperties":true},"_note":{"type":"string","description":"Optional human-readable note explaining the role of this declarative widget slot."}}},"slotValue":{"description":"Valid slot value. Either a literal slot name (body, sidebar, header-actions, footer, modal) or a tab:<id> / section:<id> pattern string.","oneOf":[{"type":"string","enum":["body","sidebar","header-actions","footer","modal"],"description":"Literal slot name."},{"type":"string","pattern":"^(tab|section):.+","description":"Named tab or section slot. Format: tab:<id> or section:<id> where <id> is at least one character."}]},"dataSource":{"type":"object","description":"Declarative data binding for widgets. Forms: (a) shorthand {register, schema, filter?, aggregate: 'count'} — the lib builds a count query and resolves to {count}. (b) raw {graphql: {query, variables?, selectors}} — the lib issues the query and runs selectors. (c) #91 Wave 3 chart aggregation — `aggregate` as an OBJECT {groupBy, metric?: count|sum, sumField?, topN?, otherBucket?, labelResolve?} grouped over the schema's objects via OpenRegister's /grouped facet (client-side collection fallback), plus an optional sibling `drilldown` {route, filterParam}. Carry-forward from v1.3.0 widgetDef.dataSource.","additionalProperties":true,"properties":{"register":{"type":"string","description":"Shorthand: register slug."},"schema":{"type":"string","description":"Shorthand: schema slug."},"filter":{"type":"object","description":"Shorthand: filter map inlined into the generated query.","additionalProperties":true},"aggregate":{"description":"Shorthand aggregation. The STRING 'count' builds a GraphQL count query resolving to {count}. The OBJECT form (#91 Wave 3) is a chart-only categorical group-by served by OpenRegister's /grouped facet.","oneOf":[{"type":"string","enum":["count"]},{"type":"object","required":["groupBy"],"additionalProperties":false,"description":"#91 Wave 3 chart aggregation: group the schema's objects by `groupBy`, count (default) or sum `sumField`, keep the `topN` largest (sorted desc) and optionally fold the remainder into a translated 'Other' slice, resolving reference keys to labels/colours via `labelResolve`.","properties":{"groupBy":{"type":"string","description":"The categorical field grouped on."},"metric":{"type":"string","enum":["count","sum"],"default":"count","description":"count (default) | sum. sum requires sumField."},"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"],"additionalProperties":false,"description":"Resolve reference (uuid) group keys to the referenced objects' display labels (and optional per-category colours) through the shared object store — the fkResolve pattern.","properties":{"register":{"type":"string","description":"Register the reference points into (defaults to the dataSource register)."},"schema":{"type":"string","description":"Schema slug the reference points into."},"labelField":{"type":"string","description":"Property used as the display label (default 'name')."},"colorField":{"type":"string","description":"Optional property read as a per-category CSS colour (feeds the chart's colorMap path)."}}}}}]},"drilldown":{"type":"object","required":["route","filterParam"],"additionalProperties":false,"description":"#91 Wave 3 (chart only): a segment/bar click navigates to `route` with the clicked category's RAW key in the query ({ [filterParam]: rawKey }). A `/`-prefixed route is a path, otherwise a route name. Works with the OBJECT `aggregate` and the legacy `groupBy` forms; the folded 'Other' bucket never navigates.","properties":{"route":{"type":"string","description":"Destination route name, or a path when it starts with '/'."},"filterParam":{"type":"string","description":"Query-param name the clicked category's raw key is written to."}}},"graphql":{"type":"object","description":"Raw GraphQL form.","additionalProperties":false,"required":["query","selectors"],"properties":{"query":{"type":"string","description":"GraphQL query document."},"variables":{"type":"object","description":"Variables map passed alongside query.","additionalProperties":true},"selectors":{"type":"object","description":"Map of result-prop name to dot-path selector.","additionalProperties":{"type":"string"}}}}}},"action":{"description":"A page action: the fields of $defs/actionFields, nothing else, with children that are actions too. Row, bulk and navigation actions use it; header actions use $defs/headerAction.","$ref":"#/$defs/actionFields","properties":{"children":{"items":{"$ref":"#/$defs/action"}}},"unevaluatedProperties":false},"actionFields":{"type":"object","required":["id","label"],"description":"The fields of an action, shared by $defs/action and the non-link $defs/headerAction, which close it and constrain `children`. A typed page action with a discriminator field. The type field determines behaviour: handler (registry function), open-modal (modal by id), open-page (named route), navigate (URL), object-op (declarative OpenRegister mutation via useObjectStore), export (opens the shared CnMassExportDialog export launcher), open-form (schema-driven create dialog — CnActionButtons), refresh (bumps the cn:page:refresh signal), api-call (POST/PUT an app endpoint + toast + refresh — CnActionButtons/dispatchAction), agent (run a governed hermiq agent), run-node (invoke one OpenRegister flow node against the page object, config collected via a dialog sourced from the node's own declared form — CnActionButtons/dispatchAction, or-flow-run-node; distinct from the UNRELATED setup-wizard step type of a similar name, 'run-action'), toggle (two-way state button — CnActionButtons only, NOT dispatchable). When type is omitted, it defaults to 'handler' (back-compatibility with v1.3.0 action declarations). The Ajv instance is compiled with useDefaults: true so the default is applied during validation.","properties":{"id":{"type":"string","description":"Unique action identifier within the page's actions array."},"label":{"type":"string","description":"i18n key resolved by the consuming app's t() function at render time."},"type":{"type":"string","enum":["handler","open-modal","open-page","navigate","object-op","export","open-form","refresh","api-call","agent","run-node","toggle"],"default":"handler","description":"Action behaviour discriminator. Defaults to 'handler' when omitted (back-compat with v1.3.0). handler: calls a registry function; open-modal: opens a modal by target id; open-page: navigates to a named route; navigate: navigates to a URL; object-op: dispatches a declarative mutation (patch | delete | create) of an OpenRegister object via the shared object store; export: opens the shared CnMassExportDialog export launcher configured via entities[]/formats[] — the confirm payload ({format, entity?}) routes to the action's `handler` in the manifest actions map (the app's export service does the download). open-form (#91 Wave 3): CnActionButtons opens a schema-driven create dialog (CnAdvancedFormDialog) for the action's register/schema, saves + toasts + refreshes + optionally navigates to onSuccessRoute. refresh (#91 Wave 3): bumps the page-level cn:page:refresh event-bus signal so every endpoint-bound widget refetches. api-call (#91 Wave 3): POST/PUT the token-interpolated url with payload/params (JSON body, payload resolves @-tokens recursively at any depth) + success/error toast + auto page-refresh (unless refresh:false); confirm gating is surface-consumed intent (object-op precedent); download:true requests a binary blob and triggers a browser file download instead (auto-refresh defaults to OFF in that mode). agent (hermiq#41): run a governed hermiq agent against the page object — POSTs { register, schema, objectId, resultField?, skill?, prompt? } to /apps/hermiq/api/agents/{agent}/run-on-object (object-RBAC-scoped; dispatches the governed AgentRunRequestedEvent). register/schema/objectId default to the page's @register/@schema/@objectId object context. A first-class companion to api-call (which remains the fallback for a bespoke body); hermiq is NOT hard-required — an app-level 404 surfaces a graceful 'agent runtime unavailable' toast. toggle (#91 Wave 3): a stateful two-way state button rendered by CnActionButtons (GET stateSource on mount, optimistic write on click) — NOT dispatchable through dispatchAction. object-op declares mutation INTENT only — authority is enforced server-side by OpenRegister RBAC (ADR-022/ADR-023); authorization-shaped fields have no client-side effect and are not accepted here."},"op":{"type":"string","enum":["patch","delete","create"],"description":"object-op only: the mutation verb. patch merges `values` into the row's object (row-scoped); delete removes the row's object (row-scoped, ALWAYS confirm-gated); create adds a new object built from `values` against the widget's source register/schema (widget-scoped footer/header affordance). Required when type is 'object-op'."},"values":{"type":"object","additionalProperties":true,"description":"object-op only: the verb's payload. For patch: the partial object merged over the row's object. For create: the new object's properties. Omitted for delete."},"confirm":{"type":"boolean","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."},"props":{"type":"object","description":"Additional props forwarded to the handler or modal.","additionalProperties":true},"createOverride":{"type":"string","description":"Registry handler name that owns the persist for an `open-form` action, replacing the default objectStore.saveObject. Resolved exactly as CnIndexPage resolves its own `createOverride` prop (a kind:'create-override' entry's `.handler`, a function-valued registry entry, or a function in the legacy customComponents map). Needed when the schema requires a field the create form cannot supply on its own — a server-minted foreign key, say — where a straight save would 400."},"includeFields":{"type":"array","items":{"type":"string"},"description":"open-form: the only fields the create dialog asks for. One schema often backs two surfaces, a detail page that edits all of it and a header button that collects what someone filing a new one types, and this is how the button says which it is. Omit for the whole schema."},"excludeFields":{"type":"array","items":{"type":"string"},"description":"open-form: fields the create dialog leaves out. Use it to drop a handful; use includeFields when it is easier to name what stays."},"fieldOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"open-form: per-field overrides passed to fieldsFromSchema, keyed by field. Recognised keys include label, order, hidden, readOnly and widget."},"advanced":{"type":"boolean","default":false,"description":"open-form: open the properties-and-JSON table (CnAdvancedFormDialog) instead of the plain form. The plain form is the default because a header button is aimed at someone filing a record, not at inspecting its schema."},"formTitle":{"type":"string","description":"open-form: overrides the create dialog's heading. Defaults to 'Create {schema title}'."},"formEyebrow":{"type":"string","description":"open-form: the context line above the create dialog's heading (board look), e.g. the list the record joins. Through the host translate. Absent: no line."},"formSubtitle":{"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"],"default":"normal","description":"open-form: how wide the create dialog is, passed straight to NcDialog. Widen a form that asks enough questions to need scrolling; pair it with columns: 2."},"columns":{"type":"integer","enum":[1,2],"default":1,"description":"open-form: how many columns the fields flow into. 2 pairs them up, which halves the scrolling on a longer form. Textareas and JSON editors still span the full width, and the layout collapses to one column below 700px. Widen the dialog with size: 'large' as well, or two columns are merely two narrow ones."},"endpoint":{"type":"object","description":"open-form only: edit a record that is not a register object through an endpoint (an OpenRegister organisation). `url` is where the save goes (token-interpolated, app-relative or absolute), `method` is PATCH (default) or PUT, or POST to create a record with an empty form, `loadUrl` reads the record when it differs from `url`, `responsePath` plucks it from the answer. The fields are `formProperties`. Without `endpoint` the action is the schema-driven create form.","properties":{"url":{"type":"string"},"method":{"type":"string","enum":["PATCH","PUT","POST"]},"loadUrl":{"type":"string"},"responsePath":{"type":"string"}},"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\" } }`).","additionalProperties":{"type":"object"}},"handler":{"type":"string","description":"Registry key for type: 'handler'. Resolved across both registries passed to CnAppRoot: a v2 `registry` entry of `kind: 'handler'` exposing the function as `.handler` (or `.fn`), a directly function-valued registry entry, or a function-valued entry in the deprecated `customComponents` map. The same resolution serves actions[], bulkActions[] and headerActions[]. Also the built-in dispatch verb when paired with `route` (handler: 'navigate' → $router.push to `route`)."},"route":{"type":"string","description":"Destination route name for handler: 'navigate'. The dispatcher (manifestActionDispatch) calls $router.push({ name: route, params }). For row-level actions params include { id: row[rowKey] } automatically — do NOT restate it as params: { id: \"{id}\" }, though that placeholder does resolve (see `params`). Page-level header actions navigate without row context (e.g. a 'New X' action to a detail route)."},"icon":{"type":"string","description":"Optional icon for the action, rendered by CnActionsBar. Either an MDI icon name (e.g. 'TextBoxOutline') or a CSS icon class (e.g. 'icon-file')."},"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 + 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":"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."}}}},"formats":{"type":"array","description":"export only: the offered export formats — bare ids ('excel', 'csv', 'json' — lifted to {id, label} with an upper-cased label) or full {id, label} entries. Optional — the dialog's built-in Excel/CSV defaults apply when absent.","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["id","label"],"properties":{"id":{"type":"string","description":"Format id handed to the confirm handler."},"label":{"type":"string","description":"Pre-translated display label."}}}]}},"description":{"type":"string","description":"export only: pre-translated description shown above the export launcher's pickers."},"url":{"type":"string","description":"api-call / toggle only: the app endpoint (POST/PUT). App-relative (routed through generateUrl) or absolute (http/https). May interpolate @objectId / @object.<field> / @workspace.<key> / @config.<key> tokens inline, as well as the literal {objectId} brace placeholder (unresolved tokens collapse to empty)."},"method":{"type":"string","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","additionalProperties":true,"description":"api-call only: the JSON request body — PREFERRED over `params`, and used instead of it when both are set. Values run the SAME token grammar as `params` (@me, @now, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId, @object.<field>, @workspace.<key>/@config.<key> with a trailing `?` marking a value OPTIONAL — dropped when unresolved) but resolve RECURSIVELY at ANY nesting depth, including arrays of objects — e.g. a Filinq document-generation body: { \"dataRefs\": [{ \"register\": \"crm\", \"schema\": \"lead\", \"id\": \"@objectId\" }] }. A required (non-`?`) token left unresolved anywhere in the tree BLOCKS the call (error toast) instead of sending the literal token string to the server."},"params":{"type":"object","additionalProperties":true,"description":"Two distinct uses depending on the action's dispatch. (1) api-call / toggle: the JSON request body (legacy — prefer `payload` for anything with nested objects/arrays; ignored when `payload` is set). Values run the shared filter-token grammar ONE level deep (@me, @today±Nd, @workspace.<key>/@config.<key> with a trailing `?` for optional values dropped when unresolved; a required unresolved token skips the call). (2) handler: 'navigate' (index-page row actions, manifestActionDispatch): vue-router params merged OVER the default { id: row[rowKey] }. OMIT it for the ordinary 'open this row's detail page' action — the row id is already injected. String values run the `{field}` ROW-token grammar (NOT the @-token grammar): \"{id}\" resolves to row.id with its type preserved, \"run-{id}\" interpolates as text, and a brace-less \"new\" stays literal (that is what makes a 'New X' action navigate with { id: 'new' }). A token naming a field the row does not carry is dropped with a console.warn — so `id` falls back to the row id — instead of being pushed as a literal %7Bid%7D path segment."},"download":{"type":"boolean","default":false,"description":"api-call only: request the response as a binary blob (axios responseType 'blob') and trigger a browser file download instead of a JSON toast+refresh cycle. The filename is taken from the response's Content-Disposition header, else the token-resolved `filename`, else 'download.pdf'. The success toast still shows; unlike a normal api-call the page does NOT auto-refresh afterwards unless `refresh: true` is explicitly set."},"filename":{"type":"string","description":"api-call `download: true` only: fallback filename used when the response carries no Content-Disposition header (falls back further to 'download.pdf'). Interpolates the same token grammar as `url` (@objectId, @object.<field>, @workspace.<key>, @config.<key>, {objectId})."},"successMessage":{"type":"string","description":"api-call / open-form / agent / run-node / toggle: pre-translated success toast text (a library default applies when absent; agent default 'Run queued')."},"errorMessage":{"type":"string","description":"api-call / agent / run-node / toggle: pre-translated error toast text (falls back to the server's error/message, then a library default; an agent 404 with no structured body toasts 'agent runtime unavailable' instead)."},"refresh":{"type":"boolean","default":true,"description":"api-call / agent / run-node / toggle: bump the page-level cn:page:refresh signal after a successful call (default true; set false to skip). EXCEPTION: for an api-call with download:true the default flips to false — a file download normally shouldn't also force-refetch every widget — set refresh:true explicitly to opt back in."},"flowId":{"type":"string","description":"run-node only (REQUIRED): the published flow's uuid whose node this action invokes. A node's config and eligibility are properties of the flow it lives in (RN-3, OpenRegister's or-flow-run-node), so the manifest names which flow's instance to call rather than a bare node type — two flows could each carry the same node TYPE wired differently."},"nodeId":{"type":"string","description":"run-node only (REQUIRED): the id of the node, within flowId's published graph, to invoke directly. POSTed to /api/flows/{flowId}/nodes/{nodeId}/run — refused (404) unless that node type has opted in to direct invocation (IFlowDirectlyInvokable)."},"subject":{"type":"string","description":"run-node only: the subject object the node acts on and is authorized against (OpenRegister object-RBAC — the caller needs update permission on it; the flow's own flow.run right is deliberately NOT what gates this). Typically @objectId; may interpolate the shared @-token grammar. An unresolved required token BLOCKS the call, matching agent's objectId."},"agent":{"type":"string","description":"agent only (REQUIRED): the hermiq agent uuid to run against the page object. POSTed to /apps/hermiq/api/agents/{agent}/run-on-object (hermiq#41) — object-RBAC-scoped, dispatches the governed AgentRunRequestedEvent. May interpolate the shared @-token grammar (@objectId, @object.<field>, @workspace.<key>, @config.<key>)."},"skill":{"type":"string","description":"agent only: optional skill id folded into the governed run (interpolates the same @-token grammar as `agent`)."},"prompt":{"type":"string","description":"agent only: optional prompt handed to the governed run. Interpolates the shared @-token grammar inline (@objectId, @object.<field>, @workspace.<key>, @config.<key>) so it can be grounded on the page object — e.g. 'Summarise @object.title'."},"resultField":{"type":"string","description":"agent only: the object field the agent's result is written back to (falls back to hermiq's default result field when absent)."},"objectId":{"type":"string","description":"agent only: the target object id. Defaults to the page object context's @objectId; an unresolved required @objectId BLOCKS the call (fail-closed) rather than sending a literal token. May be an explicit id or an @-token string."},"register":{"type":"string","description":"open-form / agent / run-node: OpenRegister register slug. open-form: the create dialog's schema register. agent / run-node: the register the subject object belongs to — falls back to the page object context's @register."},"schema":{"type":"string","description":"open-form / agent / run-node: OpenRegister schema slug. open-form: the create dialog fetches this schema and saves the new object against it. agent / run-node: the schema the subject object belongs to — falls back to the page object context's @schema."},"onSuccessRoute":{"description":"open-form only: optional post-save navigation target (#91). A bare route NAME (string) navigates with the saved object's id merged into the route params under `id` (harmless when the route has no `:id` segment — backward compatible). The object form { name, paramField?, objectParam? } names the id param (`paramField`, default `id`) and, when `objectParam` is set, also passes the whole saved object under that param key (for a props:true detail route to render without a refetch). The id is read from saved.id, then saved.uuid, then saved['@self'].id.","oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","description":"The vue-router route NAME pushed after a successful save."},"paramField":{"type":"string","default":"id","description":"Route param name populated from the saved object's id (default `id`)."},"objectParam":{"type":"string","description":"When set, the whole saved object is passed under this route param (props:true detail routes render without a refetch)."}}}]},"labelOn":{"type":"string","description":"toggle only: button label rendered while the state is ON (true). Falls back to `label`."},"labelOff":{"type":"string","description":"toggle only: button label rendered while the state is OFF (false). Falls back to `label`."},"stateSource":{"$ref":"#/$defs/endpointSource","description":"toggle only: the endpoint the initial state is read from on mount (the shared endpointSource shape — url + token-resolved params + responsePath). The `field` dot-path picks the boolean off the payload."},"writeUrl":{"type":"string","description":"toggle only: the endpoint the flipped state is written to on click (interpolates the same tokens as `url`)."},"field":{"type":"string","description":"toggle only: the state field — read off the stateSource payload on mount AND sent (as the flipped boolean) in the write body under this key."},"variant":{"type":"string","description":"CnActionButtons button variant (e.g. 'primary', 'secondary', 'error'). 'error' also styles the confirm dialog as destructive. A 'primary' action is never collapsed into CnActionButtons' overflow menu and consumes none of its `inline` slots: NcActions paints all of its inline actions with one shared variant, so a lone primary among them is not expressible."},"children":{"type":"array","description":"Sub-actions, rendered by CnActionButtons as this action's OWN chevron dropdown beside its button — deliberately not folded into the page's single overflow menu, because NcActions cannot nest and a list that grows per record (app versions, say) would swamp it. Children take the same shape as any action, `visibleWhen` included, and an action carrying them consumes none of the `inline` slots. The parent itself still renders its own button beside the chevron (`type` defaults to 'handler' when omitted), so a parent meant only to group its children needs a `handler` that does something meaningful — an untyped, handler-less parent renders a button that dispatches to nothing. Each child is an action; a header action's child is a header action, so it may be a link with `href`."},"childrenLabel":{"type":"string","description":"i18n key naming the `children` dropdown. Falls back to the parent action's own label, so the chevron is never announced unlabelled."},"group":{"type":"string","minLength":1,"description":"Detail page header actions only. i18n key / text naming the group this action sits in inside the overflow menu. Grouped actions render under a caption with this name, in first appearance order, after the ungrouped ones. Ignored on other surfaces."},"adminOnly":{"type":"boolean","default":false,"description":"Detail page header actions only. Shows the action to instance admins alone, in the last group of the overflow menu. Hides a control and nothing more: the endpoint behind the action still decides who may call it. Ignored on other surfaces."},"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"#91 Wave 3: an optional predicate gating whether the action renders — the shared banner shape ({ endpoint | source | field, op, value }). The `field`-only LOCAL form evaluates against the page object context (a detail action shown only in the right lifecycle state); endpoint / source forms fetch. Fail-safe: a broken predicate hides the action."}},"allOf":[{"description":"object-op actions MUST declare their mutation verb via `op`.","if":{"properties":{"type":{"const":"object-op"}},"required":["type"]},"then":{"required":["op"]}},{"description":"api-call actions MUST declare their endpoint via `url`.","if":{"properties":{"type":{"const":"api-call"}},"required":["type"]},"then":{"required":["url"]}},{"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":{"anyOf":[{"required":["schema"]},{"required":["endpoint","formProperties"]}]}},{"description":"agent actions MUST declare the target agent uuid via `agent`.","if":{"properties":{"type":{"const":"agent"}},"required":["type"]},"then":{"required":["agent"]}},{"description":"toggle actions MUST declare their write endpoint via `writeUrl`.","if":{"properties":{"type":{"const":"toggle"}},"required":["type"]},"then":{"required":["writeUrl"]}},{"description":"run-node actions MUST declare which flow and which node within it (RN-3, or-flow-run-node) — a node's config and eligibility are properties of the flow it lives in, so neither can be inferred.","if":{"properties":{"type":{"const":"run-node"}},"required":["type"]},"then":{"required":["flowId","nodeId"]}}]},"headerAction":{"description":"A page header action (`headerActions[]`, a detail page's `quickActions[]` / `primaryActionByStage` entry, and their `children[]`): an action ($ref action), or with `href` a link ($ref headerLinkAction). Only header actions take `href`; row, bulk and navigation actions do not (2.135.0).","if":{"type":"object","required":["href"]},"then":{"$ref":"#/$defs/headerLinkAction"},"else":{"$ref":"#/$defs/actionFields","properties":{"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}},"headerLinkAction":{"type":"object","required":["id","label","href"],"additionalProperties":false,"description":"A header action that is a real link (`<a href>`), so it can be middle-clicked, opened in a new tab, copied and bookmarked. It only navigates: it runs no handler and emits no `header-action`, so it carries no `handler` and no other `type` (2.135.0).","properties":{"id":{"type":"string","description":"Unique action identifier within the page's actions array."},"label":{"type":"string","description":"i18n key resolved by the consuming app's t() function at render time."},"href":{"type":"string","pattern":"^(https?://|mailto:|tel:|/)","description":"The link. An absolute `http(s)`, `mailto:` or `tel:` URL and a protocol-relative `//host/...` URL are used as written. A path starting with a Nextcloud entry point (`/index.php/`, `/remote.php/`, `/public.php/`, `/ocs/`) is prefixed with the instance root unless it already starts with it; any other instance path (leading `/`, e.g. `/apps/integriq/connections`) goes through `generateUrl()`."},"target":{"type":"string","enum":["_blank","_self"],"default":"_self","description":"`_blank` opens the link in a new tab; `_self` opens it in place."},"type":{"const":"handler","description":"Only the default; a link has no other behaviour."},"icon":{"type":"string","description":"Optional icon: an MDI icon name or a CSS icon class, as on any action."},"variant":{"type":"string","description":"CnActionButtons button variant, as on any action."},"group":{"type":"string","minLength":1,"description":"Detail page header actions only: the overflow menu group, as on any action."},"adminOnly":{"type":"boolean","default":false,"description":"Detail page header actions only: show the link to instance admins alone, as on any action."},"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"An optional predicate gating whether the link renders, as on any action."},"children":{"type":"array","description":"Sub-actions in the link's own chevron dropdown, as on any action.","items":{"$ref":"#/$defs/headerAction"}},"childrenLabel":{"type":"string","description":"Name of the sub-action chevron, as on any action."}}},"fieldValidation":{"type":"object","additionalProperties":false,"description":"manifest-form-logic: per-field validation rules for `type: 'form'` pages, enforced client-side by CnFormPage's validateFieldValue() before dispatch. Per-type semantics: string/password — required = non-empty after trim, min/max = string LENGTH, pattern tested against the value; number — required = finite number, min/max = numeric VALUE, pattern not applicable; boolean — required = value must be true (consent checkboxes), min/max/pattern not applicable; enum — required = a value is selected, min/max/pattern not applicable; json — required = non-null, min/max/pattern not applicable. Type-inapplicable rules are rejected by validateManifestV2() post-schema (pattern only on string/password; min/max only on string/password/number). `message` (i18n-able) overrides the built-in default message for whichever rule fails.","properties":{"required":{"type":"boolean","description":"Whether the field must be filled. Default false. Per-type pass condition — see the fieldValidation description."},"min":{"type":"number","description":"Minimum bound. String length for string/password fields, numeric value for number fields. MUST be <= max when both are set (validateManifestV2 post-schema check)."},"max":{"type":"number","description":"Maximum bound. String length for string/password fields, numeric value for number fields."},"pattern":{"type":"string","description":"ECMAScript regex source tested against string/password values (implicit full-value test via new RegExp(pattern).test(value)). MUST compile — validateManifestV2() post-schema check runs `new RegExp(pattern)` in a try/catch."},"message":{"type":"string","description":"i18n-able override message shown for ANY failing rule on this field, in place of the built-in translated default. Run through the page's `translate` prop, like `field.label`."}}},"visibleWhen":{"type":"object","additionalProperties":false,"description":"#91 Wave 3: the shared visibility predicate (the Wave-1 banner shape). THREE modes: `endpoint` (GET a JSON endpoint, `field` dot-paths into the body), `source` (an OpenRegister { register, schema, filter? } query — `field` into the first result, or omit / use @total for the collection total), or LOCAL (neither endpoint nor source — `field` dot-paths into the caller's object context, e.g. a detail-page record). `op` defaults to eq. Evaluation is FAIL-SAFE (any error → hidden).","properties":{"appInstalled":{"type":"string","minLength":1,"description":"Nextcloud app id that must be installed and enabled for the guarded element to show. A PRECONDITION rather than a fourth mode: it is checked before anything else, and on its own it is the whole condition. Combined with endpoint/source/field it gates that mode too, so both must hold. It exists because the menu's `visibleIf` already spoke this word and `visibleWhen` did not, so the same manifest vocabulary meant two different things in two places. Use it on an action that writes into a sibling app, so an install without that app shows no button rather than a form that saves nowhere."},"endpoint":{"type":"string","description":"A same-origin URL returning JSON; `field` is a dot-path into the body."},"source":{"type":"object","additionalProperties":true,"description":"An OpenRegister object query { register, schema, filter? } (filter supports the shared @-token grammar). `field` dot-paths into the first result; omit it (or use @total) to compare the collection total.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"filter":{"type":"object","additionalProperties":true}}},"workspace":{"type":"string","minLength":1,"description":"WORKSPACE mode (screens-page-lenses, 2.87.0): the key of the page workspace context to compare, read at once with no request. A page control (a `workspace-filter` with `writes`) sets the key, and a dashboard widget carrying this condition appears and disappears as it changes. Honoured on a dashboard widget or KPI-row tile only, and as a top-level condition (not inside `all` / `any`)."},"field":{"type":"string","description":"Dot-path to the compared value — into the endpoint body, the source's first result, or (LOCAL mode) the caller's object context."},"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte","empty","notEmpty","in","anyOf","contains"],"default":"eq","description":"Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `in` holds when the value is one of the listed `value` array (2.87.0). `anyOf` holds when the value, or any entry of a list value, is one of the listed `value` array: a record whose `dataCategories` holds \"health\" among others matches `anyOf [\"health\", \"ethnicity\"]` (2.126.0). `contains` holds when a list value holds the given `value`, e.g. a case type's overviewCards switching a card on (2.128.0). `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."},"value":{"description":"The right-hand side of the comparison, resolved through the shared @-token grammar so `@me` names the signed-in reader rather than a literal only the server knows. Omitted for `empty` / `notEmpty`."},"all":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/visibleWhen"},"description":"Every listed condition must hold. Use it where a gate is more than one fact — a Claim button that is both 'not closed' and 'nobody holds it'. Each entry is a full visibleWhen and may compose again."},"any":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/visibleWhen"},"description":"At least one listed condition must hold. Ignored when `all` is also present."}}},"objectTableSource":{"type":"object","required":["register","schema"],"additionalProperties":false,"description":"Declarative self-fetch source for the object-table built-in widget (CnWidgetObjectTable). The widget resolves `filter` @-tokens (@me, @today, @today±Nd, @workspace.*, ?-optional suffix) with the shared resolveFilterTokens grammar just before fetching, and drives CnDataTable's existing self-fetch. `register` MAY carry an `@resolve:` sentinel — the widget passes it through unexpanded (resolution is the host loader's responsibility). Externally supplied `rows` always win over `source`.","properties":{"register":{"type":"string","description":"OpenRegister register slug (or an `@resolve:<key>` sentinel, passed through unexpanded by the widget)."},"schema":{"type":"string","description":"OpenRegister schema slug."},"filter":{"type":"object","additionalProperties":true,"description":"Filter map (`{ field: value | value[] | { op: value | value[] } }`). Values may use the shared @-token grammar: `@me` (current user via @nextcloud/auth), `@now`, `@today`, relative-date arithmetic `@today±Nd` at day granularity (e.g. `@today-30d`, `@today+7d`), `@monthStart`/`@quarterStart`/`@yearStart`, `@objectId`/`@object.<field>` (detail context), `@workspace.<key>`/`@config.<key>` with a trailing `?` for optional clauses that drop when unresolved. IN-lists: an ARRAY value (directly, or via `{ in: [...] }`) matches any of its entries — tokens resolve item-by-item and the widget serializes the list as repeated `field[]` params (the shape OpenRegister parses)."},"order":{"type":"object","additionalProperties":{"type":"string","enum":["asc","desc"]},"description":"Fetch ordering: field name to direction map (e.g. { \"dueDate\": \"asc\" })."},"limit":{"type":"integer","minimum":0,"description":"Row cap. 0 (or omitted) shows all fetched rows; a positive value caps the rendered rows and enables the View-all footer when more rows exist."},"extend":{"type":"array","items":{"type":"string"},"description":"OpenRegister `_extend[]` values forwarded on the fetch (e.g. [\"calculations\"]) so read-time virtual/derived fields are hydrated and available as columns. Each entry is passed through verbatim."},"pageControls":{"$ref":"#/$defs/pageControls"}}},"pageControls":{"type":"object","additionalProperties":false,"description":"screens-page-controls (2.88.0): which page controls a list widget (`record-list`, `object-table`) listens to, and what they mean for it. A page control is a `workspace-filter` select or switch that writes a workspace key. `sort` picks the list's sort from the key's value; `toggle` drops filter keys while the key is on. Without it the list ignores the page's controls.","properties":{"sort":{"type":"object","required":["key","map"],"additionalProperties":false,"description":"The workspace key a sort select writes, and what each of its values sorts by. A value with no entry leaves the list's own sort.","properties":{"key":{"type":"string","minLength":1},"map":{"type":"object","additionalProperties":{"type":"object","required":["field"],"additionalProperties":false,"properties":{"field":{"type":"string","minLength":1},"dir":{"type":"string","enum":["asc","desc"],"default":"asc"}}}}}},"toggle":{"type":"object","required":["key","removes"],"additionalProperties":false,"description":"The workspace key a switch writes. While it is on (true, 1, \"1\", \"true\") the filter keys in `removes` are dropped, so a list that hides finished work shows it.","properties":{"key":{"type":"string","minLength":1},"removes":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}}}}}},"endpointSource":{"type":"object","required":["url"],"additionalProperties":false,"description":"Wave 2 (#91): ONE coherent endpoint data binding shared by the stat / delta / object-table widgets (the chart uses the extended $defs/chartEndpointSource). Reads an arbitrary app REST endpoint through the library's useEndpointSource engine: params pass the SAME @-token grammar widget filters use (@me, @today±Nd, @monthStart, @workspace.<key>/@config.<key> with a trailing `?` for optional values that drop when unresolved — a REQUIRED token that stays unresolved blocks the fetch), requests dedupe + short-TTL cache per (method, url, resolved params), and the widget refetches on cn:page:refresh / a matching cn:widget:refresh. Exactly ONE of the widget's OpenRegister source | endpointSource may be configured — a cross-field rule enforced by validateManifestV2() as a post-schema check (clear error message), matching the stats-block precedent.","properties":{"url":{"type":"string","description":"Endpoint URL — app-relative (routed through generateUrl, e.g. '/apps/pipelinq/api/analytics/overview') or absolute (http/https, left untouched). May interpolate @page.<key> / @workspace.<key> / @config.<key> / @objectId / @object.<field> tokens inline; unresolved URL tokens collapse to an empty string."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method. GET (default) sends the resolved params as query parameters; POST sends them as the JSON body."},"params":{"type":"object","additionalProperties":true,"description":"Request parameters. Values pass the shared filter-token grammar (resolveFilterTokens): @me, @now, @today, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId/@object.<field> (detail context), @workspace.<key>/@config.<key> 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."}}},"chartEndpointSource":{"type":"object","required":["url"],"additionalProperties":false,"description":"The chart widget's endpoint binding: the $defs/endpointSource transport plus the labels/series response mapping. The mapping keys live INSIDE this block (not as sibling props) because the chart's flat `series` / `labels` props already carry the static data. When the plucked payload is an ARRAY of points (e.g. pipelinq /api/analytics/trends → responsePath 'series'), labelsPath / series[].path are PER-ITEM field paths (labelsPath 'date', series [{ name: 'Leads', path: 'value' }]); when it is an OBJECT they point at parallel arrays. Pie-family charts flatten the FIRST mapped series into the flat value array ApexCharts expects. Exactly ONE of dataSource | endpointSource per chart widget (post-schema check).","properties":{"url":{"type":"string","description":"Endpoint URL — app-relative (generateUrl) or absolute. Same token interpolation as $defs/endpointSource."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method. GET (default) sends params as query parameters; POST as the JSON body."},"params":{"type":"object","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."},"labelsPath":{"type":"string","description":"Labels/categories mapping: a per-item field path for an ARRAY payload ('date'), or the path of a parallel label array for an OBJECT payload ('labels')."},"rowsPath":{"type":"string","description":"Path of an array of rows, one series each (chartKind 'matrix': one row per case type). Each row's name is at rowLabelPath and its values at rowValuesPath; the columns are at labelsPath."},"rowLabelPath":{"type":"string","description":"Per-row path of the series name (default 'label')."},"rowValuesPath":{"type":"string","description":"Per-row path of the values array (default 'values')."},"series":{"type":"array","description":"Series mapping — one entry per rendered series.","items":{"type":"object","required":["path"],"additionalProperties":false,"properties":{"name":{"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')."}}}}}},"statsBlockEntry":{"type":"object","required":["register","schema"],"additionalProperties":false,"description":"One KPI entry inside a multi-entry stats-block widget (CnStatsBlockWidget `entries[]`). Carries the same source contract as a type:'stat' widget: register + schema + metric + token-resolved filter (@today / @me / @workspace.* and ?-optional clauses), plus per-entry presentation (title, variant, countLabel, format), an optional route deep link, and hideWhenZero. A money value names its currency with currencyField (the page object's field) or currency.","properties":{"title":{"type":"string","description":"KPI title rendered by the inner CnStatsBlock."},"register":{"type":"string","description":"OpenRegister register slug for this entry's count."},"schema":{"type":"string","description":"OpenRegister schema slug for this entry's count."},"metric":{"type":"string","description":"Aggregation metric for the REST /value aggregation (default 'count')."},"field":{"type":"string","description":"Field the metric aggregates over (metric-dependent; unused for count)."},"filter":{"type":"object","additionalProperties":true,"description":"Filter map with the shared @-token grammar (@today / @me / @workspace.<key>, trailing `?` for optional clauses)."},"route":{"type":"object","additionalProperties":true,"description":"Optional vue-router location for the KPI's deep link (renders the entry clickable)."},"variant":{"type":"string","enum":["default","primary","success","warning","error"],"description":"Color variant forwarded to the inner CnStatsBlock."},"countLabel":{"type":"string","description":"Label displayed next to the count."},"iconClass":{"type":"string","description":"Optional Nextcloud core icon class (icon-link, icon-mail, …) for the entry."},"hideWhenZero":{"type":"boolean","default":false,"description":"When true, the entry is omitted from the card when its resolved count is 0."},"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":"object","additionalProperties":false,"properties":{"style":{"type":"string","enum":["number","currency","percent","decimal","duration-hours"]},"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."},"prefix":{"type":"string"},"suffix":{"type":"string"}}}]},"currency":{"type":"string","description":"Shorthand for format.currency: an ISO-4217 code, or a token such as @config.currency. Implies format 'currency'."},"currencyField":{"type":"string","description":"Shorthand for format.currencyField: the field of the detail page's object that holds the currency, e.g. 'currency'. Implies format 'currency'. The currency is taken from the object, then currency, then the app's reporting currency (config currency), then EUR."}}},"calendarView":{"type":"object","additionalProperties":false,"required":["dateField"],"description":"The calendar view of this list: the current filtered rows on a month by a date field (config.calendar). Index pages only, and only offered when config.viewModes also lists `calendar`. Only the visible month is fetched. It reads and reschedules nothing: dragging an entry to another day is not offered.","properties":{"dateField":{"type":"string","minLength":1,"description":"The date property a row is placed on."},"endDateField":{"type":"string","minLength":1,"description":"Optional end date; a row then spans every day from dateField to endDateField."},"titleField":{"type":"string","minLength":1,"description":"The property shown on the entry; falls back to title, name or the row key."}}},"formSmartPaste":{"type":"object","required":["enabled","fields"],"additionalProperties":false,"description":"Fill from pasted text on a form page (form-smart-paste). `fields` lists the config.fields[] keys the fill may write; `handler` names a customComponents entry (a function, or { fill, available }) that proposes the values; `hint` is shown above the paste box. Never offered on a public form. Cross-field rules (each field key is declared, handler present when enabled, mode not public) are checked by validateManifestV2().","properties":{"enabled":{"type":"boolean"},"fields":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}},"hint":{"type":"string"},"handler":{"type":"string","minLength":1}}},"formFieldLiveValues":{"type":"object","description":"form-live-values: the live-value keys a form field may carry on top of its type, label and key. `assign` is a list of { when, value } rules: the first whose visibleWhen-style `when` holds sets the field when an answer it reads changes (value: a literal, @answer.<field> or a sentinel token); once the user edits the field by hand its rules stop. `default` may be a sentinel token (@me, @me.displayName, @me.email, @today, @now, @object.<field>) resolved once when the form opens; an initial value wins. `calculate.inputs` names the answers a host `calculate` function reads for this read-only field. Other field keys are unchanged.","properties":{"assign":{"type":"array","minItems":1,"items":{"type":"object","required":["value"],"additionalProperties":false,"properties":{"when":{"$ref":"#/$defs/visibleWhen"},"value":{}}}},"default":{},"calculate":{"type":"object","required":["inputs"],"additionalProperties":false,"properties":{"inputs":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}}}}}},"copySettings":{"type":"object","additionalProperties":false,"description":"index-copy-with-relations: what a copy on this index page may take along. `include` lists the link kinds: relationRows (the source's relation rows are re-created on the copy), incoming (the copy is added next to the source in every array-valued reference that points at it; a single-valued reference is never moved) and files (the attached files). The copy dialogs list the included kinds, ticked, and the copy is one request to OpenRegister's copy endpoint. Without `include` a copy carries the fields only.","properties":{"include":{"type":"array","uniqueItems":true,"items":{"enum":["relationRows","incoming","files"]}}}},"page":{"type":"object","required":["id","route","type","title"],"additionalProperties":false,"description":"A v2 page definition. CnPageRenderer dispatches by type and matches by $route.name === page.id. All page types support a uniform widgets[] array for declarative slot placement. Sentinels (@resolve:*) are NOT allowed in id, route, component, headerComponent, actionsComponent, or slots.* — those are router and registry invariants.","properties":{"requiresApp":{"description":"Soft dependency gating this WHOLE page. When the named Nextcloud app is not installed, the page renders the missing-dependency screen instead of its body. This is for the DEEP LINK: an entry gated by visibleIf.appInstalled is already hidden from the menu, so the remaining ways in are a bookmark, a shared URL, a redirect or an e2e spec, and those otherwise land on an empty page where 'not installed' looks the same as 'no data'. Gate a single WIDGET with visibleIf.appInstalled instead: a detail page may carry many widgets where only one needs the app, and that widget should hide while the page renders normally.","oneOf":[{"type":"string","minLength":1},{"type":"object","required":["id"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"description":"Human-readable app name shown on the screen; defaults to the id."}}}]},"id":{"type":"string","description":"Vue-router route name. MUST be unique across pages[]. Sentinels (@resolve:*) are NOT allowed."},"route":{"type":"string","description":"Path pattern (e.g. '/decisions', '/decisions/:id'). Sentinels (@resolve:*) are NOT allowed."},"type":{"type":"string","enum":["index","detail","dashboard","logs","settings","chat","files","form","map","roadmap","reports","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","description":"i18n translation key for the page title."},"permission":{"type":"string","description":"Optional permission identifier required to access this page."},"component":{"type":"string","description":"For type='custom': name resolved against the app-provided customComponents registry. Sentinels (@resolve:*) are NOT allowed."},"headerComponent":{"type":"string","description":"Optional registry name for a component injected into the page's #header slot. Sentinels (@resolve:*) are NOT allowed."},"actionsComponent":{"type":"string","description":"Optional registry name for a component injected into the page's #actions slot. Sentinels (@resolve:*) are NOT allowed."},"sidebarComponent":{"type":"string","minLength":1,"description":"Optional registry name for a component mounted as the default sidebar content for this page."},"slots":{"type":"object","description":"Generic slot-override map. Each key is a slot name; each value is a registry component name. Sentinels (@resolve:*) are NOT allowed in values.","additionalProperties":{"type":"string"}},"primaryAction":{"$ref":"#/$defs/primaryAction","description":"Active-page-scoped primary action rendered as an NcAppNavigationNew button above the menu list when the current route resolves to this page. Page-scoped declarations win over nav.primaryAction. Also the target of the detail page's skip link: a keyboard user tabs in, follows the link and lands on this action."},"splitView":{"$ref":"#/$defs/splitView","description":"Index pages only. Opens a row beside the list rather than instead of it, at the sibling route <route>/split/:id that buildManifestRoutes() emits. Refused on any other page type."},"board":{"$ref":"#/$defs/boardView","description":"Index pages only. The board view's configuration, handed to CnIndexPage as the `board` prop. The segment appears only when `viewModes` also lists `board`. Refused on any other page type."},"dateAxis":{"$ref":"#/$defs/dateAxisView","description":"Index pages only. The date-axis view's configuration, handed to CnIndexPage as the `dateAxis` prop. The segment appears only when `viewModes` also lists `dateAxis`. Refused on any other page type."},"savedViewTree":{"$ref":"#/$defs/savedViewTree","description":"Index pages only. Renders this page's saved views as a tree with labels, and declares the views that ship with the app. Refused on any other page type: a page with no saved-view dropdown has nothing to put in a tree."},"manualOrder":{"type":"boolean","default":false,"description":"Index pages only. Lets a person drag the rows into an order of their own, held per user and per list in their Nextcloud preferences and never written onto the records. Refused on any other page type."},"tabInAddress":{"type":"boolean","default":false,"description":"Detail pages only. Puts the active tab in the address as ?_tab=<id> 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,"description":"Lets a reference on this page show a summary of the record it points at, on focus and on hover, without leaving the page. Rendered by CnReferencePreview."},"widgets":{"type":"array","items":{"$ref":"#/$defs/widgetEntry"},"description":"Uniform widget placement array. Available on ALL page types in v2 (not just dashboard). Each entry specifies a widget key, slot, and grid coordinates. The cross-field constraint gridX + gridWidth <= 12 is enforced by validateManifestV2() post-schema check. Single-widget dashboards: a type:'dashboard' page with exactly one body widget covering the full 12×12 grid whose widgetKey resolves to a custom (non-library-built-in) registry component is REJECTED — see ADR-036 Decision 1 (single-widget dashboard anti-pattern). Use type:'custom' with component:'<widgetKey>' or split into N>1 widgets instead. Library built-in widget keys are exempt from the rule; the exempt set is LIBRARY_WIDGET_KEYS in src/utils/libraryWidgetKeys.js, which is held to set equality against the BUILT_IN_WIDGETS registry and the dashboard widget catalog by tests/utils/libraryWidgetKeys.spec.js. It is not restated here, because an out-of-date second copy of that list is the defect this replaced. Enforced by validateManifestV2() post-schema check."},"actions":{"type":"array","items":{"$ref":"#/$defs/action"},"description":"Typed page-level actions with a discriminator. Each action has a type field (handler | open-modal | open-page | navigate) that defaults to 'handler' when omitted."},"config":{"type":"object","description":"Type-specific configuration. Shape varies by page type — see v1 schema for per-type config shapes (index, detail, dashboard, logs, settings, chat, files, form, map, custom). Config values under pages[].config may use the closed sentinel vocabulary (see $defs/sentinelTokenAny) — an out-of-vocabulary @-token FAILS validation via the sentinelGuardedValue allOf guard. Carry-forward from v1: register, schema, columns, actions, sidebar, sidebarProps, fields, sections, tabs, content, layout, widgets (dashboard only), cardComponent, columnGroups, etc.","additionalProperties":true,"allOf":[{"$ref":"#/$defs/sentinelGuardedValue"},{"$comment":"screens-chrome-parity: a page declares ONE place to save. `saveMode` excludes a section's own `save` (flat or inside a tab) and `autosave`.","if":{"required":["saveMode"]},"then":{"not":{"required":["autosave"],"properties":{"autosave":{"const":true}}},"properties":{"sections":{"type":"array","items":{"type":"object","not":{"required":["save"]}}},"tabs":{"type":"array","items":{"type":"object","properties":{"sections":{"type":"array","items":{"type":"object","not":{"required":["save"]}}}}}}}}}],"properties":{"favourite":{"type":"boolean","description":"Deprecated and ignored: there is no star any more, a favourite is a follow with notifications off. Kept so existing manifests stay valid; use follow."},"follow":{"type":"boolean","description":"Detail pages: the one Follow control beside the title, with a notifications on/off switch while the user follows and the follower count for a user who may update the record. Omitted means automatic: it shows when the object carries @self.watching. false removes it, and the object read then no longer asks for @self.can."},"showFollowColumn":{"type":"boolean","description":"Index pages: add a first column with a follow toggle per row, bound to each row's @self.watching. Clicking it does not open the row. Default false."},"showFavouriteColumn":{"type":"boolean","description":"Deprecated alias of showFollowColumn: the star column became the follow column. Default false."},"personalLenses":{"type":"array","description":"Index pages: personal lenses added to the quick filters, in order. watching lists the records the user follows (labelled Following), recent the records the user opened last (it owns the order, so column sorting is off), unread the records changed since the user last looked. They combine with every other filter. favourite is a deprecated alias of watching; asking for both gives one tab. Default none.","items":{"enum":["favourite","recent","watching","unread"]},"uniqueItems":true},"markRead":{"type":"boolean","description":"Detail pages: send PUT .../read-state once after the object has rendered, when it carries @self.unread, so opening a record marks it read. Default true; false keeps the page from marking anything read."},"lifecycleActions":{"description":"Detail pages: status-gated lifecycle transition buttons (CnLifecycleActions). `inputs` gives the transition dialog hints for server-declared transitions: a map of action name to a list of { field, picker?, fields? }, merged onto the server's declared input with the same `field`. `picker` { filter, excludeSelf, labelField } narrows the records a reference input offers; `fields` names the sub-properties an object input asks for. A hint for a field the server does not declare is ignored with a console warning and never adds an input.","if":{"type":"object"},"then":{"properties":{"inputs":{"type":"object","additionalProperties":{"type":"array","items":{"type":"object","required":["field"],"properties":{"field":{"type":"string","minLength":1},"picker":{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object"},"excludeSelf":{"type":"boolean"},"labelField":{"type":"string"}}},"fields":{"type":"array","items":{"type":"string"}}}}}}}}},"headerFields":{"type":"array","description":"Detail pages: fields shown as chips under the title (number, status, assignee, deadline). Each entry is a property key, or an object { key, format, labelField, colorField, warnWhenPast }. `format` is text (default), mono, badge, user or date. A `$ref` value shows `labelField` of the referenced object and falls back to the raw id in mono. An empty value shows no chip; a page without the key shows no row.","items":{"oneOf":[{"type":"string","minLength":1},{"type":"object","required":["key"],"additionalProperties":false,"properties":{"key":{"type":"string","minLength":1},"format":{"enum":["text","mono","badge","user","date"]},"labelField":{"type":"string"},"colorField":{"type":"string"},"warnWhenPast":{"type":"boolean"}}}]}},"searchInFiles":{"type":"boolean","description":"Index pages: show an \"Also search inside files\" switch beside the search box. On, the list query carries `_content_search=true` with a search term, and a row found through an attached file shows \"Found in {file}\". Default false."},"entitySource":{"type":"string","enum":["flows","flowRuns","tasks","endpoint","search","users","organisations"],"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","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`.","items":{"type":"object","additionalProperties":false,"required":["when","class"],"properties":{"when":{"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."}}}},"sourceConfig":{"type":"object","additionalProperties":true,"description":"type='index' with `entitySource`: what the named source loads with. `endpoint`: `url` (an app path, required) and `rowsPath` (default `results`); its other keys go along as query parameters. `search`: `kinds` (`{ id, label, register, schema, titleField?, subtitleField?, statusField?, href?, limit? }` or `{ id, label, files: true }`), `term` and `termQuery` (default `q`). `users`: `defaultGroup`, `functionField`, `rolesField`. `flows` / `tasks`: `app`, `scope`. Maps to CnIndexPage `sourceConfig`.","properties":{"url":{"type":"string","minLength":1},"rowsPath":{"type":"string","minLength":1},"kinds":{"type":"array","items":{"type":"object","required":["id"],"additionalProperties":true,"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string"},"register":{"type":"string"},"schema":{"type":"string"},"files":{"type":"boolean"}}}},"term":{"type":"string"},"termQuery":{"type":"string","minLength":1},"defaultGroup":{"type":"string","minLength":1},"limit":{"type":"integer","minimum":1,"description":"A paging source: rows per page (20 by default); the pager moves through the pages."}}},"app":{"type":"string","description":"Scopes a named `entitySource` to one app (e.g. entitySource='flows' with app='dossiq' lists that app's flows)."},"register":{"type":"string","description":"OpenRegister register slug."},"schema":{"type":"string","description":"OpenRegister schema slug."},"contentField":{"type":"string","description":"type='wiki': property on the article object holding the markdown body. Defaults to 'body'. Maps to CnWikiPage `contentField` prop."},"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`."},"idParam":{"type":"string","description":"type='wiki': $route.params key holding the article id. Defaults to 'id'. Maps to CnWikiPage `idParam` prop."},"treeField":{"type":"string","description":"type='wiki': property on each sidebar tree node holding its child array. Defaults to 'children'. Maps to CnWikiPage `treeField` prop."},"sidebarTitleField":{"type":"string","description":"type='wiki': property on each sidebar tree node holding its label. Defaults to `titleField`. Maps to CnWikiPage `sidebarTitleField` prop."},"sidebarRegister":{"type":"string","description":"type='wiki': register slug for the sidebar tree. Defaults to `register`. Maps to CnWikiPage `sidebarRegister` prop."},"sidebarSchema":{"type":"string","description":"type='wiki': schema slug providing the sidebar tree. When set AND `tree` is non-empty the sidebar renders. Maps to CnWikiPage `sidebarSchema` prop."},"emptyText":{"type":"string","description":"type='wiki': empty-state heading shown when the article is null. Maps to CnWikiPage `emptyText` prop."},"emptyDescription":{"type":"string","description":"type='wiki': empty-state description shown when the article is null. Maps to CnWikiPage `emptyDescription` prop."},"emptyBodyText":{"type":"string","description":"type='wiki': empty-body heading shown when the article has no content. Maps to CnWikiPage `emptyBodyText` prop."},"emptyBodyDescription":{"type":"string","description":"type='wiki': empty-body description shown when the article has no content. Maps to CnWikiPage `emptyBodyDescription` prop."},"cardComponent":{"type":"string","description":"Registry name for a custom card component on index pages. Carry-forward from v1.3.0."},"sidebar":{"description":"Sidebar configuration. For index: object with enabled, show, columnGroups[], facets, etc. For detail: boolean or object with show, enabled, register, schema, tabs[]. Carry-forward from v1.3.0.","oneOf":[{"type":"boolean"},{"type":"object","additionalProperties":true,"properties":{"show":{"type":"boolean","description":"Whether to show the sidebar on this page."},"enabled":{"type":"boolean","description":"Whether the sidebar is enabled."},"columnGroups":{"type":"array","description":"Index sidebar column-visibility groups. Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}}}}]},"tabs":{"type":"array","description":"Settings tabs (for type='settings'). Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}},"sidebarTabs":{"type":"array","description":"Sidebar tabs declaration for type='detail' pages. Each entry is the human-authored source of truth for one tab — `id` + `label` (required), optional `icon`, `order`, `component`. Widgets bound to a tab carry `tabGroup: \"<tab-id>\"` on `slot:\"sidebar\"` entries; cross-reference is validated. The CLI manifest-migrate transform lifts `config.sidebarTabs[].widgets[]` into top-level `widgets[]` with `slot:\"sidebar\"` + `tabGroup`; tab entries declaring only a `component` are carried forward as residual sidebarTabs[]. See src/cli/transforms/liftSidebarTabWidgets.js.","items":{"type":"object","additionalProperties":true,"required":["id","label"],"properties":{"id":{"type":"string","minLength":1,"description":"Unique tab identifier within the page. Referenced from widgets[].tabGroup."},"label":{"type":"string","minLength":1,"description":"i18n key (or literal) for the tab label."},"icon":{"type":"string","description":"Optional icon name (CSS class or MDI identifier) shown next to the label."},"order":{"type":"number","description":"Optional sort key for tab ordering; lower values render first."},"component":{"type":"string","description":"Optional registry name for a full-tab custom component. When set, the tab renders that component directly instead of the per-tab widgets[] grouping. Resolved against the customComponents registry."},"_note":{"type":"string","description":"Optional human note (e.g. lib-gap reason for an undecomposable tab)."}}}},"sections":{"type":"array","description":"Settings sections (for type='settings'). Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}},"fields":{"type":"array","description":"Form fields (for type='form'). Carry-forward from v1.3.0. manifest-form-logic adds two OPTIONAL typed properties — `visibleWhen` (reuses `$defs/visibleWhen`; LOCAL mode resolves `field` as a dot-path into the live form data) and `validation` (`$defs/fieldValidation`) — while every other carry-forward field shape (`key`, `label`, `type`, `enum`, `widget`, `help`, etc.) stays untyped via `additionalProperties: true` on the item.","items":{"type":"object","additionalProperties":true,"properties":{"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"manifest-form-logic: optional conditional-visibility predicate. LOCAL mode (neither `endpoint` nor `source`) resolves `field` as a dot-path into the live form data — the first dot-segment MUST match a declared `config.fields[].key` (validateManifestV2 post-schema check). `endpoint` / `source` modes keep their existing fail-safe, evaluated-once-at-mount semantics."},"validation":{"$ref":"#/$defs/fieldValidation","description":"manifest-form-logic: optional per-field validation rules, enforced client-side by CnFormPage before dispatch. See `$defs/fieldValidation` for the per-type semantics table."}}}},"steps":{"type":"array","minItems":1,"description":"manifest-form-logic: for type='form' pages, ordered multi-step wizard groups. Each entry is `{ id, title, description?, fields[] }` where `fields[]` is an ordered array of KEY REFERENCES into `config.fields[].key` — steps do not embed field objects (config.fields[] stays the single source of truth). Absent `steps` = single-step form, byte-for-byte the pre-change rendering. When present, validateManifestV2() post-schema requires: step ids unique within the page, every fields[] entry references a declared field key, and every declared field key appears in EXACTLY ONE step (complete partition — an unassigned field would silently never render; a doubly-assigned field would double-render).","items":{"type":"object","required":["id","title","fields"],"additionalProperties":false,"description":"One wizard step. `fields[]` order is the rendering order for that step's fields.","properties":{"id":{"type":"string","minLength":1,"description":"Unique step identifier within the page's `steps[]` array."},"title":{"type":"string","minLength":1,"description":"i18n key (or literal) for the step's title, shown in the step indicator."},"description":{"type":"string","description":"Optional i18n key (or literal) for a short step description/subtitle."},"fields":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Ordered field KEYS (referencing `config.fields[].key`) rendered on this step."}}}},"fieldWidgets":{"type":"array","description":"Declarative widget-component slots for type='form' / type='detail' pages. Each entry mounts a lib `Cn*` SFC inline (e.g. CnMarkdownEditor as a markdown field, CnSchemaSelect as a relation picker). Distinct from top-level pages[].widgets[] which uses widgetKey + grid coordinates against the host customComponents registry. Carries serialisable props only. See $defs/fieldWidget and ConductionNL/nextcloud-vue#314.","items":{"$ref":"#/$defs/fieldWidget"}},"columns":{"type":"array","description":"Table columns (for type='index' and type='logs'). Items may be strings (legacy shorthand) or column objects ({ key, label, sortable?, width?, formatter?, formatterOptions?, widget?, widgetProps?, format?, aggregate?, colorMap? }). `colorMap` maps an enum column's raw values to a pill tone (default, primary, success, warning, error, info, purple, teal) and wins over the schema property's own `colorMap` / `x-color-map`. `formatter` resolves against cnFormatters (built-ins incl. currency / conditionalPhrase — configured via `formatterOptions`); `widget` resolves against cnCellWidgets (built-ins incl. badge / fkResolve / link / avatar / date).","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]}},"folderSidebar":{"type":"object","additionalProperties":true,"description":"type='index': the opt-in folder navigation pane (CnFolderSidebar) rendered left of the list. Selecting a folder filters the list by `filterField` (or `field`); \"All\" clears it. Only the per-scope list-layout keys are typed here; the pane's own keys (source, register, schema, idField, nameField, folders, allLabel, title, allowCreate, ...) stay free-form. Maps to CnIndexPage `folderSidebar`.","properties":{"scopeViews":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"The view modes (`table`, `list`, `cards`, `board`, ...) a scope's look applies in; in the others the page keeps its generic look while the scope still filters (DqWerkbord, 2.129.0)."},"heading":{"type":"boolean","description":"true heads the list with the selected folder's name, its parent folder and its count (always on a `groups` pane, 2.127.0)."},"countText":{"type":"string","minLength":1,"description":"The count in that heading, `{count}` filled in, through the app's translate; a groups pane defaults to \"{count} members\" (2.127.0)."},"pane":{"type":"boolean","default":true,"description":"false keeps the folder scope (a deep link such as `?caseType=<uuid>` still applies the scope's look and filter) without drawing the pane (dossiq's simple profile, 2.126.0)."},"folders":{"type":"array","description":"Explicit folder list for source:'custom', and the place a folder declares its own list layout. Every other folder key stays free-form.","items":{"$ref":"#/$defs/folderSidebarScope"}}}},"layout":{"type":"array","description":"Dashboard layout entries (for type='dashboard', legacy). Carry-forward from v1.","items":{"type":"object","additionalProperties":true}},"content":{"type":"array","description":"Declarative content items for dashboard (widget-ref items). Carry-forward from v1.","items":{"type":"object","additionalProperties":true}},"widgets":{"type":"array","description":"Legacy dashboard widgets array in config (for type='dashboard'). In v2, prefer top-level pages[].widgets[] with the uniform widgetEntry shape. This legacy config.widgets key remains for back-compat.","items":{"type":"object","additionalProperties":true}},"stageField":{"type":"string","minLength":1,"default":"status","description":"type='detail': dot-path to the field holding the record's stage. Read by `primaryActionByStage` and `nextStep`."},"primaryActionByStage":{"type":"object","description":"type='detail': the page's primary button per stage. Keys are values of `stageField` (matched exactly first, then ignoring case). A value is an action in the header action shape, or the id of a `headerActions` entry, which then leaves the overflow menu. A stage without an entry falls back to the page's `primaryAction`.","additionalProperties":{"oneOf":[{"type":"string","minLength":1},{"$ref":"#/$defs/headerAction"}]}},"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. `{ 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"},{"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."}}}]}},"actionsMenu":{"type":"object","additionalProperties":false,"description":"type='detail': what the header's overflow menu itself shows. Omitted, the menu keeps Refresh and the three help links.","properties":{"showRefresh":{"type":"boolean","description":"false removes Refresh from the menu."},"showHelpLinks":{"type":"boolean","description":"false removes Request a feature, Report a bug and Documentation, for an app that offers them in its own help menu."},"showMarkUnread":{"type":"boolean","description":"false removes the built-in Mark as unread entry."},"showOpenSidebar":{"type":"boolean","description":"false removes the built-in Open sidebar entry of the board look."},"label":{"type":"string","minLength":1,"description":"i18n key / text naming the menu, for example 'More'. Defaults to 'Actions'."}}},"nextStep":{"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. `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,"description":"Dot-path to the stage, when it differs from `stageField`."},"stagesFrom":{"type":"string","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)."},"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."},"stages":{"type":"object","description":"Keys are stage values.","additionalProperties":{"type":"object","additionalProperties":false,"required":["checklist"],"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."},"checklist":{"type":"array","description":"What this stage still needs. An empty list shows no card.","items":{"type":"object","additionalProperties":false,"required":["label"],"properties":{"label":{"type":"string","minLength":1,"description":"i18n key / text of the item."},"hint":{"type":"string","description":"i18n key / short note after the label, for example '2 of 5 done'."},"doneField":{"type":"string","minLength":1,"description":"Dot-path on the record. The item is done when it holds a value; a list counts when it is not empty."},"doneWhen":{"type":"object","description":"A local visibleWhen condition ({ field, op, value } or { all | any }). The item is done when it is true. Wins over `doneField`."},"done":{"type":"boolean","description":"A fixed state, for an item the record cannot answer."}}}}}}}}},"typePill":{"$ref":"#/$defs/headerPill","description":"type='detail': a pill above the title saying what kind of record this is."},"statusPill":{"$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}}}]}},"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.","items":{"oneOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":true,"required":["type"],"properties":{"id":{"type":"string"},"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)."},"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}}}}}]}},"tabsFrom":{"type":"string","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`."},"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.","items":{"$ref":"#/$defs/headerAction"}},"bulkActions":{"type":"array","description":"Bulk actions rendered in the contextual selection strip that appears while rows are selected. Same shape as the row-level `actions[]` ($ref action), but the handler receives the SELECTION rather than a row: `{ actionId, selectedIds, count }`. For `type: \"open-modal\"` the selection is merged into the modal's props as `selectedIds` and `count`, so a manifest-declared modal component can act on the selection without the page hand-writing anything. Reserved ids (`copy`, `delete`) are dropped at render time: the strip already ships both as built-ins. Before this existed the strip had only a `#selection-actions` slot, which only a hand-written host component could fill — so a page declared in a manifest could carry row actions and header actions but never a bulk one.","items":{"$ref":"#/$defs/action"}},"actions":{"type":"array","items":{"anyOf":[{"type":"object","required":["label"],"additionalProperties":true,"properties":{"label":{"type":"string","minLength":1},"id":{"type":"string","not":{"pattern":"^builtin:"}},"builtin":false}},{"type":"string","enum":["builtin:view","builtin:edit","builtin:copy","builtin:delete"]}]},"allOf":[{"contains":{"const":"builtin:view"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:edit"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:copy"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:delete"},"minContains":0,"maxContains":1}],"description":"Declarative actions on a page. An object entry is governed ONLY by \"an object carrying a non-empty label\", deliberately: this key is polymorphic by page type (index rows dispatch $defs/action; detail pages also carry type='lifecycle-transition' entries), so one closed grammar here would reject working manifests. A label is what every dialect needs — CnRowActions renders an entry from its label and icon, so an entry without one became a full-height, clickable, EMPTY row in the overflow menu. An object is always an app action whatever its id: an id, when present, must be a string that does not start with 'builtin:', and the object may not set a 'builtin' key. On a type='index' page an entry may also be one of the placeholders \"builtin:view\", \"builtin:edit\", \"builtin:copy\" or \"builtin:delete\", each at most once, which puts that built-in row action at its position in the menu. A placeholder only places a built-in: config.actionToggles / the show*Action keys still decide whether it renders. Enabled built-ins the array does not place are appended after everything else in the order View, Edit, Copy, Delete. Any other string (a bare \"edit\", an unknown \"builtin:archive\") is refused, and placeholders are refused on every page type other than index."},"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, 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"}},"rowIndicatorCap":{"type":"integer","minimum":0,"default":3,"description":"type='index': how many declared indicators render on the row itself before the rest move into the row menu. Maps to CnIndexPage `rowIndicatorCap`."},"quickEditFields":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': the fields a quick edit asks for, opened from a row without leaving the list. The page names them; a record cannot open a form on a field the page did not name. A field this caller may not write (see `writableField`) renders read-only rather than missing. Maps to CnIndexPage `quickEditFields`."},"writableField":{"type":"string","default":"@self.writableFields","description":"type='index': the dotted path where a record lists the fields this caller may write. A row carrying nothing there leaves every field the quick edit named editable; an empty list is a refusal and locks them all. Maps to CnIndexPage `writableField`."},"viewTabs":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': saved view ids this page renders as tabs instead of as entries in the views control. A view appears in one place or the other, never both. An id naming a view that no longer exists produces no tab, rather than a tab that opens nothing. Maps to CnIndexPage `viewTabs`."},"viewCounts":{"description":"type='index': which saved views show how many records they match. `true` counts every saved view (tabs and the entries in the views control), an array counts only the view ids it lists. Off by default, so no count request is made. Quick filters opt in per entry with `showCount: true`. Maps to CnIndexPage `viewCounts`.","oneOf":[{"type":"boolean"},{"type":"array","items":{"type":"string","minLength":1}}],"default":false},"priorityField":{"type":"string","description":"type='index': the field carrying a record's DERIVED priority. The list reads the value and sorts on it; it never computes one. A record with no priority, or one outside `priorityLevels`, sorts after every ranked record in BOTH directions and stays in the list. Maps to CnIndexPage `priorityField`."},"priorityLevels":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': the priority values in rank order, lowest first (e.g. ['low', 'medium', 'high']). Needed because a priority does not sort alphabetically into the order a person means. Maps to CnIndexPage `priorityLevels`."},"listShortcuts":{"type":"boolean","default":false,"description":"type='index': offer the list's keyboard shortcuts. Every shortcut is also listed in the command palette and on the help key, from one catalogue, because a shortcut nobody can find does not count. Maps to CnIndexPage `listShortcuts`."},"rowActionField":{"type":"string","description":"type='index': the dotted path on each row where the server lists the actions this caller may run on that record (default '@self.actions'). A row's menu is then the intersection of what `config.actions` declares and what the server allows: an action the server allows but the page does not declare stays out, and an action the page declares but the server refuses stays out with its reason kept. A row carrying nothing at this path leaves the declaration standing. Maps to CnIndexPage `rowActionField`."},"editOpensDetail":{"type":"boolean","description":"type='index': send the Edit row action to the record's detail page instead of opening the edit modal. OPT-IN — CnPageRenderer used to set this wherever a type='detail' page or config.rowRoute existed, and it no longer does: @edit-open is bound to the same navigation as a row click, so a routed Edit just repeats the row click (the split pane, or the detail page) and the form is no longer reachable from the list. Declare true per page when the modal cannot express the record. Maps to CnIndexPage `editOpensDetail`."},"showEditAction":{"type":"boolean","description":"type='detail': show the header Edit button that opens the record's schema form. Set automatically by CnPageRenderer for every schema-bound detail page; declare false to make a detail page read-only. Maps to CnDetailPage `showEditAction`."},"formSize":{"type":"string","enum":["small","normal","large"],"default":"normal","description":"NcDialog size for the page's built-in Add/Edit form dialog (type='index' and type='detail'). A manifest `open-form` header action has always been able to ask for `large`; the built-in dialog could not, so the same app could have a roomy create form on one surface and a cramped one on another. Maps to CnIndexPage / CnDetailPage `formSize`."},"formColumns":{"type":"integer","enum":[1,2],"default":1,"description":"How many columns the page's built-in Add/Edit form flows its fields into. Pair 2 with formSize 'large', or the two columns are merely two narrow ones. CnFormDialog collapses back to one column below 700px, and a textarea or JSON editor spans both columns regardless. Maps to CnIndexPage / CnDetailPage `formColumns`."},"excludeFields":{"type":"array","items":{"type":"string"},"description":"Property names kept OUT of the page's built-in Add/Edit form dialog. Maps to CnIndexPage / CnDetailPage `excludeFields`."},"includeFields":{"type":"array","items":{"type":"string"},"description":"Property names the page's built-in Add/Edit form dialog shows, in this order — a whitelist, so anything absent is hidden regardless of excludeFields. Maps to CnIndexPage / CnDetailPage `includeFields`."},"fieldOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"Per-field overrides merged over the schema-derived field descriptors in the page's built-in Add/Edit form dialog (e.g. `{ \"description\": { \"widget\": \"textarea\" } }`). Which widget a field renders as belongs to the surface, not to the schema. Maps to CnIndexPage / CnDetailPage `fieldOverrides`."},"createDefaults":{"type":"object","additionalProperties":true,"description":"type='index': seed values for the page's built-in create dialog, never applied on edit. Resolves `@me`/`@now`/`@today` at any depth; `@object.*`/`@workspace.*`/`@config.*` need a context the page does not carry and pass through unchanged. Maps to CnIndexPage `createDefaults`."},"createSuccessRoute":{"oneOf":[{"type":"string"},{"type":"object","properties":{"name":{"type":"string"},"paramField":{"type":"string"},"objectParam":{"type":"string"}},"required":["name"],"additionalProperties":false}],"description":"type='index': route opened after a successful create from the page's built-in Add dialog; the created object's id is merged into the params. Maps to CnIndexPage `createSuccessRoute`."},"createSuccessMessage":{"type":"string","description":"type='index': toast shown after a successful create from the page's built-in Add dialog. Maps to CnIndexPage `createSuccessMessage`."},"savedViewsScope":{"type":"string","description":"type='index': which pages share this page's saved views. By default a view is shared by every page over the same register and schema and by no other, because a view is filters over one schema's fields. Set this to share views across pages over different sources, or to keep two pages over one source apart. Written into the saved view's `query.scope`; views saved before scoping stay visible everywhere. Maps to CnIndexPage `savedViewsScope`."},"defaultSavedViews":{"type":"array","description":"type='index': views the app ships for this page. Listed read-only above each user's own saved views, under Default views, and never stored: a new app version updates them for everybody and no user can delete one. A user may apply a default or save a copy, which they then own. `@`-tokens in `filters` (`@me`, `@today`, `@today+7d`) resolve when the default is applied; a copy keeps them. Maps to CnIndexPage `defaultSavedViews`.","items":{"type":"object","additionalProperties":false,"required":["slug","name","filters"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The stable name of the default. Lowercase and dash only."},"name":{"type":"string","minLength":1,"description":"What the filter is called in the control. Translatable."},"filters":{"type":"object","description":"The filters, keyed by property, in the shape a quick filter's `filter` takes."},"search":{"type":"string","description":"A search term the default applies."},"sort":{"type":"array","description":"Sort keys, in priority order.","items":{"type":"object","additionalProperties":false,"required":["key"],"properties":{"key":{"type":"string"},"order":{"type":"string","enum":["asc","desc"]}}}},"label":{"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,"description":"pill-color-field: a registered icon name the view's My views menu entry shows instead of the filter icon (a case type's own icon, say)."},"overrides":{"type":"object","additionalProperties":false,"description":"Page config this view lays over the page while it is active; leaving the view restores the page. Each key is optional and, apart from `header`, the aliases `tabs` and `views`, `sortOption`, `slots` and `badge`, takes the shape of the page config key of the same name. A saved view carries the same object in `query.overrides`, so a copy of a default keeps it (saved-view-overrides, 2.132.0).","properties":{"title":{"type":"string","minLength":1,"description":"The page title. Translatable."},"showTitle":{"type":"boolean","description":"Same as config.showTitle."},"breadcrumb":{"description":"Same as config.breadcrumb.","anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}},{"type":"null"}]},"headerMeta":{"type":"string","description":"Same as config.headerMeta."},"notice":{"type":["object","null"],"description":"Same as config.notice."},"pageTabs":{"type":["array","null"],"description":"Tabs to sibling pages or views, `countFrom` included. Same as config.pageTabs."},"headerButtons":{"type":"array","description":"Same as config.headerButtons."},"addLabel":{"type":"string","description":"Same as config.addLabel."},"searchPlaceholder":{"type":"string","description":"Same as config.searchPlaceholder."},"statRow":{"type":"array","description":"Same as config.statRow."},"quickFilters":{"type":"array","description":"Same as config.quickFilters."},"quickFiltersFrom":{"type":["object","null"],"description":"Same as config.quickFiltersFrom."},"quickFilterPlacement":{"type":"string","description":"Same as config.quickFilterPlacement."},"quickFilterMaxVisible":{"type":"integer","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."},"afterList":{"type":"array","description":"Same as config.afterList."},"columns":{"type":"array","description":"Same as config.columns."},"cardLayout":{"type":["object","null"],"description":"Same as config.cardLayout."},"cardFields":{"type":"array","items":{"type":"string"},"description":"Same as config.cardFields."},"cardComponent":{"type":"string","description":"Same as config.cardComponent."},"listConfig":{"type":"object","description":"Same as config.listConfig."},"viewModes":{"type":"array","items":{"type":"string","minLength":1},"description":"The view modes the toggle offers. Same as config.viewModes."},"viewMode":{"type":"string","minLength":1,"description":"The view mode the view opens in. Same as config.viewMode."},"viewSwitch":{"type":"array","items":{"type":"string"},"description":"Same as config.viewSwitch."},"countText":{"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."},"emptyText":{"type":"string","description":"Same as config.emptyText."},"folderSidebar":{"type":["object","null"],"description":"`null` hides the folder sidebar. Same as config.folderSidebar."},"sidebar":{"type":["object","null"],"description":"Same as config.sidebar."},"defaultSort":{"type":"array","description":"Same as config.defaultSort."},"rowClickToView":{"type":"boolean","description":"Same as config.rowClickToView."},"showViewAction":{"type":"boolean","description":"Same as config.showViewAction."},"actions":{"type":"array","description":"Same as config.actions."},"includeFields":{"type":"array","items":{"type":"string"},"description":"Same as config.includeFields."},"fieldOverrides":{"type":"object","description":"Same as config.fieldOverrides."},"tabs":{"type":["array","null"],"description":"Alias of pageTabs."},"views":{"type":"array","items":{"type":"string","minLength":1},"description":"Alias of viewModes."},"header":{"type":"object","additionalProperties":false,"description":"Groups header keys; a key spelled out beside it wins.","properties":{"title":{"type":"string","minLength":1},"showTitle":{"type":"boolean"},"breadcrumb":{"anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}},{"type":"null"}]},"notice":{"type":["object","null"]},"headerMeta":{"type":"string"},"headerButtons":{"type":"array"}}},"lockedFilters":{"type":"object","description":"Base filters added to the page's `filter` while the view is active, which the reader cannot clear, as the old Queue and Assigned to me pages had (2.132.0)."},"sortOption":{"type":"string","minLength":1,"description":"The `value` of a `sortSelectOptions` entry to select, an endpoint-sourced one included."},"slots":{"type":"object","description":"Named page slots (`below-header`, `before-collection`, ...) mapped to a registry component or a list of them drawn in order, laid over the page's `slots`; `null` or `[]` empties that slot.","additionalProperties":{"anyOf":[{"type":"string","minLength":1},{"type":"array","items":{"type":"string","minLength":1}},{"type":"null"}]}},"badge":{"description":"A count on the view's menu entry, in a menu entry's terms. `true`, or `count: true`, counts the view's own list: the page's register, schema and base filter with the view's filters. A `count` object may name another `register`, `schema` or `filter`. `counterVariant: attention` draws the attention pill.","anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"count":{"anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object"}}}]},"counterVariant":{"type":"string","enum":["default","attention"]}}}]}}}}}},"splitCloseButton":{"type":"boolean","default":true,"description":"type='index': whether the split pane draws its own close link. Set false when a `#split-pane` slot draws one from the slot's `close`. Maps to CnIndexPage `splitCloseButton`."},"splitCloseLabel":{"type":"string","description":"type='index': accessible name and tooltip for the split pane's close button. Defaults to \"Close\". Maps to CnIndexPage `splitCloseLabel`."},"look":{"type":"string","enum":["nextcloud","board"],"description":"screens-chrome-parity: draw this page in the `nextcloud` or the `board` look, over the manifest's root `look`. The nearest value wins: the page root carries `cn-look-board` or `cn-look-nextcloud`, and `cnLook` is re-provided to the page's descendants."},"saveMode":{"type":"string","enum":["section","page"],"description":"type='settings' (screens-chrome-parity): where the page saves. `section` ends each section that has fields in its own Save button and draws no page save bar. `page` draws one Save as the last header button and a Changes card listing the changed fields; no section draws a save button. Absent, the page keeps the save bar under the last section. Cannot be combined with `autosave` or with a section that declares its own `save`. Maps to CnSettingsPage `saveMode`."},"autosave":{"type":"boolean","default":false,"description":"type='settings' (screens-chrome-parity): the page saves on change. It draws no save button and its description ends with \"Changes are saved automatically.\" Cannot be combined with `saveMode`. Maps to CnSettingsPage `autosave`."},"showHeader":{"type":"boolean","default":true,"description":"type='dashboard': `false` drops the header row (title, description, header actions, edit toggle) for a page that opens with its own heading, such as a greeting widget; the title stays as a visually hidden heading (zuiddrecht-pixel-gaps). Maps to CnDashboardPage `showHeader`."},"views":{"type":"array","minItems":1,"description":"type='dashboard' | type='detail': views of the page, each its own widget grid behind one segmented control (\"My work | My team\") (view-switch-containers). The chosen view renders in a region below the page's own grid, drawn with the same grid and widgets; the page's own `widgets` and `layout` stay above it. The switch sits in the page header, or in a greeting header widget whose `content.views.options[]` carry `view` instead of `route`. The chosen view is in the address (`?view=<id>`) and in the user's preferences; the address wins. A view with nothing to draw shows `emptyText`, else a default sentence. On a detail page, views replace the automatic Data and Related body. Maps to CnDashboardPage / CnDetailPage `views`.","items":{"type":"object","required":["id","label"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"pattern":"^[A-Za-z0-9_-]+$","description":"The view id, written to the address as `?view=<id>`."},"label":{"type":"string","minLength":1,"description":"The option label and the region's accessible name (through the app's translations)."},"title":{"type":"string","minLength":1,"description":"type='dashboard': the page title while this view is open, e.g. \"My team\" (through the app's translations; view-title, 2.119.0). Without it the page keeps its own title."},"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}},"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}}}}}},"defaultView":{"type":"string","minLength":1,"description":"type='dashboard' | type='detail': the id of the view chosen when neither the address nor the user's stored choice names one; without it the first view (view-switch-containers). Maps to `defaultView`."},"viewsLabel":{"type":"string","minLength":1,"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":{"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:<name>}` in headerMeta (\"{total} contracten bij {count:suppliers} leveranciers\"); while a count loads or when it fails, the note is left out.","additionalProperties":{"type":"object","required":["register","schema"],"additionalProperties":false,"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object"}}}},"tabsLabel":{"type":"string","minLength":1,"description":"type='detail', board look: the accessible name of the tab list, for example \"Case parts\". Defaults to \"<type> parts\" from the page title (screens-detail-page-parity). Maps to CnDetailPage `tabsLabel`."},"identifierField":{"type":"string","minLength":1,"description":"type='detail', board look: the record field (a dotted path, for example `identifier`) that names the record in the last line of the side column's History card (screens-detail-page-parity). Maps to CnDetailPage `identifierField`."},"showWidgetActions":{"type":"boolean","default":true,"description":"type='dashboard' and type='detail': `false` drops the overflow Actions menu from every widget that does not set `showActions` itself, for a dashboard whose widget headers carry a header link and nothing else (zuiddrecht-pixel-gaps-2), or a record page whose data cards carry none (zuiddrecht-pixel-gaps-3; a card with an Add action keeps it). When the key is not set a detail page keeps the menu, except under the board look, where side cards carry none (screens-detail-page-parity). Maps to CnDashboardPage / CnDetailPage `showWidgetActions`."},"pageFilters":{"type":"array","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.<key>` / `@workspace.<key>` token (an endpoint widget also in its `responsePath`). Maps to the CnDashboardPage `pageFilters` prop.","items":{"type":"object","required":["key"],"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.<key>` / `@workspace.<key>` token name)."},"label":{"type":"string","description":"Control label (i18n key or plain text)."},"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":{"type":"object","additionalProperties":false,"description":"Read the options instead of listing them: from an app endpoint (`url`, `params`, `responsePath`) or from OpenRegister objects (`register`, `schema`, `filter`). `valueField` and `labelField` pick each option's value and label (default `id` and `title` or `name`); tokens resolve (2.130.0).","anyOf":[{"required":["url"]},{"required":["register","schema"]}],"properties":{"url":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"responsePath":{"type":"string","minLength":1},"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1}}},"placement":{"type":"string","enum":["row","description"],"default":"row","description":"Where the filter sits: in the header's filter row, or (a segmented group) on the description line after the period group (DqProcesanalyse)."},"default":{"type":["string","number"],"description":"Initial value seeded into the context on mount; the first option's value when omitted."},"options":{"type":"array","items":{"type":"object","required":["value","label"],"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)."},"months":{"type":"integer","minimum":1,"description":"A rolling window of this many months ending with the current one. A dashboard description's `{filter.<key>}` 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)."}}}}}}},"kpiRow":{"type":"array","items":{"type":"string"},"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."},"greeting":{"oneOf":[{"type":"boolean"},{"type":"string","enum":["first","full","date"]},{"type":"object","required":["label"],"additionalProperties":false,"properties":{"label":{"type":"string","minLength":1,"description":"i18n key drawn where the greeting would be."}}}],"default":false,"description":"type='dashboard': greet the reader in the header subtitle. `true` or \"first\" writes \"Good afternoon, Pieter · Monday 5 October 2026\", \"full\" uses the whole display name; the page `description` follows after another middle dot (screens-dashboard-greeting-header). Since 2.111.0 \"date\" leads with the date alone (\"Tuesday 7 October 2026 · ...\") and `{ \"label\": \"Customer contact\" }` puts the translated label where the greeting would be (dashboard-greeting-label). Maps to CnDashboardPage `greeting`."},"viewLinks":{"type":"array","default":[],"description":"type='dashboard': links drawn as small pills on the switch row under the header, right of the view switch (screens-dashboard-greeting-header). `route` is a route name or a `{ name, params, query }` location, `href` a plain URL; `label` is an i18n key. An entry without a label or a target is skipped. Maps to CnDashboardPage `viewLinks`.","items":{"type":"object","required":["label"],"additionalProperties":false,"properties":{"label":{"type":"string","minLength":1},"icon":{"type":"string","minLength":1},"route":{"oneOf":[{"type":"string","minLength":1},{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"params":{"type":"object"},"query":{"type":"object"}}}]},"href":{"type":"string","minLength":1}}}},"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`. 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`."},"showTitle":{"type":"boolean","default":false,"description":"type='index': show the page header (icon, title, description) inline. Default false: the title renders visually hidden and the sidebar names the page. Maps to CnIndexPage `showTitle`."},"countSubtitle":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["singular","plural"],"properties":{"singular":{"type":"string","minLength":1,"description":"i18n key of the line for a total of one, e.g. \"{total} invoice\"."},"plural":{"type":"string","minLength":1,"description":"i18n key of the line for any other total, e.g. \"{total} invoices\"."}}}],"description":"type='index': an i18n key for the description line with `{total}` replaced by the collection's total, e.g. \"{total} open cases\" (zuiddrecht-pixel-gaps). Shown in place of `description` once a total is known. `{ singular, plural }` picks the form by the total through the app's plural translation (`translatePlural`), so \"1 factuur\" and \"12 facturen\" each read right (index-count-plural). Maps to CnIndexPage `countSubtitle`."},"showTitleIcon":{"type":"boolean","default":true,"description":"type='index': `false` drops the icon before the page title, for a title that stands alone as the board draws it (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showTitleIcon`."},"showCount":{"type":"boolean","default":true,"description":"type='index': `false` drops the actions bar's \"Showing 20 of 258\" line, for a page whose title line already gives the total through `countSubtitle` (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showCount`."},"countText":{"type":"string","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`."},"footerNote":{"type":"string","minLength":1,"description":"type='index', board look: free text after the footer's count, e.g. \"click a column header to sort\" (screens-index-list-parity). Maps to CnIndexPage `footerNote`."},"queryPresets":{"type":"array","minItems":1,"description":"type='index': per-query overlays of this page (screens-index-query-presets). A menu entry that deep-links here with a query (`menu[].query`, e.g. { caseType: '<uuid>' }, the shape hydra gate-68 asks for instead of a second page) gets its own lenses, columns and copy: the first preset whose every `match` pair is in the route query replaces those keys of this config. Only the listed keys can be replaced; register, schema and permissions stay the page's. Switching preset remounts the list.","items":{"type":"object","required":["match"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"description":"A name for the preset, for people reading the manifest."},"match":{"type":"object","minProperties":1,"additionalProperties":{"type":["string","number","boolean"]},"description":"Route query pairs that must ALL be present, e.g. { \"caseType\": \"<uuid>\" }. A repeated parameter matches when it holds the value."},"title":{"type":"string","minLength":1,"description":"The page title while the preset applies."},"quickFilters":{"type":"array","description":"The lenses while the preset applies; same shape as config.quickFilters."},"columns":{"type":"array","description":"The columns while the preset applies; same shape as config.columns."},"cardFields":{"type":"array","items":{"type":"string"},"description":"The card facts while the preset applies."},"countText":{"type":"string","minLength":1,"description":"The count line while the preset applies."},"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","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)."},"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)."},"afterList":{"type":"array","description":"The cards after the list while the preset applies; same shape as config.afterList (index-scope-presentation).","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"}}}}}}},"viewSwitch":{"type":"array","minItems":1,"uniqueItems":true,"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 \"<mode>: 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","additionalProperties":false,"required":["field"],"description":"type='index': a second filter strip under the toolbar, on one field (board PqWachtrij: the kind in the quick filters, the channel in this strip). \"All\" and one pill per value (`options`, else the field's schema enum); picking one filters the list on that value, All clears it. With `showCount` each pill counts its rows within the active quick filter (screens-filter-strip, 2.92.0). Maps to CnIndexPage `filterStrip`.","properties":{"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}}}},"showCount":{"type":"boolean","default":false}}},"viewSwitchLinks":{"type":"object","additionalProperties":false,"minProperties":1,"description":"type='index', board look: view switch segments that open another PAGE instead of switching in place, by mode: `{ \"board\": { \"route\": \"CaseBoard\" } }` for a board or a map that is a page of its own. The segment sits in its place in the switch (table, cards, board, map) as a link: `route` with optional `params` and `query`, or `href`. A mode the page opens in place stays a button, and a route the router cannot resolve is left out. Ignored without the board look (screens-view-switch-links, 2.91.0). Maps to CnIndexPage `viewSwitchLinks`.","properties":{"table":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"cards":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"board":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"map":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}}}},"searchPlaceholder":{"type":"string","minLength":1,"description":"type='index': the placeholder (and accessible name) of the list's search field, e.g. \"Search by case, number or requester\". Run through the app's translate, so an English key with a Dutch catalogue entry reads Dutch. Absent: the library's \"Search…\" (screens-index-toolbar-parity). Maps to CnIndexPage `searchPlaceholder`."},"bulkHint":{"type":"string","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}},"cardLayout":{"type":"object","additionalProperties":false,"description":"type='index', board look: the record layout of each card in the cards view (board-record-card-layout): a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date. Each part names a field (a dot path) and is left out while it is empty; a pill maps values to a badge variant (`colorMap`, ignoring case) and to shown text (`labels`). Maps to CnIndexPage `cardLayout`.","properties":{"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"}}}]}},"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"]}}}]}}}},"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"}}}},"statRow":{"type":"array","default":[],"description":"type='index': KPI tiles above the list, between the header and the toolbar, in an auto-fit row (each at least 200px). Each entry is a widget definition, usually `type: \"stat\"`; the type resolves through the app's widget registry, then the dashboard catalog (screens-index-stat-row). Maps to CnIndexPage `statRow`.","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","minLength":1},"title":{"type":"string"},"content":{"type":"object"},"headerLink":{"type":"object"}}}},"sidePanel":{"type":"array","default":[],"description":"type='index': titled cards in a 300px column right of the toolbar and the list (above them below 1024px). Each entry is a widget definition with an optional `title` (i18n key) and `headerLink`; the type resolves as for `statRow` (screens-index-stat-row). Maps to CnIndexPage `sidePanel`.","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","minLength":1},"title":{"type":"string"},"content":{"type":"object"},"headerLink":{"type":"object"}}}},"headerButtons":{"type":"array","description":"type='index': buttons beside the page title, as the board's \"Export\" and \"New case\" (zuiddrecht-pixel-gaps-3). `action` is `add` (the Add flow; the label defaults to the Add label), `export` (the export leaf in `format`, else the export dialog; under the board look it reads \"Download\" by default), `actions-menu` (the page's header actions as one labelled menu button, absent when the page has none), `import`, `refresh`, or the id of a `headerActions` entry. Under the board look the buttons render in a fixed order whatever the manifest declares: export, actions-menu, other secondary buttons, the buildiq square, then the primary button. Shown only with `showTitle`; when they show, the actions bar drops its Views and Actions menus, and the Add button and Export menu when a button takes their action. Maps to CnIndexPage `headerButtons`. type='reports' (screens-reports-header-buttons, 2.96.0): the same buttons beside the reports page title, as board DcRapportages draws \"Downloaden\". There `label` is required and `action` is the id of a `headerActions` entry (same shape as on an index page), which says what the click does; every click also emits `header-action`. Under the board look the buildiq square sits between the secondary buttons and the primary one. Maps to CnReportsPage `headerButtons`.","items":{"type":"object","additionalProperties":false,"required":["action"],"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1,"description":"i18n key; optional for `add`."},"action":{"type":"string","minLength":1},"variant":{"type":"string","enum":["primary","secondary"],"default":"secondary"},"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)."},"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\": \"<i18n key>\" }` 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)."}}}},"search":{"type":"boolean","default":true,"description":"type='index': `false` draws no inline search in the actions bar, also in the board look, which otherwise draws one on every index page. Wins over `inlineSearch` (2.127.0)."},"searchRefusalHint":{"type":"string","description":"type='index': what to do after a refused search, through the app's translate; the default explains AND, OR, NOT and quotes (2.133.0)."},"searchPlacement":{"type":"string","enum":["","toolbar"],"default":"","description":"type='index', board look: where the search field sits. `toolbar` puts it on row 1 before the Filter button and the view switch instead of opening row 2 (DqTeamwachtrij, 2.127.0)."},"sortSelectOptions":{"type":"array","description":"type='index': the orders the index sidebar's Sort section offers in the list, card and map views, `{ value, label, order?, default?, source? }`. An option with a `source` takes its rows from an app endpoint, in that endpoint's order, for an order the server cannot sort by; the page's search, sidebar filters and pager run over those rows in the browser, and it is chosen by its value. `default: true` opens the page on it unless the address carries an order; a column header click in the table view leaves it. When the endpoint fails the list falls back to the option's `order` and shows `failedNote` (2.131.0).","items":{"type":"object","required":["value","label"],"properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"order":{"anyOf":[{"type":"string","minLength":1},{"type":"object"},{"type":"array"}]},"default":{"type":"boolean"},"source":{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","minLength":1,"description":"An app path, resolved with generateUrl and fetched with the session."},"params":{"type":"object","description":"Query parameters sent with the request."},"rowsPath":{"type":"string","minLength":1,"description":"Where the entries sit in the answer; `results` by default, a bare array as is."},"rowPath":{"type":"string","minLength":1,"description":"Where each entry carries its row; entries without one are left out."},"carry":{"type":"array","items":{"type":"string","minLength":1},"description":"Entry fields copied onto each row, such as a tier a card's head pill reads."},"idPath":{"type":"string","minLength":1,"description":"The row's identity for `annotate`; `id` by default."},"annotate":{"type":"boolean","description":"Copy the carried fields onto the rows of every other order too, matched on idPath."},"searchFields":{"type":"array","items":{"type":"string","minLength":1},"description":"The fields the search box matches; every top-level text by default."},"failedNote":{"type":"string","minLength":1,"description":"Shown above the list when the endpoint failed and the option's order stands in, through the app's translate."}}}}}},"defaultOrder":{"description":"type='index', self-fetch: the order the page opens on when neither the address nor sortKeys gives one: `\"-createdAt\"`, `\"priority:desc,title\"`, an `_order` map or a `[{ key, order }]` list (2.127.0).","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":{"type":"string","enum":["asc","desc","ASC","DESC"]}},{"type":"array","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","minLength":1},"order":{"type":"string","enum":["asc","desc"]}}}}]},"listConfig":{"type":"object","additionalProperties":true,"description":"type='index': the field mapping of the list view's rows (CnObjectRow): titleField, subtitleField, imageField, iconField, iconName, badgeField and the badge keys stay free-form. 2.127.0 types the row cells and the meta template (DqTeamwachtrij).","properties":{"subtitleTemplate":{"type":"string","minLength":1,"description":"The meta line: `{field}` tokens filled from the row; empty parts and their separators drop."},"leading":{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}},"description":"One cell before the title, drawn as a table column draws it (`{ key: \"createdAt\", widget: \"age\" }`)."},"trailing":{"description":"A cell, or a list of cells, after the badges, each drawn as a table column draws it.","anyOf":[{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}}},{"type":"array","items":{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}}}}]}}},"rowTitle":{"type":"string","enum":["link","plain"],"default":"link","description":"type='index' (screens-table-rows-parity): how the board look draws the table's title column. `link` (the default) is the 15px weight 600 underlined title of the DqZaken board; `plain` is the 15px bold title without an underline over a 13px muted secondary line, as the PqTickets and OcPublicaties boards draw it. The row stays clickable either way. The Nextcloud look ignores it. Maps to CnIndexPage `rowTitle`."},"showTypeEyebrow":{"type":"boolean","default":true,"description":"type='detail': `false` drops the type eyebrow (the type label above the record name) once the record resolves, for a header that says the type in a pill instead (zuiddrecht-pixel-gaps). Maps to CnDetailPage `showTypeEyebrow`."},"breadcrumb":{"description":"A breadcrumb line. type='detail' (zuiddrecht-pixel-gaps): one crumb naming the list the record belongs to (`route` + optional `params`, or `href`); the record's display name follows as the current crumb. Maps to CnDetailPage `breadcrumb`. type='index' | 'dashboard' | 'settings' (page-header-breadcrumb): one crumb or a list, drawn under the title in the same row style; the page title follows as the current crumb. Maps to the page's `breadcrumb`.","anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}}]},"headerCard":{"type":"boolean","default":false,"description":"type='detail': draw the header (pills, title, actions) as a bordered card on the surface colour, as the board's case header (zuiddrecht-pixel-gaps-2). Maps to CnDetailPage `headerCard`."},"headerWidget":{"type":"string","minLength":1,"description":"type='detail': the id of a widget in `widgets` to render inside the header on its own row under the title, without a card of its own, e.g. the stages bars (zuiddrecht-pixel-gaps-2). The widget leaves the body grid. Maps to CnDetailPage `headerWidget`."},"visibilityContext":{"type":"object","description":"type='detail': records the page reads beside the loaded one, so a widget's or a tab's `visibleWhen` can ask about them (detail-widgets-visible-when). `{ <name>: { field, register, schema } }`: `field` is the property of the loaded record holding the other record's id, and a condition reads that record as `@context.<name>.<path>`, e.g. `{ \"field\": \"@context.caseType.category\", \"op\": \"in\", \"value\": [\"Sociaal domein\"] }`. A record that cannot be read is absent, so its condition fails safe. Maps to CnDetailPage `visibilityContext`.","additionalProperties":{"type":"object","required":["field","register","schema"],"additionalProperties":false,"properties":{"field":{"type":"string","minLength":1,"description":"Dot-path on the loaded record to the id of the record to read."},"register":{"type":"string","minLength":1,"description":"Register slug or id of the record to read."},"schema":{"type":"string","minLength":1,"description":"Schema slug or id of the record to read."}}}},"accessPurpose":{"type":"object","required":["when","url"],"additionalProperties":false,"description":"type='detail': ask why the reader opens the record before the page shows it, and record the answer (detail-access-purpose). `when` is a condition in the visibleWhen grammar on the record and its visibilityContext records; `options` the purposes offered (\"Other\" with a written purpose is always added); `url` the app endpoint that records the purpose (POST { purpose, note }, {objectId} filled in). Nothing of the record shows until the endpoint accepts; the same record is not asked again in the same browser session. Maps to CnDetailPage `accessPurpose`.","properties":{"when":{"type":"object","description":"The condition under which the page asks, e.g. { \"field\": \"@context.caseType.specialDataCategories\", \"op\": \"notEmpty\" }."},"options":{"type":"array","items":{"type":"object","required":["value","label"],"additionalProperties":false,"properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1,"description":"i18n key."}}}},"url":{"type":"string","minLength":1,"description":"App endpoint that records the purpose; {objectId} is filled in."},"title":{"type":"string","description":"i18n key of the dialog's title."},"description":{"type":"string","description":"i18n key of the sentence under the title."}}},"mapConfig":{"type":"object","additionalProperties":true,"description":"type='index': how rows are plotted when the Map view mode is available. Non-empty (and not excluded by an explicit viewModes) adds a Map segment to the view toggle. Maps to CnIndexPage `mapConfig`.","properties":{"latField":{"type":"string","description":"Object (or `@self`) property path holding the marker's latitude. Dotted paths are supported, e.g. `@self.geo.lat`."},"lngField":{"type":"string","description":"Object (or `@self`) property path holding the marker's longitude."},"geoField":{"type":"string","description":"Alternative single property holding a GeoJSON Point geometry (`{ type: 'Point', coordinates: [lng, lat] }`). Takes precedence over latField/lngField when present and resolvable."},"popupField":{"type":"string","description":"Object property rendered in the marker popup."},"center":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"Fallback centre `[lat, lng]` when the filtered set has no plottable rows."},"clustering":{"type":"boolean","default":true,"description":"Whether coincident markers are grouped into a cluster that spiderfies on click. ON by default here — the opposite of CnMapWidget's own default — because an index map plots whatever the filter returns, and several rows at one address is the normal case: unclustered they land on identical pixels, the topmost takes every click and the rest are unreachable with nothing on screen saying they exist. Non-point geometry skips the cluster group, so areas are unaffected. Set false for the old behaviour."},"markerColor":{"type":"object","required":["field"],"additionalProperties":false,"description":"Colour each marker by a field of its row. Without `source` the value is looked up in `colorMap`; with `source` the field is a reference and the referenced row's `colorField` is the colour, or the key `colorMap` maps to one. A value nothing maps to gets `default`, else the map's own colour.","properties":{"field":{"type":"string","description":"The row field (dotted path, also under `@self`)."},"colorMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Value (or source colour key) to CSS colour; `var(--x, #fallback)` is resolved for the SVG marker."},"source":{"type":"object","required":["colorField"],"properties":{"register":{"type":"string"},"schema":{"type":"string"},"colorField":{"type":"string"},"filter":{"type":"object"}}},"default":{"type":"string"}}},"plotAll":{"oneOf":[{"type":"boolean"},{"type":"integer","minimum":1}],"description":"Plot every row that matches the filters instead of the current page: `true` for up to 500, or a number. When more rows match, a note under the map says the first N are shown."},"count":{"oneOf":[{"type":"boolean"},{"type":"string"}],"description":"A count line under the map: `true` for \"Showing {located} of {total} located\", or a template with `{located}` and `{total}` through the app's translate."}}},"quickFilterMaxVisible":{"type":"integer","minimum":0,"default":0,"description":"type='index', chips mode: how many quickFilters render as pills before the rest move behind one more chip — a `⋯` pill that opens a panel of the hidden lenses, and that wears the label and the active fill of whichever one is on. 0 (the default) renders every lens, which wraps the actions bar onto a second line once a page declares more than a handful. The visible set is the FIRST n entries of quickFilters, so reorder that array to change which lenses stay out. Maps to CnIndexPage `quickFilterMaxVisible`."},"actionToggles":{"type":"object","description":"Built-in action toggles for type='index' pages. Each key is sugar for the matching CnIndexPage prop — manifest authors can hide built-in mass actions / row actions / the Add button without writing a wrapper. Mirrors the CnIndexPage prop names. Explicit config.<key> wins over config.actionToggles.<key> (matches the readOnly precedence rule). Distinct from config.actions[] which is the typed row-actions array.","additionalProperties":true,"properties":{"showAdd":{"type":"boolean","description":"Whether to show the Add button in the actions bar. Maps to CnIndexPage `showAdd`."},"showFormDialog":{"type":"boolean","description":"Whether the built-in create / edit form dialog is enabled. Maps to CnIndexPage `showFormDialog`."},"showViewAction":{"type":"boolean","description":"Whether to add a View action to row actions. Maps to CnIndexPage `showViewAction`."},"showEditAction":{"type":"boolean","description":"Whether to add an Edit action to row actions. Maps to CnIndexPage `showEditAction`."},"showCopyAction":{"type":"boolean","description":"Whether to add a Copy action to row actions. Maps to CnIndexPage `showCopyAction`."},"showDeleteAction":{"type":"boolean","description":"Whether to add a Delete action to row actions. Maps to CnIndexPage `showDeleteAction`."},"showMassImport":{"type":"boolean","description":"Whether to show the built-in mass Import action. Maps to CnIndexPage `showMassImport`."},"showMassExport":{"type":"boolean","description":"Whether to show the built-in mass Export action. Maps to CnIndexPage `showMassExport`."},"showMassCopy":{"type":"boolean","description":"Whether to show the built-in mass Copy button. Maps to CnIndexPage `showMassCopy`."},"showMassDelete":{"type":"boolean","description":"Whether to show the built-in mass Delete button. Maps to CnIndexPage `showMassDelete`."},"showViewToggle":{"type":"boolean","description":"Whether to show the table/cards view-mode toggle. Maps to CnIndexPage `showViewToggle`."},"selectable":{"type":"boolean","description":"Whether rows are selectable (driving the mass-action surface). Maps to CnIndexPage `selectable`."}}},"mode":{"type":"string","enum":["edit","create","public"],"description":"Authoring / access mode. type='form': 'edit' loads from `data`, 'create' starts blank, 'public' shows the public-form success banner and skips auth-state requirements. type='detail': 'public' marks the route as unauthenticated (token-scoped reader pages — pair with a route-param token via the @route.<param> sentinel). Maps to CnFormPage / CnDetailPage `mode` prop."},"subscribe":{"type":"boolean","default":true,"description":"Collaborative-editing default for type='detail'. When true (the default) CnDetailPage calls `objectStore.subscribe(objectType, objectId)` on mount via `useObjectSubscription` and unsubscribes on unmount. Set to false for read-only or archive views. Mirrors the `subscribe` Boolean prop on CnDetailPage / CnObjectSidebar. See the `collaborative-editing-defaults` capability."},"lock":{"type":"boolean","default":true,"description":"Collaborative-editing default for type='detail'. When true (the default) the page wires `useObjectLock` so the locked-by-other banner renders when a remote lock is active. Explicit lock acquire/release on the edit toggle is the v2 follow-up; at v1 this flag controls whether read-side `lockState` is wired. Mirrors the `lock` Boolean prop on CnDetailPage / CnObjectSidebar."}}},"sidebar":{"type":"object","description":"Per-page top-level sidebar visibility gate. Sibling of config, applies to every page type. show: boolean gates CnPageRenderer's #sidebar slot.","additionalProperties":true,"properties":{"show":{"type":"boolean","description":"Whether the host App's #sidebar slot renders for this page. Defaults to true."}}},"_note":{"type":"string","description":"Human-readable note. REQUIRED when type is 'custom' — must document why a standard page type was not feasible."}},"allOf":[{"description":"map-image-layer: on a map page an `image` layer is a picture in flat pixel coordinates and needs a url and its width and height in pixels; markers on such a map read xField and yField.","if":{"properties":{"type":{"const":"map"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"layers":{"items":{"if":{"properties":{"type":{"const":"image"}},"required":["type"]},"then":{"required":["url","width","height"],"properties":{"url":{"type":"string","minLength":1},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}}}},"markers":{"properties":{"xField":{"type":"string","minLength":1,"description":"Image maps: the row property holding x, in pixels from the picture's left. Default x."},"yField":{"type":"string","minLength":1,"description":"Image maps: the row property holding y, in pixels from the picture's top. Default y."}}}}}}}},{"description":"screens-board-reference-columns (2.97.0): config.board on an index page, the block CnPageRenderer hands to CnIndexPage's `board` prop, is typed by boardView like pages[].board.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"board":{"$ref":"#/$defs/boardView"}}}}}},{"description":"index-calendar-view-mode: config.calendar on an index page is typed by calendarView.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"calendar":{"$ref":"#/$defs/calendarView"}}}}}},{"description":"index-copy-with-relations: config.copy on an index page is typed by copySettings.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"copy":{"$ref":"#/$defs/copySettings"}}}}}},{"description":"index-column-order-and-pinning: config.personalColumns on an index page lets people order and pin columns and keeps the layout per user; false keeps show and hide only.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"personalColumns":{"type":"boolean","default":true}}}}}},{"description":"form-live-values: config.fields[] on a form page may carry assign, a token default and calculate.","if":{"properties":{"type":{"const":"form"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"fields":{"type":"array","items":{"$ref":"#/$defs/formFieldLiveValues"}}}}}}},{"description":"form-smart-paste: config.smartPaste on a form page is typed by formSmartPaste.","if":{"properties":{"type":{"const":"form"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"smartPaste":{"$ref":"#/$defs/formSmartPaste"}}}}}},{"description":"splitView, manualOrder, savedViewTree, board and dateAxis are index-page mechanics. splitView emits a second route beside an index route and mounts the detail pane inside CnIndexPage; manualOrder holds a per-user row order for a list. Declared on a detail, dashboard or custom page they would validate and then do nothing, which is the failure this branch refuses out loud (case-page-and-list-as-a-place).","if":{"not":{"properties":{"type":{"const":"index"}},"required":["type"]}},"then":{"not":{"anyOf":[{"required":["splitView"]},{"required":["manualOrder"]},{"required":["savedViewTree"]},{"required":["board"]},{"required":["dateAxis"]}]}}},{"description":"The \"builtin:<id>\" placeholders in config.actions place CnIndexPage's built-in row actions, so only a type='index' page may use them. On every other page type config.actions holds objects only; a placeholder there would validate and then render nothing (row-action-builtin-placement).","if":{"not":{"properties":{"type":{"const":"index"}},"required":["type"]}},"then":{"properties":{"config":{"properties":{"actions":{"items":{"type":"object"}}}}}}},{"description":"tabInAddress puts the active tab of a record in the address. Only a detail page has a record and a tab strip, so the key is refused elsewhere rather than validating into a no-op (case-page-and-list-as-a-place).","if":{"not":{"properties":{"type":{"const":"detail"}},"required":["type"]}},"then":{"not":{"required":["tabInAddress"]}}},{"description":"type='wiki' (CnWikiPage) renders one manifest-declared markdown article. It MUST declare both config.register and config.schema as non-empty strings so the manifest stays the source of truth for which OpenRegister register/schema the article body is read from (manifest-wiki-page-type).","if":{"properties":{"type":{"const":"wiki"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","required":["register","schema"],"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1}}}},"required":["config"]}},{"description":"type='links' (CnLinkCardsPage) renders config.cards as link cards grouped by config.categories. It MUST declare a non-empty cards list, and every card is a $defs/linkCard, so a card that can go nowhere is refused here and not dropped in silence at render time (link-cards-page).","if":{"properties":{"type":{"const":"links"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","required":["cards"],"properties":{"description":{"type":"string"},"emptyLabel":{"type":"string"},"categories":{"type":"object","additionalProperties":{"type":"string","minLength":1}},"cards":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/linkCard"}},"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)."}}}},"required":["config"]}},{"description":"type='custom' requires _note documenting why decomposition into a standard page type was not feasible. Relaxed by ConductionNL/nextcloud-vue#315: when `component` matches the lib `Cn[A-Z]\\w+` pattern, the component name already documents the choice and _note is optional. When `component` is absent or non-Cn (host-app SFC), _note remains required.","if":{"properties":{"type":{"const":"custom"}},"required":["type"]},"then":{"if":{"properties":{"component":{"type":"string","pattern":"^Cn[A-Z]\\w+$"}},"required":["component"]},"then":{"description":"Lib-SFC custom page: component is self-documenting, _note is optional."},"else":{"description":"Host-app SFC custom page (or no component): _note is required to document why a standard page type was not feasible.","required":["_note"]}}}]},"visibleIfCondition":{"type":"object","description":"A visibleIf condition block. Each key is either a reserved specialised condition (appInstalled) or a dot-separated path into manifest.runtime. Values are predicate expressions (scalar for eq shorthand, or operator object). All conditions are combined with implicit AND.","additionalProperties":true,"properties":{"appInstalled":{"type":"string","minLength":1,"description":"Nextcloud app id that MUST be installed and enabled for this entry to render."}}},"deepLink":{"type":"object","additionalProperties":true,"required":["registerSlug","schemaSlug","urlTemplate"],"description":"ADR-040 object deep-link descriptor. Maps an OR register+schema to an in-app URL template so other apps / the integration registry can resolve a deep link to an object. Permissive: extra keys are tolerated for forward-compatibility.","properties":{"registerSlug":{"type":"string","description":"OR register slug the deep link targets."},"schemaSlug":{"type":"string","description":"OR schema slug the deep link targets."},"urlTemplate":{"type":"string","description":"In-app URL template; {uuid} (and other object placeholders) are substituted at resolve time."},"displayName":{"type":"string","description":"Human-readable label for the linked object type."}}},"adminSettingsEntry":{"type":"object","required":["id","label"],"additionalProperties":false,"oneOf":[{"required":["type"]},{"required":["component"]}],"description":"One admin-only settings section rendered by CnAppRoot's generic admin NcAppSettingsDialog, sorted into the dialog by `order`. Exactly one of `type` (a built-in section) or `component` (a custom section resolved from the renderer registry) MUST be present.","properties":{"id":{"type":"string","description":"Unique identifier for this admin-settings entry; also the rendered NcAppSettingsSection id."},"label":{"type":"string","description":"i18n translation key (English source) resolved by the consuming app's t() function, shown as the section header."},"type":{"type":"string","enum":["organisation-credentials"],"description":"Built-in admin-settings section. Closed enum. 'organisation-credentials' renders CnCredentials with scope=\"organisation\" (the OpenRegister credential broker's organisation-scope pane). Mutually exclusive with `component`."},"component":{"type":"string","description":"Registry key of a custom admin-settings section component, resolved from the CnAppRoot custom-components registry at render time. Mutually exclusive with `type`."},"order":{"type":"integer","description":"Sort order of this section within the admin dialog. Entries without an order render last, in array position."},"permission":{"type":"string","description":"Optional permission string that further narrows visibility of this section WITHIN the already owner-gated admin dialog, reusing the existing per-item permission grammar. Narrow-only: it can never widen access to a caller who is not an owner of the app."},"props":{"type":"object","additionalProperties":true,"description":"Props forwarded to a custom `component` entry. Ignored for built-in `type` entries."}}}}};const schema32 = {"type":"object","additionalProperties":false,"description":"Override for one of the four built-in support-note buttons (donate, support, feature-request, app-store). Every field is optional; omit the whole entry to keep the shell's default for that button.","properties":{"enabled":{"type":"boolean","description":"Show this button. Set false to hide it."},"label":{"type":"string","description":"Button text. Blank keeps the default label."},"url":{"type":"string","description":"Where the button goes. The feature-request and app-store buttons have no useful default, so an app that wants them must set this."},"variant":{"type":"string","enum":["primary","secondary","tertiary","error","warning","success"],"description":"NcButton variant."},"icon":{"type":"string","description":"Material Design Icon component name, e.g. HeartOutline."}}};const schema36 = {"type":"object","additionalProperties":false,"description":"The app's own defaults for the per-user options CnPersonalPreferences renders. The values a person chooses are stored in their Nextcloud preferences, never here; this block is only the bottom layer of the app default, administered value, personal value order.","properties":{"enabled":{"type":"boolean","default":true,"description":"Whether the personal layer applies at all. An administrator setting this false collapses it: the options render as unavailable with the reason, rather than being silently ignored."},"disabledReason":{"type":"string","description":"What to tell a person when the personal layer is off. Shown beside the unavailable options."},"landingPage":{"type":"string","description":"Page id the app opens on when a person has chosen nothing."},"dateDisplay":{"type":"string","enum":["absolute","relative"],"default":"absolute","description":"How dates read by default. A relative date always carries its absolute date in the accessible name and the tooltip, because a term read as '3 dagen geleden' hides the date the term runs to."},"rememberLastView":{"type":"boolean","default":false,"description":"Whether each list reopens in the view mode that person last used on it."},"pinnedMenu":{"type":"array","items":{"type":"string"},"description":"Menu ids pinned to the top of the navigation by default. A person may reorder and repin their own."}}};const schema47 = {"type":"object","additionalProperties":false,"required":["source","tokenSet"],"description":"A leaf app's NL Design token-set selection, as returned by nldesign's GET /api/token-sets catalogue (app-token-set-selection). Applied by CnAppRoot via useScopedTheme — the ONLY consumer; the backend does not read this field.","properties":{"source":{"type":"string","enum":["nldesign"],"description":"Theme provider. Closed to 'nldesign' — the only scoped-theme source this schema (and useScopedTheme) currently supports."},"tokenSet":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"The nldesign token-set id (a GET /api/token-sets entry's `id`), kebab-case. Resolves the token CSS at css/tokens/<tokenSet>.css."},"tokenSetName":{"type":"string","description":"Optional human-readable name (the matching catalogue entry's `name`), cached at selection time so a picker can display it without a second catalogue fetch."},"preview":{"type":"object","additionalProperties":false,"description":"Optional cached swatch preview (the matching catalogue entry's theming colors), for a picker to render a preview chip without re-fetching the catalogue.","properties":{"primaryColor":{"type":"string","description":"Cached theming.primary_color from the catalogue entry."},"backgroundColor":{"type":"string","description":"Cached theming.background_color from the catalogue entry."}}}}};const schema123 = {"type":"object","required":["id","label"],"additionalProperties":false,"oneOf":[{"required":["type"]},{"required":["component"]}],"description":"One admin-only settings section rendered by CnAppRoot's generic admin NcAppSettingsDialog, sorted into the dialog by `order`. Exactly one of `type` (a built-in section) or `component` (a custom section resolved from the renderer registry) MUST be present.","properties":{"id":{"type":"string","description":"Unique identifier for this admin-settings entry; also the rendered NcAppSettingsSection id."},"label":{"type":"string","description":"i18n translation key (English source) resolved by the consuming app's t() function, shown as the section header."},"type":{"type":"string","enum":["organisation-credentials"],"description":"Built-in admin-settings section. Closed enum. 'organisation-credentials' renders CnCredentials with scope=\"organisation\" (the OpenRegister credential broker's organisation-scope pane). Mutually exclusive with `component`."},"component":{"type":"string","description":"Registry key of a custom admin-settings section component, resolved from the CnAppRoot custom-components registry at render time. Mutually exclusive with `type`."},"order":{"type":"integer","description":"Sort order of this section within the admin dialog. Entries without an order render last, in array position."},"permission":{"type":"string","description":"Optional permission string that further narrows visibility of this section WITHIN the already owner-gated admin dialog, reusing the existing per-item permission grammar. Narrow-only: it can never widen access to a caller who is not an owner of the app."},"props":{"type":"object","additionalProperties":true,"description":"Props forwarded to a custom `component` entry. Ignored for built-in `type` entries."}}};const schema124 = {"type":"object","additionalProperties":false,"description":"Health checks + metric descriptors executed by the AppHost engine.","properties":{"_note":{"type":"string","description":"Free-form note for maintainers; ignored by the engine."},"health":{"type":"object","additionalProperties":false,"description":"Declarative health checks rendered as the ADR-006 {status, app, version, checks} shape.","properties":{"statusCodePolicy":{"type":"string","enum":["adr006","always200"],"default":"adr006","description":"HTTP status-code policy. 'adr006' returns 503 on a failed critical check; 'always200' always returns 200."},"checks":{"type":"array","description":"Health check descriptors.","items":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"type":"string","description":"Check id, surfaced as the key in the response's checks map."},"type":{"type":"string","enum":["database","filesystem","appEnabled","appConfig","orAvailable"],"description":"Engine check kind."},"severity":{"type":"string","enum":["critical","degraded"],"default":"critical","description":"critical → a failure flips status to error (503 under adr006); degraded → status degraded (200)."},"app":{"type":"string","description":"App id, for the appEnabled check."},"key":{"type":"string","description":"Config key, for the appConfig check."}}}},"cors":{"type":"boolean","default":false,"description":"ADR-040: when true the engine emits permissive CORS headers on the /api/health response."}}},"metrics":{"type":"array","description":"Prometheus metric descriptors. The {app}_ prefix, exposition format, and implicit {app}_info / {app}_up gauges are engine-owned.","items":{"type":"object","additionalProperties":false,"required":["name","type","source"],"properties":{"name":{"type":"string","description":"Metric name (without the {app}_ prefix the engine adds)."},"type":{"type":"string","enum":["gauge","counter"],"description":"Prometheus metric type."},"help":{"type":"string","description":"HELP text for the metric family."},"cacheTtl":{"type":"integer","minimum":0,"description":"ADR-040: optional per-metric cache TTL in seconds."},"source":{"type":"object","additionalProperties":false,"required":["kind"],"description":"Where the engine sources the value.","properties":{"kind":{"type":"string","enum":["tableCount","objectCount","objectSum","appConfig","provider"],"description":"Source kind. tableCount = COUNT over an allowlisted own-table; objectCount = OR objects by register/schema; appConfig = a numeric app-config value; provider = an IMetricsProvider implementation."},"table":{"type":"string","description":"tableCount: allowlisted [a-z0-9_] table name."},"register":{"type":"string","description":"objectCount: OR register slug."},"schema":{"type":"string","description":"objectCount / objectSum: OR schema slug."},"field":{"type":"string","description":"objectSum: the numeric object property to SUM."},"groupBy":{"type":"array","items":{"type":"string"},"description":"Columns to GROUP BY (one label per column)."},"labelMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Rename a groupBy column to a different Prometheus label name."},"labelDefaults":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Default label value for NULL/empty group columns."},"filter":{"type":"object","additionalProperties":true,"description":"Per-column operator filters (eq/neq/lt/lte/gt/gte/like)."},"key":{"type":"string","description":"appConfig: the config key whose numeric value is the sample."},"id":{"type":"string","description":"provider: the IMetricsProvider id to invoke."}}}}}}}};const func1 = Object.prototype.hasOwnProperty;const func2 = (function ucs2length(str) {
4
+ validateManifestV2_compiled.exports = validate20;validateManifestV2_compiled.exports.default = validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$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.156.0","type":"object","required":["$schema","version"],"allOf":[{"$comment":"UI manifests: a manifest that declares pages MUST also declare menu (unchanged v2 coupling).","if":{"required":["pages"]},"then":{"required":["menu"]}},{"$comment":"Observability-only profile: a manifest with no pages MUST carry an observability block. ADR-040 Tier-0 adopters (e.g. nldesign, planix) ship src/manifest.json solely as AppHost engine config — the app renders no manifest-driven UI, so forcing menu/pages on them produced permanently-failing gate-22 manifests (2026-07-06 fleet audit, item 3b).","if":{"not":{"required":["pages"]}},"then":{"required":["observability"]}}],"additionalProperties":false,"properties":{"$schema":{"type":"string","format":"uri","description":"REQUIRED in v2. Must point to the v2 schema URL: https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json. Presence of this field enables the validator to dispatch to the v2 Ajv instance rather than the v1 hand-rolled validator."},"version":{"type":"string","pattern":"^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$","description":"Semver of the manifest content. Bump when the manifest changes meaningfully. Used for cache busting and app-builder migration tracking. Distinct from the schema's own version."},"look":{"type":"string","enum":["nextcloud","board"],"default":"nextcloud","description":"screens-chrome-parity: the look the whole app is drawn in. `nextcloud` (the default) renders exactly as before. `board` moves the app to the look of the screens on identity.conduction.nl: CnAppRoot puts `cn-look-board` on its root and provides `cnLook` to dialogs and other teleported components. A page can override it with `config.look`."},"i18n":{"type":"object","required":["sourceLanguage","languages","labels"],"additionalProperties":false,"description":"manifest-i18n-labels: translations of the app's labels. Labels are written once, in `sourceLanguage`, and `labels` maps a language to a map of the text as written to its translation. CnAppRoot looks a label up here for the user's language (then its base language, `en_GB` falls back to `en`), then in the host's translate, then shows the text as written; the fallback is per label. `languages` lists the languages translated into (not the source language); every key of `labels` must be one of them. Language codes are Nextcloud language codes (nl, en_GB, pt_BR).","properties":{"sourceLanguage":{"type":"string","minLength":2},"languages":{"type":"array","uniqueItems":true,"items":{"type":"string","minLength":2}},"labels":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"string"}}}}},"openbuildEditable":{"type":"boolean","default":true,"description":"Whether the Buildiq in-app edit button (ADR-041) is offered on this app's pages. Default true. Set false to suppress it — e.g. Buildiq's own UI, which does not edit itself with itself. Read by CnAppRoot to gate `cnOpenBuildAvailable`. The property name and the inject key keep their `openbuild` spelling: both are data/runtime contracts that shipped manifests and consumer apps already carry, so the 2026-08-21 OpenBuild → Buildiq rename deliberately left them untouched."},"dependencies":{"type":"array","default":[],"items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","description":"Nextcloud app id."},"required":{"type":"boolean","default":true,"description":"true (default) = HARD: absence blocks the app shell behind CnDependencyMissing. false = SOFT: an optional integration whose absence surfaces a dismissible in-shell notice and never blocks."},"name":{"type":"string","description":"Human-readable display label; falls back to id."}}}]},"description":"Nextcloud app dependencies. Each entry is either a string (a HARD dependency — the app cannot run without it, absence blocks the shell) or an object { id, required?, name? } where required:false marks a SOFT (optional) dependency (required defaults to true). CnAppRoot checks each via useAppStatus(id); unresolved HARD deps block behind CnDependencyMissing, unresolved SOFT deps show a dismissible NcNoteCard banner. Sentinels (@resolve:*) are NOT allowed here — resolved at build time against known app IDs."},"setup":{"type":"object","additionalProperties":false,"required":["steps"],"description":"First-time setup wizard (ADR-042). Rendered by CnSetupWizard; required steps gate the app via CnAppRoot's setup phase. Steps run privileged server-side actions via /api/setup/action — the wizard never writes OpenRegister objects from the browser.","properties":{"enabled":{"type":"boolean","default":true,"description":"Whether the setup wizard is active for this app."},"version":{"type":"integer","default":1,"description":"Setup contract version; bump to re-trigger setup after a breaking change."},"completionConfigKey":{"type":"string","description":"App-config key storing the completed setup version."},"dismissAction":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"Setup action that records a closed or finished setup wizard on the server, so it stays closed in every browser. CnAppRoot posts `/apps/{appId}/api/setup/action/{dismissAction}` with `{ finished }` once when the user closes or finishes the wizard. The app either answers the outstanding optional steps or reports `dismissed` (true, or the setup version it was closed at) in `GET /api/setup/status`. Without it, the close is remembered only in the browser (localStorage), as before."},"steps":{"type":"array","description":"Ordered setup steps rendered by CnSetupWizard.","items":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"type":"string","description":"Unique step id; also the #step-<id> slot name."},"type":{"type":"string","enum":["info","config-fields","choice","run-action","summary","component"],"description":"Built-in step renderer."},"title":{"type":"string"},"body":{"type":"string","description":"info: explanatory copy."},"note":{"type":"string","minLength":1,"description":"choice: a footnote under the options, translated like body."},"visibleWhen":{"description":"Offer the step only when the setup status says so: a dotted path into the setup status document whose value must be truthy, or { path, equals } for an exact value. Hidden while the status loads.","oneOf":[{"type":"string","minLength":1},{"type":"object","required":["path"],"additionalProperties":false,"properties":{"path":{"type":"string","minLength":1},"equals":{}}}]},"footnote":{"type":"string","minLength":1,"description":"choice: the same footnote under another name; note wins when both are set."},"required":{"type":"boolean","default":false,"description":"When true the step gates the app until done."},"schema":{"description":"config-fields: a JSON Schema ref (string) or inline object rendered via fieldsFromSchema."},"configKey":{"type":"string","description":"choice: app-config key the selection is written to."},"configKeys":{"type":"array","items":{"type":"string"},"description":"config-fields: app-config keys the fields write."},"options":{"type":"array","description":"choice: selectable options.","items":{"type":"object","additionalProperties":false,"required":["value","label"],"properties":{"value":{},"label":{"type":"string"}}}},"optionsSource":{"type":"string","description":"choice: where to read the option list from at runtime, instead of declaring it here. Names a key in the app's own GET /api/setup/status document (dots walk into nested objects, e.g. `profiles`). Entries accept `id` for `value` and `name` for `label`, and a numeric `objectCount` becomes the stat a card shows. Use it when the SERVER owns what is on offer, so the manifest cannot drift from it."},"display":{"type":"string","enum":["select","cards","rows"],"default":"select","description":"choice: how the options are rendered. `select` is a dropdown; `cards` is a grid of CnChoiceCards, which shows each option's description and stats; `rows` is CnChoiceCards' rows layout, one radio row per option with its description below the label. Pick cards or rows when the choice needs explaining."},"dependsOn":{"type":"string","description":"choice: the configKey of a previous choice step this one cascades from. Disabled until the parent is set; its option list is taken from optionsByParent[parentValue] (e.g. organisation-type depends on country)."},"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."},"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."},"loadAction":{"type":"string","minLength":1,"description":"choice with display cards: an actionId each option card loads itself with. Every card except the one whose value is `none` gets its own Load button, which POSTs { dataset: <value> } to /api/setup/action/{loadAction}, spins while it runs and shows the result on that card. A successful load also selects the card. With loadAction the separate run-action step that loaded the picked dataset is no longer needed; manifests that keep it still work."},"requires":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true,"description":"App ids this step needs. When any of them is not installed and enabled, the wizard skips the step and the summary shows it as skipped, naming the missing apps. Setup status then counts the step as not applicable, neither met nor unmet, so it never reopens the wizard or gates the app. This holds for a required step too."},"action":{"type":"string","description":"run-action: actionId POSTed to /api/setup/action/{action}."},"onDemand":{"type":"boolean","default":false,"description":"run-action: the step runs only when the user clicks Run, never automatically, and is never outstanding work (it does not open the wizard or decide where a resumed wizard starts). The summary ticks it only when its action succeeded in the current session and shows it as not run otherwise, whatever the server reports. For destructive steps an app reports as done on purpose so they never auto-run, such as removing example data."},"healthCheck":{"type":"boolean","default":false,"description":"summary: render a health recap."},"component":{"type":"string","description":"component: app-registered component name (or use the #step-<id> slot)."}}}}}},"walkthrough":{"type":"object","additionalProperties":false,"description":"Abstract product walkthrough (ADR-043). One or more spotlighted, versioned tours rendered by CnWalkthrough over the live shell.","required":["tours"],"properties":{"enabled":{"type":"boolean","default":true,"description":"Master switch; when false no tour auto-starts."},"version":{"type":"integer","description":"Walkthrough contract version."},"completionConfigKey":{"type":"string","description":"Per-user app-config key holding the last app version whose tour the user has seen."},"tours":{"type":"array","description":"The app's tours.","items":{"type":"object","additionalProperties":false,"required":["id","steps"],"properties":{"id":{"type":"string","description":"Unique tour id (globally unique for cross-app hand-off, e.g. 'pipelinq:lead-to-bill')."},"title":{"type":"string","description":"i18n key / text for the tour name (restart menu)."},"trigger":{"type":"string","enum":["first-visit","version-bump","empty-index","manual"],"default":"manual","description":"When the tour auto-starts."},"minAppVersion":{"type":"string","description":"Semver; the tour is inert below this app version."},"startOn":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"uniqueItems":true,"description":"Route names the tour may auto-start on. A user who first lands anywhere else, such as on a case opened from a link, does not get the tour there: it waits until they reach one of these routes. Without it a tour whose first step is centered starts wherever the user lands. Has no effect on a tour started by hand."},"steps":{"type":"array","description":"Ordered steps.","items":{"type":"object","additionalProperties":false,"required":["id","target","advanceOn"],"properties":{"id":{"type":"string","description":"Unique step id within the tour."},"title":{"type":"string","description":"i18n key / text for the coachmark title."},"body":{"type":"string","description":"i18n key / text for the coachmark body; supports {{contextVar}} interpolation."},"task":{"type":"string","description":"Optional 'go do this' instruction; presence + a non-manual advanceOn makes this an enforced action step."},"sinceVersion":{"type":"string","description":"Semver the step was introduced in; drives the 'what's new' composition."},"placement":{"type":"string","enum":["auto","top","bottom","left","right","center"],"default":"auto","description":"Coachmark placement; 'center' renders anchorless with no cutout."},"optional":{"type":"boolean","default":false,"description":"Skip gracefully when the target/condition is absent."},"allowManualNext":{"type":"boolean","default":false,"description":"Show a manual Next escape hatch on an enforced action step."},"handoff":{"type":"object","additionalProperties":false,"required":["url"],"description":"Cross-app hand-off (ADR-043). When present, the coachmark primary action becomes 'Continue in {app}' and navigates to `url` carrying a cn_resume_tour/cn_resume_step token so the destination app resumes the named tour.","properties":{"app":{"type":"string","description":"Destination Conduction app id (label + intent)."},"url":{"type":"string","description":"Destination URL to navigate to."},"tour":{"type":"string","description":"Tour id to resume in the destination (defaults to this tour's id)."},"step":{"type":"string","description":"Step id to resume at (defaults to the destination tour's first step)."}}},"target":{"type":"object","additionalProperties":false,"required":["kind"],"description":"What the step spotlights.","properties":{"kind":{"type":"string","enum":["nav-item","widget","action","page","element","selector"],"description":"Resolution strategy."},"ref":{"type":"string","description":"Stable id: route name (nav-item/page), widgetKey (widget), action id (action), or data-walkthrough-id / data-testid (element)."},"app":{"type":"string","description":"Optional other Conduction app id for a cross-app target (page kind)."},"selector":{"type":"string","description":"Raw CSS selector for kind:selector (last resort)."}}},"advanceOn":{"type":"object","additionalProperties":false,"required":["type"],"description":"Condition that advances the step.","properties":{"type":{"type":"string","enum":["manual","click-target","route-match","element-appears","object-created","delay"],"description":"Advance trigger."},"route":{"type":"string","description":"route-match: the route name to await."},"register":{"type":"string","description":"object-created: OR register slug."},"schema":{"type":"string","description":"object-created: OR schema slug."},"selector":{"type":"string","description":"element-appears: selector to await."},"ms":{"type":"integer","minimum":0,"description":"delay: milliseconds before auto-advance."},"capture":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of contextVar -> ':param' (route-match) or ':id' (object-created) captured into the tour context for {{var}} interpolation."}}}}}}}}}}},"support":{"type":"object","additionalProperties":false,"description":"First-open support and donation note. CnAppRoot mounts CnSupportDialog once per user on first open and reads this block; CnEditSupportModal edits it. Omitting the block keeps the default-on behaviour with the shell's own copy, so an app only declares what it wants to override. The block was read by CnAppRoot and written by CnEditSupportModal before it was declared here, which made `additionalProperties: false` reject every manifest that set it.","properties":{"enabled":{"type":"boolean","description":"Whether the note is shown on first open. Omit for the default (true); set false to suppress it for this app."},"title":{"type":"string","description":"Dialog heading. Blank keeps the shell default."},"appName":{"type":"string","description":"Display name interpolated into the body copy. Defaults to the host app's name."},"bodyParagraphs":{"type":"array","description":"Body copy, one entry per paragraph, replacing the shell's default paragraphs.","items":{"type":"string"}},"founderName":{"type":"string","description":"Signature name."},"founderTitle":{"type":"string","description":"Signature role, shown under the name."},"founderAvatarUrl":{"type":"string","description":"Signature avatar image URL."},"founderProfileUrl":{"type":"string","description":"Profile the signature avatar links to."},"buttons":{"type":"object","additionalProperties":false,"description":"Per-button overrides, keyed by the four built-in button ids. Each may be hidden, relabelled, restyled or re-pointed; omit a key to keep its default.","properties":{"donate":{"$ref":"#/$defs/supportButton"},"support":{"$ref":"#/$defs/supportButton"},"feature-request":{"$ref":"#/$defs/supportButton"},"app-store":{"$ref":"#/$defs/supportButton"}}}}},"personalisation":{"$ref":"#/$defs/personalisation","description":"App defaults for the per-user options CnPersonalPreferences renders, and the switch that turns the personal layer off instance wide."},"nav":{"type":"object","additionalProperties":false,"description":"Navigation-level configuration consumed by CnAppNav.","properties":{"includePersonalSettings":{"type":"boolean","default":true,"description":"Auto-prepend a 'Personal settings' entry at the top of the settings foldout (opens the host's NcAppSettingsDialog via cnOpenUserSettings). Set false for apps with no per-user settings."},"settingsLabel":{"type":"string","description":"Override label for the settings foldout's gear button. Defaults to 'Advanced'."},"card":{"type":"object","additionalProperties":false,"required":["title"],"description":"A card above the footer entries, rendered by CnAppNav (zuiddrecht-pixel-gaps): a title, a line of text and one link. Title, text and link label are i18n keys. Omitted, nothing renders there.","properties":{"title":{"type":"string","minLength":1},"text":{"type":"string"},"link":{"type":"object","additionalProperties":false,"required":["label"],"description":"The card's link: a `route` (named page, optional `params`) is a router link, an `href` a plain anchor, an `action` id a button that emits `card-action`.","properties":{"label":{"type":"string","minLength":1},"route":{"type":"string"},"params":{"type":"object","additionalProperties":true},"href":{"type":"string"},"action":{"type":"string"}}}}},"help":{"type":"object","additionalProperties":false,"required":["label"],"description":"A help entry with a help icon in the navigation footer, above the settings foldout (zuiddrecht-pixel-gaps): `route` (named page) or `href` (opens in a new tab).","properties":{"label":{"type":"string","minLength":1},"route":{"type":"string"},"href":{"type":"string"}}},"footer":{"type":"array","items":{"type":"string","minLength":1},"description":"Declare the navigation footer (zuiddrecht-pixel-gaps-2): an ordered list of `section: \"footer\"` menu entry ids, plus the reserved ids `help` (the `nav.help` entry) and `settings` (the settings foldout). Only the named footer entries render there, in that order; footer entries left out move into the settings foldout so their pages stay reachable. `settings` first puts the foldout above the list (the board's `[\"settings\", \"help\"]`). Omitted, the footer renders as before: help, every footer entry, then the foldout."},"primaryAction":{"$ref":"#/$defs/primaryAction","description":"App-wide default primary action rendered as an NcAppNavigationNew button above the main menu list. A page-scoped pages[].primaryAction for the current route wins over this default."},"brand":{"type":"object","additionalProperties":false,"description":"A brand block at the very top of the app navigation, above the primary action: a logo, a name and a caption. Rendered by CnAppNav. Omitted, nothing renders there.","properties":{"logo":{"type":"string","description":"URL of the logo image. Decorative when `name` is set."},"emblem":{"type":["string","boolean"],"description":"An emblem drawn in place of the logo (zuiddrecht-pixel-gaps): the municipality's mark beside the app name, distinct from the wordmark in the top bar. A URL, or `true` to draw the theme's emblem from the `--nldesign-emblem-url` custom property. Decorative when `name` is set."},"name":{"type":"string","description":"i18n key / text for the app or organisation name."},"caption":{"type":"string","description":"i18n key / text for the line under the name, for example the organisation."},"alt":{"type":"string","description":"i18n key / alternative text for the logo. Used only when no `name` is set."},"placement":{"type":"string","enum":["header","nav"],"description":"Where the brand block is drawn (screens-brand-block-top-bar). Under `look: \"board\"` the default is `header`: a 237px block at the start of Nextcloud's own header (#header), before the app menu, drawn by CnAppRoot; an app without CnAppNav has it too. Nextcloud offers no extension point there, so when `#header` is absent the block falls back to the navigation. `nav` keeps the block at the top of the app navigation. Ignored without the board look, where the navigation draws the block."}}},"searchPlaceholder":{"type":"string","minLength":1,"description":"i18n key / text for the line on the search field in Nextcloud's header (screens-board-top-bar-search, 2.118.0), naming what the app's search finds, for example \"Search cases, documents or contacts\". Applied by CnAppRoot under `look: \"board\"` on a page with `#header`; it replaces only the text of the resting field. Omitted, the field keeps Nextcloud's own line."},"featureRequestRepo":{"type":"string","description":"`<owner>/<repo>` slug on the forge that the in-product feature-request deep-link targets (provided to descendants as `cnFeatureRequestRepo`). Falls back to `Conduction/<appId>` when omitted."},"forge":{"type":"object","additionalProperties":false,"description":"Forge that the in-product feature-request deep-link targets. Switching the whole fleet's forge (onto a self-hosted Forgejo/Gitea, or back to Codeberg) is just this one field. Defaults to GitHub, the only host the fleet publishes to.","properties":{"type":{"type":"string","enum":["codeberg","forgejo","gitea","github"],"default":"github","description":"Forge type. Selects how the 'new issue' form is pre-filled: `github` uses per-field Issue-Form query params; `codeberg`/`forgejo`/`gitea` assemble a Markdown body (only title + body are supported there)."},"baseUrl":{"type":"string","description":"Override the forge host. Required for self-hosted `forgejo`/`gitea`; optional for `codeberg`/`github` (their canonical public hosts are used when omitted)."}}}}},"runtime":{"type":"object","description":"Runtime context data injected into the manifest by the backend (e.g. OpenRegister) at serve time. Carries per-user fields that visibleIf context-path predicates resolve against. The canonical sub-object is 'user', which exposes user-specific flags and role information.","additionalProperties":true,"properties":{"user":{"type":"object","description":"Per-user runtime context. Populated by the backend for authenticated requests. All fields are optional; the FE predicate evaluator treats missing fields as undefined.","additionalProperties":true},"theme":{"$ref":"#/$defs/runtimeTheme","description":"NL Design System scoped theme selection (scoped-theme-applier). Consumed by CnAppRoot's useScopedTheme wiring, not by the backend."}}},"menu":{"type":"array","items":{"$ref":"#/$defs/menuItem"},"description":"Top-level navigation entries rendered by CnAppNav."},"pages":{"type":"array","items":{"$ref":"#/$defs/page"},"description":"Page definitions dispatched by CnPageRenderer. Each page's id is also its vue-router route name. Ids MUST be unique across the array; uniqueness is enforced by validateManifestV2 as a post-schema check."},"adminSettings":{"type":"array","items":{"$ref":"#/$defs/adminSettingsEntry"},"description":"Admin-only settings sections rendered by CnAppRoot's generic admin NcAppSettingsDialog, gated on app-owner-group membership (not OC.isUserAdmin()). An absent key or an empty array yields no admin nav entry and no admin dialog. See the manifest-admin-settings and admin-settings-owner-gating capabilities."},"credentials":{"type":"array","description":"External-provider credentials this app can use via the OpenRegister credential broker (github, gitlab, …). Each entry declares which provider, why, and at what scope; the app never receives the secret — the broker performs the outbound call on the user's behalf. See the credential-broker capability.","items":{"type":"object","required":["provider"],"additionalProperties":false,"properties":{"provider":{"type":"string","description":"Catalogue provider identifier (e.g. \"github\", \"gitlab\") — a key in OpenRegister's credential-providers catalogue."},"reason":{"type":"string","description":"Human-readable reason shown to the user in credential settings (why the app wants this provider)."},"scopes":{"type":"array","items":{"type":"string"},"description":"Advisory scopes the app needs (e.g. [\"repo\"])."}}}},"schedules":{"type":"array","description":"Declarative scheduled tasks (apphost-scheduling capability). Each entry declares a recurring task the OpenRegister AppHost schedule-reconciler turns into an Integriq job that runs on the existing background-job path — so a manifest-driven app (including a pure-virtual Buildiq app with no PHP on disk) can own its ingestion/maintenance cadence without shipping a TimedJob. The temporal peer of `credentials`/`observability`: consumed by the OpenRegister engine, NOT by the Vue renderer. Optional and additive.","items":{"type":"object","required":["id","action"],"additionalProperties":false,"oneOf":[{"required":["interval"],"not":{"required":["cron"]}},{"required":["cron"],"not":{"required":["interval"]}}],"properties":{"id":{"type":"string","description":"Stable identifier for this schedule, unique within the manifest. The reconciled job is keyed on applicationId + this id, so renaming it creates a new job and orphans the old one (which is then garbage-collected)."},"interval":{"type":"integer","minimum":1,"description":"Run cadence in seconds (e.g. 604800 = weekly). Exactly one of `interval` or `cron` must be set."},"cron":{"type":"string","description":"Standard 5-field cron expression (e.g. \"0 3 * * 1\"). The reconciler computes the job's nextRun from it. Exactly one of `interval` or `cron` must be set."},"action":{"type":"string","pattern":"^[a-z0-9]+:[a-z0-9-]+$","description":"A server-allow-listed generic action type, NOT a PHP class name. Only vetted types are accepted (v1: \"openconnector:synchronization\"); the reconciler maps the type to a trusted jobClass — a manifest-supplied class name is never executed. A non-allow-listed action is rejected and logged."},"arguments":{"type":"object","description":"Free-form arguments passed to the action (e.g. the synchronization id/ref). Interpreted by the vetted action, not by the reconciler.","additionalProperties":true},"enabled":{"type":"boolean","description":"Whether this schedule is active. Defaults to true. Setting false disables the reconciled job (preserving its run history) rather than deleting it."}}}},"observability":{"$ref":"#/$defs/observability","description":"Declarative observability block (ADR-040). Consumed at request time by the OpenRegister AppHost observability engine (GenericHealthController + GenericMetricsController), NOT by the Vue renderer. Declares the app's health checks and Prometheus metric descriptors so adopting apps ship zero hand-written observability PHP while keeping their /api/health + /api/metrics URLs and the ADR-006 contract."},"store":{"type":"object","description":"ADR-080 / ADR-114 Decision 4. The declarative store plane. An app that declares this block gets a working Store surface with NO backend of its own: OpenRegister's AppHost aliases /api/store/items and /api/store/items/{slug}/install at its GenericStoreController, which reads this block at request time. Discovery stays in the engine (the SSRF guard, the redirect refusal and the registry token never leave it) and the app builds no registry URL.","additionalProperties":false,"anyOf":[{"required":["schema"],"title":"An objects store names the remote schema its registry serves."},{"required":["types"],"title":"A configuration store names the shareable types it surfaces."}],"properties":{"schema":{"type":"string","description":"Remote schema slug the registry serves (e.g. \"case-type-template\", \"application-template\")."},"register":{"type":"string","description":"Remote register segment, used when the instance's registry_register config is unset or empty."},"localRegister":{"type":"string","description":"The register an install WRITES into. Defaults to the app id. A schema slug is NOT unique across the fleet (`case`, `task` and `document` exist in several apps at once), so an unscoped write can land in another app's register: an app whose OpenRegister register slug differs from its app id must set this."},"installable":{"type":"array","items":{"type":"string"},"description":"Schema slugs an install may write. THIS IS A SECURITY BOUNDARY, not a convenience: a registry is somebody else's server, and without the allowlist an item could name any schema the app owns. An absent or empty list refuses EVERY install rather than permitting every install."},"kinds":{"type":"array","items":{"type":"string"},"description":"Kind quick-filters (ADR-080 Decision 5). Declared ONCE here, beside the allowlist: the engine returns them in the search response and CnStorePage prefers them over its page config. Defaults to the shared vocabulary when omitted."},"types":{"type":"array","items":{"type":"string"},"description":"Shareable configuration type ids this store surfaces (store-over-federated-config). Declaring these makes the store exchange CONFIGURATION rather than rows: an item is a configuration set (`openregister.configset`), a flow (`openregister.flows`), a register with its schemas (`openregister.registers`), or any schema that marked itself `x-openregister-shareable`. Cards come from federated discovery and an install runs through the owning type, so a set arrives as registers, schemas, objects, views, flows, sources and mappings. An app that declares none keeps the remote objects API described by `schema`/`register`, and makes no discovery call at all."},"cardFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Card field to remote property map. Defaults to the field names the fleet's registries already publish."},"builtIn":{"type":"array","description":"The items this app ships itself, rendered when no registry is configured or the registry does not answer. ADR-080 Decision 4 makes this the price of the word Store: a surface that goes blank without a registry was never a store.","items":{"type":"object","additionalProperties":false,"required":["slug","title"],"properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string"}}}},"_note":{"type":"string","description":"Free-text rationale, ignored at runtime."}}},"deepLinks":{"type":"array","items":{"$ref":"#/$defs/deepLink"},"description":"ADR-040: object deep-link descriptors consumed by the integration registry / cross-app linking, NOT by the Vue renderer. Optional and additive."},"pageTemplates":{"type":"array","items":{"$ref":"#/$defs/pageTemplate"},"description":"Entity-scaffold page templates (manifest-entity-scaffold-templating). A template declares one reusable index/detail page shape ONCE, with `{{param}}` placeholders for the parts that vary per entity (register/schema binding, label, field/column subset) and a declared parameter list. The expander (utils/expandPageTemplates) materialises `pageInstances[]` into concrete `pages[]` at build/boot time, so CnPageRenderer only ever sees concrete pages and is unchanged by templating. Optional and additive: a manifest without pageTemplates is unaffected."},"pageInstances":{"type":"array","items":{"$ref":"#/$defs/pageInstance"},"description":"Per-entity template instantiations. Each entry references a `pageTemplates[]` template by `templateRef` and supplies only the values that vary. The expander substitutes them into the referenced template and appends the resulting concrete pages to `pages[]`. Expansion FAILS (named error) on an unknown templateRef or a missing required template parameter. Optional and additive."},"sets":{"type":"object","description":"Named, reusable field/column/sidebar sets referenced from templates via a `{{set:NAME}}` placeholder, so a repeated field/column/sidebar block is declared ONCE and shared across templates and pages instead of being re-listed. Each value is arbitrary JSON (typically an array of field/column defs or a sidebar object). Optional and additive.","additionalProperties":true},"mcp":{"type":"object","title":"MCP tool visibility hints","description":"OPTIONAL. Advisory visibility/UX hints for MCP tool surfaces (Hermiq agent tool picker, openbuild tool browser). Purely presentational: per ADR-063, OpenRegister's register (x-openregister-mcp dialect + #[McpTool] attributes) is the single source of CRUD-tool truth and OpenRegister RBAC is the authoritative invoke-time gate. This block grants no access and gates nothing; nothing in nextcloud-vue consumes it.","additionalProperties":false,"properties":{"expose":{"type":"boolean","title":"App-level exposure hint","description":"Advisory app-wide default for whether this app's register-derived MCP tools should be shown in agent / tool-picker surfaces. Advisory only — the authoritative gate is OpenRegister RBAC plus per-agent whitelists. Defaults to false (opt-in), matching ADR-063's default-OFF exposure model.","default":false},"pageTools":{"type":"object","title":"Per-page tool hints","description":"Maps a pages[].id to the ordered list of MCP tool ids that are contextually relevant on that page. Advisory grouping for UX surfaces only; it does not grant access. Keys are expected to match an existing pages[].id; the register remains authoritative for whether each listed tool actually exists.","additionalProperties":{"type":"array","title":"Relevant tool ids for a page","description":"Ordered MCP tool ids relevant to the page keyed above. Advisory ordering only.","items":{"type":"string","title":"MCP tool id","description":"A fully-qualified MCP tool id in the register's derived-CRUD shape {appId}.{schemaSlug}.{verb}, verb one of search|get|create|update|delete (ADR-063). Example only: \"pipelinq.lead.search\". The register is authoritative for existence; this is a hint.","pattern":"^[a-z0-9_-]+\\.[a-zA-Z0-9_-]+\\.(search|get|create|update|delete)$"}}},"agentHints":{"type":"object","title":"Agent-facing hints","description":"Advisory metadata for agent surfaces (Hermiq). All values are non-authoritative UX hints. Additional properties are permitted so consuming surfaces can introduce new advisory hints without a schema release (forward compatibility).","additionalProperties":true,"properties":{"summary":{"type":"string","title":"App tool summary","description":"One-line description of what this app's tools help an agent accomplish. Shown in agent / tool-picker surfaces."},"defaultTools":{"type":"array","title":"Default tool ids","description":"MCP tool ids to surface first / preselect when an agent has no page context. Advisory ordering hint only; not a grant.","items":{"type":"string","title":"MCP tool id","description":"Fully-qualified MCP tool id {appId}.{schemaSlug}.{verb}. Example only: \"pipelinq.lead.search\"."}},"keywords":{"type":"array","title":"Discovery keywords","description":"Keywords helping progressive-disclosure / tool-search surfaces (Hermiq) rank this app's tools. Advisory only.","items":{"type":"string","title":"Keyword","description":"A single discovery keyword."}}}}}}},"$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.<key>` 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.<key>` 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."},"optionIcon":{"type":"string","minLength":1,"description":"look 'search': one icon for every row."},"itemOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"widget 'sub-objects': field overrides for the row form (sub-objects-editor)."},"newItem":{"type":"object","description":"widget 'sub-objects': the values a new row starts with."},"itemLabel":{"type":"string","minLength":1,"description":"widget 'sub-objects': what one row is called (\"Checklist item\"), for its accessible name and dialog title."},"emptyText":{"type":"string","description":"widget 'sub-objects': shown while there are no rows."},"pruneWhen":{"type":"object","additionalProperties":false,"description":"widget 'sub-objects': tidy the rows on save: trim strings, omit empty optional keys, drop rows with a `blank` key, and with `invalid: true` rows missing a required key.","properties":{"blank":{"type":"array","items":{"type":"string","minLength":1}},"invalid":{"type":"boolean"}}},"placeholderFrom":{"type":"string","minLength":1,"description":"A text field's placeholder is this other field's value (form-small-features)."},"requiredWhen":{"type":"object","description":"Required while this condition holds: a `visibleWhen` predicate, or `{ all }` / `{ any }` of them (form-small-features)."},"warnWhen":{"description":"A note under the field that does not block the save, shown while `when` holds (form-small-features).","anyOf":[{"$ref":"#/$defs/fieldWarning"},{"type":"array","items":{"$ref":"#/$defs/fieldWarning"}}]},"clearWhenHidden":{"type":"boolean","description":"Write the field empty when its visibleWhen hides it (form-small-features)."},"stored":{"type":"boolean","description":"`false`: the field steers the form only and is not saved; it may be a key the schema does not have, with `type` and `label` (form-small-features)."},"initialWhen":{"type":"object","description":"A field that is not stored starts true when this condition holds on the loaded values (form-small-features)."},"sectionCollapsed":{"type":"boolean","description":"Makes the field's section foldable from its heading; true opens it folded (form-small-features)."},"optionSwatches":{"type":"object","additionalProperties":{"type":"string"},"description":"A select option's colour by value, drawn before its label (form-small-features)."},"optionIcons":{"type":"object","additionalProperties":{"type":"string"},"description":"A select option's icon by value, drawn before its label (form-small-features)."},"unique":{"description":"Refuse a value another stored object of this schema already has, checked before the save: `true`, or `{ filter }` to compare only among the objects the filter matches (`{ \"caseType\": \"@objectId\" }`) (list-reorder-order-field).","anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object"}}}]},"defaults":{"type":"object","description":"widget 'object-switch': the object the switch fills in when turned on; off writes null. Its keys are edited as fields at dotted paths."},"optionsFrom":{"$ref":"#/$defs/optionsFrom","description":"Draw the field as a select (a multiselect on an array field) over options read from a source; the option's value is stored and its label shown. A register schema property can say the same with `x-options-from` (form-field-options-from, 2.133.0)."}}},"fieldWarning":{"type":"object","additionalProperties":false,"required":["when","text"],"properties":{"when":{"type":"object","description":"The condition, as visibleWhen takes it, or `{ all }` / `{ any }`."},"text":{"type":"string","minLength":1,"description":"The note, through the app's translate."}}},"optionsFrom":{"description":"Select options read from a source: rows from an app endpoint (`url`, `params`, `responsePath`) or OpenRegister objects (`register`, `schema`, `filter`; tokens resolve). `valueField` and `labelField` are dot-paths into a row: by default the value is `id` (else the object's uuid) and the label `title` (else `name`, else the value).","oneOf":[{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","minLength":1,"description":"An app path, resolved with generateUrl."},"params":{"type":"object","description":"Query parameters sent with the request."},"responsePath":{"type":"string","minLength":1,"description":"Where the rows sit in the answer."},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"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.<key>`, 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)."}}},{"type":"object","additionalProperties":false,"required":["register","schema"],"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","description":"Filters on the objects, as a list filter takes them."},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"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.<key>`, 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)."}}}]},"breadcrumbCrumb":{"type":"object","additionalProperties":false,"required":["label"],"description":"One breadcrumb crumb (config.breadcrumb).","properties":{"label":{"type":"string","minLength":1},"route":{"type":"string"},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true,"description":"Query of the `route` link, such as `{ savedView: 'default:<slug>' }` to land on a view (saved-view-overrides)."},"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)."}}},"rowIndicator":{"type":"object","additionalProperties":false,"required":["field","text"],"description":"One declared state indicator on an index page's rows. Renders as an icon with a text alternative and a tooltip, never as colour alone.","properties":{"id":{"type":"string","minLength":1,"description":"Stable id for this indicator, used as its test id and its render key. Defaults to `field`."},"field":{"type":"string","minLength":1,"description":"Dotted path on the row the condition reads."},"equals":{"description":"The indicator applies when the field equals this value exactly."},"in":{"type":"array","description":"The indicator applies when the field's value is one of these."},"after":{"type":"string","minLength":1,"description":"The indicator applies while the field holds a readable date after this moment: `now` or an ISO date (\"held until a later date\")."},"before":{"type":"string","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."},"text":{"type":"string","minLength":1,"description":"The text alternative, read out beside the icon. Required: an indicator without one does not render."},"tooltip":{"type":"string","description":"Hover text. Defaults to `text`."}}},"folderSidebarScope":{"type":"object","additionalProperties":true,"description":"One folder in `config.folderSidebar.folders[]`, which doubles as a SCOPE: the three optional list-layout keys below let one index page show each folder the way that folder needs. Every other key (id, name, icon, ...) stays free-form, so an existing folder list validates unchanged. A scope (or the `x-index` block of the row a register-derived folder comes from) may also carry the presentation keys a query preset sets, under the same names: title, quickFilters, cardFields, countText, searchPlaceholder, footerNote, viewSwitch, addLabel, headerButtons, breadcrumb, headerMeta and afterList (index-scope-presentation); a scope's value wins over a preset's. The page's `config.columns` still decides which columns this page HAS; a scope only decides which of them are shown, in what order, sorted by what and searched over what. A scope naming a column the page does not declare is refused by validateManifestV2, not silently rendered.","properties":{"columns":{"type":"array","description":"The columns this scope shows, in order. Same shapes as `config.columns`: a bare key string or a column object. Every key must be one the page's `config.columns` declares. Omit to show the page's own columns.","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]}},"defaultSort":{"description":"The sort the fetch carries while this scope is active. One `{ key, order }` entry or a list of them; `order` is 'asc' (default) or 'desc'. `field` is accepted in place of `key`, matching the CnIndexPage `defaultSort` prop.","oneOf":[{"$ref":"#/$defs/folderSidebarScopeSort"},{"type":"array","items":{"$ref":"#/$defs/folderSidebarScopeSort"}}]},"searchFields":{"type":"array","items":{"type":"string","minLength":1},"description":"The fields the search box searches over while this scope is active. Omit to search whatever the page searches by default."}}},"folderSidebarScopeSort":{"type":"object","additionalProperties":false,"description":"One sort entry for a `folderSidebar` scope. Name the column with `key` (manifest spelling) or `field` (the CnIndexPage prop spelling); an entry carrying neither is dropped rather than sent as an empty sort key.","properties":{"key":{"type":"string","minLength":1},"field":{"type":"string","minLength":1},"order":{"type":"string","enum":["asc","desc"],"default":"asc"}}},"supportButton":{"type":"object","additionalProperties":false,"description":"Override for one of the four built-in support-note buttons (donate, support, feature-request, app-store). Every field is optional; omit the whole entry to keep the shell's default for that button.","properties":{"enabled":{"type":"boolean","description":"Show this button. Set false to hide it."},"label":{"type":"string","description":"Button text. Blank keeps the default label."},"url":{"type":"string","description":"Where the button goes. The feature-request and app-store buttons have no useful default, so an app that wants them must set this."},"variant":{"type":"string","enum":["primary","secondary","tertiary","error","warning","success"],"description":"NcButton variant."},"icon":{"type":"string","description":"Material Design Icon component name, e.g. HeartOutline."}}},"runtimeTheme":{"type":"object","additionalProperties":false,"required":["source","tokenSet"],"description":"A leaf app's NL Design token-set selection, as returned by nldesign's GET /api/token-sets catalogue (app-token-set-selection). Applied by CnAppRoot via useScopedTheme — the ONLY consumer; the backend does not read this field.","properties":{"source":{"type":"string","enum":["nldesign"],"description":"Theme provider. Closed to 'nldesign' — the only scoped-theme source this schema (and useScopedTheme) currently supports."},"tokenSet":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"The nldesign token-set id (a GET /api/token-sets entry's `id`), kebab-case. Resolves the token CSS at css/tokens/<tokenSet>.css."},"tokenSetName":{"type":"string","description":"Optional human-readable name (the matching catalogue entry's `name`), cached at selection time so a picker can display it without a second catalogue fetch."},"preview":{"type":"object","additionalProperties":false,"description":"Optional cached swatch preview (the matching catalogue entry's theming colors), for a picker to render a preview chip without re-fetching the catalogue.","properties":{"primaryColor":{"type":"string","description":"Cached theming.primary_color from the catalogue entry."},"backgroundColor":{"type":"string","description":"Cached theming.background_color from the catalogue entry."}}}}},"pageTemplate":{"type":"object","additionalProperties":false,"required":["id","page"],"description":"A reusable page shape declared once and instantiated per entity. `page` is an ordinary v2 page object whose string values may contain `{{param}}` placeholders (and `{{set:NAME}}` set references); `params` declares which placeholders exist and which are required. Fixed structure lives in `page`; only declared params vary per instantiation.","properties":{"id":{"type":"string","description":"Unique template identifier — the `templateRef` target. Uniqueness across pageTemplates[] is enforced by validateManifestV2 as a post-schema check."},"_note":{"type":"string","description":"Free-form maintainer note; ignored by the expander."},"params":{"type":"array","description":"Declared parameters. A placeholder `{{name}}` in `page` is substituted with the instantiation's value for `name`. A `required` param absent from an instantiation is an expansion error; an optional param absent from an instantiation causes its exact-match placeholder key to be omitted from the expanded page.","items":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","description":"Parameter name, referenced as `{{name}}` in the template `page`."},"required":{"type":"boolean","default":false,"description":"When true, an instantiation omitting this parameter is an expansion error."},"description":{"type":"string","description":"Human-readable description of the parameter."}}}},"page":{"type":"object","description":"The parameterised page shape. An ordinary v2 page whose string leaves may embed `{{param}}` placeholders and `{{set:NAME}}` set references. After substitution the result MUST be a valid concrete page (it is validated as part of the expanded pages[])."}}},"pageInstance":{"type":"object","additionalProperties":false,"required":["templateRef"],"description":"A per-entity instantiation of a pageTemplate. Supplies only the values that vary. `register`, `schema` and `label` are first-class shortcuts folded into the parameter map (equivalent to listing them under `params`); `params` may carry any additional declared parameters (e.g. a field/column subset).","properties":{"templateRef":{"type":"string","description":"Id of the `pageTemplates[]` entry to instantiate. An unknown templateRef is an expansion error."},"register":{"type":"string","description":"Shortcut for the `register` parameter."},"schema":{"type":"string","description":"Shortcut for the `schema` parameter."},"label":{"type":"string","description":"Shortcut for the `label` parameter."},"params":{"type":"object","description":"Parameter values keyed by the template's declared parameter names. Values may be any JSON type (a field/column subset is an array value). Overrides the register/schema/label shortcuts on conflict.","additionalProperties":true},"override":{"type":"object","description":"Optional structural delta applied over the substituted page via the SAME base+delta merge semantics as mergeManifestDelta (layered-versioned-app-deltas alignment). Lets an instantiation patch a single widget/field without re-listing the page, and is where a per-user/app override sits on top of the template base. No second merge model is introduced.","additionalProperties":true}}},"sentinelFilterToken":{"type":"string","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.<key>}), @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","pattern":"^@resolve:[a-z][a-z0-9_-]*$|^@config\\.[A-Za-z][A-Za-z0-9_.]*\\??$","description":"Config context: IAppConfig-sourced values. @resolve:<key> resolved at manifest-load time; @config.<key> (trailing ? optional) at fetch time. Mirrors SENTINEL_TOKEN_PATTERNS.config."},"sentinelObjectToken":{"type":"string","pattern":"^@objectId$|^@object\\.[A-Za-z][A-Za-z0-9_]*$","description":"Object context: detail-page object tokens (@objectId, @object.<field>) 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.<field>|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_]*\\??$","description":"Workspace context: page-level workspace state (@workspace.<key>; trailing ? marks optional). Mirrors SENTINEL_TOKEN_PATTERNS.workspace."},"sentinelRouteToken":{"type":"string","pattern":"^@route\\.[A-Za-z][A-Za-z0-9_-]*$","description":"Route context: vue-router param substitution (@route.<param>) resolved by resolveRouteSentinels. Mirrors SENTINEL_TOKEN_PATTERNS.route."},"sentinelDeclarativeToken":{"type":"string","pattern":"^@self\\.[A-Za-z][A-Za-z0-9_]*$|^@ref:[A-Za-z][A-Za-z0-9_./-]*$|^@aggregate:.+$","description":"Declarative context (OpenRegister direction, resolved server-side): @self.<field> (the object's own field in a cross-object calc), @ref:<path>, @aggregate:<expr>. @ref/@aggregate are first-class members even though currently unused fleet-wide. Mirrors SENTINEL_TOKEN_PATTERNS.declarative."},"sentinelDeprecatedToken":{"type":"string","pattern":"^@currentFiscalYear$|^@page\\.[A-Za-z][A-Za-z0-9_]*$|^@runtime(?:\\.[A-Za-z][A-Za-z0-9_]*)?$","description":"Deprecated single-app inventions kept in the union so deployed manifests still validate during the migration window. The hydra token-vocabulary gate downgrades these to a WARN (see SENTINEL_DEPRECATIONS): @currentFiscalYear → @config.fiscalYear, @page.<key> → @workspace.<key>, @runtime → removal. Mirrors SENTINEL_TOKEN_PATTERNS.deprecated."},"sentinelVisibleWhenToken":{"type":"string","pattern":"^@total$","description":"manifest-form-logic: the shared visibleWhen predicate's source-mode collection-total marker, resolved client-side by evaluateVisibleWhen (utils/visibleWhen.js) — NOT one of the four IAppConfig/OpenRegister resolvers, but a pre-existing convention (already documented on $defs.visibleWhen and the banner widget) reachable through the sentinelGuardedValue-guarded pages[].config subtree for the first time via config.fields[].visibleWhen. Mirrors SENTINEL_TOKEN_PATTERNS.visibleWhen."},"sentinelAnswerToken":{"type":"string","pattern":"^@answer\\.[A-Za-z][A-Za-z0-9_]*$","description":"form-live-values: another answer in the same form (@answer.<field>), read by a form field's assign rule and resolved client-side by CnFormPage / CnFormDialog. Mirrors SENTINEL_TOKEN_PATTERNS.answer."},"sentinelRowToken":{"type":"string","pattern":"^@row\\.[A-Za-z][A-Za-z0-9_.@]*$","description":"Row context: a field of the row a row action runs on (@row.<field>, dot path allowed), read by the field-list widget's rowActions request url and payload (payload values resolve at any depth). Mirrors SENTINEL_TOKEN_PATTERNS.row."},"sentinelContextToken":{"type":"string","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.<name>.<field> (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.<field>, dot path allowed), read by a fact row's `route` params. Mirrors SENTINEL_TOKEN_PATTERNS.source."},"sentinelSubjectToken":{"type":"string","pattern":"^@subject\\.[A-Za-z][A-Za-z0-9_.]*$","description":"Subject context: a field of the record an attention card names (the first row of its ordered `visibleWhen` source query), read by the banner widget's action route params and query (@subject.<field>, dot path allowed). Mirrors SENTINEL_TOKEN_PATTERNS.subject."},"sentinelTokenAny":{"$comment":"Union of every canonical context plus the deprecated-during-window overlay. Single source the shared resolver + gate import.","anyOf":[{"$ref":"#/$defs/sentinelFilterToken"},{"$ref":"#/$defs/sentinelConfigToken"},{"$ref":"#/$defs/sentinelObjectToken"},{"$ref":"#/$defs/sentinelLinkToken"},{"$ref":"#/$defs/sentinelWorkspaceToken"},{"$ref":"#/$defs/sentinelRouteToken"},{"$ref":"#/$defs/sentinelDeclarativeToken"},{"$ref":"#/$defs/sentinelVisibleWhenToken"},{"$ref":"#/$defs/sentinelAnswerToken"},{"$ref":"#/$defs/sentinelRowToken"},{"$ref":"#/$defs/sentinelSubjectToken"},{"$ref":"#/$defs/sentinelContextToken"},{"$ref":"#/$defs/sentinelSourceToken"},{"$ref":"#/$defs/sentinelDeprecatedToken"}]},"sentinelTokenLeaf":{"$comment":"A string leaf: either NOT a sentinel (does not start with @) or a member of the closed vocabulary. An @-prefixed out-of-vocabulary string FAILS here instead of passing through additionalProperties:true.","anyOf":[{"not":{"pattern":"^@"}},{"$ref":"#/$defs/sentinelTokenAny"}]},"sentinelGuardedValue":{"$comment":"Recursive guard applied (via allOf) to pages[].config and widgetEntry so every string leaf anywhere beneath is checked against the closed vocabulary. Additive + backward-compatible: only @-prefixed out-of-vocabulary strings newly fail; all other shapes are untouched.","if":{"type":"string"},"then":{"$ref":"#/$defs/sentinelTokenLeaf"},"else":{"if":{"type":"array"},"then":{"items":{"$ref":"#/$defs/sentinelGuardedValue"}},"else":{"if":{"type":"object"},"then":{"additionalProperties":{"$ref":"#/$defs/sentinelGuardedValue"}}}}},"observability":{"type":"object","additionalProperties":false,"description":"Health checks + metric descriptors executed by the AppHost engine.","properties":{"_note":{"type":"string","description":"Free-form note for maintainers; ignored by the engine."},"health":{"type":"object","additionalProperties":false,"description":"Declarative health checks rendered as the ADR-006 {status, app, version, checks} shape.","properties":{"statusCodePolicy":{"type":"string","enum":["adr006","always200"],"default":"adr006","description":"HTTP status-code policy. 'adr006' returns 503 on a failed critical check; 'always200' always returns 200."},"checks":{"type":"array","description":"Health check descriptors.","items":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"type":"string","description":"Check id, surfaced as the key in the response's checks map."},"type":{"type":"string","enum":["database","filesystem","appEnabled","appConfig","orAvailable"],"description":"Engine check kind."},"severity":{"type":"string","enum":["critical","degraded"],"default":"critical","description":"critical → a failure flips status to error (503 under adr006); degraded → status degraded (200)."},"app":{"type":"string","description":"App id, for the appEnabled check."},"key":{"type":"string","description":"Config key, for the appConfig check."}}}},"cors":{"type":"boolean","default":false,"description":"ADR-040: when true the engine emits permissive CORS headers on the /api/health response."}}},"metrics":{"type":"array","description":"Prometheus metric descriptors. The {app}_ prefix, exposition format, and implicit {app}_info / {app}_up gauges are engine-owned.","items":{"type":"object","additionalProperties":false,"required":["name","type","source"],"properties":{"name":{"type":"string","description":"Metric name (without the {app}_ prefix the engine adds)."},"type":{"type":"string","enum":["gauge","counter"],"description":"Prometheus metric type."},"help":{"type":"string","description":"HELP text for the metric family."},"cacheTtl":{"type":"integer","minimum":0,"description":"ADR-040: optional per-metric cache TTL in seconds."},"source":{"type":"object","additionalProperties":false,"required":["kind"],"description":"Where the engine sources the value.","properties":{"kind":{"type":"string","enum":["tableCount","objectCount","objectSum","appConfig","provider"],"description":"Source kind. tableCount = COUNT over an allowlisted own-table; objectCount = OR objects by register/schema; appConfig = a numeric app-config value; provider = an IMetricsProvider implementation."},"table":{"type":"string","description":"tableCount: allowlisted [a-z0-9_] table name."},"register":{"type":"string","description":"objectCount: OR register slug."},"schema":{"type":"string","description":"objectCount / objectSum: OR schema slug."},"field":{"type":"string","description":"objectSum: the numeric object property to SUM."},"groupBy":{"type":"array","items":{"type":"string"},"description":"Columns to GROUP BY (one label per column)."},"labelMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Rename a groupBy column to a different Prometheus label name."},"labelDefaults":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Default label value for NULL/empty group columns."},"filter":{"type":"object","additionalProperties":true,"description":"Per-column operator filters (eq/neq/lt/lte/gt/gte/like)."},"key":{"type":"string","description":"appConfig: the config key whose numeric value is the sample."},"id":{"type":"string","description":"provider: the IMetricsProvider id to invoke."}}}}}}}},"splitView":{"type":"object","additionalProperties":false,"description":"The list and one open record, side by side. Declared on an index page; buildManifestRoutes() then emits a second route <route>/split/:id under the name <pageId>__split, carrying meta.cnSplitOf so CnPageRenderer still resolves it to this page and the list never unmounts.","properties":{"enabled":{"type":"boolean","default":false,"description":"Whether opening a row opens it beside the list. False renders exactly as today: a full page list and a full page detail."},"breakpoint":{"type":"integer","minimum":320,"maximum":2560,"default":1024,"description":"Viewport width in pixels below which the split address renders the full detail page instead of the pane, so a link sent from a laptop opens on a phone."},"paneWidth":{"type":"string","description":"CSS width of the detail pane, e.g. '38%' or '520px'. Defaults to '42%'."}}},"savedViewTree":{"type":"object","additionalProperties":false,"description":"Saved views render as a tree rather than a flat list, carry labels, and may ship with the app. Two hundred personal views in one dropdown is the problem this solves. Index pages only, and only meaningful alongside allowSavedViews: without the dropdown there is no view to put in a tree. Absent, the page renders the flat dropdown it renders today. The view entity itself, its parent, its labels and its slug are OpenRegister's (saved-search-views); this key declares how this page presents them.","properties":{"enabled":{"type":"boolean","default":false,"description":"Whether this page renders its saved views as a tree. False reads as a page that considered it and declined, and renders exactly as a page that never named the key."},"maxDepth":{"type":"integer","minimum":1,"maximum":5,"default":3,"description":"How deep a parent chain may go. A bound rather than a taste: inheritance resolves by walking the chain, so an unbounded depth turns one render into an unbounded walk, and a person who can nest for ever will. Past the bound a save is refused naming the view, which is louder than a tree nobody can read."},"templates":{"type":"array","description":"What a new view may start from. A template presets the columns, the sort and the export field set, and the person may change each. Declare none and Save current view means what it has always meant: the list as it stands.","items":{"type":"object","additionalProperties":false,"required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"name":{"type":"string"},"columns":{"type":"array","items":{"type":"string"}},"defaultSort":{"type":"string"},"exportFields":{"type":"array","items":{"type":"string"}}}}},"landingView":{"type":"object","additionalProperties":false,"description":"Which view a role opens this page on, as { role: slug }. A personal choice always wins and Reset returns here, so this is where somebody starts rather than where they are kept. A role's landing view changing mid-session applies on the next arrival: moving a reader's list out from under them is worse than a stale default.","patternProperties":{"^[A-Za-z0-9_.-]+$":{"type":"string"}}},"columnsPerRole":{"type":"object","additionalProperties":false,"description":"Which columns a role sees, as { role: [column] }. NARROWS index-columns-per-scope rather than replacing it: a role naming a column the scope does not offer gets nothing extra, because a per-role list that could add one would be a second, quieter way to put a field on screen the scope deliberately left off.","patternProperties":{"^[A-Za-z0-9_.-]+$":{"type":"array","items":{"type":"string"}}}},"groupBy":{"type":"string","description":"A field the list groups on, with a count per group. The counts are of the rows the list HOLDS, so on a paged list they are of the page and the surface says so; a second fetch per group is the cost this grouping exists to avoid."},"seeded":{"type":"array","deprecated":true,"description":"DEPRECATED since 2.129.0: declare default views with the index page's `config.defaultSavedViews`, which carries the criteria this key never had and is what the page actually lists. Nothing ever read this key, so moving off it loses nothing. Views that ship with the app. They render as their own group above the user's own, and a user may copy one but not delete it: a view the product promises is not a view one person can remove from everybody's install.","items":{"type":"object","additionalProperties":false,"required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The stable name a dashboard widget, an export action or an API caller cites this view by. Lowercase and dash only, because it appears in an address and in a manifest somebody else writes. Required: a seeded view with no slug is a view nothing can call, which is most of the reason to seed it."},"name":{"type":"string","description":"What the view is called in the control. Translatable."},"label":{"type":"string","description":"One label this view carries, for the label filter. A view may carry none and stays reachable when no label filter is active."},"parent":{"type":"string","description":"The slug of the view this one hangs under. Omitted, it sits at the root of the seeded group."},"actions":{"type":"array","description":"The action ids this view offers, so a triage view offers three and not thirty. INTERSECTED with what the reader may run and never added to: a view declaring an action the reader has no right to does not grant it. Declare none and the view offers the page's actions, as today.","items":{"type":"string"},"uniqueItems":true},"inherits":{"type":"array","description":"Which parts this view takes from its parent. Each part resolves independently, so a child may narrow the criteria and keep the parent's columns. Declared on a view with no parent it is a contradiction and the validator refuses it: there is nothing to inherit from.","items":{"type":"string","enum":["criteria","columns","sorting","defaultSort","exportFields"]},"uniqueItems":true}},"allOf":[{"description":"inherits without a parent is a declaration that reads as configured and resolves to nothing. Refused here rather than silently ignored at render, which is the shape this whole change exists to stop.","if":{"required":["inherits"]},"then":{"required":["parent"]}}]}}}},"boardView":{"type":"object","additionalProperties":false,"required":["statusField"],"description":"The board view of this list: one column per stage of a status field. Index pages only, and only offered when `viewModes` also lists `board`. The COLUMNS COME FROM THE SCHEMA, not from the values present in the loaded rows: a board built from the data would lose the stage nothing is in, which is the one you drag a card into, and would reorder itself as work moved. A move goes through the host's transition and never writes the status field, so a board can never move a case past a rule the case page enforces.","properties":{"statusField":{"type":"string","description":"The property the columns come from. Required: a board with no status field has nothing to be a board of. Its enum or lifecycle states are read from the register schema at RUNTIME, so a field with neither cannot be caught here; the view says so on screen instead of drawing empty columns."},"cardFields":{"type":"array","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."},"card":{"type":"object","additionalProperties":false,"description":"screens-kanban-parity: the board look's card, by role instead of by list. Each value is a dot-path to a field on the row. Missing, the board look takes the first of `cardFields` as the title and the rest as the sub line. The Nextcloud look ignores it.","properties":{"title":{"type":"string","description":"The field that is the card's title, and the link that opens it."},"sub":{"description":"The field, or fields, of the sub line; several are joined by a middle dot and an empty one drops out with its separator.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"pill":{"type":"string","description":"The field shown as a status pill."},"resolve":{"type":"object","additionalProperties":false,"description":"pill-color-field: the `pill` 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}}},"pillColors":{"type":"object","description":"Pill value to badge variant. A value without an entry takes the default variant.","additionalProperties":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"]}},"due":{"type":"string","description":"The date field of the footer's due text, marked by `dueRule`."},"owner":{"type":"string","description":"The field of the owner shown as an avatar (initials) at the footer's end."},"formatters":{"type":"object","description":"screens-board-reference-columns (2.97.0): a card field (one of the role fields above) to the id of a formatter in the app's `cnFormatters` registry, the one table columns name in `formatter`. The card shows the formatted text, so a reference reads as its name (`{ \"caseType\": \"caseTypeName\" }`).","additionalProperties":{"type":"string","minLength":1}}}},"colorField":{"type":"string","description":"screens-kanban-parity: the key on a column's state that holds its dot colour. Without it the lifecycle state's declared `color` is used, else the secondary text colour."},"sumField":{"type":"string","description":"screens-kanban-parity: a numeric field whose sum each column shows under its heading, formatted as the field's format declares."},"sumFormat":{"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"]},"currency":{"type":"string"},"decimals":{"type":"integer","minimum":0}}},"columnLimit":{"type":"integer","minimum":0,"default":0,"description":"screens-kanban-parity: how many cards a column draws before a dashed \"Show N more\" button. 0 draws every card. The count badge keeps showing the column's total."},"dueRule":{"$ref":"#/$defs/dueRule","description":"Marks late cards. A card whose date is before today gets an error edge and the label 'Overdue'; one due within `soonDays` gets 'Due soon'. Omitted, no card is marked."},"columnFields":{"type":"array","minItems":1,"description":"screens-board-reference-columns (2.97.0): the fields the columns can come from, offered in a \"Columns:\" switch after the search when there are two or more (\"status | type | assignee\"). The entry on `statusField` is the default. An entry with a `source` takes its columns from the rows the field REFERENCES (a case's status pointing at statusType rows), fetched, not from the values on the cards; `kind: \"values\"` (a user id, which references no schema) takes the values present plus a column for no value. Without a `source` or `kind` the field's enum or lifecycle states are read as before. A drop writes the reference: through `moveHandler` when named, else a PATCH of the field on the row.","items":{"type":"object","additionalProperties":false,"required":["field"],"properties":{"field":{"type":"string","minLength":1,"description":"The row field the columns come from."},"label":{"type":"string","description":"Its name in the column switch (an i18n key). Defaults to the field."},"kind":{"type":"string","enum":["values"],"description":"`values`: the field references no schema (a Nextcloud user id); the columns are the values present on the rows, by `valueLabels` or the value itself, then one column for no value (`emptyLabel`)."},"valueLabels":{"type":"object","additionalProperties":{"type":"string"},"description":"`kind: values`: raw value to the column label."},"emptyLabel":{"type":"string","description":"`kind: values`: the label of the column for no value (an i18n key). Defaults to \"Nobody\"."},"source":{"type":"object","additionalProperties":false,"description":"The referenced rows that are the columns: one list request to `register`/`schema` (defaults: the page's register and the property's `$ref`).","properties":{"register":{"type":"string","minLength":1},"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."},"neutralColor":{"type":"string","minLength":1,"description":"board-neutral-column-colour (2.101.0): the colour a referenced row holds when nobody chose one (the schema default, e.g. \"grey\"). A column merged from several rows (`mergeBy`) then takes the first row colour that is not this one, and this one only when every row has it. Compared as written: \"Grey\" is not \"grey\", and a hex value is not a name. Unset, a merged column takes its first row's colour."},"colorMap":{"type":"object","description":"board-column-colour-map (2.102.0): a stored row colour to the CSS colour its column is drawn in, e.g. `{ \"orange\": \"var(--nl-color-orange, #e17000)\" }`, so a palette name draws a theme token. Applied after the merge, so `neutralColor` still compares the stored values. A colour it does not map draws as before.","additionalProperties":{"type":"string","minLength":1}},"mergeBy":{"type":"string","description":"Merge referenced rows with the same value of this field into one column (statuses of every case type by `name`). A drop then writes the row of the card's own parent (`narrowBy`), and is refused when that parent has none."},"narrowBy":{"type":"object","additionalProperties":false,"required":["field","rowField"],"description":"The parent link: the referenced row's `field` equals the card's `rowField` (statusType.caseType = case.caseType). While the page filters on `rowField` with one value, the columns are that parent's rows only.","properties":{"field":{"type":"string","minLength":1},"rowField":{"type":"string","minLength":1}}}}},"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."},"states":{"$ref":"#/$defs/boardDeclaredStates"}}}},"moveHandler":{"type":"string","minLength":1,"description":"screens-board-reference-columns (2.97.0): a handler in the app's registry that moves a card on `statusField` (the app's transition), with the `columnFields[].moveHandler` call shape. Lets a manifest-only page run the host's transition, which the `runTransition` prop could not."},"moveTargetsHandler":{"type":"string","minLength":1,"description":"screens-board-reference-columns (2.97.0): where a card on `statusField` may go, with the `columnFields[].moveTargetsHandler` call shape."},"narrow":{"type":"object","additionalProperties":false,"required":["field"],"description":"screens-board-reference-columns (2.97.0): a select after the search (\"Case type: All case types | Woo-verzoek | ...\") that sets the page filter on `field`. With a reference column's `narrowBy.rowField` on the same field, the columns narrow to that parent's rows.","properties":{"field":{"type":"string","minLength":1,"description":"The row field the select filters on."},"label":{"type":"string","description":"The select's label (an i18n key), shown with a colon. Defaults to the property's title."},"allLabel":{"type":"string","description":"The option for no narrowing (an i18n key). Defaults to \"All\"."},"source":{"type":"object","additionalProperties":false,"description":"Where the options come from (defaults: the page's register and the property's `$ref`).","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"labelField":{"type":"string","description":"The option row's label field. Defaults to `title`."},"filter":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}}}}}},"columnSwitchLabel":{"type":"string","description":"screens-board-reference-columns (2.97.0): the column switch's label (an i18n key), shown with a colon. Defaults to \"Columns\"."},"hint":{"type":"string","description":"screens-board-reference-columns (2.97.0): one sentence after the board's controls, in the secondary colour (an i18n key), e.g. how a move works."},"wrap":{"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":{"type":"object","additionalProperties":false,"anyOf":[{"required":["field"]},{"required":["text"]},{"required":["countFrom"]}],"description":"A pill in a detail page header, rendered through CnStatusBadge: from one field of the record (`field`; nothing renders while it is empty), a constant (`text`), or a count of a related list (`countFrom` with `countLabel`; nothing renders at 0, while counting or after a failed count). screens-relation-pages, 2.85.0.","properties":{"field":{"type":"string","minLength":1,"description":"Dot-path to the value on the record."},"values":{"type":"array","items":{"type":"string"},"description":"With `field`: draw the pill only when the raw value is one of these (a priority pill for high and urgent only)."},"text":{"type":"string","minLength":1,"description":"A constant label (an i18n key), such as \"Inwoner\" or \"Klant\". Used when `field` is not set."},"countFrom":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"The list the pill counts with one list request (`_limit=1`, the response's `total`). `filter` values take the `@objectId` and `@object.<field>` tokens. Used when `field` is not set.","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true}}},"resolve":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"detail-pill-resolve: the field holds the id of a record of this register and schema; the pill shows that record's `labelField` (else its title or name) and keys `colorMap` by it. The raw value shows while the lookup runs and when it finds nothing.","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"colorField":{"type":"string","minLength":1,"description":"pill-color-field: the referenced record's property holding a colour name (an NL Design System hue such as `red` or `blue-light`, or a badge role); the pill takes the tone it maps onto."},"iconField":{"type":"string","minLength":1,"description":"pill-color-field: the referenced record's property holding a registered icon name, shown before the label."}}},"countLabel":{"type":"object","additionalProperties":false,"description":"The count pill's label for one and for more, i18n keys with `{count}`: `{ \"one\": \"{count} lopende zaak\", \"other\": \"{count} lopende zaken\" }`.","properties":{"one":{"type":"string","minLength":1},"other":{"type":"string","minLength":1}}},"colorMap":{"type":"object","description":"Raw value to badge variant. Keys are matched ignoring case.","additionalProperties":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"]}},"labels":{"type":"object","description":"Raw value to the i18n key / text to show. A value without an entry is shown as is.","additionalProperties":{"type":"string"}},"variant":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"],"default":"default","description":"Badge variant for a value `colorMap` does not name."}}},"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,"required":["field"],"description":"How a surface decides that a dated item is late. One shape for boards and lists, so a list and its board agree. A date before today is overdue. Days are whole calendar days in the reader's own timezone.","properties":{"field":{"type":"string","minLength":1,"description":"Dot-path to the due date on the row."},"soonDays":{"type":"integer","minimum":0,"default":3,"description":"How many days ahead still counts as due soon. 0 marks only today."},"variantWhen":{"type":"array","description":"The full rule list, in the shape a table's date cell uses, for a board that needs more than `soonDays`. Compared against the calendar days until the date (0 is today, negative is past); first match wins. `error` marks the card overdue, `warning` due soon. When set, `soonDays` is ignored.","items":{"type":"object","additionalProperties":false,"required":["op","value","variant"],"properties":{"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte"]},"value":{"type":"number"},"variant":{"type":"string","enum":["default","success","warning","error","danger"]}}}}}},"dateAxisView":{"type":"object","additionalProperties":false,"required":["startField","endField"],"description":"The date-axis view of this list: rows on a time scale, overlaps visible. Index pages only, and only offered when `viewModes` also lists `dateAxis`. IT HIDES NOTHING AND RESCHEDULES NOTHING: overlapping bars get their own track so the lane grows taller rather than losing a bar, rows with no usable dates go to a visible unplanned lane, and the only gesture is opening a row. A view that moved a bar on drag would be changing statutory dates from a picture.","properties":{"startField":{"type":"string","description":"Where a row's start is. Required: a bar needs both ends, and a row missing one is unplanned rather than drawn."},"endField":{"type":"string","description":"Where a row's end is. Required, for the same reason."},"laneField":{"type":"string","description":"What to put in a lane together, usually the person the work is on. Omitted, everything shares one lane."},"labelField":{"type":"string","description":"The field a bar is named by, on screen and in its accessible name. A bar positioned by percentage says nothing at all to a reader who cannot see it."}}},"personalisation":{"type":"object","additionalProperties":false,"description":"The app's own defaults for the per-user options CnPersonalPreferences renders. The values a person chooses are stored in their Nextcloud preferences, never here; this block is only the bottom layer of the app default, administered value, personal value order.","properties":{"enabled":{"type":"boolean","default":true,"description":"Whether the personal layer applies at all. An administrator setting this false collapses it: the options render as unavailable with the reason, rather than being silently ignored."},"disabledReason":{"type":"string","description":"What to tell a person when the personal layer is off. Shown beside the unavailable options."},"landingPage":{"type":"string","description":"Page id the app opens on when a person has chosen nothing."},"dateDisplay":{"type":"string","enum":["absolute","relative"],"default":"absolute","description":"How dates read by default. A relative date always carries its absolute date in the accessible name and the tooltip, because a term read as '3 dagen geleden' hides the date the term runs to."},"rememberLastView":{"type":"boolean","default":false,"description":"Whether each list reopens in the view mode that person last used on it."},"pinnedMenu":{"type":"array","items":{"type":"string"},"description":"Menu ids pinned to the top of the navigation by default. A person may reorder and repin their own."}}},"primaryAction":{"type":"object","required":["label"],"additionalProperties":false,"description":"A primary action declared on either a pages[] entry (active-page scoped) or nav.primaryAction (app-wide default). Rendered above the menu list as an NcAppNavigationNew button.","properties":{"id":{"type":"string","description":"Stable identifier for this action."},"label":{"type":"string","description":"i18n translation key / text for the button."},"icon":{"type":"string","description":"MDI icon name (e.g. 'Plus'). Defaults to 'Plus' when omitted."},"route":{"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. On a detail page it may name the record's fields as @object.<field> tokens."},"payload":{"description":"Free-form payload echoed back to the host inside the @primary-action-click event."},"action":{"$ref":"#/$defs/action","description":"A page action to run on click (zuiddrecht-pixel-gaps): CnAppNav then renders the button through CnActionButtons, so `type: \"open-form\"` (with `register` + `schema`) opens the create dialog without a create page, `navigate` pushes a route, `api-call` calls an endpoint. Drawn as one solid full-width primary button. `route` / `href` on the primary action itself are ignored while `action` is set."},"solid":{"type":"boolean","default":false,"description":"Draw the primary action as a solid, full-width primary button whatever its form (zuiddrecht-pixel-gaps). Default: a link action is already a primary button; a plain one is NcAppNavigationNew."},"permission":{"type":"string","description":"Permission the reader must hold for the action to render, checked like `menuItem.permission` (zuiddrecht-pixel-gaps)."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Display condition evaluated at render time, like `menuItem.visibleIf` (zuiddrecht-pixel-gaps). A gated-out page-scoped action falls back to `nav.primaryAction`."}}},"menuItem":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"A top-level navigation entry. May contain one level of nested children.","properties":{"id":{"type":"string","description":"Unique identifier for this menu entry. Sentinels (@resolve:*) are NOT allowed — this is a router invariant."},"label":{"type":"string","description":"i18n translation key resolved by the consuming app's t() function at render time."},"translateLabel":{"type":"boolean","default":true,"description":"Whether CnAppNav runs `label` through the translate function. Set false for a user-authored title (e.g. a catalog name) that must render as written even when it equals a translation key such as 'Search'. Rendered as text either way, never as HTML."},"icon":{"type":"string","description":"CSS class for the icon (e.g. 'icon-checkmark') or MDI component name."},"route":{"type":"string","description":"Vue-router route name (matches a pages[].id) that this entry navigates to. Sentinels (@resolve:*) are NOT allowed — this is a router invariant."},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Optional vue-router query parameters merged into the target route link (e.g. a filter like { caseType: '<uuid>' }). Deep-links a nav entry to a pre-filtered index page."},"params":{"type":"object","additionalProperties":{"type":["string","number"],"minLength":1},"description":"Optional vue-router route params for a parameterised route (e.g. { catalogSlug: 'news' } for a page at '/catalogs/:catalogSlug'). String values must be non-empty. CnAppNav links to { name: route, params, query }. The entry is active only while every declared param equals the current route's param (compared as strings), so sibling entries on one route that differ in params are never marked together. On pages whose path sits below its page path with these params filled in, it is active too: by the same params check when those routes carry the same param names, otherwise as the one entry marked below its route."},"order":{"type":"integer","description":"Display order in the menu. Items without an order render last."},"permission":{"type":"string","description":"Permission string the user must hold for this entry to render."},"section":{"type":"string","enum":["main","footer","settings","integrations"],"default":"main","description":"Where the entry renders inside CnAppNav. 'main' (default) places it in the top list. 'footer' pins the entry to the bottom as a flat item above the settings foldout. 'settings' places it inside the NcAppNavigationSettings gear-icon foldout. 'integrations' renders the entry NOT in the navigation at all but in the Integrations section at the bottom of the per-user settings modal — the destination for links that leave this app for another (ADR-110). An 'integrations' entry MUST be clickable (href); a group with children is meaningless there."},"type":{"type":"string","enum":["item","caption"],"default":"item","description":"Render kind. 'item' (default) renders a clickable NcAppNavigationItem. 'caption' renders an NcAppNavigationCaption section divider — only label, id, order, and section are honoured."},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]},{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"A FILTERED count (zuiddrecht-pixel-gaps): the total of this register and schema with `filter` applied (filter-value tokens such as `@me` and `@today` resolve at fetch time), fetched by CnAppRoot under the entry's own key so an index page's whole-schema total is untouched. Renders \"My work 6\" beside \"All cases 48\".","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true}}}],"description":"Counter badge in the entry's #counter slot. Positive integer renders as-is; 'auto' resolves from cnMenuCounts (populated by CnAppRoot from useObjectStore totals) for the entry's resolved index-type page (register + schema); an object `{ register, schema, filter? }` is the total of that filtered list."},"counterVariant":{"type":"string","enum":["default","attention"],"default":"default","description":"screens-chrome-parity: how the entry's `count` reads. `attention` asks for the red attention pill under the board look (unread or overdue work) and maps to the counter bubble's highlighted state without it. `default` is the neutral pill."},"pinned":{"type":"boolean","default":false,"description":"Forwarded to NcAppNavigationItem's pinned prop. NC bottom-pins pinned items inside the parent list region."},"open":{"type":"boolean","default":false,"description":"Initial expansion state for a parent entry with children. When true and children[] is non-empty, the parent renders with :open=\"true\" so children are visible on mount."},"href":{"type":"string","description":"External URL. When set, the entry opens the URL in a new tab instead of dispatching a vue-router navigation."},"action":{"type":"string","enum":["user-settings","admin-settings","replay-walkthrough"],"description":"Built-in action to invoke when the entry is clicked. Closed enum. 'admin-settings' opens the app-level admin-settings dialog (distinct from the per-user settings dialog). 'replay-walkthrough' re-runs the product walkthrough (ADR-043), optionally for a specific tourId."},"tourId":{"type":"string","description":"Tour id for the 'replay-walkthrough' action (ADR-043); replays that tour from its first step. Omit to replay the default tour."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Optional display condition evaluated at render time."},"dynamicSource":{"$ref":"#/$defs/dynamicSource","description":"Optional dynamic source for populating nested menu items from a data endpoint. Carry-forward from v1.3.0."},"children":{"type":"array","items":{"$ref":"#/$defs/menuItemLeaf"},"description":"Nested entries. Only one level of nesting is supported."}}},"menuItemLeaf":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"A nested menu entry. Has no further children.","properties":{"id":{"type":"string"},"label":{"type":"string"},"translateLabel":{"type":"boolean","default":true,"description":"Same as menuItem.translateLabel: false renders `label` as written instead of translating it."},"icon":{"type":"string"},"route":{"type":"string"},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Optional vue-router query parameters merged into the target route link (e.g. a filter like { caseType: '<uuid>' }). Deep-links a nav entry to a pre-filtered index page."},"params":{"type":"object","additionalProperties":{"type":["string","number"],"minLength":1},"description":"Optional vue-router route params for a parameterised route; string values must be non-empty. Same contract as menuItem.params: part of the link, and the entry is active only while every declared param equals the current route's param."},"order":{"type":"integer"},"permission":{"type":"string"},"section":{"type":"string","enum":["main","footer","settings","integrations"],"default":"main"},"type":{"type":"string","enum":["item","caption"],"default":"item"},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]}]},"counterVariant":{"type":"string","enum":["default","attention"],"default":"default"},"pinned":{"type":"boolean","default":false},"href":{"type":"string"},"action":{"type":"string","enum":["user-settings","admin-settings","replay-walkthrough"]},"tourId":{"type":"string","description":"Tour id for the 'replay-walkthrough' action (ADR-043)."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition"}}},"dynamicSource":{"type":"object","description":"Dynamic source configuration for populating menu items from a backend endpoint. Carry-forward from v1.3.0 — identical shape to v1.","additionalProperties":true,"properties":{"url":{"type":"string","description":"URL to fetch menu items from."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method for the fetch."},"labelField":{"type":"string","description":"Property name in the response items to use as the menu entry label."},"idField":{"type":"string","description":"Property name in the response items to use as the menu entry id."},"routeField":{"type":"string","description":"Property name in the response items to use as the menu entry route."}}},"linkModule":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"One module on a type='links' page. The app decides whether it is in the menu (`enabled`, or `enabledIf` against runtime). Every card opens; a module that is off links to the admin settings (`settings`).","properties":{"id":{"type":"string","minLength":1,"description":"Unique within the page's modules."},"label":{"type":"string","minLength":1,"description":"i18n key / the module's title."},"description":{"type":"string","description":"i18n key / one or two lines under the title."},"icon":{"type":"string","description":"MDI icon name."},"enabled":{"type":"boolean","description":"Whether the module is in the menu. Wins over enabledIf."},"enabledIf":{"$ref":"#/$defs/visibleIfCondition","description":"Condition, evaluated like visibleIf, that says the module is in the menu."},"route":{"type":"string","minLength":1,"description":"Where Open goes: a route name or a path starting with '/'. Defaults to the first page."},"params":{"type":"object","additionalProperties":{"type":["string","number"]}},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}},"pages":{"type":"array","description":"The module's pages, drawn as links and named in the 'N pages' line.","items":{"type":"object","required":["label"],"additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"label":{"type":"string","minLength":1},"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":{"type":["string","number"]}},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}},"href":{"type":"string","minLength":1},"highlight":{"type":"boolean","description":"Named in the line of a module with more than four pages ('15 pages, including ...')."}}}},"settings":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"description":"Where an admin puts a module that is off in the menu (the admin settings), shown on its card as '<prefix> <label>'.","properties":{"label":{"type":"string","description":"i18n key / link text. Defaults to 'Admin settings'."},"prefix":{"type":"string","description":"i18n key / text before the link. Defaults to 'Put it in the menu:'."},"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":{"type":["string","number"]}},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}},"href":{"type":"string","minLength":1},"permission":{"type":"string"},"visibleIf":{"$ref":"#/$defs/visibleIfCondition"}}},"permission":{"type":"string","description":"Permission the user must hold for the module to render."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Display condition, evaluated the way a menu entry's is."}}},"linkCard":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"One card on a type='links' page: a link to another page of the app, or to a URL. Same vocabulary as menuItem and navCardEntry.","properties":{"id":{"type":"string","minLength":1,"description":"Unique within the page's cards."},"label":{"type":"string","minLength":1,"description":"i18n key / text of the card's title."},"description":{"type":"string","description":"i18n key / one line under the title."},"icon":{"type":"string","description":"MDI icon name (e.g. 'CashMultiple')."},"category":{"type":"string","description":"Key in config.categories naming the group this card sits in. A card without one, or with an unknown one, renders first under no caption."},"route":{"type":"string","minLength":1,"description":"Route name (a pages[].id), or a router path when it starts with '/'. Resolved through the router, so it works with and without /index.php. Mutually exclusive with href."},"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."},"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."}},"allOf":[{"not":{"required":["route","href"]}},{"anyOf":[{"required":["route"]},{"required":["href"]}]}]},"navCardEntry":{"type":"object","required":["id","label"],"additionalProperties":false,"description":"One card in a nav-card-grid built-in widget (ADR-044 §4 cards-collapse) — an arbitrary navigation link, not an OpenRegister object. Mirrors the menuItem/primaryAction vocabulary: id + label required, route/href are alternative navigation targets and mutually exclusive, count supports the same integer-or-\"auto\" shape as menuItem.count.","properties":{"id":{"type":"string","description":"Unique identifier for this card within its widget's entries[] array."},"label":{"type":"string","description":"i18n translation key / text rendered as the card's title."},"description":{"type":"string","description":"Optional explanatory text rendered below the label, associated to the card via aria-describedby (never aria-label)."},"icon":{"type":"string","description":"MDI icon name (e.g. 'ChartLine'), resolved the same way as menuItem.icon."},"route":{"type":"string","description":"Vue-router route name (matches a pages[].id) this card navigates to. Mutually exclusive with href."},"href":{"type":"string","description":"External URL opened per the rendering component's target handling. Mutually exclusive with route."},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]}],"description":"Count badge on the card. Positive integer renders as-is; 'auto' resolves from cnMenuCounts (populated by CnAppRoot) for the card's resolved route page (register + schema), the same mechanism menuItem.count:\"auto\" uses."},"order":{"type":"integer","description":"Display order among sibling cards. Cards without an order render last."},"permission":{"type":"string","description":"Permission string the user must hold for this card to render."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Optional display condition evaluated at render time."}},"allOf":[{"description":"route and href are mutually exclusive navigation targets — a card links either in-app or externally, never both.","not":{"required":["route","href"]}}]},"widgetEntry":{"type":"object","required":["widgetKey","slot","gridX","gridY","gridWidth","gridHeight"],"additionalProperties":false,"description":"A uniform widget placement entry used on every page type in v2. Replaces the v1 widgetDef + layoutItem pair with a single shape. Its props/dataSource/filter string leaves are guarded against the closed sentinel vocabulary via the sentinelGuardedValue allOf. The cross-field constraint gridX + gridWidth <= 12 CANNOT be expressed in JSON Schema (arithmetic over sibling fields) — it is documented here and enforced at runtime by validateManifestV2() as a post-schema check. Failure message: \"Widget '{widgetKey}' in slot '{slot}': gridX ({gridX}) + gridWidth ({gridWidth}) exceeds 12\".","properties":{"roles":{"type":"array","items":{"type":"string","minLength":1},"description":"Group ids that may see this widget. A DECLARATION THE HOST APP ENFORCES, not something this library evaluates: the check belongs on the read, where the figures are, because a widget hidden in the browser still had its data in the page payload (ADR-004). Pair it with `visibleWhen` pointing at an endpoint of the app's own, which IS evaluated here and collapses the cell when the fetch or the shape fails, so the declaration and the layout cannot disagree. Omitting it means every reader of the page sees the widget. Added because the key was expressible in the legacy `config.widgets[]` array, whose items are open, and refused here, so the same declaration validated on one page and was rejected on another.","examples":[["controllers","beheerders","admin"]]},"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"Render this widget only while the predicate holds. The same shape every other surface takes it in. It was already honoured at runtime for widgets (CnDashboardPage reads `def.visibleWhen`) and could not be declared in this shape, so a widget in the uniform entry had no way to express a visibility condition at all while one in the legacy array did."},"requiredApp":{"type":"string","minLength":1,"description":"Nextcloud app id this widget leans on — its data lives in that app's register, or its component comes from that app's integration leaf. When the app is absent the widget renders its normal chrome and a set-up state and issues NO request. Declared rather than inferred from content.register, because a register slug is not always the app id and a widget can depend on an app without querying it."},"id":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"Optional stable, kebab-case identity for this widget entry. Used as the merge key under the `delta` merge mode (mergeManifestDelta keys widgets[] by id) so a stored delta can patch a single widget without resending the array. Omitting it keeps the entry valid; a delta simply cannot target it by key. Must be unique within a page's widgets[]."},"widgetKey":{"type":"string","description":"Registry key identifying the widget component. Resolved against the customComponents map passed to CnAppRoot at boot."},"slot":{"$ref":"#/$defs/slotValue","description":"Placement slot. Valid values: body, sidebar, header-actions, footer, modal (literals) or tab:<id> / section:<id> (pattern). Per-slot grid constraints are enforced via allOf/if-then below."},"gridX":{"type":"integer","minimum":0,"description":"0-based column start position. gridX + gridWidth MUST be <= 12 (enforced by post-schema check, not JSON Schema)."},"gridY":{"type":"integer","minimum":0,"description":"0-based row start position. For header-actions slot, MUST be 0 (enforced by allOf/if-then)."},"gridWidth":{"type":"integer","minimum":1,"maximum":24,"description":"Column span. For the default 12-column slots, 1-12. A page MAY widen a slot via config.slotColumns (up to the schema cap of 24); the real per-slot bound gridX + gridWidth <= resolvedColumns is enforced by the post-schema check, not JSON Schema. For sidebar slot, MUST be exactly 1 (enforced by allOf/if-then)."},"gridHeight":{"type":"integer","minimum":1,"description":"Row span (>= 1)."},"headerLink":{"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, a `tab` a `#<tab>` 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"},"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":{"type":"boolean","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."},"sizeToContent":{"type":["boolean","integer"],"minimum":1,"description":"Opt-in: hand this placement's height to whatever it renders, so a widget that decides AT RUNTIME it has nothing to show costs no row. `true` sizes purely to content; an integer sizes to content too but caps the height at that many rows (GridStack's maximum, NOT a minimum: content shorter than N rows is as short as it is, content taller is clipped at N). It makes gridHeight advisory for this widget — right for anything whose height is whatever it turned out to be (a banner whose condition is false, a list that came back empty), wrong for anything with a deliberate size. Maps to CnDashboardGrid's per-item `sizeToContent`."},"props":{"type":"object","description":"Optional widget-specific props passed at render time. Shape is widget-specific; not constrained here.","additionalProperties":true},"tabGroup":{"type":"string","description":"Optional: groups this widget into a named tab within the slot. Only meaningful for tab:* and section:* slots."},"dataSource":{"$ref":"#/$defs/dataSource","description":"Optional declarative data binding. Carry-forward from v1 widgetDef.dataSource — same shape."},"dateChip":{"type":"boolean","default":false,"description":"Opt-in (custom widgets only): when true AND the page's `dateRange.enabled` is true, the shared date-range chip renders in this widget's title bar (chart widgets with a `dataSource.bucket` get it automatically). Carry-forward from v1 layoutItem.dateChip — same semantics."},"_note":{"type":"string","description":"Human-readable note. Optional on most widgets. REQUIRED when the containing page has type 'custom' — in that case this field must document why a standard page type was not feasible."},"ncDashboard":{"type":"object","additionalProperties":false,"description":"Opt-in: also publish this placement as a widget on the native Nextcloud dashboard, alongside the app's own pages. An object rather than a boolean because a Nextcloud panel is a single narrow column with its own title, icon and link, none of which the grid placement carries. Presence of this key REQUIRES an `id` on the same entry (enforced in allOf below), and that id MUST be unique across the WHOLE manifest, not merely within its own page's widgets[] — Nextcloud's widget namespace is per app, so two pages both declaring id 'recent' derive one shared panel. Manifest-wide uniqueness is beyond JSON Schema; validateManifestV2 enforces it post-schema. Omitting the key leaves the widget visible only inside the app.","properties":{"title":{"type":"string","description":"Panel title on the Nextcloud dashboard. Falls back to the widget's own title when omitted."},"icon":{"type":"string","description":"MDI icon name (PascalCase), e.g. ViewDashboardOutline."},"order":{"type":"integer","description":"Initial ordering hint, passed through as IWidget::getOrder(). It decides where the panel first appears; once a user rearranges their own dashboard their arrangement wins, so this is a hint and never a guarantee."},"link":{"type":"string","pattern":"^(?![a-zA-Z][a-zA-Z0-9+.-]*:)(?!//)\\S+$","description":"In-app route the panel header links to, so a reader can open the page where the widget's full context lives. Written the way `pages[].route` is, e.g. '/reporting' — the host resolves it against its own app root with generateUrl() and never renders it bare. A scheme ('https:', and 'javascript:' above all) and a protocol-relative '//host' are both refused: the panel is rendered by Nextcloud's dashboard, outside this app's own pages, so a manifest is not a place to hand it an offsite destination."}}}},"allOf":[{"$ref":"#/$defs/sentinelGuardedValue"},{"description":"ncDashboard requires a stable id. The host derives a permanent Nextcloud widget id from `id`, and Nextcloud stores each user's chosen widgets by that id in its own namespace, which no migration of this app can reach. A widget identified by array position would move onto a different widget whenever the array is reordered, silently changing what a user sees. `id`'s own pattern already refuses an empty string, so requiring the key is the whole of the rule expressible here; the manifest-wide uniqueness half lives in validateManifestV2.","if":{"required":["ncDashboard"]},"then":{"required":["id"]}},{"description":"sidebar slot: gridWidth MUST equal 1. The sidebar is a fixed-width panel; widgets always span the full width.","if":{"properties":{"slot":{"const":"sidebar"}},"required":["slot"]},"then":{"properties":{"gridWidth":{"const":1}}}},{"description":"header-actions slot: gridY MUST equal 0. All header action widgets live in a single row.","if":{"properties":{"slot":{"const":"header-actions"}},"required":["slot"]},"then":{"properties":{"gridY":{"const":0}}}},{"description":"object-table built-in widget: when props declares a `source` it must match the declarative self-fetch shape, an `endpointSource` (Wave 2) must match the shared endpoint binding (exactly one of source | endpointSource — post-schema check), and `actions` must be typed manifest actions (incl. type:'object-op'). Other props stay free-form (pass-through to CnDataTable).","if":{"properties":{"widgetKey":{"const":"object-table"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"source":{"$ref":"#/$defs/objectTableSource"},"endpointSource":{"$ref":"#/$defs/endpointSource","description":"Wave 2 (#91): endpoint-bound rows — the payload at responsePath must be an array; columns / formatters / rowRoute / actions apply unchanged on top. Exactly one of source | endpointSource (post-schema check)."},"actions":{"type":"array","items":{"$ref":"#/$defs/action"},"description":"Declarative row/widget actions rendered by CnWidgetObjectTable. object-op patch/delete render per row; object-op create renders as a widget-scoped footer affordance."},"pageSize":{"type":"integer","minimum":1,"description":"Rows per page: the table shows one page with the board pager under it (\"4 of 7\", DqCatalogus). Under a pager `source.limit` is how many rows are read (2.127.0)."},"rowClass":{"type":"array","description":"#91: declarative per-row CSS class rules compiled into CnDataTable's rowClass function. Each rule { when: { field, op?, value }, class } adds `class` to a row when the shared visibleWhen predicate holds against that row (field = dot-path into the row, op = eq|neq|gt|gte|lt|lte default eq, value = literal). Rules evaluate in order and every matching class is space-joined — so overdue / at-risk rows can be highlighted from the manifest. A host-supplied rowClass FUNCTION (runtime-only) still passes straight through.","items":{"type":"object","additionalProperties":false,"required":["when","class"],"properties":{"when":{"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."}}}}}}}}},{"description":"nav-card-grid built-in widget (ADR-044 §4 cards-collapse): props.entries is REQUIRED and each entry must match the navCardEntry shape. Unlike stats-block's optional entries[], nav-card-grid has no dataSource alternative — it renders arbitrary navigation links, not fetched data.","if":{"properties":{"widgetKey":{"const":"nav-card-grid"}},"required":["widgetKey"]},"then":{"required":["props"],"properties":{"props":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","items":{"$ref":"#/$defs/navCardEntry"},"description":"One card per array entry, rendered by CnNavCardGrid."}}}}}},{"description":"stats-block built-in widget: when props declares `entries[]` each entry must match the statsBlockEntry shape. The exactly-one-of dataSource | props.entries constraint is a cross-field rule enforced by validateManifestV2() as a post-schema check (clear error message), not by JSON Schema.","if":{"properties":{"widgetKey":{"const":"stats-block"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/$defs/statsBlockEntry"},"description":"Multi-entry KPI sources — each entry renders one CnStatsBlock within the widget card. Mutually exclusive with a single dataSource."}}}}}},{"description":"banner built-in widget (CnBannerWidget, Wave 1): a declarative notice banner. props { variant, text, visibleWhen?, route? } — visibleWhen is a simple {endpoint|source, field?, op?, value} predicate evaluated at mount (fail-safe: hidden on fetch failure).","if":{"properties":{"widgetKey":{"const":"banner"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"variant":{"type":"string","enum":["info","warning","error","success"],"description":"Banner severity variant (NcNoteCard type). Default info."},"text":{"type":"string","description":"Pre-translated banner text (required for the banner to render)."},"visibleWhen":{"type":"object","additionalProperties":false,"description":"Optional visibility condition: exactly one of `endpoint` (same-origin JSON URL) or `source` (OpenRegister object query). `field` is a dot-path into the response (source: into the first result; omit or use '@total' for the collection total). Fail-safe: the banner stays hidden until the predicate evaluates true.","properties":{"endpoint":{"type":"string","description":"Same-origin URL returning JSON."},"source":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"OpenRegister object query; `filter` supports the shared @-token grammar.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"filter":{"type":"object","additionalProperties":true},"order":{"type":"object","additionalProperties":{"type":"string","enum":["asc","desc"]},"description":"Order of the query (`{ field: \"asc\" | \"desc\" }`), so the first result is the first of an ordered list. With it an attention card names that row: `{subject.<field>}` in its title, reason and kicker and `@subject.<field>` in an action route read it (screens-start-here-banner, 2.89.0)."}}},"field":{"type":"string","description":"Dot-path into the response ('@total' or omitted = collection total for a source)."},"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte","empty","notEmpty"],"default":"eq","description":"Comparison operator. `notEmpty` on `id` is the \"there is a first row\" test of a card that names one."},"value":{"description":"Literal right-hand side of the comparison."}}},"route":{"description":"Optional click-through: a vue-router route NAME (string) or a full location object. Makes the banner text an accessible button.","oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]},"layout":{"type":"string","enum":["banner","attention"],"default":"banner","description":"How the banner renders. `banner` (default) is the note card. `attention` is a card with a severity edge, a kicker, a title, a reason line and up to two actions."},"stacked":{"type":"boolean","default":false,"description":"Attention card: put the actions under the text instead of beside it, with a 16px title (PqMijnWerk \"Begin hier\"; screens-start-here-banner, 2.89.0)."},"kicker":{"type":"string","description":"Attention card: the small label above the title."},"title":{"type":"string","description":"Attention card: the title. Falls back to `text`."},"reason":{"type":"string","description":"Attention card: the line that says why this needs attention. `{value}` is replaced by the value visibleWhen read."},"actions":{"type":"array","maxItems":2,"description":"Attention card: at most two actions. The first is the primary one unless an action sets `primary: true`.","items":{"type":"object","required":["label"],"additionalProperties":false,"properties":{"id":{"type":"string","description":"Identifier passed with the `action` event of an action that has no route or href."},"label":{"type":"string","description":"The text on the action."},"route":{"description":"A vue-router route NAME (string) or a full location object.","oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]},"href":{"type":"string","description":"An URL, used when there is no route."},"primary":{"type":"boolean","description":"Render this action as the primary one."}}}}}}}}},{"description":"record-list built-in widget (CnRecordListWidget): the action under the list, `props.content.footerAction` (2.126.0), and a row's colour `dot` and `flags` (2.129.0). Other content keys stay free-form.","if":{"properties":{"widgetKey":{"const":"record-list"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"footerAction":{"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":{}}}}}}}}}}},{"description":"notice built-in widget (CnNoticeWidget): one toned box with an icon, a bold title and a text, through the host's translate, in the side column or the grid (DqSociaalDomein, 2.128.0).","if":{"properties":{"widgetKey":{"const":"notice"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"variant":{"type":"string","enum":["info","warning","error","success"],"default":"info"},"title":{"type":"string"},"text":{"type":"string"},"icon":{"type":"string","minLength":1}}}}}}}},{"description":"related built-in widget (CnRelatedObjectsWidget): grouping by relation (2.129.0). Other content keys stay free-form.","if":{"properties":{"widgetKey":{"const":"related"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"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."},"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\")."}}}}}}}},{"description":"nc-widget built-in widget (CnNcWidgetWidget): leaving items out (2.129.0). Other content keys stay free-form.","if":{"properties":{"widgetKey":{"const":"nc-widget"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"excludeApps":{"type":"array","items":{"type":"string","minLength":1},"description":"Apps whose items are left out (an item's app is its own app field, else the /apps/<app>/ part of its link or icon), e.g. [\"systemtags\"]."},"excludeTypes":{"type":"array","items":{"type":"string","minLength":1},"description":"Item types that are left out."}}}}}}}},{"description":"decision-list built-in widget (CnDecisionListWidget): per-row sign-offs, each with an answer, its fields and a reason, sent to an app endpoint (2.133.0). Texts go through the host translate.","if":{"properties":{"widgetKey":{"const":"decision-list"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"source":{"type":"object","additionalProperties":true,"description":"An endpointSource: `url`, `params?`, `responsePath?`."},"itemsPath":{"type":"string","minLength":1},"idField":{"type":"string","minLength":1},"title":{"type":"string","description":"A template filled from a row."},"meta":{"type":"string","description":"A template filled from a row."},"answers":{"type":"array","items":{"type":"object","required":["value","label"],"additionalProperties":true,"properties":{"value":{},"label":{"type":"string","minLength":1},"fields":{"type":"array","items":{"type":"object","required":["field"],"additionalProperties":true,"properties":{"field":{"type":"string","minLength":1},"type":{"type":"string","enum":["date","text"]},"label":{"type":"string"},"required":{"type":"boolean"}}}}}}},"reason":{"description":"`{ label?, required?, field? }` (required by default, sent as `field`, default `reason`); `false` drops it.","anyOf":[{"type":"boolean","const":false},{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string"},"required":{"type":"boolean"},"field":{"type":"string","minLength":1}}}]},"submit":{"type":"object","required":["request"],"additionalProperties":true,"properties":{"label":{"type":"string"},"request":{"type":"object","required":["url"],"additionalProperties":true,"properties":{"method":{"type":"string","minLength":1},"url":{"type":"string","minLength":1},"body":{"type":"object","additionalProperties":true}}}}},"answerField":{"type":"string","minLength":1},"emptyText":{"type":"string"},"errorTitle":{"type":"string"},"answerLabel":{"type":"string"}}}}}}}},{"description":"retention built-in widget (CnRetentionWidget): a record's stored retention, and a recompute for archivists (2.133.0). Texts go through the host translate.","if":{"properties":{"widgetKey":{"const":"retention"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"title":{"type":"string"},"subtitle":{"type":"string"},"statusField":{"type":"string","minLength":1},"archivedValues":{"type":"array","items":{"type":"string"}},"archivistGroups":{"type":"array","items":{"type":"string","minLength":1},"description":"Groups that may recompute (default [\"archivaris\"]; admins always may)."},"recompute":{"type":"boolean","description":"false hides the recompute control."}}}}}}}},{"description":"plan built-in widget (CnPlanWidget): a record's planned steps from OpenRegister's case layer (2.133.0).","if":{"properties":{"widgetKey":{"const":"plan"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"emptyText":{"type":"string","description":"Through the host translate."}}}}}}}},{"description":"work-queue built-in widget (CnWorkQueue): one queue of everything waiting on the reader, in groups that can be hidden until tomorrow (2.133.0).","if":{"properties":{"widgetKey":{"const":"work-queue"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","properties":{"source":{"type":"object","additionalProperties":true,"description":"An endpointSource answering `{ groups, hiddenGroups?, unavailable? }`."},"hide":{"type":"string","minLength":1,"description":"A url template with `{group}` that hides a group until tomorrow."},"show":{"type":"string","minLength":1,"description":"A url template with `{group}` that shows a hidden group again."},"itemHref":{"type":"string","minLength":1,"description":"A template filled from an item, for items without `route` or `href`."},"waitingParties":{"type":"object","additionalProperties":{"type":"string"}},"emptyText":{"type":"string"}}}}}}}},{"description":"audit-trail built-in widget (CnAuditTrailWidget, Wave 1): the object change-log card. All props are optional — register/schema/objectId normally arrive via the detail-page object-context merge; explicit props win.","if":{"properties":{"widgetKey":{"const":"audit-trail"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"register":{"type":"string","description":"OpenRegister register slug/id (context merge fallback when omitted)."},"schema":{"type":"string","description":"OpenRegister schema slug (context merge fallback when omitted)."},"objectId":{"type":"string","description":"The audited object's id (context merge fallback when omitted)."},"title":{"type":"string","description":"Optional card title override."},"maxDisplay":{"type":"integer","minimum":1,"description":"Maximum audit rows to render (default 5)."}}}}}},{"description":"chart widget display passthrough (Wave 1): additive presentation keys forwarded verbatim to CnChartWidget by CnDashboardPage's dispatcher and the v2 grid. Other chart props stay free-form.","if":{"properties":{"widgetKey":{"const":"chart"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"horizontal":{"type":"boolean","description":"Render type:'bar' charts horizontally (row bars)."},"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'.","oneOf":[{"type":"string","enum":["currency","currency-compact","percent","number"]},{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","enum":["currency","currency-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'."}}}]},"colorMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Per-category colour map ({ categoryLabel: cssColor }) for pie/donut/radialBar slices and (distributed) bar categories."},"emptyLabel":{"type":"string","description":"Empty-state message rendered instead of the chart when the resolved series contain no data points."},"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","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","additionalProperties":false,"required":["row","column"],"description":"chartKind 'matrix': the cell to outline, usually the record's on a detail page. `row` matches a series key, name or 1-based position; `column` an entry of columnKeys, a category label or a 1-based position. Both take tokens such as '@object.behaviour'.","properties":{"row":{"type":["string","number"]},"column":{"type":["string","number"]},"columnKeys":{"type":"array","items":{"type":["string","number"]}}}},"cellSource":{"type":"object","additionalProperties":false,"required":["register","schema","rowField","columnField","valueField"],"description":"chartKind 'matrix': read the cells from records that each hold one cell. The records are listed from register/schema with `filter` (tokens such as '@object.matrixVersion' resolved; the list waits while one is unresolved), then laid out by rowField / columnField with valueField as the cell. Labels come from rowLabels / columnLabels maps or rowLabelField / columnLabelField; order from rowOrder / columnOrder lists, else rowOrderField / columnOrderField, else natural order. valueLabels maps a stored value to the text shown; colorMap may key on either. With cellsPath the matrix sits inside ONE record: the first record filter finds (else fallbackFilter), its array at cellsPath narrowed by cellFilter (tokens allowed), and the axis order from rowOrderPath / columnOrderPath on that record. Wins over series / categories.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"filter":{"type":"object","additionalProperties":true},"rowField":{"type":"string"},"columnField":{"type":"string"},"valueField":{"type":"string"},"rowLabelField":{"type":"string"},"columnLabelField":{"type":"string"},"rowLabels":{"type":"object","additionalProperties":{"type":"string"}},"columnLabels":{"type":"object","additionalProperties":{"type":"string"}},"valueLabels":{"type":"object","additionalProperties":{"type":"string"}},"rowOrder":{"type":"array","items":{"type":["string","number"]}},"columnOrder":{"type":"array","items":{"type":["string","number"]}},"rowOrderField":{"type":"string"},"columnOrderField":{"type":"string"},"limit":{"type":"integer","minimum":1},"cellsPath":{"type":"string","description":"Path of the cells array inside the one record the source finds."},"cellFilter":{"type":"object","additionalProperties":true,"description":"Equality filter on the cells of that record, e.g. { actorType: '@object.actorType' }."},"fallbackFilter":{"type":"object","additionalProperties":true,"description":"Filter used when `filter` finds no record, e.g. { active: true }."},"rowOrderPath":{"type":"string","description":"Path of an array on that record giving the row order."},"columnOrderPath":{"type":"string","description":"Path of an array on that record giving the column order."}}},"endpointSource":{"$ref":"#/$defs/chartEndpointSource","description":"Wave 2 (#91): endpoint-bound series/labels — the shared endpoint transport plus the labelsPath / series[] {name, path} response mapping. Exactly one of dataSource | endpointSource (post-schema check)."}}}}}},{"description":"tabs built-in widget (CnTabsWidget): `props.content.tabs[]` entries are a widget id or an object { id?, widgetId, label?, icon?, count?, countField?, unreadKey?, overflow?, visibleWhen? }. `visibleWhen` reuses the shared predicate: a tab whose condition is false is absent from the strip, not empty. LOCAL form reads a field of the page object; the `source` form counts related rows (`@total`) once on mount and after a reported write, and shows the tab disabled while it is pending. A tab without the key always renders.","if":{"properties":{"widgetKey":{"const":"tabs"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"tabs":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true,"properties":{"visibleWhen":{"$ref":"#/$defs/visibleWhen"}}}]}}}}}}}}},{"description":"header / text / divider on the v2 grid (Wave 1): the dashboard catalog's content-only presentation widgets reused as built-ins. Each takes a single `props.content` object with the same shape as its dashboard `content` blob.","if":{"properties":{"widgetKey":{"enum":["header","text","divider"]}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"description":"The widget's content blob — header: { title, subtitle, backgroundImageUrl, overlayMode, height, cta, … }; text: { text, contentMode, fontSize, … }; divider: { style, lineColor, headingText, … }."}}}}}},{"description":"stat / delta catalog widgets on the v2 grid (Wave 2, #91): the KPI tiles take a `props.content` blob (same shape as their dashboard `content`). The endpoint-binding keys are typed here; the rest of the blob stays free-form (label, icon, format, source, …). Exactly one of content.source | content.endpointSource — a cross-field rule enforced by validateManifestV2() as a post-schema check (stats-block precedent), which also covers the legacy pages[].config.widgets[] placement.","if":{"properties":{"widgetKey":{"enum":["stat","delta"]}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"endpointSource":{"$ref":"#/$defs/endpointSource","description":"Wave 2 (#91): endpoint-bound KPI value(s) — exactly one of source | endpointSource (post-schema check)."},"valueField":{"type":"string","description":"Dot-path into the endpoint payload for the displayed (current) value. Omitted = the payload itself."},"previousField":{"type":"string","description":"Dot-path into the endpoint payload for the previous-period value — renders the trend sublabel (arrow + percent-vs-previous; the pipelinq previousPeriod contract)."},"deltaField":{"type":"string","description":"Dot-path into the endpoint payload for a SERVER-computed delta percent — wins over the client-side previousField computation."},"goodDirection":{"type":"string","enum":["up","down"],"default":"up","description":"Which trend direction tints green ('up' default; 'down' for cost-like KPIs). The delta widget's source.goodDirection still wins for the OpenRegister form."},"variantWhen":{"type":"array","description":"First-match threshold styling rules for the stat tile — the matched rule's variant re-tints the value + icon circle, and its optional icon overrides content.icon.","items":{"type":"object","required":["op","value","variant"],"additionalProperties":false,"properties":{"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte"],"description":"Comparison operator against the resolved display value (numeric when both sides coerce; eq/neq fall back to string equality)."},"value":{"description":"Literal right-hand side of the comparison."},"variant":{"type":"string","enum":["default","primary","success","warning","error"],"description":"Colour variant applied on match ('default' keeps the configured colours). The component also accepts 'danger' as an alias of 'error' for doriath-migrated content."},"icon":{"type":"string","description":"Optional MDI icon name overriding content.icon while this rule matches."}}}},"clickRoute":{"description":"Whole-tile click-through: a vue-router route NAME (string) or full location object. Alias of content.route (route wins when both are set).","oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]}}}}}}}},{"description":"workspace-filter catalog widget (#91 Wave 3): a choice list whose selection writes into the page workspace context so sibling widgets refetch. The endpoint/OR-source binding keys of `props.content` are typed here; the rest stays free-form.","if":{"properties":{"widgetKey":{"const":"workspace-filter"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"writes":{"type":"string","description":"The workspace context key the selection writes (`@workspace.<key>` or a bare `<key>`). Sibling widgets whose declarative source interpolates `@workspace.<key>` refetch on change."},"style":{"type":"string","enum":["radio","select","chips","toggle"],"default":"radio","description":"Choice style: 'radio' (default) list, 'select' dropdown, 'chips' (one compact segmented group with a count after each label; screens-page-lenses, 2.87.0) or 'toggle' (a switch writing `onValue` / `offValue`; screens-page-controls, 2.88.0)."},"layout":{"type":"string","enum":["start","bar"],"description":"'start' draws the start card; 'bar' draws `controls` side by side in one row (screens-page-controls, 2.88.0)."},"controls":{"type":"array","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)."},"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)."},"urlKey":{"type":"string","minLength":1,"description":"Keep the choice in the address as `?<urlKey>=<value>` (replacing the history entry, keeping other query keys). The address wins over `default` on load, so a chip is linkable (screens-page-lenses, 2.87.0)."},"allLabel":{"type":"string","description":"Optional leading 'All' option — writes the empty string so an optional sibling token (`@workspace.<key>?`) drops (unfiltered set)."},"showCounts":{"type":"boolean","default":true,"description":"Render per-option counts."},"options":{"type":"array","description":"Static options — each { value, label, count?, countFrom? } (or { id, name } / bare strings, normalised). `countFrom` is `{ register, schema, filter? }`, or a list of those that are added up; the option shows that list's total (one list request each; none when it fails). screens-page-lenses, 2.87.0.","items":{"type":["object","string","number"]}},"source":{"type":"object","additionalProperties":true,"description":"OpenRegister /grouped facet source { register, schema, groupBy, filter? } — each distinct value becomes an option with its object count.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"groupBy":{"type":"string"},"filter":{"type":"object","additionalProperties":true}}},"endpointSource":{"$ref":"#/$defs/endpointSource","description":"App endpoint returning an array of { value, label, count? } options at responsePath."}}}}}}}},{"description":"kb-search catalog widget (#91 Wave 3): summary-driven knowledge-base search through a PLUGGABLE PROVIDER. The provider seam keys of `props.content` are typed here; the rest stays free-form.","if":{"properties":{"widgetKey":{"const":"kb-search"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"provider":{"type":"string","description":"Provider registry key (default 'default' — the built-in endpoint search). An app registers bespoke providers (the xwiki proxy) on CnAppRoot's kbSearchProviders; the xwiki client stays app-side."},"space":{"type":"string","description":"Provider-specific KB space filter (passed to provider.search)."},"tags":{"description":"Provider-specific tag filter — a single tag or an array.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"endpoint":{"type":"string","description":"The default provider's search endpoint (OpenRegister xWiki leaf by default)."},"queryParam":{"type":"string","description":"The default provider's query parameter name (default 'q')."},"bindTo":{"type":"string","description":"Workspace context key to auto-search on (default 'activeSummary')."},"minChars":{"type":"integer","minimum":0,"description":"Minimum characters before a search fires (default 3)."},"limit":{"type":"integer","minimum":1,"description":"Result cap passed to the provider (default 8)."},"externalOpen":{"type":"boolean","description":"Whether result links open in a new tab (defaults to the provider's own externalOpen flag)."},"unavailableFallback":{"type":"string","description":"Text shown when the provider rejects / is unavailable (replaces the default 'Knowledge base unavailable')."}}}}}}}},{"description":"week-strip catalog widget (CnWeekStripWidget): the current week as day columns with the dated items under each day. Takes a `props.content` blob. Items come from an OpenRegister `source` (one request for the week, bucketed by `dateField`) or from static `items`.","if":{"properties":{"widgetKey":{"const":"week-strip"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"days":{"type":"integer","enum":[5,7],"default":5,"description":"5 working days (default) or the whole week. The week starts on Monday."},"source":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"OpenRegister object query; `filter` supports the shared @-token grammar.","properties":{"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)."}}},"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."},"itemRoute":{"type":"string","description":"Route name an item links to; the record id goes in params.id."},"lateWhen":{"type":"object","additionalProperties":false,"description":"Rule on the number of days until the date that marks an item late. Default: before today ({ op: 'lt', value: 0 }).","properties":{"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte"]},"value":{"type":"number"}}},"lateField":{"type":"string","description":"A boolean field that marks a record late, whatever its date."},"items":{"type":"array","description":"Static items, used when there is no source.","items":{"type":"object","required":["title","date"],"additionalProperties":false,"properties":{"title":{"type":"string"},"meta":{"type":"string"},"date":{"type":"string","description":"ISO date or date-time."},"route":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]},"href":{"type":"string"},"late":{"type":"boolean"}}}},"emptyText":{"type":"string","description":"Text of a day without items."},"weekOffset":{"type":"integer","description":"Whole weeks from the current one (1 = next week)."},"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."}}}}}}}},{"description":"stacked-bar catalog widget (CnStackedBarWidget): one segmented bar with a legend that carries the label and count of every segment. Takes a `props.content` blob. Segments come from one grouped count request (`source.groupBy`) or from static `segments`.","if":{"properties":{"widgetKey":{"const":"stacked-bar"}},"required":["widgetKey"]},"then":{"properties":{"props":{"type":"object","properties":{"content":{"type":"object","additionalProperties":true,"properties":{"source":{"type":"object","additionalProperties":false,"required":["register","schema","groupBy"],"description":"OpenRegister grouped count; `filter` supports the shared @-token grammar.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"groupBy":{"type":"string","description":"The field the records are counted per."},"filter":{"type":"object","additionalProperties":true}}},"segments":{"type":"array","description":"Static segments, used when there is no source.","items":{"type":"object","required":["label","value"],"additionalProperties":false,"properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":"number","minimum":0}}}},"order":{"type":"array","items":{"type":"string"},"description":"Group keys in the order they render. Unlisted groups follow; a listed key without records shows 0."},"labels":{"type":"object","additionalProperties":{"type":"string"},"description":"The label of a group key."},"emptyText":{"type":"string","description":"Shown when the total is 0."},"inset":{"type":"boolean","default":false,"description":"Draw the bar and legend 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."}}}}}}}}]},"fieldWidget":{"type":"object","required":["id","component"],"additionalProperties":false,"description":"A declarative widget-component slot used inside `form` / `detail` page config. Distinct from the top-level pages[].widgets[] grid placement (which carries widgetKey + grid coordinates). fieldWidget mounts a lib `Cn*` SFC inline as a form field or detail data slot; the renderer resolves `component` against the lib's exported component map, NOT the host-app customComponents registry. Carries serialisable `props` only — handler / function props must be wired via the host App's manifest-level mounts. Replaces the untyped `fieldWidgets[]` key relied on by pipelinq#415 (which rode through on additionalProperties: true). See ConductionNL/nextcloud-vue#314.","properties":{"id":{"type":"string","pattern":"^[a-z][a-zA-Z0-9_-]*$","description":"Unique identifier within the fieldWidgets[] array. Lower-camel-case-ish: must start with a lowercase letter and contain only letters, digits, underscores, or hyphens. Used as the form field key / data binding key by CnFormPage / CnDetailPage."},"component":{"type":"string","pattern":"^Cn[A-Z]\\w+$","description":"Name of a lib component (e.g. CnMarkdownEditor, CnSchemaSelect). MUST be Cn-prefixed and resolved against the @conduction/nextcloud-vue exported component map. Host-app SFCs are not allowed here — those go through pages[].component on a type:'custom' page."},"props":{"type":"object","description":"Serialisable props passed to the resolved component at render time. Shape is component-specific; not constrained here. Handler / function values are not supported through the manifest (the manifest is JSON).","additionalProperties":true},"_note":{"type":"string","description":"Optional human-readable note explaining the role of this declarative widget slot."}}},"slotValue":{"description":"Valid slot value. Either a literal slot name (body, sidebar, header-actions, footer, modal) or a tab:<id> / section:<id> pattern string.","oneOf":[{"type":"string","enum":["body","sidebar","header-actions","footer","modal"],"description":"Literal slot name."},{"type":"string","pattern":"^(tab|section):.+","description":"Named tab or section slot. Format: tab:<id> or section:<id> where <id> is at least one character."}]},"dataSource":{"type":"object","description":"Declarative data binding for widgets. Forms: (a) shorthand {register, schema, filter?, aggregate: 'count'} — the lib builds a count query and resolves to {count}. (b) raw {graphql: {query, variables?, selectors}} — the lib issues the query and runs selectors. (c) #91 Wave 3 chart aggregation — `aggregate` as an OBJECT {groupBy, metric?: count|sum, sumField?, topN?, otherBucket?, labelResolve?} grouped over the schema's objects via OpenRegister's /grouped facet (client-side collection fallback), plus an optional sibling `drilldown` {route, filterParam}. Carry-forward from v1.3.0 widgetDef.dataSource.","additionalProperties":true,"properties":{"register":{"type":"string","description":"Shorthand: register slug."},"schema":{"type":"string","description":"Shorthand: schema slug."},"filter":{"type":"object","description":"Shorthand: filter map inlined into the generated query.","additionalProperties":true},"aggregate":{"description":"Shorthand aggregation. The STRING 'count' builds a GraphQL count query resolving to {count}. The OBJECT form (#91 Wave 3) is a chart-only categorical group-by served by OpenRegister's /grouped facet.","oneOf":[{"type":"string","enum":["count"]},{"type":"object","required":["groupBy"],"additionalProperties":false,"description":"#91 Wave 3 chart aggregation: group the schema's objects by `groupBy`, count (default) or sum `sumField`, keep the `topN` largest (sorted desc) and optionally fold the remainder into a translated 'Other' slice, resolving reference keys to labels/colours via `labelResolve`.","properties":{"groupBy":{"type":"string","description":"The categorical field grouped on."},"metric":{"type":"string","enum":["count","sum"],"default":"count","description":"count (default) | sum. sum requires sumField."},"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"],"additionalProperties":false,"description":"Resolve reference (uuid) group keys to the referenced objects' display labels (and optional per-category colours) through the shared object store — the fkResolve pattern.","properties":{"register":{"type":"string","description":"Register the reference points into (defaults to the dataSource register)."},"schema":{"type":"string","description":"Schema slug the reference points into."},"labelField":{"type":"string","description":"Property used as the display label (default 'name')."},"colorField":{"type":"string","description":"Optional property read as a per-category CSS colour (feeds the chart's colorMap path)."}}}}}]},"drilldown":{"type":"object","required":["route","filterParam"],"additionalProperties":false,"description":"#91 Wave 3 (chart only): a segment/bar click navigates to `route` with the clicked category's RAW key in the query ({ [filterParam]: rawKey }). A `/`-prefixed route is a path, otherwise a route name. Works with the OBJECT `aggregate` and the legacy `groupBy` forms; the folded 'Other' bucket never navigates.","properties":{"route":{"type":"string","description":"Destination route name, or a path when it starts with '/'."},"filterParam":{"type":"string","description":"Query-param name the clicked category's raw key is written to."}}},"graphql":{"type":"object","description":"Raw GraphQL form.","additionalProperties":false,"required":["query","selectors"],"properties":{"query":{"type":"string","description":"GraphQL query document."},"variables":{"type":"object","description":"Variables map passed alongside query.","additionalProperties":true},"selectors":{"type":"object","description":"Map of result-prop name to dot-path selector.","additionalProperties":{"type":"string"}}}}}},"action":{"description":"A page action: the fields of $defs/actionFields, nothing else, with children that are actions too. Row, bulk and navigation actions use it; header actions use $defs/headerAction.","$ref":"#/$defs/actionFields","properties":{"children":{"items":{"$ref":"#/$defs/action"}}},"unevaluatedProperties":false},"actionFields":{"type":"object","required":["id","label"],"description":"The fields of an action, shared by $defs/action and the non-link $defs/headerAction, which close it and constrain `children`. A typed page action with a discriminator field. The type field determines behaviour: handler (registry function), open-modal (modal by id), open-page (named route), navigate (URL), object-op (declarative OpenRegister mutation via useObjectStore), export (opens the shared CnMassExportDialog export launcher), open-form (schema-driven create dialog — CnActionButtons), refresh (bumps the cn:page:refresh signal), api-call (POST/PUT an app endpoint + toast + refresh — CnActionButtons/dispatchAction), agent (run a governed hermiq agent), run-node (invoke one OpenRegister flow node against the page object, config collected via a dialog sourced from the node's own declared form — CnActionButtons/dispatchAction, or-flow-run-node; distinct from the UNRELATED setup-wizard step type of a similar name, 'run-action'), toggle (two-way state button — CnActionButtons only, NOT dispatchable). When type is omitted, it defaults to 'handler' (back-compatibility with v1.3.0 action declarations). The Ajv instance is compiled with useDefaults: true so the default is applied during validation.","properties":{"id":{"type":"string","description":"Unique action identifier within the page's actions array."},"label":{"type":"string","description":"i18n key resolved by the consuming app's t() function at render time."},"type":{"type":"string","enum":["handler","open-modal","open-page","navigate","object-op","export","open-form","refresh","api-call","agent","run-node","toggle"],"default":"handler","description":"Action behaviour discriminator. Defaults to 'handler' when omitted (back-compat with v1.3.0). handler: calls a registry function; open-modal: opens a modal by target id; open-page: navigates to a named route; navigate: navigates to a URL; object-op: dispatches a declarative mutation (patch | delete | create) of an OpenRegister object via the shared object store; export: opens the shared CnMassExportDialog export launcher configured via entities[]/formats[] — the confirm payload ({format, entity?}) routes to the action's `handler` in the manifest actions map (the app's export service does the download). open-form (#91 Wave 3): CnActionButtons opens a schema-driven create dialog (CnAdvancedFormDialog) for the action's register/schema, saves + toasts + refreshes + optionally navigates to onSuccessRoute. refresh (#91 Wave 3): bumps the page-level cn:page:refresh event-bus signal so every endpoint-bound widget refetches. api-call (#91 Wave 3): POST/PUT the token-interpolated url with payload/params (JSON body, payload resolves @-tokens recursively at any depth) + success/error toast + auto page-refresh (unless refresh:false); confirm gating is surface-consumed intent (object-op precedent); download:true requests a binary blob and triggers a browser file download instead (auto-refresh defaults to OFF in that mode). agent (hermiq#41): run a governed hermiq agent against the page object — POSTs { register, schema, objectId, resultField?, skill?, prompt? } to /apps/hermiq/api/agents/{agent}/run-on-object (object-RBAC-scoped; dispatches the governed AgentRunRequestedEvent). register/schema/objectId default to the page's @register/@schema/@objectId object context. A first-class companion to api-call (which remains the fallback for a bespoke body); hermiq is NOT hard-required — an app-level 404 surfaces a graceful 'agent runtime unavailable' toast. toggle (#91 Wave 3): a stateful two-way state button rendered by CnActionButtons (GET stateSource on mount, optimistic write on click) — NOT dispatchable through dispatchAction. object-op declares mutation INTENT only — authority is enforced server-side by OpenRegister RBAC (ADR-022/ADR-023); authorization-shaped fields have no client-side effect and are not accepted here."},"op":{"type":"string","enum":["patch","delete","create"],"description":"object-op only: the mutation verb. patch merges `values` into the row's object (row-scoped); delete removes the row's object (row-scoped, ALWAYS confirm-gated); create adds a new object built from `values` against the widget's source register/schema (widget-scoped footer/header affordance). Required when type is 'object-op'."},"values":{"type":"object","additionalProperties":true,"description":"object-op only: the verb's payload. For patch: the partial object merged over the row's object. For create: the new object's properties. Omitted for delete."},"confirm":{"type":"boolean","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."},"props":{"type":"object","description":"Additional props forwarded to the handler or modal.","additionalProperties":true},"createOverride":{"type":"string","description":"Registry handler name that owns the persist for an `open-form` action, replacing the default objectStore.saveObject. Resolved exactly as CnIndexPage resolves its own `createOverride` prop (a kind:'create-override' entry's `.handler`, a function-valued registry entry, or a function in the legacy customComponents map). Needed when the schema requires a field the create form cannot supply on its own — a server-minted foreign key, say — where a straight save would 400."},"includeFields":{"type":"array","items":{"type":"string"},"description":"open-form: the only fields the create dialog asks for. One schema often backs two surfaces, a detail page that edits all of it and a header button that collects what someone filing a new one types, and this is how the button says which it is. Omit for the whole schema."},"excludeFields":{"type":"array","items":{"type":"string"},"description":"open-form: fields the create dialog leaves out. Use it to drop a handful; use includeFields when it is easier to name what stays."},"fieldOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"open-form: per-field overrides passed to fieldsFromSchema, keyed by field. Recognised keys include label, order, hidden, readOnly and widget."},"advanced":{"type":"boolean","default":false,"description":"open-form: open the properties-and-JSON table (CnAdvancedFormDialog) instead of the plain form. The plain form is the default because a header button is aimed at someone filing a record, not at inspecting its schema."},"formTitle":{"type":"string","description":"open-form: overrides the create dialog's heading. Defaults to 'Create {schema title}'."},"formEyebrow":{"type":"string","description":"open-form: the context line above the create dialog's heading (board look), e.g. the list the record joins. Through the host translate. Absent: no line."},"formSubtitle":{"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"],"default":"normal","description":"open-form: how wide the create dialog is, passed straight to NcDialog. Widen a form that asks enough questions to need scrolling; pair it with columns: 2."},"columns":{"type":"integer","enum":[1,2],"default":1,"description":"open-form: how many columns the fields flow into. 2 pairs them up, which halves the scrolling on a longer form. Textareas and JSON editors still span the full width, and the layout collapses to one column below 700px. Widen the dialog with size: 'large' as well, or two columns are merely two narrow ones."},"endpoint":{"type":"object","description":"open-form only: edit a record that is not a register object through an endpoint (an OpenRegister organisation). `url` is where the save goes (token-interpolated, app-relative or absolute), `method` is PATCH (default) or PUT, or POST to create a record with an empty form, `loadUrl` reads the record when it differs from `url`, `responsePath` plucks it from the answer. The fields are `formProperties`. Without `endpoint` the action is the schema-driven create form.","properties":{"url":{"type":"string"},"method":{"type":"string","enum":["PATCH","PUT","POST"]},"loadUrl":{"type":"string"},"responsePath":{"type":"string"}},"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\" } }`).","additionalProperties":{"type":"object"}},"handler":{"type":"string","description":"Registry key for type: 'handler'. Resolved across both registries passed to CnAppRoot: a v2 `registry` entry of `kind: 'handler'` exposing the function as `.handler` (or `.fn`), a directly function-valued registry entry, or a function-valued entry in the deprecated `customComponents` map. The same resolution serves actions[], bulkActions[] and headerActions[]. Also the built-in dispatch verb when paired with `route` (handler: 'navigate' → $router.push to `route`)."},"route":{"type":"string","description":"Destination route name for handler: 'navigate'. The dispatcher (manifestActionDispatch) calls $router.push({ name: route, params }). For row-level actions params include { id: row[rowKey] } automatically — do NOT restate it as params: { id: \"{id}\" }, though that placeholder does resolve (see `params`). Page-level header actions navigate without row context (e.g. a 'New X' action to a detail route)."},"icon":{"type":"string","description":"Optional icon for the action, rendered by CnActionsBar. Either an MDI icon name (e.g. 'TextBoxOutline') or a CSS icon class (e.g. 'icon-file')."},"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 + 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":"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."}}}},"formats":{"type":"array","description":"export only: the offered export formats — bare ids ('excel', 'csv', 'json' — lifted to {id, label} with an upper-cased label) or full {id, label} entries. Optional — the dialog's built-in Excel/CSV defaults apply when absent.","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["id","label"],"properties":{"id":{"type":"string","description":"Format id handed to the confirm handler."},"label":{"type":"string","description":"Pre-translated display label."}}}]}},"description":{"type":"string","description":"export only: pre-translated description shown above the export launcher's pickers."},"url":{"type":"string","description":"api-call / toggle only: the app endpoint (POST/PUT). App-relative (routed through generateUrl) or absolute (http/https). May interpolate @objectId / @object.<field> / @workspace.<key> / @config.<key> tokens inline, as well as the literal {objectId} brace placeholder (unresolved tokens collapse to empty)."},"method":{"type":"string","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","additionalProperties":true,"description":"api-call only: the JSON request body — PREFERRED over `params`, and used instead of it when both are set. Values run the SAME token grammar as `params` (@me, @now, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId, @object.<field>, @workspace.<key>/@config.<key> with a trailing `?` marking a value OPTIONAL — dropped when unresolved) but resolve RECURSIVELY at ANY nesting depth, including arrays of objects — e.g. a Filinq document-generation body: { \"dataRefs\": [{ \"register\": \"crm\", \"schema\": \"lead\", \"id\": \"@objectId\" }] }. A required (non-`?`) token left unresolved anywhere in the tree BLOCKS the call (error toast) instead of sending the literal token string to the server."},"params":{"type":"object","additionalProperties":true,"description":"Two distinct uses depending on the action's dispatch. (1) api-call / toggle: the JSON request body (legacy — prefer `payload` for anything with nested objects/arrays; ignored when `payload` is set). Values run the shared filter-token grammar ONE level deep (@me, @today±Nd, @workspace.<key>/@config.<key> with a trailing `?` for optional values dropped when unresolved; a required unresolved token skips the call). (2) handler: 'navigate' (index-page row actions, manifestActionDispatch): vue-router params merged OVER the default { id: row[rowKey] }. OMIT it for the ordinary 'open this row's detail page' action — the row id is already injected. String values run the `{field}` ROW-token grammar (NOT the @-token grammar): \"{id}\" resolves to row.id with its type preserved, \"run-{id}\" interpolates as text, and a brace-less \"new\" stays literal (that is what makes a 'New X' action navigate with { id: 'new' }). A token naming a field the row does not carry is dropped with a console.warn — so `id` falls back to the row id — instead of being pushed as a literal %7Bid%7D path segment."},"download":{"type":"boolean","default":false,"description":"api-call only: request the response as a binary blob (axios responseType 'blob') and trigger a browser file download instead of a JSON toast+refresh cycle. The filename is taken from the response's Content-Disposition header, else the token-resolved `filename`, else 'download.pdf'. The success toast still shows; unlike a normal api-call the page does NOT auto-refresh afterwards unless `refresh: true` is explicitly set."},"filename":{"type":"string","description":"api-call `download: true` only: fallback filename used when the response carries no Content-Disposition header (falls back further to 'download.pdf'). Interpolates the same token grammar as `url` (@objectId, @object.<field>, @workspace.<key>, @config.<key>, {objectId})."},"successMessage":{"type":"string","description":"api-call / open-form / agent / run-node / toggle: pre-translated success toast text (a library default applies when absent; agent default 'Run queued')."},"errorMessage":{"type":"string","description":"api-call / agent / run-node / toggle: pre-translated error toast text (falls back to the server's error/message, then a library default; an agent 404 with no structured body toasts 'agent runtime unavailable' instead)."},"refresh":{"type":"boolean","default":true,"description":"api-call / agent / run-node / toggle: bump the page-level cn:page:refresh signal after a successful call (default true; set false to skip). EXCEPTION: for an api-call with download:true the default flips to false — a file download normally shouldn't also force-refetch every widget — set refresh:true explicitly to opt back in."},"flowId":{"type":"string","description":"run-node only (REQUIRED): the published flow's uuid whose node this action invokes. A node's config and eligibility are properties of the flow it lives in (RN-3, OpenRegister's or-flow-run-node), so the manifest names which flow's instance to call rather than a bare node type — two flows could each carry the same node TYPE wired differently."},"nodeId":{"type":"string","description":"run-node only (REQUIRED): the id of the node, within flowId's published graph, to invoke directly. POSTed to /api/flows/{flowId}/nodes/{nodeId}/run — refused (404) unless that node type has opted in to direct invocation (IFlowDirectlyInvokable)."},"subject":{"type":"string","description":"run-node only: the subject object the node acts on and is authorized against (OpenRegister object-RBAC — the caller needs update permission on it; the flow's own flow.run right is deliberately NOT what gates this). Typically @objectId; may interpolate the shared @-token grammar. An unresolved required token BLOCKS the call, matching agent's objectId."},"agent":{"type":"string","description":"agent only (REQUIRED): the hermiq agent uuid to run against the page object. POSTed to /apps/hermiq/api/agents/{agent}/run-on-object (hermiq#41) — object-RBAC-scoped, dispatches the governed AgentRunRequestedEvent. May interpolate the shared @-token grammar (@objectId, @object.<field>, @workspace.<key>, @config.<key>)."},"skill":{"type":"string","description":"agent only: optional skill id folded into the governed run (interpolates the same @-token grammar as `agent`)."},"prompt":{"type":"string","description":"agent only: optional prompt handed to the governed run. Interpolates the shared @-token grammar inline (@objectId, @object.<field>, @workspace.<key>, @config.<key>) so it can be grounded on the page object — e.g. 'Summarise @object.title'."},"resultField":{"type":"string","description":"agent only: the object field the agent's result is written back to (falls back to hermiq's default result field when absent)."},"objectId":{"type":"string","description":"agent only: the target object id. Defaults to the page object context's @objectId; an unresolved required @objectId BLOCKS the call (fail-closed) rather than sending a literal token. May be an explicit id or an @-token string."},"register":{"type":"string","description":"open-form / agent / run-node: OpenRegister register slug. open-form: the create dialog's schema register. agent / run-node: the register the subject object belongs to — falls back to the page object context's @register."},"schema":{"type":"string","description":"open-form / agent / run-node: OpenRegister schema slug. open-form: the create dialog fetches this schema and saves the new object against it. agent / run-node: the schema the subject object belongs to — falls back to the page object context's @schema."},"onSuccessRoute":{"description":"open-form only: optional post-save navigation target (#91). A bare route NAME (string) navigates with the saved object's id merged into the route params under `id` (harmless when the route has no `:id` segment — backward compatible). The object form { name, paramField?, objectParam? } names the id param (`paramField`, default `id`) and, when `objectParam` is set, also passes the whole saved object under that param key (for a props:true detail route to render without a refetch). The id is read from saved.id, then saved.uuid, then saved['@self'].id.","oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","description":"The vue-router route NAME pushed after a successful save."},"paramField":{"type":"string","default":"id","description":"Route param name populated from the saved object's id (default `id`)."},"objectParam":{"type":"string","description":"When set, the whole saved object is passed under this route param (props:true detail routes render without a refetch)."}}}]},"labelOn":{"type":"string","description":"toggle only: button label rendered while the state is ON (true). Falls back to `label`."},"labelOff":{"type":"string","description":"toggle only: button label rendered while the state is OFF (false). Falls back to `label`."},"stateSource":{"$ref":"#/$defs/endpointSource","description":"toggle only: the endpoint the initial state is read from on mount (the shared endpointSource shape — url + token-resolved params + responsePath). The `field` dot-path picks the boolean off the payload."},"writeUrl":{"type":"string","description":"toggle only: the endpoint the flipped state is written to on click (interpolates the same tokens as `url`)."},"field":{"type":"string","description":"toggle only: the state field — read off the stateSource payload on mount AND sent (as the flipped boolean) in the write body under this key."},"variant":{"type":"string","description":"CnActionButtons button variant (e.g. 'primary', 'secondary', 'error'). 'error' also styles the confirm dialog as destructive. A 'primary' action is never collapsed into CnActionButtons' overflow menu and consumes none of its `inline` slots: NcActions paints all of its inline actions with one shared variant, so a lone primary among them is not expressible."},"children":{"type":"array","description":"Sub-actions, rendered by CnActionButtons as this action's OWN chevron dropdown beside its button — deliberately not folded into the page's single overflow menu, because NcActions cannot nest and a list that grows per record (app versions, say) would swamp it. Children take the same shape as any action, `visibleWhen` included, and an action carrying them consumes none of the `inline` slots. The parent itself still renders its own button beside the chevron (`type` defaults to 'handler' when omitted), so a parent meant only to group its children needs a `handler` that does something meaningful — an untyped, handler-less parent renders a button that dispatches to nothing. Each child is an action; a header action's child is a header action, so it may be a link with `href`."},"childrenLabel":{"type":"string","description":"i18n key naming the `children` dropdown. Falls back to the parent action's own label, so the chevron is never announced unlabelled."},"group":{"type":"string","minLength":1,"description":"Detail page header actions only. i18n key / text naming the group this action sits in inside the overflow menu. Grouped actions render under a caption with this name, in first appearance order, after the ungrouped ones. Ignored on other surfaces."},"adminOnly":{"type":"boolean","default":false,"description":"Detail page header actions only. Shows the action to instance admins alone, in the last group of the overflow menu. Hides a control and nothing more: the endpoint behind the action still decides who may call it. Ignored on other surfaces."},"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"#91 Wave 3: an optional predicate gating whether the action renders — the shared banner shape ({ endpoint | source | field, op, value }). The `field`-only LOCAL form evaluates against the page object context (a detail action shown only in the right lifecycle state); endpoint / source forms fetch. Fail-safe: a broken predicate hides the action."}},"allOf":[{"description":"object-op actions MUST declare their mutation verb via `op`.","if":{"properties":{"type":{"const":"object-op"}},"required":["type"]},"then":{"required":["op"]}},{"description":"api-call actions MUST declare their endpoint via `url`.","if":{"properties":{"type":{"const":"api-call"}},"required":["type"]},"then":{"required":["url"]}},{"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":{"anyOf":[{"required":["schema"]},{"required":["endpoint","formProperties"]}]}},{"description":"agent actions MUST declare the target agent uuid via `agent`.","if":{"properties":{"type":{"const":"agent"}},"required":["type"]},"then":{"required":["agent"]}},{"description":"toggle actions MUST declare their write endpoint via `writeUrl`.","if":{"properties":{"type":{"const":"toggle"}},"required":["type"]},"then":{"required":["writeUrl"]}},{"description":"run-node actions MUST declare which flow and which node within it (RN-3, or-flow-run-node) — a node's config and eligibility are properties of the flow it lives in, so neither can be inferred.","if":{"properties":{"type":{"const":"run-node"}},"required":["type"]},"then":{"required":["flowId","nodeId"]}}]},"headerAction":{"description":"A page header action (`headerActions[]`, a detail page's `quickActions[]` / `primaryActionByStage` entry, and their `children[]`): an action ($ref action), or with `href` a link ($ref headerLinkAction). Only header actions take `href`; row, bulk and navigation actions do not (2.135.0).","if":{"type":"object","required":["href"]},"then":{"$ref":"#/$defs/headerLinkAction"},"else":{"$ref":"#/$defs/actionFields","properties":{"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}},"headerLinkAction":{"type":"object","required":["id","label","href"],"additionalProperties":false,"description":"A header action that is a real link (`<a href>`), so it can be middle-clicked, opened in a new tab, copied and bookmarked. It only navigates: it runs no handler and emits no `header-action`, so it carries no `handler` and no other `type` (2.135.0).","properties":{"id":{"type":"string","description":"Unique action identifier within the page's actions array."},"label":{"type":"string","description":"i18n key resolved by the consuming app's t() function at render time."},"href":{"type":"string","pattern":"^(https?://|mailto:|tel:|/)","description":"The link. An absolute `http(s)`, `mailto:` or `tel:` URL and a protocol-relative `//host/...` URL are used as written. A path starting with a Nextcloud entry point (`/index.php/`, `/remote.php/`, `/public.php/`, `/ocs/`) is prefixed with the instance root unless it already starts with it; any other instance path (leading `/`, e.g. `/apps/integriq/connections`) goes through `generateUrl()`."},"target":{"type":"string","enum":["_blank","_self"],"default":"_self","description":"`_blank` opens the link in a new tab; `_self` opens it in place."},"type":{"const":"handler","description":"Only the default; a link has no other behaviour."},"icon":{"type":"string","description":"Optional icon: an MDI icon name or a CSS icon class, as on any action."},"variant":{"type":"string","description":"CnActionButtons button variant, as on any action."},"group":{"type":"string","minLength":1,"description":"Detail page header actions only: the overflow menu group, as on any action."},"adminOnly":{"type":"boolean","default":false,"description":"Detail page header actions only: show the link to instance admins alone, as on any action."},"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"An optional predicate gating whether the link renders, as on any action."},"children":{"type":"array","description":"Sub-actions in the link's own chevron dropdown, as on any action.","items":{"$ref":"#/$defs/headerAction"}},"childrenLabel":{"type":"string","description":"Name of the sub-action chevron, as on any action."}}},"fieldValidation":{"type":"object","additionalProperties":false,"description":"manifest-form-logic: per-field validation rules for `type: 'form'` pages, enforced client-side by CnFormPage's validateFieldValue() before dispatch. Per-type semantics: string/password — required = non-empty after trim, min/max = string LENGTH, pattern tested against the value; number — required = finite number, min/max = numeric VALUE, pattern not applicable; boolean — required = value must be true (consent checkboxes), min/max/pattern not applicable; enum — required = a value is selected, min/max/pattern not applicable; json — required = non-null, min/max/pattern not applicable. Type-inapplicable rules are rejected by validateManifestV2() post-schema (pattern only on string/password; min/max only on string/password/number). `message` (i18n-able) overrides the built-in default message for whichever rule fails.","properties":{"required":{"type":"boolean","description":"Whether the field must be filled. Default false. Per-type pass condition — see the fieldValidation description."},"min":{"type":"number","description":"Minimum bound. String length for string/password fields, numeric value for number fields. MUST be <= max when both are set (validateManifestV2 post-schema check)."},"max":{"type":"number","description":"Maximum bound. String length for string/password fields, numeric value for number fields."},"pattern":{"type":"string","description":"ECMAScript regex source tested against string/password values (implicit full-value test via new RegExp(pattern).test(value)). MUST compile — validateManifestV2() post-schema check runs `new RegExp(pattern)` in a try/catch."},"message":{"type":"string","description":"i18n-able override message shown for ANY failing rule on this field, in place of the built-in translated default. Run through the page's `translate` prop, like `field.label`."}}},"visibleWhen":{"type":"object","additionalProperties":false,"description":"#91 Wave 3: the shared visibility predicate (the Wave-1 banner shape). THREE modes: `endpoint` (GET a JSON endpoint, `field` dot-paths into the body), `source` (an OpenRegister { register, schema, filter? } query — `field` into the first result, or omit / use @total for the collection total), or LOCAL (neither endpoint nor source — `field` dot-paths into the caller's object context, e.g. a detail-page record). `op` defaults to eq. Evaluation is FAIL-SAFE (any error → hidden).","properties":{"appInstalled":{"type":"string","minLength":1,"description":"Nextcloud app id that must be installed and enabled for the guarded element to show. A PRECONDITION rather than a fourth mode: it is checked before anything else, and on its own it is the whole condition. Combined with endpoint/source/field it gates that mode too, so both must hold. It exists because the menu's `visibleIf` already spoke this word and `visibleWhen` did not, so the same manifest vocabulary meant two different things in two places. Use it on an action that writes into a sibling app, so an install without that app shows no button rather than a form that saves nowhere."},"endpoint":{"type":"string","description":"A same-origin URL returning JSON; `field` is a dot-path into the body."},"source":{"type":"object","additionalProperties":true,"description":"An OpenRegister object query { register, schema, filter? } (filter supports the shared @-token grammar). `field` dot-paths into the first result; omit it (or use @total) to compare the collection total.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"filter":{"type":"object","additionalProperties":true}}},"workspace":{"type":"string","minLength":1,"description":"WORKSPACE mode (screens-page-lenses, 2.87.0): the key of the page workspace context to compare, read at once with no request. A page control (a `workspace-filter` with `writes`) sets the key, and a dashboard widget carrying this condition appears and disappears as it changes. Honoured on a dashboard widget or KPI-row tile only, and as a top-level condition (not inside `all` / `any`)."},"field":{"type":"string","description":"Dot-path to the compared value — into the endpoint body, the source's first result, or (LOCAL mode) the caller's object context."},"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte","empty","notEmpty","in","anyOf","contains"],"default":"eq","description":"Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `in` holds when the value is one of the listed `value` array (2.87.0). `anyOf` holds when the value, or any entry of a list value, is one of the listed `value` array: a record whose `dataCategories` holds \"health\" among others matches `anyOf [\"health\", \"ethnicity\"]` (2.126.0). `contains` holds when a list value holds the given `value`, e.g. a case type's overviewCards switching a card on (2.128.0). `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."},"value":{"description":"The right-hand side of the comparison, resolved through the shared @-token grammar so `@me` names the signed-in reader rather than a literal only the server knows. Omitted for `empty` / `notEmpty`."},"all":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/visibleWhen"},"description":"Every listed condition must hold. Use it where a gate is more than one fact — a Claim button that is both 'not closed' and 'nobody holds it'. Each entry is a full visibleWhen and may compose again."},"any":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/visibleWhen"},"description":"At least one listed condition must hold. Ignored when `all` is also present."}}},"objectTableSource":{"type":"object","required":["register","schema"],"additionalProperties":false,"description":"Declarative self-fetch source for the object-table built-in widget (CnWidgetObjectTable). The widget resolves `filter` @-tokens (@me, @today, @today±Nd, @workspace.*, ?-optional suffix) with the shared resolveFilterTokens grammar just before fetching, and drives CnDataTable's existing self-fetch. `register` MAY carry an `@resolve:` sentinel — the widget passes it through unexpanded (resolution is the host loader's responsibility). Externally supplied `rows` always win over `source`.","properties":{"register":{"type":"string","description":"OpenRegister register slug (or an `@resolve:<key>` sentinel, passed through unexpanded by the widget)."},"schema":{"type":"string","description":"OpenRegister schema slug."},"filter":{"type":"object","additionalProperties":true,"description":"Filter map (`{ field: value | value[] | { op: value | value[] } }`). Values may use the shared @-token grammar: `@me` (current user via @nextcloud/auth), `@now`, `@today`, relative-date arithmetic `@today±Nd` at day granularity (e.g. `@today-30d`, `@today+7d`), `@monthStart`/`@quarterStart`/`@yearStart`, `@objectId`/`@object.<field>` (detail context), `@workspace.<key>`/`@config.<key>` with a trailing `?` for optional clauses that drop when unresolved. IN-lists: an ARRAY value (directly, or via `{ in: [...] }`) matches any of its entries — tokens resolve item-by-item and the widget serializes the list as repeated `field[]` params (the shape OpenRegister parses)."},"order":{"type":"object","additionalProperties":{"type":"string","enum":["asc","desc"]},"description":"Fetch ordering: field name to direction map (e.g. { \"dueDate\": \"asc\" })."},"limit":{"type":"integer","minimum":0,"description":"Row cap. 0 (or omitted) shows all fetched rows; a positive value caps the rendered rows and enables the View-all footer when more rows exist."},"extend":{"type":"array","items":{"type":"string"},"description":"OpenRegister `_extend[]` values forwarded on the fetch (e.g. [\"calculations\"]) so read-time virtual/derived fields are hydrated and available as columns. Each entry is passed through verbatim."},"pageControls":{"$ref":"#/$defs/pageControls"}}},"pageControls":{"type":"object","additionalProperties":false,"description":"screens-page-controls (2.88.0): which page controls a list widget (`record-list`, `object-table`) listens to, and what they mean for it. A page control is a `workspace-filter` select or switch that writes a workspace key. `sort` picks the list's sort from the key's value; `toggle` drops filter keys while the key is on. Without it the list ignores the page's controls.","properties":{"sort":{"type":"object","required":["key","map"],"additionalProperties":false,"description":"The workspace key a sort select writes, and what each of its values sorts by. A value with no entry leaves the list's own sort.","properties":{"key":{"type":"string","minLength":1},"map":{"type":"object","additionalProperties":{"type":"object","required":["field"],"additionalProperties":false,"properties":{"field":{"type":"string","minLength":1},"dir":{"type":"string","enum":["asc","desc"],"default":"asc"}}}}}},"toggle":{"type":"object","required":["key","removes"],"additionalProperties":false,"description":"The workspace key a switch writes. While it is on (true, 1, \"1\", \"true\") the filter keys in `removes` are dropped, so a list that hides finished work shows it.","properties":{"key":{"type":"string","minLength":1},"removes":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}}}}}},"endpointSource":{"type":"object","required":["url"],"additionalProperties":false,"description":"Wave 2 (#91): ONE coherent endpoint data binding shared by the stat / delta / object-table widgets (the chart uses the extended $defs/chartEndpointSource). Reads an arbitrary app REST endpoint through the library's useEndpointSource engine: params pass the SAME @-token grammar widget filters use (@me, @today±Nd, @monthStart, @workspace.<key>/@config.<key> with a trailing `?` for optional values that drop when unresolved — a REQUIRED token that stays unresolved blocks the fetch), requests dedupe + short-TTL cache per (method, url, resolved params), and the widget refetches on cn:page:refresh / a matching cn:widget:refresh. Exactly ONE of the widget's OpenRegister source | endpointSource may be configured — a cross-field rule enforced by validateManifestV2() as a post-schema check (clear error message), matching the stats-block precedent.","properties":{"url":{"type":"string","description":"Endpoint URL — app-relative (routed through generateUrl, e.g. '/apps/pipelinq/api/analytics/overview') or absolute (http/https, left untouched). May interpolate @page.<key> / @workspace.<key> / @config.<key> / @objectId / @object.<field> tokens inline; unresolved URL tokens collapse to an empty string."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method. GET (default) sends the resolved params as query parameters; POST sends them as the JSON body."},"params":{"type":"object","additionalProperties":true,"description":"Request parameters. Values pass the shared filter-token grammar (resolveFilterTokens): @me, @now, @today, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId/@object.<field> (detail context), @workspace.<key>/@config.<key> 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."}}},"chartEndpointSource":{"type":"object","required":["url"],"additionalProperties":false,"description":"The chart widget's endpoint binding: the $defs/endpointSource transport plus the labels/series response mapping. The mapping keys live INSIDE this block (not as sibling props) because the chart's flat `series` / `labels` props already carry the static data. When the plucked payload is an ARRAY of points (e.g. pipelinq /api/analytics/trends → responsePath 'series'), labelsPath / series[].path are PER-ITEM field paths (labelsPath 'date', series [{ name: 'Leads', path: 'value' }]); when it is an OBJECT they point at parallel arrays. Pie-family charts flatten the FIRST mapped series into the flat value array ApexCharts expects. Exactly ONE of dataSource | endpointSource per chart widget (post-schema check).","properties":{"url":{"type":"string","description":"Endpoint URL — app-relative (generateUrl) or absolute. Same token interpolation as $defs/endpointSource."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method. GET (default) sends params as query parameters; POST as the JSON body."},"params":{"type":"object","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."},"labelsPath":{"type":"string","description":"Labels/categories mapping: a per-item field path for an ARRAY payload ('date'), or the path of a parallel label array for an OBJECT payload ('labels')."},"rowsPath":{"type":"string","description":"Path of an array of rows, one series each (chartKind 'matrix': one row per case type). Each row's name is at rowLabelPath and its values at rowValuesPath; the columns are at labelsPath."},"rowLabelPath":{"type":"string","description":"Per-row path of the series name (default 'label')."},"rowValuesPath":{"type":"string","description":"Per-row path of the values array (default 'values')."},"series":{"type":"array","description":"Series mapping — one entry per rendered series.","items":{"type":"object","required":["path"],"additionalProperties":false,"properties":{"name":{"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')."}}}}}},"statsBlockEntry":{"type":"object","required":["register","schema"],"additionalProperties":false,"description":"One KPI entry inside a multi-entry stats-block widget (CnStatsBlockWidget `entries[]`). Carries the same source contract as a type:'stat' widget: register + schema + metric + token-resolved filter (@today / @me / @workspace.* and ?-optional clauses), plus per-entry presentation (title, variant, countLabel, format), an optional route deep link, and hideWhenZero. A money value names its currency with currencyField (the page object's field) or currency.","properties":{"title":{"type":"string","description":"KPI title rendered by the inner CnStatsBlock."},"register":{"type":"string","description":"OpenRegister register slug for this entry's count."},"schema":{"type":"string","description":"OpenRegister schema slug for this entry's count."},"metric":{"type":"string","description":"Aggregation metric for the REST /value aggregation (default 'count')."},"field":{"type":"string","description":"Field the metric aggregates over (metric-dependent; unused for count)."},"filter":{"type":"object","additionalProperties":true,"description":"Filter map with the shared @-token grammar (@today / @me / @workspace.<key>, trailing `?` for optional clauses)."},"route":{"type":"object","additionalProperties":true,"description":"Optional vue-router location for the KPI's deep link (renders the entry clickable)."},"variant":{"type":"string","enum":["default","primary","success","warning","error"],"description":"Color variant forwarded to the inner CnStatsBlock."},"countLabel":{"type":"string","description":"Label displayed next to the count."},"iconClass":{"type":"string","description":"Optional Nextcloud core icon class (icon-link, icon-mail, …) for the entry."},"hideWhenZero":{"type":"boolean","default":false,"description":"When true, the entry is omitted from the card when its resolved count is 0."},"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":"object","additionalProperties":false,"properties":{"style":{"type":"string","enum":["number","currency","percent","decimal","duration-hours"]},"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."},"prefix":{"type":"string"},"suffix":{"type":"string"}}}]},"currency":{"type":"string","description":"Shorthand for format.currency: an ISO-4217 code, or a token such as @config.currency. Implies format 'currency'."},"currencyField":{"type":"string","description":"Shorthand for format.currencyField: the field of the detail page's object that holds the currency, e.g. 'currency'. Implies format 'currency'. The currency is taken from the object, then currency, then the app's reporting currency (config currency), then EUR."}}},"calendarView":{"type":"object","additionalProperties":false,"required":["dateField"],"description":"The calendar view of this list: the current filtered rows on a month by a date field (config.calendar). Index pages only, and only offered when config.viewModes also lists `calendar`. Only the visible month is fetched. It reads and reschedules nothing: dragging an entry to another day is not offered.","properties":{"dateField":{"type":"string","minLength":1,"description":"The date property a row is placed on."},"endDateField":{"type":"string","minLength":1,"description":"Optional end date; a row then spans every day from dateField to endDateField."},"titleField":{"type":"string","minLength":1,"description":"The property shown on the entry; falls back to title, name or the row key."}}},"formSmartPaste":{"type":"object","required":["enabled","fields"],"additionalProperties":false,"description":"Fill from pasted text on a form page (form-smart-paste). `fields` lists the config.fields[] keys the fill may write; `handler` names a customComponents entry (a function, or { fill, available }) that proposes the values; `hint` is shown above the paste box. Never offered on a public form. Cross-field rules (each field key is declared, handler present when enabled, mode not public) are checked by validateManifestV2().","properties":{"enabled":{"type":"boolean"},"fields":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}},"hint":{"type":"string"},"handler":{"type":"string","minLength":1}}},"formFieldLiveValues":{"type":"object","description":"form-live-values: the live-value keys a form field may carry on top of its type, label and key. `assign` is a list of { when, value } rules: the first whose visibleWhen-style `when` holds sets the field when an answer it reads changes (value: a literal, @answer.<field> or a sentinel token); once the user edits the field by hand its rules stop. `default` may be a sentinel token (@me, @me.displayName, @me.email, @today, @now, @object.<field>) resolved once when the form opens; an initial value wins. `calculate.inputs` names the answers a host `calculate` function reads for this read-only field. Other field keys are unchanged.","properties":{"assign":{"type":"array","minItems":1,"items":{"type":"object","required":["value"],"additionalProperties":false,"properties":{"when":{"$ref":"#/$defs/visibleWhen"},"value":{}}}},"default":{},"calculate":{"type":"object","required":["inputs"],"additionalProperties":false,"properties":{"inputs":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}}}}}},"copySettings":{"type":"object","additionalProperties":false,"description":"index-copy-with-relations: what a copy on this index page may take along. `include` lists the link kinds: relationRows (the source's relation rows are re-created on the copy), incoming (the copy is added next to the source in every array-valued reference that points at it; a single-valued reference is never moved) and files (the attached files). The copy dialogs list the included kinds, ticked, and the copy is one request to OpenRegister's copy endpoint. Without `include` a copy carries the fields only.","properties":{"include":{"type":"array","uniqueItems":true,"items":{"enum":["relationRows","incoming","files"]}}}},"page":{"type":"object","required":["id","route","type","title"],"additionalProperties":false,"description":"A v2 page definition. CnPageRenderer dispatches by type and matches by $route.name === page.id. All page types support a uniform widgets[] array for declarative slot placement. Sentinels (@resolve:*) are NOT allowed in id, route, component, headerComponent, actionsComponent, or slots.* — those are router and registry invariants.","properties":{"requiresApp":{"description":"Soft dependency gating this WHOLE page. When the named Nextcloud app is not installed, the page renders the missing-dependency screen instead of its body. This is for the DEEP LINK: an entry gated by visibleIf.appInstalled is already hidden from the menu, so the remaining ways in are a bookmark, a shared URL, a redirect or an e2e spec, and those otherwise land on an empty page where 'not installed' looks the same as 'no data'. Gate a single WIDGET with visibleIf.appInstalled instead: a detail page may carry many widgets where only one needs the app, and that widget should hide while the page renders normally.","oneOf":[{"type":"string","minLength":1},{"type":"object","required":["id"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"description":"Human-readable app name shown on the screen; defaults to the id."}}}]},"id":{"type":"string","description":"Vue-router route name. MUST be unique across pages[]. Sentinels (@resolve:*) are NOT allowed."},"route":{"type":"string","description":"Path pattern (e.g. '/decisions', '/decisions/:id'). Sentinels (@resolve:*) are NOT allowed."},"type":{"type":"string","enum":["index","detail","dashboard","logs","settings","chat","files","form","map","roadmap","reports","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","description":"i18n translation key for the page title."},"permission":{"type":"string","description":"Optional permission identifier required to access this page."},"component":{"type":"string","description":"For type='custom': name resolved against the app-provided customComponents registry. Sentinels (@resolve:*) are NOT allowed."},"headerComponent":{"type":"string","description":"Optional registry name for a component injected into the page's #header slot. Sentinels (@resolve:*) are NOT allowed."},"actionsComponent":{"type":"string","description":"Optional registry name for a component injected into the page's #actions slot. Sentinels (@resolve:*) are NOT allowed."},"sidebarComponent":{"type":"string","minLength":1,"description":"Optional registry name for a component mounted as the default sidebar content for this page."},"slots":{"type":"object","description":"Generic slot-override map. Each key is a slot name; each value is a registry component name. Sentinels (@resolve:*) are NOT allowed in values.","additionalProperties":{"type":"string"}},"primaryAction":{"$ref":"#/$defs/primaryAction","description":"Active-page-scoped primary action rendered as an NcAppNavigationNew button above the menu list when the current route resolves to this page. Page-scoped declarations win over nav.primaryAction. Also the target of the detail page's skip link: a keyboard user tabs in, follows the link and lands on this action."},"splitView":{"$ref":"#/$defs/splitView","description":"Index pages only. Opens a row beside the list rather than instead of it, at the sibling route <route>/split/:id that buildManifestRoutes() emits. Refused on any other page type."},"board":{"$ref":"#/$defs/boardView","description":"Index pages only. The board view's configuration, handed to CnIndexPage as the `board` prop. The segment appears only when `viewModes` also lists `board`. Refused on any other page type."},"dateAxis":{"$ref":"#/$defs/dateAxisView","description":"Index pages only. The date-axis view's configuration, handed to CnIndexPage as the `dateAxis` prop. The segment appears only when `viewModes` also lists `dateAxis`. Refused on any other page type."},"savedViewTree":{"$ref":"#/$defs/savedViewTree","description":"Index pages only. Renders this page's saved views as a tree with labels, and declares the views that ship with the app. Refused on any other page type: a page with no saved-view dropdown has nothing to put in a tree."},"manualOrder":{"type":"boolean","default":false,"description":"Index pages only. Lets a person drag the rows into an order of their own, held per user and per list in their Nextcloud preferences and never written onto the records. Refused on any other page type."},"tabInAddress":{"type":"boolean","default":false,"description":"Detail pages only. Puts the active tab in the address as ?_tab=<id> 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,"description":"Lets a reference on this page show a summary of the record it points at, on focus and on hover, without leaving the page. Rendered by CnReferencePreview."},"widgets":{"type":"array","items":{"$ref":"#/$defs/widgetEntry"},"description":"Uniform widget placement array. Available on ALL page types in v2 (not just dashboard). Each entry specifies a widget key, slot, and grid coordinates. The cross-field constraint gridX + gridWidth <= 12 is enforced by validateManifestV2() post-schema check. Single-widget dashboards: a type:'dashboard' page with exactly one body widget covering the full 12×12 grid whose widgetKey resolves to a custom (non-library-built-in) registry component is REJECTED — see ADR-036 Decision 1 (single-widget dashboard anti-pattern). Use type:'custom' with component:'<widgetKey>' or split into N>1 widgets instead. Library built-in widget keys are exempt from the rule; the exempt set is LIBRARY_WIDGET_KEYS in src/utils/libraryWidgetKeys.js, which is held to set equality against the BUILT_IN_WIDGETS registry and the dashboard widget catalog by tests/utils/libraryWidgetKeys.spec.js. It is not restated here, because an out-of-date second copy of that list is the defect this replaced. Enforced by validateManifestV2() post-schema check."},"actions":{"type":"array","items":{"$ref":"#/$defs/action"},"description":"Typed page-level actions with a discriminator. Each action has a type field (handler | open-modal | open-page | navigate) that defaults to 'handler' when omitted."},"config":{"type":"object","description":"Type-specific configuration. Shape varies by page type — see v1 schema for per-type config shapes (index, detail, dashboard, logs, settings, chat, files, form, map, custom). Config values under pages[].config may use the closed sentinel vocabulary (see $defs/sentinelTokenAny) — an out-of-vocabulary @-token FAILS validation via the sentinelGuardedValue allOf guard. Carry-forward from v1: register, schema, columns, actions, sidebar, sidebarProps, fields, sections, tabs, content, layout, widgets (dashboard only), cardComponent, columnGroups, etc.","additionalProperties":true,"allOf":[{"$ref":"#/$defs/sentinelGuardedValue"},{"$comment":"screens-chrome-parity: a page declares ONE place to save. `saveMode` excludes a section's own `save` (flat or inside a tab) and `autosave`.","if":{"required":["saveMode"]},"then":{"not":{"required":["autosave"],"properties":{"autosave":{"const":true}}},"properties":{"sections":{"type":"array","items":{"type":"object","not":{"required":["save"]}}},"tabs":{"type":"array","items":{"type":"object","properties":{"sections":{"type":"array","items":{"type":"object","not":{"required":["save"]}}}}}}}}}],"properties":{"favourite":{"type":"boolean","description":"Deprecated and ignored: there is no star any more, a favourite is a follow with notifications off. Kept so existing manifests stay valid; use follow."},"follow":{"type":"boolean","description":"Detail pages: the one Follow control beside the title, with a notifications on/off switch while the user follows and the follower count for a user who may update the record. Omitted means automatic: it shows when the object carries @self.watching. false removes it, and the object read then no longer asks for @self.can."},"showFollowColumn":{"type":"boolean","description":"Index pages: add a first column with a follow toggle per row, bound to each row's @self.watching. Clicking it does not open the row. Default false."},"showFavouriteColumn":{"type":"boolean","description":"Deprecated alias of showFollowColumn: the star column became the follow column. Default false."},"personalLenses":{"type":"array","description":"Index pages: personal lenses added to the quick filters, in order. watching lists the records the user follows (labelled Following), recent the records the user opened last (it owns the order, so column sorting is off), unread the records changed since the user last looked. They combine with every other filter. favourite is a deprecated alias of watching; asking for both gives one tab. Default none.","items":{"enum":["favourite","recent","watching","unread"]},"uniqueItems":true},"markRead":{"type":"boolean","description":"Detail pages: send PUT .../read-state once after the object has rendered, when it carries @self.unread, so opening a record marks it read. Default true; false keeps the page from marking anything read."},"lifecycleActions":{"description":"Detail pages: status-gated lifecycle transition buttons (CnLifecycleActions). `inputs` gives the transition dialog hints for server-declared transitions: a map of action name to a list of { field, picker?, fields? }, merged onto the server's declared input with the same `field`. `picker` { filter, excludeSelf, labelField } narrows the records a reference input offers; `fields` names the sub-properties an object input asks for. A hint for a field the server does not declare is ignored with a console warning and never adds an input.","if":{"type":"object"},"then":{"properties":{"inputs":{"type":"object","additionalProperties":{"type":"array","items":{"type":"object","required":["field"],"properties":{"field":{"type":"string","minLength":1},"picker":{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object"},"excludeSelf":{"type":"boolean"},"labelField":{"type":"string"}}},"fields":{"type":"array","items":{"type":"string"}}}}}}}}},"headerFields":{"type":"array","description":"Detail pages: fields shown as chips under the title (number, status, assignee, deadline). Each entry is a property key, or an object { key, format, labelField, colorField, warnWhenPast }. `format` is text (default), mono, badge, user or date. A `$ref` value shows `labelField` of the referenced object and falls back to the raw id in mono. An empty value shows no chip; a page without the key shows no row.","items":{"oneOf":[{"type":"string","minLength":1},{"type":"object","required":["key"],"additionalProperties":false,"properties":{"key":{"type":"string","minLength":1},"format":{"enum":["text","mono","badge","user","date"]},"labelField":{"type":"string"},"colorField":{"type":"string"},"warnWhenPast":{"type":"boolean"}}}]}},"searchInFiles":{"type":"boolean","description":"Index pages: show an \"Also search inside files\" switch beside the search box. On, the list query carries `_content_search=true` with a search term, and a row found through an attached file shows \"Found in {file}\". Default false."},"entitySource":{"type":"string","enum":["flows","flowRuns","tasks","endpoint","search","users","organisations"],"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","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`.","items":{"type":"object","additionalProperties":false,"required":["when","class"],"properties":{"when":{"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."}}}},"sourceConfig":{"type":"object","additionalProperties":true,"description":"type='index' with `entitySource`: what the named source loads with. `endpoint`: `url` (an app path, required) and `rowsPath` (default `results`); its other keys go along as query parameters. `search`: `kinds` (`{ id, label, register, schema, titleField?, subtitleField?, statusField?, href?, limit? }` or `{ id, label, files: true }`), `term` and `termQuery` (default `q`). `users`: `defaultGroup`, `functionField`, `rolesField`. `flows` / `tasks`: `app`, `scope`. Maps to CnIndexPage `sourceConfig`.","properties":{"url":{"type":"string","minLength":1},"rowsPath":{"type":"string","minLength":1},"kinds":{"type":"array","items":{"type":"object","required":["id"],"additionalProperties":true,"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string"},"register":{"type":"string"},"schema":{"type":"string"},"files":{"type":"boolean"}}}},"term":{"type":"string"},"termQuery":{"type":"string","minLength":1},"defaultGroup":{"type":"string","minLength":1},"limit":{"type":"integer","minimum":1,"description":"A paging source: rows per page (20 by default); the pager moves through the pages."}}},"app":{"type":"string","description":"Scopes a named `entitySource` to one app (e.g. entitySource='flows' with app='dossiq' lists that app's flows)."},"register":{"type":"string","description":"OpenRegister register slug."},"schema":{"type":"string","description":"OpenRegister schema slug."},"contentField":{"type":"string","description":"type='wiki': property on the article object holding the markdown body. Defaults to 'body'. Maps to CnWikiPage `contentField` prop."},"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`."},"idParam":{"type":"string","description":"type='wiki': $route.params key holding the article id. Defaults to 'id'. Maps to CnWikiPage `idParam` prop."},"treeField":{"type":"string","description":"type='wiki': property on each sidebar tree node holding its child array. Defaults to 'children'. Maps to CnWikiPage `treeField` prop."},"sidebarTitleField":{"type":"string","description":"type='wiki': property on each sidebar tree node holding its label. Defaults to `titleField`. Maps to CnWikiPage `sidebarTitleField` prop."},"sidebarRegister":{"type":"string","description":"type='wiki': register slug for the sidebar tree. Defaults to `register`. Maps to CnWikiPage `sidebarRegister` prop."},"sidebarSchema":{"type":"string","description":"type='wiki': schema slug providing the sidebar tree. When set AND `tree` is non-empty the sidebar renders. Maps to CnWikiPage `sidebarSchema` prop."},"emptyText":{"type":"string","description":"type='wiki': empty-state heading shown when the article is null. Maps to CnWikiPage `emptyText` prop."},"emptyDescription":{"type":"string","description":"type='wiki': empty-state description shown when the article is null. Maps to CnWikiPage `emptyDescription` prop."},"emptyBodyText":{"type":"string","description":"type='wiki': empty-body heading shown when the article has no content. Maps to CnWikiPage `emptyBodyText` prop."},"emptyBodyDescription":{"type":"string","description":"type='wiki': empty-body description shown when the article has no content. Maps to CnWikiPage `emptyBodyDescription` prop."},"cardComponent":{"type":"string","description":"Registry name for a custom card component on index pages. Carry-forward from v1.3.0."},"sidebar":{"description":"Sidebar configuration. For index: object with enabled, show, columnGroups[], facets, etc. For detail: boolean or object with show, enabled, register, schema, tabs[]. Carry-forward from v1.3.0.","oneOf":[{"type":"boolean"},{"type":"object","additionalProperties":true,"properties":{"show":{"type":"boolean","description":"Whether to show the sidebar on this page."},"enabled":{"type":"boolean","description":"Whether the sidebar is enabled."},"columnGroups":{"type":"array","description":"Index sidebar column-visibility groups. Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}}}}]},"tabs":{"type":"array","description":"Settings tabs (for type='settings'). Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}},"sidebarTabs":{"type":"array","description":"Sidebar tabs declaration for type='detail' pages. Each entry is the human-authored source of truth for one tab — `id` + `label` (required), optional `icon`, `order`, `component`. Widgets bound to a tab carry `tabGroup: \"<tab-id>\"` on `slot:\"sidebar\"` entries; cross-reference is validated. The CLI manifest-migrate transform lifts `config.sidebarTabs[].widgets[]` into top-level `widgets[]` with `slot:\"sidebar\"` + `tabGroup`; tab entries declaring only a `component` are carried forward as residual sidebarTabs[]. See src/cli/transforms/liftSidebarTabWidgets.js.","items":{"type":"object","additionalProperties":true,"required":["id","label"],"properties":{"id":{"type":"string","minLength":1,"description":"Unique tab identifier within the page. Referenced from widgets[].tabGroup."},"label":{"type":"string","minLength":1,"description":"i18n key (or literal) for the tab label."},"icon":{"type":"string","description":"Optional icon name (CSS class or MDI identifier) shown next to the label."},"order":{"type":"number","description":"Optional sort key for tab ordering; lower values render first."},"component":{"type":"string","description":"Optional registry name for a full-tab custom component. When set, the tab renders that component directly instead of the per-tab widgets[] grouping. Resolved against the customComponents registry."},"_note":{"type":"string","description":"Optional human note (e.g. lib-gap reason for an undecomposable tab)."}}}},"sections":{"type":"array","description":"Settings sections (for type='settings'). Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}},"fields":{"type":"array","description":"Form fields (for type='form'). Carry-forward from v1.3.0. manifest-form-logic adds two OPTIONAL typed properties — `visibleWhen` (reuses `$defs/visibleWhen`; LOCAL mode resolves `field` as a dot-path into the live form data) and `validation` (`$defs/fieldValidation`) — while every other carry-forward field shape (`key`, `label`, `type`, `enum`, `widget`, `help`, etc.) stays untyped via `additionalProperties: true` on the item.","items":{"type":"object","additionalProperties":true,"properties":{"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"manifest-form-logic: optional conditional-visibility predicate. LOCAL mode (neither `endpoint` nor `source`) resolves `field` as a dot-path into the live form data — the first dot-segment MUST match a declared `config.fields[].key` (validateManifestV2 post-schema check). `endpoint` / `source` modes keep their existing fail-safe, evaluated-once-at-mount semantics."},"validation":{"$ref":"#/$defs/fieldValidation","description":"manifest-form-logic: optional per-field validation rules, enforced client-side by CnFormPage before dispatch. See `$defs/fieldValidation` for the per-type semantics table."}}}},"steps":{"type":"array","minItems":1,"description":"manifest-form-logic: for type='form' pages, ordered multi-step wizard groups. Each entry is `{ id, title, description?, fields[] }` where `fields[]` is an ordered array of KEY REFERENCES into `config.fields[].key` — steps do not embed field objects (config.fields[] stays the single source of truth). Absent `steps` = single-step form, byte-for-byte the pre-change rendering. When present, validateManifestV2() post-schema requires: step ids unique within the page, every fields[] entry references a declared field key, and every declared field key appears in EXACTLY ONE step (complete partition — an unassigned field would silently never render; a doubly-assigned field would double-render).","items":{"type":"object","required":["id","title","fields"],"additionalProperties":false,"description":"One wizard step. `fields[]` order is the rendering order for that step's fields.","properties":{"id":{"type":"string","minLength":1,"description":"Unique step identifier within the page's `steps[]` array."},"title":{"type":"string","minLength":1,"description":"i18n key (or literal) for the step's title, shown in the step indicator."},"description":{"type":"string","description":"Optional i18n key (or literal) for a short step description/subtitle."},"fields":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Ordered field KEYS (referencing `config.fields[].key`) rendered on this step."}}}},"fieldWidgets":{"type":"array","description":"Declarative widget-component slots for type='form' / type='detail' pages. Each entry mounts a lib `Cn*` SFC inline (e.g. CnMarkdownEditor as a markdown field, CnSchemaSelect as a relation picker). Distinct from top-level pages[].widgets[] which uses widgetKey + grid coordinates against the host customComponents registry. Carries serialisable props only. See $defs/fieldWidget and ConductionNL/nextcloud-vue#314.","items":{"$ref":"#/$defs/fieldWidget"}},"columns":{"type":"array","description":"Table columns (for type='index' and type='logs'). Items may be strings (legacy shorthand) or column objects ({ key, label, sortable?, width?, formatter?, formatterOptions?, widget?, widgetProps?, format?, aggregate?, colorMap? }). `colorMap` maps an enum column's raw values to a pill tone (default, primary, success, warning, error, info, purple, teal) and wins over the schema property's own `colorMap` / `x-color-map`. `formatter` resolves against cnFormatters (built-ins incl. currency / conditionalPhrase — configured via `formatterOptions`); `widget` resolves against cnCellWidgets (built-ins incl. badge / fkResolve / link / avatar / date).","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]}},"folderSidebar":{"type":"object","additionalProperties":true,"description":"type='index': the opt-in folder navigation pane (CnFolderSidebar) rendered left of the list. Selecting a folder filters the list by `filterField` (or `field`); \"All\" clears it. Only the per-scope list-layout keys are typed here; the pane's own keys (source, register, schema, idField, nameField, folders, allLabel, title, allowCreate, ...) stay free-form. Maps to CnIndexPage `folderSidebar`.","properties":{"scopeViews":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"The view modes (`table`, `list`, `cards`, `board`, ...) a scope's look applies in; in the others the page keeps its generic look while the scope still filters (DqWerkbord, 2.129.0)."},"heading":{"type":"boolean","description":"true heads the list with the selected folder's name, its parent folder and its count (always on a `groups` pane, 2.127.0)."},"countText":{"type":"string","minLength":1,"description":"The count in that heading, `{count}` filled in, through the app's translate; a groups pane defaults to \"{count} members\" (2.127.0)."},"pane":{"type":"boolean","default":true,"description":"false keeps the folder scope (a deep link such as `?caseType=<uuid>` still applies the scope's look and filter) without drawing the pane (dossiq's simple profile, 2.126.0)."},"folders":{"type":"array","description":"Explicit folder list for source:'custom', and the place a folder declares its own list layout. Every other folder key stays free-form.","items":{"$ref":"#/$defs/folderSidebarScope"}}}},"layout":{"type":"array","description":"Dashboard layout entries (for type='dashboard', legacy). Carry-forward from v1.","items":{"type":"object","additionalProperties":true}},"content":{"type":"array","description":"Declarative content items for dashboard (widget-ref items). Carry-forward from v1.","items":{"type":"object","additionalProperties":true}},"widgets":{"type":"array","description":"Legacy dashboard widgets array in config (for type='dashboard'). In v2, prefer top-level pages[].widgets[] with the uniform widgetEntry shape. This legacy config.widgets key remains for back-compat.","items":{"type":"object","additionalProperties":true}},"stageField":{"type":"string","minLength":1,"default":"status","description":"type='detail': dot-path to the field holding the record's stage. Read by `primaryActionByStage` and `nextStep`."},"primaryActionByStage":{"type":"object","description":"type='detail': the page's primary button per stage. Keys are values of `stageField` (matched exactly first, then ignoring case). A value is an action in the header action shape, or the id of a `headerActions` entry, which then leaves the overflow menu. A stage without an entry falls back to the page's `primaryAction`.","additionalProperties":{"oneOf":[{"type":"string","minLength":1},{"$ref":"#/$defs/headerAction"}]}},"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. `{ 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"},{"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."}}}]}},"actionsMenu":{"type":"object","additionalProperties":false,"description":"type='detail': what the header's overflow menu itself shows. Omitted, the menu keeps Refresh and the three help links.","properties":{"showRefresh":{"type":"boolean","description":"false removes Refresh from the menu."},"showHelpLinks":{"type":"boolean","description":"false removes Request a feature, Report a bug and Documentation, for an app that offers them in its own help menu."},"showMarkUnread":{"type":"boolean","description":"false removes the built-in Mark as unread entry."},"showOpenSidebar":{"type":"boolean","description":"false removes the built-in Open sidebar entry of the board look."},"label":{"type":"string","minLength":1,"description":"i18n key / text naming the menu, for example 'More'. Defaults to 'Actions'."}}},"nextStep":{"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. `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,"description":"Dot-path to the stage, when it differs from `stageField`."},"stagesFrom":{"type":"string","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)."},"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."},"stages":{"type":"object","description":"Keys are stage values.","additionalProperties":{"type":"object","additionalProperties":false,"required":["checklist"],"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."},"checklist":{"type":"array","description":"What this stage still needs. An empty list shows no card.","items":{"type":"object","additionalProperties":false,"required":["label"],"properties":{"label":{"type":"string","minLength":1,"description":"i18n key / text of the item."},"hint":{"type":"string","description":"i18n key / short note after the label, for example '2 of 5 done'."},"doneField":{"type":"string","minLength":1,"description":"Dot-path on the record. The item is done when it holds a value; a list counts when it is not empty."},"doneWhen":{"type":"object","description":"A local visibleWhen condition ({ field, op, value } or { all | any }). The item is done when it is true. Wins over `doneField`."},"done":{"type":"boolean","description":"A fixed state, for an item the record cannot answer."}}}}}}}}},"typePill":{"$ref":"#/$defs/headerPill","description":"type='detail': a pill above the title saying what kind of record this is."},"statusPill":{"$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}}}]}},"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.","items":{"oneOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":true,"required":["type"],"properties":{"id":{"type":"string"},"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)."},"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}}}}}]}},"tabsFrom":{"type":"string","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`."},"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.","items":{"$ref":"#/$defs/headerAction"}},"bulkActions":{"type":"array","description":"Bulk actions rendered in the contextual selection strip that appears while rows are selected. Same shape as the row-level `actions[]` ($ref action), but the handler receives the SELECTION rather than a row: `{ actionId, selectedIds, count }`. For `type: \"open-modal\"` the selection is merged into the modal's props as `selectedIds` and `count`, so a manifest-declared modal component can act on the selection without the page hand-writing anything. Reserved ids (`copy`, `delete`) are dropped at render time: the strip already ships both as built-ins. Before this existed the strip had only a `#selection-actions` slot, which only a hand-written host component could fill — so a page declared in a manifest could carry row actions and header actions but never a bulk one.","items":{"$ref":"#/$defs/action"}},"actions":{"type":"array","items":{"anyOf":[{"type":"object","required":["label"],"additionalProperties":true,"properties":{"label":{"type":"string","minLength":1},"id":{"type":"string","not":{"pattern":"^builtin:"}},"builtin":false}},{"type":"string","enum":["builtin:view","builtin:edit","builtin:copy","builtin:delete"]}]},"allOf":[{"contains":{"const":"builtin:view"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:edit"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:copy"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:delete"},"minContains":0,"maxContains":1}],"description":"Declarative actions on a page. An object entry is governed ONLY by \"an object carrying a non-empty label\", deliberately: this key is polymorphic by page type (index rows dispatch $defs/action; detail pages also carry type='lifecycle-transition' entries), so one closed grammar here would reject working manifests. A label is what every dialect needs — CnRowActions renders an entry from its label and icon, so an entry without one became a full-height, clickable, EMPTY row in the overflow menu. An object is always an app action whatever its id: an id, when present, must be a string that does not start with 'builtin:', and the object may not set a 'builtin' key. On a type='index' page an entry may also be one of the placeholders \"builtin:view\", \"builtin:edit\", \"builtin:copy\" or \"builtin:delete\", each at most once, which puts that built-in row action at its position in the menu. A placeholder only places a built-in: config.actionToggles / the show*Action keys still decide whether it renders. Enabled built-ins the array does not place are appended after everything else in the order View, Edit, Copy, Delete. Any other string (a bare \"edit\", an unknown \"builtin:archive\") is refused, and placeholders are refused on every page type other than index."},"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, 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"}},"rowIndicatorCap":{"type":"integer","minimum":0,"default":3,"description":"type='index': how many declared indicators render on the row itself before the rest move into the row menu. Maps to CnIndexPage `rowIndicatorCap`."},"quickEditFields":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': the fields a quick edit asks for, opened from a row without leaving the list. The page names them; a record cannot open a form on a field the page did not name. A field this caller may not write (see `writableField`) renders read-only rather than missing. Maps to CnIndexPage `quickEditFields`."},"writableField":{"type":"string","default":"@self.writableFields","description":"type='index': the dotted path where a record lists the fields this caller may write. A row carrying nothing there leaves every field the quick edit named editable; an empty list is a refusal and locks them all. Maps to CnIndexPage `writableField`."},"viewTabs":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': saved view ids this page renders as tabs instead of as entries in the views control. A view appears in one place or the other, never both. An id naming a view that no longer exists produces no tab, rather than a tab that opens nothing. Maps to CnIndexPage `viewTabs`."},"viewCounts":{"description":"type='index': which saved views show how many records they match. `true` counts every saved view (tabs and the entries in the views control), an array counts only the view ids it lists. Off by default, so no count request is made. Quick filters opt in per entry with `showCount: true`. Maps to CnIndexPage `viewCounts`.","oneOf":[{"type":"boolean"},{"type":"array","items":{"type":"string","minLength":1}}],"default":false},"priorityField":{"type":"string","description":"type='index': the field carrying a record's DERIVED priority. The list reads the value and sorts on it; it never computes one. A record with no priority, or one outside `priorityLevels`, sorts after every ranked record in BOTH directions and stays in the list. Maps to CnIndexPage `priorityField`."},"priorityLevels":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': the priority values in rank order, lowest first (e.g. ['low', 'medium', 'high']). Needed because a priority does not sort alphabetically into the order a person means. Maps to CnIndexPage `priorityLevels`."},"listShortcuts":{"type":"boolean","default":false,"description":"type='index': offer the list's keyboard shortcuts. Every shortcut is also listed in the command palette and on the help key, from one catalogue, because a shortcut nobody can find does not count. Maps to CnIndexPage `listShortcuts`."},"rowActionField":{"type":"string","description":"type='index': the dotted path on each row where the server lists the actions this caller may run on that record (default '@self.actions'). A row's menu is then the intersection of what `config.actions` declares and what the server allows: an action the server allows but the page does not declare stays out, and an action the page declares but the server refuses stays out with its reason kept. A row carrying nothing at this path leaves the declaration standing. Maps to CnIndexPage `rowActionField`."},"editOpensDetail":{"type":"boolean","description":"type='index': send the Edit row action to the record's detail page instead of opening the edit modal. OPT-IN — CnPageRenderer used to set this wherever a type='detail' page or config.rowRoute existed, and it no longer does: @edit-open is bound to the same navigation as a row click, so a routed Edit just repeats the row click (the split pane, or the detail page) and the form is no longer reachable from the list. Declare true per page when the modal cannot express the record. Maps to CnIndexPage `editOpensDetail`."},"showEditAction":{"type":"boolean","description":"type='detail': show the header Edit button that opens the record's schema form. Set automatically by CnPageRenderer for every schema-bound detail page; declare false to make a detail page read-only. Maps to CnDetailPage `showEditAction`."},"formSize":{"type":"string","enum":["small","normal","large"],"default":"normal","description":"NcDialog size for the page's built-in Add/Edit form dialog (type='index' and type='detail'). A manifest `open-form` header action has always been able to ask for `large`; the built-in dialog could not, so the same app could have a roomy create form on one surface and a cramped one on another. Maps to CnIndexPage / CnDetailPage `formSize`."},"formColumns":{"type":"integer","enum":[1,2],"default":1,"description":"How many columns the page's built-in Add/Edit form flows its fields into. Pair 2 with formSize 'large', or the two columns are merely two narrow ones. CnFormDialog collapses back to one column below 700px, and a textarea or JSON editor spans both columns regardless. Maps to CnIndexPage / CnDetailPage `formColumns`."},"excludeFields":{"type":"array","items":{"type":"string"},"description":"Property names kept OUT of the page's built-in Add/Edit form dialog. Maps to CnIndexPage / CnDetailPage `excludeFields`."},"includeFields":{"type":"array","items":{"type":"string"},"description":"Property names the page's built-in Add/Edit form dialog shows, in this order — a whitelist, so anything absent is hidden regardless of excludeFields. Maps to CnIndexPage / CnDetailPage `includeFields`."},"fieldOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"Per-field overrides merged over the schema-derived field descriptors in the page's built-in Add/Edit form dialog (e.g. `{ \"description\": { \"widget\": \"textarea\" } }`). Which widget a field renders as belongs to the surface, not to the schema. Maps to CnIndexPage / CnDetailPage `fieldOverrides`."},"createDefaults":{"type":"object","additionalProperties":true,"description":"type='index': seed values for the page's built-in create dialog, never applied on edit. Resolves `@me`/`@now`/`@today` at any depth; `@object.*`/`@workspace.*`/`@config.*` need a context the page does not carry and pass through unchanged. Maps to CnIndexPage `createDefaults`."},"createSuccessRoute":{"oneOf":[{"type":"string"},{"type":"object","properties":{"name":{"type":"string"},"paramField":{"type":"string"},"objectParam":{"type":"string"}},"required":["name"],"additionalProperties":false}],"description":"type='index': route opened after a successful create from the page's built-in Add dialog; the created object's id is merged into the params. Maps to CnIndexPage `createSuccessRoute`."},"createSuccessMessage":{"type":"string","description":"type='index': toast shown after a successful create from the page's built-in Add dialog. Maps to CnIndexPage `createSuccessMessage`."},"savedViewsScope":{"type":"string","description":"type='index': which pages share this page's saved views. By default a view is shared by every page over the same register and schema and by no other, because a view is filters over one schema's fields. Set this to share views across pages over different sources, or to keep two pages over one source apart. Written into the saved view's `query.scope`; views saved before scoping stay visible everywhere. Maps to CnIndexPage `savedViewsScope`."},"defaultSavedViews":{"type":"array","description":"type='index': views the app ships for this page. Listed read-only above each user's own saved views, under Default views, and never stored: a new app version updates them for everybody and no user can delete one. A user may apply a default or save a copy, which they then own. `@`-tokens in `filters` (`@me`, `@today`, `@today+7d`) resolve when the default is applied; a copy keeps them. Maps to CnIndexPage `defaultSavedViews`.","items":{"type":"object","additionalProperties":false,"required":["slug","name","filters"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The stable name of the default. Lowercase and dash only."},"name":{"type":"string","minLength":1,"description":"What the filter is called in the control. Translatable."},"filters":{"type":"object","description":"The filters, keyed by property, in the shape a quick filter's `filter` takes."},"search":{"type":"string","description":"A search term the default applies."},"sort":{"type":"array","description":"Sort keys, in priority order.","items":{"type":"object","additionalProperties":false,"required":["key"],"properties":{"key":{"type":"string"},"order":{"type":"string","enum":["asc","desc"]}}}},"label":{"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,"description":"pill-color-field: a registered icon name the view's My views menu entry shows instead of the filter icon (a case type's own icon, say)."},"overrides":{"type":"object","additionalProperties":false,"description":"Page config this view lays over the page while it is active; leaving the view restores the page. Each key is optional and, apart from `header`, the aliases `tabs` and `views`, `sortOption`, `slots` and `badge`, takes the shape of the page config key of the same name. A saved view carries the same object in `query.overrides`, so a copy of a default keeps it (saved-view-overrides, 2.132.0).","properties":{"title":{"type":"string","minLength":1,"description":"The page title. Translatable."},"showTitle":{"type":"boolean","description":"Same as config.showTitle."},"breadcrumb":{"description":"Same as config.breadcrumb.","anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}},{"type":"null"}]},"headerMeta":{"type":"string","description":"Same as config.headerMeta."},"notice":{"type":["object","null"],"description":"Same as config.notice."},"pageTabs":{"type":["array","null"],"description":"Tabs to sibling pages or views, `countFrom` included. Same as config.pageTabs."},"headerButtons":{"type":"array","description":"Same as config.headerButtons."},"addLabel":{"type":"string","description":"Same as config.addLabel."},"searchPlaceholder":{"type":"string","description":"Same as config.searchPlaceholder."},"statRow":{"type":"array","description":"Same as config.statRow."},"quickFilters":{"type":"array","description":"Same as config.quickFilters."},"quickFiltersFrom":{"type":["object","null"],"description":"Same as config.quickFiltersFrom."},"quickFilterPlacement":{"type":"string","description":"Same as config.quickFilterPlacement."},"quickFilterMaxVisible":{"type":"integer","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."},"afterList":{"type":"array","description":"Same as config.afterList."},"columns":{"type":"array","description":"Same as config.columns."},"cardLayout":{"type":["object","null"],"description":"Same as config.cardLayout."},"cardFields":{"type":"array","items":{"type":"string"},"description":"Same as config.cardFields."},"cardComponent":{"type":"string","description":"Same as config.cardComponent."},"listConfig":{"type":"object","description":"Same as config.listConfig."},"viewModes":{"type":"array","items":{"type":"string","minLength":1},"description":"The view modes the toggle offers. Same as config.viewModes."},"viewMode":{"type":"string","minLength":1,"description":"The view mode the view opens in. Same as config.viewMode."},"viewSwitch":{"type":"array","items":{"type":"string"},"description":"Same as config.viewSwitch."},"countText":{"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."},"emptyText":{"type":"string","description":"Same as config.emptyText."},"folderSidebar":{"type":["object","null"],"description":"`null` hides the folder sidebar. Same as config.folderSidebar."},"sidebar":{"type":["object","null"],"description":"Same as config.sidebar."},"defaultSort":{"type":"array","description":"Same as config.defaultSort."},"rowClickToView":{"type":"boolean","description":"Same as config.rowClickToView."},"showViewAction":{"type":"boolean","description":"Same as config.showViewAction."},"actions":{"type":"array","description":"Same as config.actions."},"includeFields":{"type":"array","items":{"type":"string"},"description":"Same as config.includeFields."},"fieldOverrides":{"type":"object","description":"Same as config.fieldOverrides."},"tabs":{"type":["array","null"],"description":"Alias of pageTabs."},"views":{"type":"array","items":{"type":"string","minLength":1},"description":"Alias of viewModes."},"header":{"type":"object","additionalProperties":false,"description":"Groups header keys; a key spelled out beside it wins.","properties":{"title":{"type":"string","minLength":1},"showTitle":{"type":"boolean"},"breadcrumb":{"anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}},{"type":"null"}]},"notice":{"type":["object","null"]},"headerMeta":{"type":"string"},"headerButtons":{"type":"array"}}},"lockedFilters":{"type":"object","description":"Base filters added to the page's `filter` while the view is active, which the reader cannot clear, as the old Queue and Assigned to me pages had (2.132.0)."},"sortOption":{"type":"string","minLength":1,"description":"The `value` of a `sortSelectOptions` entry to select, an endpoint-sourced one included."},"slots":{"type":"object","description":"Named page slots (`below-header`, `before-collection`, ...) mapped to a registry component or a list of them drawn in order, laid over the page's `slots`; `null` or `[]` empties that slot.","additionalProperties":{"anyOf":[{"type":"string","minLength":1},{"type":"array","items":{"type":"string","minLength":1}},{"type":"null"}]}},"badge":{"description":"A count on the view's menu entry, in a menu entry's terms. `true`, or `count: true`, counts the view's own list: the page's register, schema and base filter with the view's filters. A `count` object may name another `register`, `schema` or `filter`. `counterVariant: attention` draws the attention pill.","anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"count":{"anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object"}}}]},"counterVariant":{"type":"string","enum":["default","attention"]}}}]}}}}}},"splitCloseButton":{"type":"boolean","default":true,"description":"type='index': whether the split pane draws its own close link. Set false when a `#split-pane` slot draws one from the slot's `close`. Maps to CnIndexPage `splitCloseButton`."},"splitCloseLabel":{"type":"string","description":"type='index': accessible name and tooltip for the split pane's close button. Defaults to \"Close\". Maps to CnIndexPage `splitCloseLabel`."},"look":{"type":"string","enum":["nextcloud","board"],"description":"screens-chrome-parity: draw this page in the `nextcloud` or the `board` look, over the manifest's root `look`. The nearest value wins: the page root carries `cn-look-board` or `cn-look-nextcloud`, and `cnLook` is re-provided to the page's descendants."},"saveMode":{"type":"string","enum":["section","page"],"description":"type='settings' (screens-chrome-parity): where the page saves. `section` ends each section that has fields in its own Save button and draws no page save bar. `page` draws one Save as the last header button and a Changes card listing the changed fields; no section draws a save button. Absent, the page keeps the save bar under the last section. Cannot be combined with `autosave` or with a section that declares its own `save`. Maps to CnSettingsPage `saveMode`."},"autosave":{"type":"boolean","default":false,"description":"type='settings' (screens-chrome-parity): the page saves on change. It draws no save button and its description ends with \"Changes are saved automatically.\" Cannot be combined with `saveMode`. Maps to CnSettingsPage `autosave`."},"showHeader":{"type":"boolean","default":true,"description":"type='dashboard': `false` drops the header row (title, description, header actions, edit toggle) for a page that opens with its own heading, such as a greeting widget; the title stays as a visually hidden heading (zuiddrecht-pixel-gaps). Maps to CnDashboardPage `showHeader`."},"views":{"type":"array","minItems":1,"description":"type='dashboard' | type='detail': views of the page, each its own widget grid behind one segmented control (\"My work | My team\") (view-switch-containers). The chosen view renders in a region below the page's own grid, drawn with the same grid and widgets; the page's own `widgets` and `layout` stay above it. The switch sits in the page header, or in a greeting header widget whose `content.views.options[]` carry `view` instead of `route`. The chosen view is in the address (`?view=<id>`) and in the user's preferences; the address wins. A view with nothing to draw shows `emptyText`, else a default sentence. On a detail page, views replace the automatic Data and Related body. Maps to CnDashboardPage / CnDetailPage `views`.","items":{"type":"object","required":["id","label"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"pattern":"^[A-Za-z0-9_-]+$","description":"The view id, written to the address as `?view=<id>`."},"label":{"type":"string","minLength":1,"description":"The option label and the region's accessible name (through the app's translations)."},"title":{"type":"string","minLength":1,"description":"type='dashboard': the page title while this view is open, e.g. \"My team\" (through the app's translations; view-title, 2.119.0). Without it the page keeps its own title."},"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}},"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}}}}}},"defaultView":{"type":"string","minLength":1,"description":"type='dashboard' | type='detail': the id of the view chosen when neither the address nor the user's stored choice names one; without it the first view (view-switch-containers). Maps to `defaultView`."},"viewsLabel":{"type":"string","minLength":1,"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":{"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:<name>}` in headerMeta (\"{total} contracten bij {count:suppliers} leveranciers\"); while a count loads or when it fails, the note is left out.","additionalProperties":{"type":"object","required":["register","schema"],"additionalProperties":false,"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object"}}}},"tabsLabel":{"type":"string","minLength":1,"description":"type='detail', board look: the accessible name of the tab list, for example \"Case parts\". Defaults to \"<type> parts\" from the page title (screens-detail-page-parity). Maps to CnDetailPage `tabsLabel`."},"identifierField":{"type":"string","minLength":1,"description":"type='detail', board look: the record field (a dotted path, for example `identifier`) that names the record in the last line of the side column's History card (screens-detail-page-parity). Maps to CnDetailPage `identifierField`."},"showWidgetActions":{"type":"boolean","default":true,"description":"type='dashboard' and type='detail': `false` drops the overflow Actions menu from every widget that does not set `showActions` itself, for a dashboard whose widget headers carry a header link and nothing else (zuiddrecht-pixel-gaps-2), or a record page whose data cards carry none (zuiddrecht-pixel-gaps-3; a card with an Add action keeps it). When the key is not set a detail page keeps the menu, except under the board look, where side cards carry none (screens-detail-page-parity). Maps to CnDashboardPage / CnDetailPage `showWidgetActions`."},"pageFilters":{"type":"array","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.<key>` / `@workspace.<key>` token (an endpoint widget also in its `responsePath`). Maps to the CnDashboardPage `pageFilters` prop.","items":{"type":"object","required":["key"],"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.<key>` / `@workspace.<key>` token name)."},"label":{"type":"string","description":"Control label (i18n key or plain text)."},"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":{"type":"object","additionalProperties":false,"description":"Read the options instead of listing them: from an app endpoint (`url`, `params`, `responsePath`) or from OpenRegister objects (`register`, `schema`, `filter`). `valueField` and `labelField` pick each option's value and label (default `id` and `title` or `name`); tokens resolve (2.130.0).","anyOf":[{"required":["url"]},{"required":["register","schema"]}],"properties":{"url":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"responsePath":{"type":"string","minLength":1},"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1}}},"placement":{"type":"string","enum":["row","description"],"default":"row","description":"Where the filter sits: in the header's filter row, or (a segmented group) on the description line after the period group (DqProcesanalyse)."},"default":{"type":["string","number"],"description":"Initial value seeded into the context on mount; the first option's value when omitted."},"options":{"type":"array","items":{"type":"object","required":["value","label"],"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)."},"months":{"type":"integer","minimum":1,"description":"A rolling window of this many months ending with the current one. A dashboard description's `{filter.<key>}` 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)."}}}}}}},"kpiRow":{"type":"array","items":{"type":"string"},"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."},"greeting":{"oneOf":[{"type":"boolean"},{"type":"string","enum":["first","full","date"]},{"type":"object","required":["label"],"additionalProperties":false,"properties":{"label":{"type":"string","minLength":1,"description":"i18n key drawn where the greeting would be."}}}],"default":false,"description":"type='dashboard': greet the reader in the header subtitle. `true` or \"first\" writes \"Good afternoon, Pieter · Monday 5 October 2026\", \"full\" uses the whole display name; the page `description` follows after another middle dot (screens-dashboard-greeting-header). Since 2.111.0 \"date\" leads with the date alone (\"Tuesday 7 October 2026 · ...\") and `{ \"label\": \"Customer contact\" }` puts the translated label where the greeting would be (dashboard-greeting-label). Maps to CnDashboardPage `greeting`."},"viewLinks":{"type":"array","default":[],"description":"type='dashboard': links drawn as small pills on the switch row under the header, right of the view switch (screens-dashboard-greeting-header). `route` is a route name or a `{ name, params, query }` location, `href` a plain URL; `label` is an i18n key. An entry without a label or a target is skipped. Maps to CnDashboardPage `viewLinks`.","items":{"type":"object","required":["label"],"additionalProperties":false,"properties":{"label":{"type":"string","minLength":1},"icon":{"type":"string","minLength":1},"route":{"oneOf":[{"type":"string","minLength":1},{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"params":{"type":"object"},"query":{"type":"object"}}}]},"href":{"type":"string","minLength":1}}}},"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`. 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' 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","default":false,"description":"type='index': show the page header (icon, title, description) inline. Default false: the title renders visually hidden and the sidebar names the page. Maps to CnIndexPage `showTitle`."},"countSubtitle":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["singular","plural"],"properties":{"singular":{"type":"string","minLength":1,"description":"i18n key of the line for a total of one, e.g. \"{total} invoice\"."},"plural":{"type":"string","minLength":1,"description":"i18n key of the line for any other total, e.g. \"{total} invoices\"."}}}],"description":"type='index': an i18n key for the description line with `{total}` replaced by the collection's total, e.g. \"{total} open cases\" (zuiddrecht-pixel-gaps). Shown in place of `description` once a total is known. `{ singular, plural }` picks the form by the total through the app's plural translation (`translatePlural`), so \"1 factuur\" and \"12 facturen\" each read right (index-count-plural). Maps to CnIndexPage `countSubtitle`."},"showTitleIcon":{"type":"boolean","default":true,"description":"type='index': `false` drops the icon before the page title, for a title that stands alone as the board draws it (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showTitleIcon`."},"showCount":{"type":"boolean","default":true,"description":"type='index': `false` drops the actions bar's \"Showing 20 of 258\" line, for a page whose title line already gives the total through `countSubtitle` (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showCount`."},"countText":{"type":"string","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`."},"footerNote":{"type":"string","minLength":1,"description":"type='index', board look: free text after the footer's count, e.g. \"click a column header to sort\" (screens-index-list-parity). Maps to CnIndexPage `footerNote`."},"queryPresets":{"type":"array","minItems":1,"description":"type='index': per-query overlays of this page (screens-index-query-presets). A menu entry that deep-links here with a query (`menu[].query`, e.g. { caseType: '<uuid>' }, the shape hydra gate-68 asks for instead of a second page) gets its own lenses, columns and copy: the first preset whose every `match` pair is in the route query replaces those keys of this config. Only the listed keys can be replaced; register, schema and permissions stay the page's. Switching preset remounts the list.","items":{"type":"object","required":["match"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"description":"A name for the preset, for people reading the manifest."},"match":{"type":"object","minProperties":1,"additionalProperties":{"type":["string","number","boolean"]},"description":"Route query pairs that must ALL be present, e.g. { \"caseType\": \"<uuid>\" }. A repeated parameter matches when it holds the value."},"title":{"type":"string","minLength":1,"description":"The page title while the preset applies."},"quickFilters":{"type":"array","description":"The lenses while the preset applies; same shape as config.quickFilters."},"columns":{"type":"array","description":"The columns while the preset applies; same shape as config.columns."},"cardFields":{"type":"array","items":{"type":"string"},"description":"The card facts while the preset applies."},"countText":{"type":"string","minLength":1,"description":"The count line while the preset applies."},"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","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)."},"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)."},"afterList":{"type":"array","description":"The cards after the list while the preset applies; same shape as config.afterList (index-scope-presentation).","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"}}}}}}},"viewSwitch":{"type":"array","minItems":1,"uniqueItems":true,"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 \"<mode>: 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","additionalProperties":false,"required":["field"],"description":"type='index': a second filter strip under the toolbar, on one field (board PqWachtrij: the kind in the quick filters, the channel in this strip). \"All\" and one pill per value (`options`, else the field's schema enum); picking one filters the list on that value, All clears it. With `showCount` each pill counts its rows within the active quick filter (screens-filter-strip, 2.92.0). Maps to CnIndexPage `filterStrip`.","properties":{"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}}}},"showCount":{"type":"boolean","default":false}}},"viewSwitchLinks":{"type":"object","additionalProperties":false,"minProperties":1,"description":"type='index', board look: view switch segments that open another PAGE instead of switching in place, by mode: `{ \"board\": { \"route\": \"CaseBoard\" } }` for a board or a map that is a page of its own. The segment sits in its place in the switch (table, cards, board, map) as a link: `route` with optional `params` and `query`, or `href`. A mode the page opens in place stays a button, and a route the router cannot resolve is left out. Ignored without the board look (screens-view-switch-links, 2.91.0). Maps to CnIndexPage `viewSwitchLinks`.","properties":{"table":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"cards":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"board":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"map":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}}}},"searchPlaceholder":{"type":"string","minLength":1,"description":"type='index': the placeholder (and accessible name) of the list's search field, e.g. \"Search by case, number or requester\". Run through the app's translate, so an English key with a Dutch catalogue entry reads Dutch. Absent: the library's \"Search…\" (screens-index-toolbar-parity). Maps to CnIndexPage `searchPlaceholder`."},"bulkHint":{"type":"string","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}},"cardLayout":{"type":"object","additionalProperties":false,"description":"type='index', board look: the record layout of each card in the cards view (board-record-card-layout): a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date. Each part names a field (a dot path) and is left out while it is empty; a pill maps values to a badge variant (`colorMap`, ignoring case) and to shown text (`labels`). Maps to CnIndexPage `cardLayout`.","properties":{"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"}}}]}},"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"]}}}]}}}},"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"}}}},"statRow":{"type":"array","default":[],"description":"type='index': KPI tiles above the list, between the header and the toolbar, in an auto-fit row (each at least 200px). Each entry is a widget definition, usually `type: \"stat\"`; the type resolves through the app's widget registry, then the dashboard catalog (screens-index-stat-row). Maps to CnIndexPage `statRow`.","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","minLength":1},"title":{"type":"string"},"content":{"type":"object"},"headerLink":{"type":"object"}}}},"sidePanel":{"type":"array","default":[],"description":"type='index': titled cards in a 300px column right of the toolbar and the list (above them below 1024px). Each entry is a widget definition with an optional `title` (i18n key) and `headerLink`; the type resolves as for `statRow` (screens-index-stat-row). Maps to CnIndexPage `sidePanel`.","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","minLength":1},"title":{"type":"string"},"content":{"type":"object"},"headerLink":{"type":"object"}}}},"headerButtons":{"type":"array","description":"type='index': buttons beside the page title, as the board's \"Export\" and \"New case\" (zuiddrecht-pixel-gaps-3). `action` is `add` (the Add flow; the label defaults to the Add label), `export` (the export leaf in `format`, else the export dialog; under the board look it reads \"Download\" by default), `actions-menu` (the page's header actions as one labelled menu button, absent when the page has none), `import`, `refresh`, or the id of a `headerActions` entry. Under the board look the buttons render in a fixed order whatever the manifest declares: export, actions-menu, other secondary buttons, the buildiq square, then the primary button. Shown only with `showTitle`; when they show, the actions bar drops its Views and Actions menus, and the Add button and Export menu when a button takes their action. Maps to CnIndexPage `headerButtons`. type='reports' (screens-reports-header-buttons, 2.96.0): the same buttons beside the reports page title, as board DcRapportages draws \"Downloaden\". There `label` is required and `action` is the id of a `headerActions` entry (same shape as on an index page), which says what the click does; every click also emits `header-action`. Under the board look the buildiq square sits between the secondary buttons and the primary one. Maps to CnReportsPage `headerButtons`.","items":{"type":"object","additionalProperties":false,"required":["action"],"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1,"description":"i18n key; optional for `add`."},"action":{"type":"string","minLength":1},"variant":{"type":"string","enum":["primary","secondary"],"default":"secondary"},"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)."},"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\": \"<i18n key>\" }` 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)."}}}},"search":{"type":"boolean","default":true,"description":"type='index': `false` draws no inline search in the actions bar, also in the board look, which otherwise draws one on every index page. Wins over `inlineSearch` (2.127.0)."},"searchRefusalHint":{"type":"string","description":"type='index': what to do after a refused search, through the app's translate; the default explains AND, OR, NOT and quotes (2.133.0)."},"searchPlacement":{"type":"string","enum":["","toolbar"],"default":"","description":"type='index', board look: where the search field sits. `toolbar` puts it on row 1 before the Filter button and the view switch instead of opening row 2 (DqTeamwachtrij, 2.127.0)."},"sortSelectOptions":{"type":"array","description":"type='index': the orders the index sidebar's Sort section offers in the list, card and map views, `{ value, label, order?, default?, source? }`. An option with a `source` takes its rows from an app endpoint, in that endpoint's order, for an order the server cannot sort by; the page's search, sidebar filters and pager run over those rows in the browser, and it is chosen by its value. `default: true` opens the page on it unless the address carries an order; a column header click in the table view leaves it. When the endpoint fails the list falls back to the option's `order` and shows `failedNote` (2.131.0).","items":{"type":"object","required":["value","label"],"properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"order":{"anyOf":[{"type":"string","minLength":1},{"type":"object"},{"type":"array"}]},"default":{"type":"boolean"},"source":{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","minLength":1,"description":"An app path, resolved with generateUrl and fetched with the session."},"params":{"type":"object","description":"Query parameters sent with the request."},"rowsPath":{"type":"string","minLength":1,"description":"Where the entries sit in the answer; `results` by default, a bare array as is."},"rowPath":{"type":"string","minLength":1,"description":"Where each entry carries its row; entries without one are left out."},"carry":{"type":"array","items":{"type":"string","minLength":1},"description":"Entry fields copied onto each row, such as a tier a card's head pill reads."},"idPath":{"type":"string","minLength":1,"description":"The row's identity for `annotate`; `id` by default."},"annotate":{"type":"boolean","description":"Copy the carried fields onto the rows of every other order too, matched on idPath."},"searchFields":{"type":"array","items":{"type":"string","minLength":1},"description":"The fields the search box matches; every top-level text by default."},"failedNote":{"type":"string","minLength":1,"description":"Shown above the list when the endpoint failed and the option's order stands in, through the app's translate."}}}}}},"defaultOrder":{"description":"type='index', self-fetch: the order the page opens on when neither the address nor sortKeys gives one: `\"-createdAt\"`, `\"priority:desc,title\"`, an `_order` map or a `[{ key, order }]` list (2.127.0).","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":{"type":"string","enum":["asc","desc","ASC","DESC"]}},{"type":"array","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","minLength":1},"order":{"type":"string","enum":["asc","desc"]}}}}]},"listConfig":{"type":"object","additionalProperties":true,"description":"type='index': the field mapping of the list view's rows (CnObjectRow): titleField, subtitleField, imageField, iconField, iconName, badgeField and the badge keys stay free-form. 2.127.0 types the row cells and the meta template (DqTeamwachtrij).","properties":{"subtitleTemplate":{"type":"string","minLength":1,"description":"The meta line: `{field}` tokens filled from the row; empty parts and their separators drop."},"leading":{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}},"description":"One cell before the title, drawn as a table column draws it (`{ key: \"createdAt\", widget: \"age\" }`)."},"trailing":{"description":"A cell, or a list of cells, after the badges, each drawn as a table column draws it.","anyOf":[{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}}},{"type":"array","items":{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}}}}]}}},"rowTitle":{"type":"string","enum":["link","plain"],"default":"link","description":"type='index' (screens-table-rows-parity): how the board look draws the table's title column. `link` (the default) is the 15px weight 600 underlined title of the DqZaken board; `plain` is the 15px bold title without an underline over a 13px muted secondary line, as the PqTickets and OcPublicaties boards draw it. The row stays clickable either way. The Nextcloud look ignores it. Maps to CnIndexPage `rowTitle`."},"showTypeEyebrow":{"type":"boolean","default":true,"description":"type='detail': `false` drops the type eyebrow (the type label above the record name) once the record resolves, for a header that says the type in a pill instead (zuiddrecht-pixel-gaps). Maps to CnDetailPage `showTypeEyebrow`."},"breadcrumb":{"description":"A breadcrumb line. type='detail' (zuiddrecht-pixel-gaps): one crumb naming the list the record belongs to (`route` + optional `params`, or `href`); the record's display name follows as the current crumb. Maps to CnDetailPage `breadcrumb`. type='index' | 'dashboard' | 'settings' (page-header-breadcrumb): one crumb or a list, drawn under the title in the same row style; the page title follows as the current crumb. Maps to the page's `breadcrumb`.","anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}}]},"headerCard":{"type":"boolean","default":false,"description":"type='detail': draw the header (pills, title, actions) as a bordered card on the surface colour, as the board's case header (zuiddrecht-pixel-gaps-2). Maps to CnDetailPage `headerCard`."},"headerWidget":{"type":"string","minLength":1,"description":"type='detail': the id of a widget in `widgets` to render inside the header on its own row under the title, without a card of its own, e.g. the stages bars (zuiddrecht-pixel-gaps-2). The widget leaves the body grid. Maps to CnDetailPage `headerWidget`."},"visibilityContext":{"type":"object","description":"type='detail': records the page reads beside the loaded one, so a widget's or a tab's `visibleWhen` can ask about them (detail-widgets-visible-when). `{ <name>: { field, register, schema } }`: `field` is the property of the loaded record holding the other record's id, and a condition reads that record as `@context.<name>.<path>`, e.g. `{ \"field\": \"@context.caseType.category\", \"op\": \"in\", \"value\": [\"Sociaal domein\"] }`. A record that cannot be read is absent, so its condition fails safe. Maps to CnDetailPage `visibilityContext`.","additionalProperties":{"type":"object","required":["field","register","schema"],"additionalProperties":false,"properties":{"field":{"type":"string","minLength":1,"description":"Dot-path on the loaded record to the id of the record to read."},"register":{"type":"string","minLength":1,"description":"Register slug or id of the record to read."},"schema":{"type":"string","minLength":1,"description":"Schema slug or id of the record to read."}}}},"accessPurpose":{"type":"object","required":["when","url"],"additionalProperties":false,"description":"type='detail': ask why the reader opens the record before the page shows it, and record the answer (detail-access-purpose). `when` is a condition in the visibleWhen grammar on the record and its visibilityContext records; `options` the purposes offered (\"Other\" with a written purpose is always added); `url` the app endpoint that records the purpose (POST { purpose, note }, {objectId} filled in). Nothing of the record shows until the endpoint accepts; the same record is not asked again in the same browser session. Maps to CnDetailPage `accessPurpose`.","properties":{"when":{"type":"object","description":"The condition under which the page asks, e.g. { \"field\": \"@context.caseType.specialDataCategories\", \"op\": \"notEmpty\" }."},"options":{"type":"array","items":{"type":"object","required":["value","label"],"additionalProperties":false,"properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1,"description":"i18n key."}}}},"url":{"type":"string","minLength":1,"description":"App endpoint that records the purpose; {objectId} is filled in."},"title":{"type":"string","description":"i18n key of the dialog's title."},"description":{"type":"string","description":"i18n key of the sentence under the title."}}},"mapConfig":{"type":"object","additionalProperties":true,"description":"type='index': how rows are plotted when the Map view mode is available. Non-empty (and not excluded by an explicit viewModes) adds a Map segment to the view toggle. Maps to CnIndexPage `mapConfig`.","properties":{"latField":{"type":"string","description":"Object (or `@self`) property path holding the marker's latitude. Dotted paths are supported, e.g. `@self.geo.lat`."},"lngField":{"type":"string","description":"Object (or `@self`) property path holding the marker's longitude."},"geoField":{"type":"string","description":"Alternative single property holding a GeoJSON Point geometry (`{ type: 'Point', coordinates: [lng, lat] }`). Takes precedence over latField/lngField when present and resolvable."},"popupField":{"type":"string","description":"Object property rendered in the marker popup."},"center":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"Fallback centre `[lat, lng]` when the filtered set has no plottable rows."},"clustering":{"type":"boolean","default":true,"description":"Whether coincident markers are grouped into a cluster that spiderfies on click. ON by default here — the opposite of CnMapWidget's own default — because an index map plots whatever the filter returns, and several rows at one address is the normal case: unclustered they land on identical pixels, the topmost takes every click and the rest are unreachable with nothing on screen saying they exist. Non-point geometry skips the cluster group, so areas are unaffected. Set false for the old behaviour."},"markerColor":{"type":"object","required":["field"],"additionalProperties":false,"description":"Colour each marker by a field of its row. Without `source` the value is looked up in `colorMap`; with `source` the field is a reference and the referenced row's `colorField` is the colour, or the key `colorMap` maps to one. A value nothing maps to gets `default`, else the map's own colour.","properties":{"field":{"type":"string","description":"The row field (dotted path, also under `@self`)."},"colorMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Value (or source colour key) to CSS colour; `var(--x, #fallback)` is resolved for the SVG marker."},"source":{"type":"object","required":["colorField"],"properties":{"register":{"type":"string"},"schema":{"type":"string"},"colorField":{"type":"string"},"filter":{"type":"object"}}},"default":{"type":"string"}}},"plotAll":{"oneOf":[{"type":"boolean"},{"type":"integer","minimum":1}],"description":"Plot every row that matches the filters instead of the current page: `true` for up to 500, or a number. When more rows match, a note under the map says the first N are shown."},"count":{"oneOf":[{"type":"boolean"},{"type":"string"}],"description":"A count line under the map: `true` for \"Showing {located} of {total} located\", or a template with `{located}` and `{total}` through the app's translate."}}},"quickFilterMaxVisible":{"type":"integer","minimum":0,"default":0,"description":"type='index', chips mode: how many quickFilters render as pills before the rest move behind one more chip — a `⋯` pill that opens a panel of the hidden lenses, and that wears the label and the active fill of whichever one is on. 0 (the default) renders every lens, which wraps the actions bar onto a second line once a page declares more than a handful. The visible set is the FIRST n entries of quickFilters, so reorder that array to change which lenses stay out. Maps to CnIndexPage `quickFilterMaxVisible`."},"actionToggles":{"type":"object","description":"Built-in action toggles for type='index' pages. Each key is sugar for the matching CnIndexPage prop — manifest authors can hide built-in mass actions / row actions / the Add button without writing a wrapper. Mirrors the CnIndexPage prop names. Explicit config.<key> wins over config.actionToggles.<key> (matches the readOnly precedence rule). Distinct from config.actions[] which is the typed row-actions array.","additionalProperties":true,"properties":{"showAdd":{"type":"boolean","description":"Whether to show the Add button in the actions bar. Maps to CnIndexPage `showAdd`."},"showFormDialog":{"type":"boolean","description":"Whether the built-in create / edit form dialog is enabled. Maps to CnIndexPage `showFormDialog`."},"showViewAction":{"type":"boolean","description":"Whether to add a View action to row actions. Maps to CnIndexPage `showViewAction`."},"showEditAction":{"type":"boolean","description":"Whether to add an Edit action to row actions. Maps to CnIndexPage `showEditAction`."},"showCopyAction":{"type":"boolean","description":"Whether to add a Copy action to row actions. Maps to CnIndexPage `showCopyAction`."},"showDeleteAction":{"type":"boolean","description":"Whether to add a Delete action to row actions. Maps to CnIndexPage `showDeleteAction`."},"showMassImport":{"type":"boolean","description":"Whether to show the built-in mass Import action. Maps to CnIndexPage `showMassImport`."},"showMassExport":{"type":"boolean","description":"Whether to show the built-in mass Export action. Maps to CnIndexPage `showMassExport`."},"showMassCopy":{"type":"boolean","description":"Whether to show the built-in mass Copy button. Maps to CnIndexPage `showMassCopy`."},"showMassDelete":{"type":"boolean","description":"Whether to show the built-in mass Delete button. Maps to CnIndexPage `showMassDelete`."},"showViewToggle":{"type":"boolean","description":"Whether to show the table/cards view-mode toggle. Maps to CnIndexPage `showViewToggle`."},"selectable":{"type":"boolean","description":"Whether rows are selectable (driving the mass-action surface). Maps to CnIndexPage `selectable`."}}},"mode":{"type":"string","enum":["edit","create","public"],"description":"Authoring / access mode. type='form': 'edit' loads from `data`, 'create' starts blank, 'public' shows the public-form success banner and skips auth-state requirements. type='detail': 'public' marks the route as unauthenticated (token-scoped reader pages — pair with a route-param token via the @route.<param> sentinel). Maps to CnFormPage / CnDetailPage `mode` prop."},"subscribe":{"type":"boolean","default":true,"description":"Collaborative-editing default for type='detail'. When true (the default) CnDetailPage calls `objectStore.subscribe(objectType, objectId)` on mount via `useObjectSubscription` and unsubscribes on unmount. Set to false for read-only or archive views. Mirrors the `subscribe` Boolean prop on CnDetailPage / CnObjectSidebar. See the `collaborative-editing-defaults` capability."},"lock":{"type":"boolean","default":true,"description":"Collaborative-editing default for type='detail'. When true (the default) the page wires `useObjectLock` so the locked-by-other banner renders when a remote lock is active. Explicit lock acquire/release on the edit toggle is the v2 follow-up; at v1 this flag controls whether read-side `lockState` is wired. Mirrors the `lock` Boolean prop on CnDetailPage / CnObjectSidebar."}}},"sidebar":{"type":"object","description":"Per-page top-level sidebar visibility gate. Sibling of config, applies to every page type. show: boolean gates CnPageRenderer's #sidebar slot.","additionalProperties":true,"properties":{"show":{"type":"boolean","description":"Whether the host App's #sidebar slot renders for this page. Defaults to true."}}},"_note":{"type":"string","description":"Human-readable note. REQUIRED when type is 'custom' — must document why a standard page type was not feasible."}},"allOf":[{"description":"map-image-layer: on a map page an `image` layer is a picture in flat pixel coordinates and needs a url and its width and height in pixels; markers on such a map read xField and yField.","if":{"properties":{"type":{"const":"map"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"layers":{"items":{"if":{"properties":{"type":{"const":"image"}},"required":["type"]},"then":{"required":["url","width","height"],"properties":{"url":{"type":"string","minLength":1},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}}}},"markers":{"properties":{"xField":{"type":"string","minLength":1,"description":"Image maps: the row property holding x, in pixels from the picture's left. Default x."},"yField":{"type":"string","minLength":1,"description":"Image maps: the row property holding y, in pixels from the picture's top. Default y."}}}}}}}},{"description":"screens-board-reference-columns (2.97.0): config.board on an index page, the block CnPageRenderer hands to CnIndexPage's `board` prop, is typed by boardView like pages[].board.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"board":{"$ref":"#/$defs/boardView"}}}}}},{"description":"index-calendar-view-mode: config.calendar on an index page is typed by calendarView.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"calendar":{"$ref":"#/$defs/calendarView"}}}}}},{"description":"index-copy-with-relations: config.copy on an index page is typed by copySettings.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"copy":{"$ref":"#/$defs/copySettings"}}}}}},{"description":"index-column-order-and-pinning: config.personalColumns on an index page lets people order and pin columns and keeps the layout per user; false keeps show and hide only.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"personalColumns":{"type":"boolean","default":true}}}}}},{"description":"form-live-values: config.fields[] on a form page may carry assign, a token default and calculate.","if":{"properties":{"type":{"const":"form"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"fields":{"type":"array","items":{"$ref":"#/$defs/formFieldLiveValues"}}}}}}},{"description":"form-smart-paste: config.smartPaste on a form page is typed by formSmartPaste.","if":{"properties":{"type":{"const":"form"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"smartPaste":{"$ref":"#/$defs/formSmartPaste"}}}}}},{"description":"splitView, manualOrder, savedViewTree, board and dateAxis are index-page mechanics. splitView emits a second route beside an index route and mounts the detail pane inside CnIndexPage; manualOrder holds a per-user row order for a list. Declared on a detail, dashboard or custom page they would validate and then do nothing, which is the failure this branch refuses out loud (case-page-and-list-as-a-place).","if":{"not":{"properties":{"type":{"const":"index"}},"required":["type"]}},"then":{"not":{"anyOf":[{"required":["splitView"]},{"required":["manualOrder"]},{"required":["savedViewTree"]},{"required":["board"]},{"required":["dateAxis"]}]}}},{"description":"The \"builtin:<id>\" placeholders in config.actions place CnIndexPage's built-in row actions, so only a type='index' page may use them. On every other page type config.actions holds objects only; a placeholder there would validate and then render nothing (row-action-builtin-placement).","if":{"not":{"properties":{"type":{"const":"index"}},"required":["type"]}},"then":{"properties":{"config":{"properties":{"actions":{"items":{"type":"object"}}}}}}},{"description":"tabInAddress puts the active tab of a record in the address. Only a detail page has a record and a tab strip, so the key is refused elsewhere rather than validating into a no-op (case-page-and-list-as-a-place).","if":{"not":{"properties":{"type":{"const":"detail"}},"required":["type"]}},"then":{"not":{"required":["tabInAddress"]}}},{"description":"type='wiki' (CnWikiPage) renders one manifest-declared markdown article. It MUST declare both config.register and config.schema as non-empty strings so the manifest stays the source of truth for which OpenRegister register/schema the article body is read from (manifest-wiki-page-type).","if":{"properties":{"type":{"const":"wiki"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","required":["register","schema"],"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1}}}},"required":["config"]}},{"description":"type='links' (CnLinkCardsPage) renders config.cards as link cards grouped by config.categories. It MUST declare a non-empty cards list, and every card is a $defs/linkCard, so a card that can go nowhere is refused here and not dropped in silence at render time (link-cards-page).","if":{"properties":{"type":{"const":"links"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","required":["cards"],"properties":{"description":{"type":"string"},"emptyLabel":{"type":"string"},"categories":{"type":"object","additionalProperties":{"type":"string","minLength":1}},"cards":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/linkCard"}},"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)."}}}},"required":["config"]}},{"description":"type='custom' requires _note documenting why decomposition into a standard page type was not feasible. Relaxed by ConductionNL/nextcloud-vue#315: when `component` matches the lib `Cn[A-Z]\\w+` pattern, the component name already documents the choice and _note is optional. When `component` is absent or non-Cn (host-app SFC), _note remains required.","if":{"properties":{"type":{"const":"custom"}},"required":["type"]},"then":{"if":{"properties":{"component":{"type":"string","pattern":"^Cn[A-Z]\\w+$"}},"required":["component"]},"then":{"description":"Lib-SFC custom page: component is self-documenting, _note is optional."},"else":{"description":"Host-app SFC custom page (or no component): _note is required to document why a standard page type was not feasible.","required":["_note"]}}}]},"visibleIfCondition":{"type":"object","description":"A visibleIf condition block. Each key is either a reserved specialised condition (appInstalled) or a dot-separated path into manifest.runtime. Values are predicate expressions (scalar for eq shorthand, or operator object). All conditions are combined with implicit AND.","additionalProperties":true,"properties":{"appInstalled":{"type":"string","minLength":1,"description":"Nextcloud app id that MUST be installed and enabled for this entry to render."}}},"deepLink":{"type":"object","additionalProperties":true,"required":["registerSlug","schemaSlug","urlTemplate"],"description":"ADR-040 object deep-link descriptor. Maps an OR register+schema to an in-app URL template so other apps / the integration registry can resolve a deep link to an object. Permissive: extra keys are tolerated for forward-compatibility.","properties":{"registerSlug":{"type":"string","description":"OR register slug the deep link targets."},"schemaSlug":{"type":"string","description":"OR schema slug the deep link targets."},"urlTemplate":{"type":"string","description":"In-app URL template; {uuid} (and other object placeholders) are substituted at resolve time."},"displayName":{"type":"string","description":"Human-readable label for the linked object type."}}},"adminSettingsEntry":{"type":"object","required":["id","label"],"additionalProperties":false,"oneOf":[{"required":["type"]},{"required":["component"]}],"description":"One admin-only settings section rendered by CnAppRoot's generic admin NcAppSettingsDialog, sorted into the dialog by `order`. Exactly one of `type` (a built-in section) or `component` (a custom section resolved from the renderer registry) MUST be present.","properties":{"id":{"type":"string","description":"Unique identifier for this admin-settings entry; also the rendered NcAppSettingsSection id."},"label":{"type":"string","description":"i18n translation key (English source) resolved by the consuming app's t() function, shown as the section header."},"type":{"type":"string","enum":["organisation-credentials"],"description":"Built-in admin-settings section. Closed enum. 'organisation-credentials' renders CnCredentials with scope=\"organisation\" (the OpenRegister credential broker's organisation-scope pane). Mutually exclusive with `component`."},"component":{"type":"string","description":"Registry key of a custom admin-settings section component, resolved from the CnAppRoot custom-components registry at render time. Mutually exclusive with `type`."},"order":{"type":"integer","description":"Sort order of this section within the admin dialog. Entries without an order render last, in array position."},"permission":{"type":"string","description":"Optional permission string that further narrows visibility of this section WITHIN the already owner-gated admin dialog, reusing the existing per-item permission grammar. Narrow-only: it can never widen access to a caller who is not an owner of the app."},"props":{"type":"object","additionalProperties":true,"description":"Props forwarded to a custom `component` entry. Ignored for built-in `type` entries."}}}}};const schema32 = {"type":"object","additionalProperties":false,"description":"Override for one of the four built-in support-note buttons (donate, support, feature-request, app-store). Every field is optional; omit the whole entry to keep the shell's default for that button.","properties":{"enabled":{"type":"boolean","description":"Show this button. Set false to hide it."},"label":{"type":"string","description":"Button text. Blank keeps the default label."},"url":{"type":"string","description":"Where the button goes. The feature-request and app-store buttons have no useful default, so an app that wants them must set this."},"variant":{"type":"string","enum":["primary","secondary","tertiary","error","warning","success"],"description":"NcButton variant."},"icon":{"type":"string","description":"Material Design Icon component name, e.g. HeartOutline."}}};const schema36 = {"type":"object","additionalProperties":false,"description":"The app's own defaults for the per-user options CnPersonalPreferences renders. The values a person chooses are stored in their Nextcloud preferences, never here; this block is only the bottom layer of the app default, administered value, personal value order.","properties":{"enabled":{"type":"boolean","default":true,"description":"Whether the personal layer applies at all. An administrator setting this false collapses it: the options render as unavailable with the reason, rather than being silently ignored."},"disabledReason":{"type":"string","description":"What to tell a person when the personal layer is off. Shown beside the unavailable options."},"landingPage":{"type":"string","description":"Page id the app opens on when a person has chosen nothing."},"dateDisplay":{"type":"string","enum":["absolute","relative"],"default":"absolute","description":"How dates read by default. A relative date always carries its absolute date in the accessible name and the tooltip, because a term read as '3 dagen geleden' hides the date the term runs to."},"rememberLastView":{"type":"boolean","default":false,"description":"Whether each list reopens in the view mode that person last used on it."},"pinnedMenu":{"type":"array","items":{"type":"string"},"description":"Menu ids pinned to the top of the navigation by default. A person may reorder and repin their own."}}};const schema47 = {"type":"object","additionalProperties":false,"required":["source","tokenSet"],"description":"A leaf app's NL Design token-set selection, as returned by nldesign's GET /api/token-sets catalogue (app-token-set-selection). Applied by CnAppRoot via useScopedTheme — the ONLY consumer; the backend does not read this field.","properties":{"source":{"type":"string","enum":["nldesign"],"description":"Theme provider. Closed to 'nldesign' — the only scoped-theme source this schema (and useScopedTheme) currently supports."},"tokenSet":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"The nldesign token-set id (a GET /api/token-sets entry's `id`), kebab-case. Resolves the token CSS at css/tokens/<tokenSet>.css."},"tokenSetName":{"type":"string","description":"Optional human-readable name (the matching catalogue entry's `name`), cached at selection time so a picker can display it without a second catalogue fetch."},"preview":{"type":"object","additionalProperties":false,"description":"Optional cached swatch preview (the matching catalogue entry's theming colors), for a picker to render a preview chip without re-fetching the catalogue.","properties":{"primaryColor":{"type":"string","description":"Cached theming.primary_color from the catalogue entry."},"backgroundColor":{"type":"string","description":"Cached theming.background_color from the catalogue entry."}}}}};const schema123 = {"type":"object","required":["id","label"],"additionalProperties":false,"oneOf":[{"required":["type"]},{"required":["component"]}],"description":"One admin-only settings section rendered by CnAppRoot's generic admin NcAppSettingsDialog, sorted into the dialog by `order`. Exactly one of `type` (a built-in section) or `component` (a custom section resolved from the renderer registry) MUST be present.","properties":{"id":{"type":"string","description":"Unique identifier for this admin-settings entry; also the rendered NcAppSettingsSection id."},"label":{"type":"string","description":"i18n translation key (English source) resolved by the consuming app's t() function, shown as the section header."},"type":{"type":"string","enum":["organisation-credentials"],"description":"Built-in admin-settings section. Closed enum. 'organisation-credentials' renders CnCredentials with scope=\"organisation\" (the OpenRegister credential broker's organisation-scope pane). Mutually exclusive with `component`."},"component":{"type":"string","description":"Registry key of a custom admin-settings section component, resolved from the CnAppRoot custom-components registry at render time. Mutually exclusive with `type`."},"order":{"type":"integer","description":"Sort order of this section within the admin dialog. Entries without an order render last, in array position."},"permission":{"type":"string","description":"Optional permission string that further narrows visibility of this section WITHIN the already owner-gated admin dialog, reusing the existing per-item permission grammar. Narrow-only: it can never widen access to a caller who is not an owner of the app."},"props":{"type":"object","additionalProperties":true,"description":"Props forwarded to a custom `component` entry. Ignored for built-in `type` entries."}}};const schema124 = {"type":"object","additionalProperties":false,"description":"Health checks + metric descriptors executed by the AppHost engine.","properties":{"_note":{"type":"string","description":"Free-form note for maintainers; ignored by the engine."},"health":{"type":"object","additionalProperties":false,"description":"Declarative health checks rendered as the ADR-006 {status, app, version, checks} shape.","properties":{"statusCodePolicy":{"type":"string","enum":["adr006","always200"],"default":"adr006","description":"HTTP status-code policy. 'adr006' returns 503 on a failed critical check; 'always200' always returns 200."},"checks":{"type":"array","description":"Health check descriptors.","items":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"type":"string","description":"Check id, surfaced as the key in the response's checks map."},"type":{"type":"string","enum":["database","filesystem","appEnabled","appConfig","orAvailable"],"description":"Engine check kind."},"severity":{"type":"string","enum":["critical","degraded"],"default":"critical","description":"critical → a failure flips status to error (503 under adr006); degraded → status degraded (200)."},"app":{"type":"string","description":"App id, for the appEnabled check."},"key":{"type":"string","description":"Config key, for the appConfig check."}}}},"cors":{"type":"boolean","default":false,"description":"ADR-040: when true the engine emits permissive CORS headers on the /api/health response."}}},"metrics":{"type":"array","description":"Prometheus metric descriptors. The {app}_ prefix, exposition format, and implicit {app}_info / {app}_up gauges are engine-owned.","items":{"type":"object","additionalProperties":false,"required":["name","type","source"],"properties":{"name":{"type":"string","description":"Metric name (without the {app}_ prefix the engine adds)."},"type":{"type":"string","enum":["gauge","counter"],"description":"Prometheus metric type."},"help":{"type":"string","description":"HELP text for the metric family."},"cacheTtl":{"type":"integer","minimum":0,"description":"ADR-040: optional per-metric cache TTL in seconds."},"source":{"type":"object","additionalProperties":false,"required":["kind"],"description":"Where the engine sources the value.","properties":{"kind":{"type":"string","enum":["tableCount","objectCount","objectSum","appConfig","provider"],"description":"Source kind. tableCount = COUNT over an allowlisted own-table; objectCount = OR objects by register/schema; appConfig = a numeric app-config value; provider = an IMetricsProvider implementation."},"table":{"type":"string","description":"tableCount: allowlisted [a-z0-9_] table name."},"register":{"type":"string","description":"objectCount: OR register slug."},"schema":{"type":"string","description":"objectCount / objectSum: OR schema slug."},"field":{"type":"string","description":"objectSum: the numeric object property to SUM."},"groupBy":{"type":"array","items":{"type":"string"},"description":"Columns to GROUP BY (one label per column)."},"labelMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Rename a groupBy column to a different Prometheus label name."},"labelDefaults":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Default label value for NULL/empty group columns."},"filter":{"type":"object","additionalProperties":true,"description":"Per-column operator filters (eq/neq/lt/lte/gt/gte/like)."},"key":{"type":"string","description":"appConfig: the config key whose numeric value is the sample."},"id":{"type":"string","description":"provider: the IMetricsProvider id to invoke."}}}}}}}};const func1 = Object.prototype.hasOwnProperty;const func2 = (function ucs2length(str) {
5
5
  const len = str.length;
6
6
  let length = 0;
7
7
  let pos = 0;
@@ -20,7 +20,7 @@ validateManifestV2_compiled.exports = validate20;validateManifestV2_compiled.exp
20
20
  });const formats0 = (function(){const NOT_URI_FRAGMENT = /\/|:/;const URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;return function uri(str) {
21
21
  // http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "."
22
22
  return NOT_URI_FRAGMENT.test(str) && URI.test(str);
23
- };})();const pattern4 = new RegExp("^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$", "u");const pattern5 = new RegExp("^[a-z0-9][a-z0-9-]*$", "u");const pattern6 = new RegExp("^[a-z0-9]+(?:-[a-z0-9]+)*$", "u");const pattern38 = new RegExp("^[a-z0-9]+:[a-z0-9-]+$", "u");const pattern39 = new RegExp("^[a-z0-9_-]+\\.[a-zA-Z0-9_-]+\\.(search|get|create|update|delete)$", "u");const schema37 = {"type":"object","required":["label"],"additionalProperties":false,"description":"A primary action declared on either a pages[] entry (active-page scoped) or nav.primaryAction (app-wide default). Rendered above the menu list as an NcAppNavigationNew button.","properties":{"id":{"type":"string","description":"Stable identifier for this action."},"label":{"type":"string","description":"i18n translation key / text for the button."},"icon":{"type":"string","description":"MDI icon name (e.g. 'Plus'). Defaults to 'Plus' when omitted."},"route":{"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. On a detail page it may name the record's fields as @object.<field> tokens."},"payload":{"description":"Free-form payload echoed back to the host inside the @primary-action-click event."},"action":{"$ref":"#/$defs/action","description":"A page action to run on click (zuiddrecht-pixel-gaps): CnAppNav then renders the button through CnActionButtons, so `type: \"open-form\"` (with `register` + `schema`) opens the create dialog without a create page, `navigate` pushes a route, `api-call` calls an endpoint. Drawn as one solid full-width primary button. `route` / `href` on the primary action itself are ignored while `action` is set."},"solid":{"type":"boolean","default":false,"description":"Draw the primary action as a solid, full-width primary button whatever its form (zuiddrecht-pixel-gaps). Default: a link action is already a primary button; a plain one is NcAppNavigationNew."},"permission":{"type":"string","description":"Permission the reader must hold for the action to render, checked like `menuItem.permission` (zuiddrecht-pixel-gaps)."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Display condition evaluated at render time, like `menuItem.visibleIf` (zuiddrecht-pixel-gaps). A gated-out page-scoped action falls back to `nav.primaryAction`."}}};const schema39 = {"type":"object","required":["id","label"],"description":"The fields of an action, shared by $defs/action and the non-link $defs/headerAction, which close it and constrain `children`. A typed page action with a discriminator field. The type field determines behaviour: handler (registry function), open-modal (modal by id), open-page (named route), navigate (URL), object-op (declarative OpenRegister mutation via useObjectStore), export (opens the shared CnMassExportDialog export launcher), open-form (schema-driven create dialog — CnActionButtons), refresh (bumps the cn:page:refresh signal), api-call (POST/PUT an app endpoint + toast + refresh — CnActionButtons/dispatchAction), agent (run a governed hermiq agent), run-node (invoke one OpenRegister flow node against the page object, config collected via a dialog sourced from the node's own declared form — CnActionButtons/dispatchAction, or-flow-run-node; distinct from the UNRELATED setup-wizard step type of a similar name, 'run-action'), toggle (two-way state button — CnActionButtons only, NOT dispatchable). When type is omitted, it defaults to 'handler' (back-compatibility with v1.3.0 action declarations). The Ajv instance is compiled with useDefaults: true so the default is applied during validation.","properties":{"id":{"type":"string","description":"Unique action identifier within the page's actions array."},"label":{"type":"string","description":"i18n key resolved by the consuming app's t() function at render time."},"type":{"type":"string","enum":["handler","open-modal","open-page","navigate","object-op","export","open-form","refresh","api-call","agent","run-node","toggle"],"default":"handler","description":"Action behaviour discriminator. Defaults to 'handler' when omitted (back-compat with v1.3.0). handler: calls a registry function; open-modal: opens a modal by target id; open-page: navigates to a named route; navigate: navigates to a URL; object-op: dispatches a declarative mutation (patch | delete | create) of an OpenRegister object via the shared object store; export: opens the shared CnMassExportDialog export launcher configured via entities[]/formats[] — the confirm payload ({format, entity?}) routes to the action's `handler` in the manifest actions map (the app's export service does the download). open-form (#91 Wave 3): CnActionButtons opens a schema-driven create dialog (CnAdvancedFormDialog) for the action's register/schema, saves + toasts + refreshes + optionally navigates to onSuccessRoute. refresh (#91 Wave 3): bumps the page-level cn:page:refresh event-bus signal so every endpoint-bound widget refetches. api-call (#91 Wave 3): POST/PUT the token-interpolated url with payload/params (JSON body, payload resolves @-tokens recursively at any depth) + success/error toast + auto page-refresh (unless refresh:false); confirm gating is surface-consumed intent (object-op precedent); download:true requests a binary blob and triggers a browser file download instead (auto-refresh defaults to OFF in that mode). agent (hermiq#41): run a governed hermiq agent against the page object — POSTs { register, schema, objectId, resultField?, skill?, prompt? } to /apps/hermiq/api/agents/{agent}/run-on-object (object-RBAC-scoped; dispatches the governed AgentRunRequestedEvent). register/schema/objectId default to the page's @register/@schema/@objectId object context. A first-class companion to api-call (which remains the fallback for a bespoke body); hermiq is NOT hard-required — an app-level 404 surfaces a graceful 'agent runtime unavailable' toast. toggle (#91 Wave 3): a stateful two-way state button rendered by CnActionButtons (GET stateSource on mount, optimistic write on click) — NOT dispatchable through dispatchAction. object-op declares mutation INTENT only — authority is enforced server-side by OpenRegister RBAC (ADR-022/ADR-023); authorization-shaped fields have no client-side effect and are not accepted here."},"op":{"type":"string","enum":["patch","delete","create"],"description":"object-op only: the mutation verb. patch merges `values` into the row's object (row-scoped); delete removes the row's object (row-scoped, ALWAYS confirm-gated); create adds a new object built from `values` against the widget's source register/schema (widget-scoped footer/header affordance). Required when type is 'object-op'."},"values":{"type":"object","additionalProperties":true,"description":"object-op only: the verb's payload. For patch: the partial object merged over the row's object. For create: the new object's properties. Omitted for delete."},"confirm":{"type":"boolean","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."},"props":{"type":"object","description":"Additional props forwarded to the handler or modal.","additionalProperties":true},"createOverride":{"type":"string","description":"Registry handler name that owns the persist for an `open-form` action, replacing the default objectStore.saveObject. Resolved exactly as CnIndexPage resolves its own `createOverride` prop (a kind:'create-override' entry's `.handler`, a function-valued registry entry, or a function in the legacy customComponents map). Needed when the schema requires a field the create form cannot supply on its own — a server-minted foreign key, say — where a straight save would 400."},"includeFields":{"type":"array","items":{"type":"string"},"description":"open-form: the only fields the create dialog asks for. One schema often backs two surfaces, a detail page that edits all of it and a header button that collects what someone filing a new one types, and this is how the button says which it is. Omit for the whole schema."},"excludeFields":{"type":"array","items":{"type":"string"},"description":"open-form: fields the create dialog leaves out. Use it to drop a handful; use includeFields when it is easier to name what stays."},"fieldOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"open-form: per-field overrides passed to fieldsFromSchema, keyed by field. Recognised keys include label, order, hidden, readOnly and widget."},"advanced":{"type":"boolean","default":false,"description":"open-form: open the properties-and-JSON table (CnAdvancedFormDialog) instead of the plain form. The plain form is the default because a header button is aimed at someone filing a record, not at inspecting its schema."},"formTitle":{"type":"string","description":"open-form: overrides the create dialog's heading. Defaults to 'Create {schema title}'."},"formEyebrow":{"type":"string","description":"open-form: the context line above the create dialog's heading (board look), e.g. the list the record joins. Through the host translate. Absent: no line."},"formSubtitle":{"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"],"default":"normal","description":"open-form: how wide the create dialog is, passed straight to NcDialog. Widen a form that asks enough questions to need scrolling; pair it with columns: 2."},"columns":{"type":"integer","enum":[1,2],"default":1,"description":"open-form: how many columns the fields flow into. 2 pairs them up, which halves the scrolling on a longer form. Textareas and JSON editors still span the full width, and the layout collapses to one column below 700px. Widen the dialog with size: 'large' as well, or two columns are merely two narrow ones."},"endpoint":{"type":"object","description":"open-form only: edit a record that is not a register object through an endpoint (an OpenRegister organisation). `url` is where the save goes (token-interpolated, app-relative or absolute), `method` is PATCH (default) or PUT, or POST to create a record with an empty form, `loadUrl` reads the record when it differs from `url`, `responsePath` plucks it from the answer. The fields are `formProperties`. Without `endpoint` the action is the schema-driven create form.","properties":{"url":{"type":"string"},"method":{"type":"string","enum":["PATCH","PUT","POST"]},"loadUrl":{"type":"string"},"responsePath":{"type":"string"}},"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\" } }`).","additionalProperties":{"type":"object"}},"handler":{"type":"string","description":"Registry key for type: 'handler'. Resolved across both registries passed to CnAppRoot: a v2 `registry` entry of `kind: 'handler'` exposing the function as `.handler` (or `.fn`), a directly function-valued registry entry, or a function-valued entry in the deprecated `customComponents` map. The same resolution serves actions[], bulkActions[] and headerActions[]. Also the built-in dispatch verb when paired with `route` (handler: 'navigate' → $router.push to `route`)."},"route":{"type":"string","description":"Destination route name for handler: 'navigate'. The dispatcher (manifestActionDispatch) calls $router.push({ name: route, params }). For row-level actions params include { id: row[rowKey] } automatically — do NOT restate it as params: { id: \"{id}\" }, though that placeholder does resolve (see `params`). Page-level header actions navigate without row context (e.g. a 'New X' action to a detail route)."},"icon":{"type":"string","description":"Optional icon for the action, rendered by CnActionsBar. Either an MDI icon name (e.g. 'TextBoxOutline') or a CSS icon class (e.g. 'icon-file')."},"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 + 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":"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."}}}},"formats":{"type":"array","description":"export only: the offered export formats — bare ids ('excel', 'csv', 'json' — lifted to {id, label} with an upper-cased label) or full {id, label} entries. Optional — the dialog's built-in Excel/CSV defaults apply when absent.","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["id","label"],"properties":{"id":{"type":"string","description":"Format id handed to the confirm handler."},"label":{"type":"string","description":"Pre-translated display label."}}}]}},"description":{"type":"string","description":"export only: pre-translated description shown above the export launcher's pickers."},"url":{"type":"string","description":"api-call / toggle only: the app endpoint (POST/PUT). App-relative (routed through generateUrl) or absolute (http/https). May interpolate @objectId / @object.<field> / @workspace.<key> / @config.<key> tokens inline, as well as the literal {objectId} brace placeholder (unresolved tokens collapse to empty)."},"method":{"type":"string","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","additionalProperties":true,"description":"api-call only: the JSON request body — PREFERRED over `params`, and used instead of it when both are set. Values run the SAME token grammar as `params` (@me, @now, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId, @object.<field>, @workspace.<key>/@config.<key> with a trailing `?` marking a value OPTIONAL — dropped when unresolved) but resolve RECURSIVELY at ANY nesting depth, including arrays of objects — e.g. a Filinq document-generation body: { \"dataRefs\": [{ \"register\": \"crm\", \"schema\": \"lead\", \"id\": \"@objectId\" }] }. A required (non-`?`) token left unresolved anywhere in the tree BLOCKS the call (error toast) instead of sending the literal token string to the server."},"params":{"type":"object","additionalProperties":true,"description":"Two distinct uses depending on the action's dispatch. (1) api-call / toggle: the JSON request body (legacy — prefer `payload` for anything with nested objects/arrays; ignored when `payload` is set). Values run the shared filter-token grammar ONE level deep (@me, @today±Nd, @workspace.<key>/@config.<key> with a trailing `?` for optional values dropped when unresolved; a required unresolved token skips the call). (2) handler: 'navigate' (index-page row actions, manifestActionDispatch): vue-router params merged OVER the default { id: row[rowKey] }. OMIT it for the ordinary 'open this row's detail page' action — the row id is already injected. String values run the `{field}` ROW-token grammar (NOT the @-token grammar): \"{id}\" resolves to row.id with its type preserved, \"run-{id}\" interpolates as text, and a brace-less \"new\" stays literal (that is what makes a 'New X' action navigate with { id: 'new' }). A token naming a field the row does not carry is dropped with a console.warn — so `id` falls back to the row id — instead of being pushed as a literal %7Bid%7D path segment."},"download":{"type":"boolean","default":false,"description":"api-call only: request the response as a binary blob (axios responseType 'blob') and trigger a browser file download instead of a JSON toast+refresh cycle. The filename is taken from the response's Content-Disposition header, else the token-resolved `filename`, else 'download.pdf'. The success toast still shows; unlike a normal api-call the page does NOT auto-refresh afterwards unless `refresh: true` is explicitly set."},"filename":{"type":"string","description":"api-call `download: true` only: fallback filename used when the response carries no Content-Disposition header (falls back further to 'download.pdf'). Interpolates the same token grammar as `url` (@objectId, @object.<field>, @workspace.<key>, @config.<key>, {objectId})."},"successMessage":{"type":"string","description":"api-call / open-form / agent / run-node / toggle: pre-translated success toast text (a library default applies when absent; agent default 'Run queued')."},"errorMessage":{"type":"string","description":"api-call / agent / run-node / toggle: pre-translated error toast text (falls back to the server's error/message, then a library default; an agent 404 with no structured body toasts 'agent runtime unavailable' instead)."},"refresh":{"type":"boolean","default":true,"description":"api-call / agent / run-node / toggle: bump the page-level cn:page:refresh signal after a successful call (default true; set false to skip). EXCEPTION: for an api-call with download:true the default flips to false — a file download normally shouldn't also force-refetch every widget — set refresh:true explicitly to opt back in."},"flowId":{"type":"string","description":"run-node only (REQUIRED): the published flow's uuid whose node this action invokes. A node's config and eligibility are properties of the flow it lives in (RN-3, OpenRegister's or-flow-run-node), so the manifest names which flow's instance to call rather than a bare node type — two flows could each carry the same node TYPE wired differently."},"nodeId":{"type":"string","description":"run-node only (REQUIRED): the id of the node, within flowId's published graph, to invoke directly. POSTed to /api/flows/{flowId}/nodes/{nodeId}/run — refused (404) unless that node type has opted in to direct invocation (IFlowDirectlyInvokable)."},"subject":{"type":"string","description":"run-node only: the subject object the node acts on and is authorized against (OpenRegister object-RBAC — the caller needs update permission on it; the flow's own flow.run right is deliberately NOT what gates this). Typically @objectId; may interpolate the shared @-token grammar. An unresolved required token BLOCKS the call, matching agent's objectId."},"agent":{"type":"string","description":"agent only (REQUIRED): the hermiq agent uuid to run against the page object. POSTed to /apps/hermiq/api/agents/{agent}/run-on-object (hermiq#41) — object-RBAC-scoped, dispatches the governed AgentRunRequestedEvent. May interpolate the shared @-token grammar (@objectId, @object.<field>, @workspace.<key>, @config.<key>)."},"skill":{"type":"string","description":"agent only: optional skill id folded into the governed run (interpolates the same @-token grammar as `agent`)."},"prompt":{"type":"string","description":"agent only: optional prompt handed to the governed run. Interpolates the shared @-token grammar inline (@objectId, @object.<field>, @workspace.<key>, @config.<key>) so it can be grounded on the page object — e.g. 'Summarise @object.title'."},"resultField":{"type":"string","description":"agent only: the object field the agent's result is written back to (falls back to hermiq's default result field when absent)."},"objectId":{"type":"string","description":"agent only: the target object id. Defaults to the page object context's @objectId; an unresolved required @objectId BLOCKS the call (fail-closed) rather than sending a literal token. May be an explicit id or an @-token string."},"register":{"type":"string","description":"open-form / agent / run-node: OpenRegister register slug. open-form: the create dialog's schema register. agent / run-node: the register the subject object belongs to — falls back to the page object context's @register."},"schema":{"type":"string","description":"open-form / agent / run-node: OpenRegister schema slug. open-form: the create dialog fetches this schema and saves the new object against it. agent / run-node: the schema the subject object belongs to — falls back to the page object context's @schema."},"onSuccessRoute":{"description":"open-form only: optional post-save navigation target (#91). A bare route NAME (string) navigates with the saved object's id merged into the route params under `id` (harmless when the route has no `:id` segment — backward compatible). The object form { name, paramField?, objectParam? } names the id param (`paramField`, default `id`) and, when `objectParam` is set, also passes the whole saved object under that param key (for a props:true detail route to render without a refetch). The id is read from saved.id, then saved.uuid, then saved['@self'].id.","oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","description":"The vue-router route NAME pushed after a successful save."},"paramField":{"type":"string","default":"id","description":"Route param name populated from the saved object's id (default `id`)."},"objectParam":{"type":"string","description":"When set, the whole saved object is passed under this route param (props:true detail routes render without a refetch)."}}}]},"labelOn":{"type":"string","description":"toggle only: button label rendered while the state is ON (true). Falls back to `label`."},"labelOff":{"type":"string","description":"toggle only: button label rendered while the state is OFF (false). Falls back to `label`."},"stateSource":{"$ref":"#/$defs/endpointSource","description":"toggle only: the endpoint the initial state is read from on mount (the shared endpointSource shape — url + token-resolved params + responsePath). The `field` dot-path picks the boolean off the payload."},"writeUrl":{"type":"string","description":"toggle only: the endpoint the flipped state is written to on click (interpolates the same tokens as `url`)."},"field":{"type":"string","description":"toggle only: the state field — read off the stateSource payload on mount AND sent (as the flipped boolean) in the write body under this key."},"variant":{"type":"string","description":"CnActionButtons button variant (e.g. 'primary', 'secondary', 'error'). 'error' also styles the confirm dialog as destructive. A 'primary' action is never collapsed into CnActionButtons' overflow menu and consumes none of its `inline` slots: NcActions paints all of its inline actions with one shared variant, so a lone primary among them is not expressible."},"children":{"type":"array","description":"Sub-actions, rendered by CnActionButtons as this action's OWN chevron dropdown beside its button — deliberately not folded into the page's single overflow menu, because NcActions cannot nest and a list that grows per record (app versions, say) would swamp it. Children take the same shape as any action, `visibleWhen` included, and an action carrying them consumes none of the `inline` slots. The parent itself still renders its own button beside the chevron (`type` defaults to 'handler' when omitted), so a parent meant only to group its children needs a `handler` that does something meaningful — an untyped, handler-less parent renders a button that dispatches to nothing. Each child is an action; a header action's child is a header action, so it may be a link with `href`."},"childrenLabel":{"type":"string","description":"i18n key naming the `children` dropdown. Falls back to the parent action's own label, so the chevron is never announced unlabelled."},"group":{"type":"string","minLength":1,"description":"Detail page header actions only. i18n key / text naming the group this action sits in inside the overflow menu. Grouped actions render under a caption with this name, in first appearance order, after the ungrouped ones. Ignored on other surfaces."},"adminOnly":{"type":"boolean","default":false,"description":"Detail page header actions only. Shows the action to instance admins alone, in the last group of the overflow menu. Hides a control and nothing more: the endpoint behind the action still decides who may call it. Ignored on other surfaces."},"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"#91 Wave 3: an optional predicate gating whether the action renders — the shared banner shape ({ endpoint | source | field, op, value }). The `field`-only LOCAL form evaluates against the page object context (a detail action shown only in the right lifecycle state); endpoint / source forms fetch. Fail-safe: a broken predicate hides the action."}},"allOf":[{"description":"object-op actions MUST declare their mutation verb via `op`.","if":{"properties":{"type":{"const":"object-op"}},"required":["type"]},"then":{"required":["op"]}},{"description":"api-call actions MUST declare their endpoint via `url`.","if":{"properties":{"type":{"const":"api-call"}},"required":["type"]},"then":{"required":["url"]}},{"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":{"anyOf":[{"required":["schema"]},{"required":["endpoint","formProperties"]}]}},{"description":"agent actions MUST declare the target agent uuid via `agent`.","if":{"properties":{"type":{"const":"agent"}},"required":["type"]},"then":{"required":["agent"]}},{"description":"toggle actions MUST declare their write endpoint via `writeUrl`.","if":{"properties":{"type":{"const":"toggle"}},"required":["type"]},"then":{"required":["writeUrl"]}},{"description":"run-node actions MUST declare which flow and which node within it (RN-3, or-flow-run-node) — a node's config and eligibility are properties of the flow it lives in, so neither can be inferred.","if":{"properties":{"type":{"const":"run-node"}},"required":["type"]},"then":{"required":["flowId","nodeId"]}}]};const schema44 = {"type":"object","required":["url"],"additionalProperties":false,"description":"Wave 2 (#91): ONE coherent endpoint data binding shared by the stat / delta / object-table widgets (the chart uses the extended $defs/chartEndpointSource). Reads an arbitrary app REST endpoint through the library's useEndpointSource engine: params pass the SAME @-token grammar widget filters use (@me, @today±Nd, @monthStart, @workspace.<key>/@config.<key> with a trailing `?` for optional values that drop when unresolved — a REQUIRED token that stays unresolved blocks the fetch), requests dedupe + short-TTL cache per (method, url, resolved params), and the widget refetches on cn:page:refresh / a matching cn:widget:refresh. Exactly ONE of the widget's OpenRegister source | endpointSource may be configured — a cross-field rule enforced by validateManifestV2() as a post-schema check (clear error message), matching the stats-block precedent.","properties":{"url":{"type":"string","description":"Endpoint URL — app-relative (routed through generateUrl, e.g. '/apps/pipelinq/api/analytics/overview') or absolute (http/https, left untouched). May interpolate @page.<key> / @workspace.<key> / @config.<key> / @objectId / @object.<field> tokens inline; unresolved URL tokens collapse to an empty string."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method. GET (default) sends the resolved params as query parameters; POST sends them as the JSON body."},"params":{"type":"object","additionalProperties":true,"description":"Request parameters. Values pass the shared filter-token grammar (resolveFilterTokens): @me, @now, @today, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId/@object.<field> (detail context), @workspace.<key>/@config.<key> 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."}}};const schema40 = {"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.<key>` 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.<key>` 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."},"optionIcon":{"type":"string","minLength":1,"description":"look 'search': one icon for every row."},"itemOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"widget 'sub-objects': field overrides for the row form (sub-objects-editor)."},"newItem":{"type":"object","description":"widget 'sub-objects': the values a new row starts with."},"itemLabel":{"type":"string","minLength":1,"description":"widget 'sub-objects': what one row is called (\"Checklist item\"), for its accessible name and dialog title."},"emptyText":{"type":"string","description":"widget 'sub-objects': shown while there are no rows."},"pruneWhen":{"type":"object","additionalProperties":false,"description":"widget 'sub-objects': tidy the rows on save: trim strings, omit empty optional keys, drop rows with a `blank` key, and with `invalid: true` rows missing a required key.","properties":{"blank":{"type":"array","items":{"type":"string","minLength":1}},"invalid":{"type":"boolean"}}},"placeholderFrom":{"type":"string","minLength":1,"description":"A text field's placeholder is this other field's value (form-small-features)."},"requiredWhen":{"type":"object","description":"Required while this condition holds: a `visibleWhen` predicate, or `{ all }` / `{ any }` of them (form-small-features)."},"warnWhen":{"description":"A note under the field that does not block the save, shown while `when` holds (form-small-features).","anyOf":[{"$ref":"#/$defs/fieldWarning"},{"type":"array","items":{"$ref":"#/$defs/fieldWarning"}}]},"clearWhenHidden":{"type":"boolean","description":"Write the field empty when its visibleWhen hides it (form-small-features)."},"stored":{"type":"boolean","description":"`false`: the field steers the form only and is not saved; it may be a key the schema does not have, with `type` and `label` (form-small-features)."},"initialWhen":{"type":"object","description":"A field that is not stored starts true when this condition holds on the loaded values (form-small-features)."},"sectionCollapsed":{"type":"boolean","description":"Makes the field's section foldable from its heading; true opens it folded (form-small-features)."},"optionSwatches":{"type":"object","additionalProperties":{"type":"string"},"description":"A select option's colour by value, drawn before its label (form-small-features)."},"optionIcons":{"type":"object","additionalProperties":{"type":"string"},"description":"A select option's icon by value, drawn before its label (form-small-features)."},"unique":{"description":"Refuse a value another stored object of this schema already has, checked before the save: `true`, or `{ filter }` to compare only among the objects the filter matches (`{ \"caseType\": \"@objectId\" }`) (list-reorder-order-field).","anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object"}}}]},"defaults":{"type":"object","description":"widget 'object-switch': the object the switch fills in when turned on; off writes null. Its keys are edited as fields at dotted paths."},"optionsFrom":{"$ref":"#/$defs/optionsFrom","description":"Draw the field as a select (a multiselect on an array field) over options read from a source; the option's value is stored and its label shown. A register schema property can say the same with `x-options-from` (form-field-options-from, 2.133.0)."}}};const schema43 = {"description":"Select options read from a source: rows from an app endpoint (`url`, `params`, `responsePath`) or OpenRegister objects (`register`, `schema`, `filter`; tokens resolve). `valueField` and `labelField` are dot-paths into a row: by default the value is `id` (else the object's uuid) and the label `title` (else `name`, else the value).","oneOf":[{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","minLength":1,"description":"An app path, resolved with generateUrl."},"params":{"type":"object","description":"Query parameters sent with the request."},"responsePath":{"type":"string","minLength":1,"description":"Where the rows sit in the answer."},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"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.<key>`, 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)."}}},{"type":"object","additionalProperties":false,"required":["register","schema"],"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","description":"Filters on the objects, as a list filter takes them."},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"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.<key>`, 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)."}}}]};const wrapper0 = {validate: validate24};function validate24(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate24.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.widget !== undefined){let data0 = data.widget;if(typeof data0 === "string"){if(func2(data0) < 1){const err0 = {instancePath:instancePath+"/widget",schemaPath:"#/properties/widget/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}else {const err1 = {instancePath:instancePath+"/widget",schemaPath:"#/properties/widget/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}if(data.createModal !== undefined){let data1 = data.createModal;if(typeof data1 === "string"){if(func2(data1) < 1){const err2 = {instancePath:instancePath+"/createModal",schemaPath:"#/properties/createModal/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}else {const err3 = {instancePath:instancePath+"/createModal",schemaPath:"#/properties/createModal/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.createModalProps !== undefined){let data2 = data.createModalProps;if(data2 && typeof data2 == "object" && !Array.isArray(data2));else {const err4 = {instancePath:instancePath+"/createModalProps",schemaPath:"#/properties/createModalProps/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.pickerFilter !== undefined){let data3 = data.pickerFilter;if(data3 && typeof data3 == "object" && !Array.isArray(data3));else {const err5 = {instancePath:instancePath+"/pickerFilter",schemaPath:"#/properties/pickerFilter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.hintWidget !== undefined){let data4 = data.hintWidget;if(typeof data4 === "string"){if(func2(data4) < 1){const err6 = {instancePath:instancePath+"/hintWidget",schemaPath:"#/properties/hintWidget/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}else {const err7 = {instancePath:instancePath+"/hintWidget",schemaPath:"#/properties/hintWidget/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.look !== undefined){let data5 = data.look;if(typeof data5 !== "string"){const err8 = {instancePath:instancePath+"/look",schemaPath:"#/properties/look/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!(data5 === "search")){const err9 = {instancePath:instancePath+"/look",schemaPath:"#/properties/look/enum",keyword:"enum",params:{allowedValues: schema40.properties.look.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.optionSecondary !== undefined){if(typeof data.optionSecondary !== "string"){const err10 = {instancePath:instancePath+"/optionSecondary",schemaPath:"#/properties/optionSecondary/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.optionAvatar !== undefined){if(typeof data.optionAvatar !== "boolean"){const err11 = {instancePath:instancePath+"/optionAvatar",schemaPath:"#/properties/optionAvatar/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.optionIcon !== undefined){let data8 = data.optionIcon;if(typeof data8 === "string"){if(func2(data8) < 1){const err12 = {instancePath:instancePath+"/optionIcon",schemaPath:"#/properties/optionIcon/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}else {const err13 = {instancePath:instancePath+"/optionIcon",schemaPath:"#/properties/optionIcon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.itemOverrides !== undefined){let data9 = data.itemOverrides;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){for(const key0 in data9){if(!(wrapper0.validate(data9[key0], {instancePath:instancePath+"/itemOverrides/" + key0.replace(/~/g, "~0").replace(/\//g, "~1"),parentData:data9,parentDataProperty:key0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? wrapper0.validate.errors : vErrors.concat(wrapper0.validate.errors);errors = vErrors.length;}}}else {const err14 = {instancePath:instancePath+"/itemOverrides",schemaPath:"#/properties/itemOverrides/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.newItem !== undefined){let data11 = data.newItem;if(!(data11 && typeof data11 == "object" && !Array.isArray(data11))){const err15 = {instancePath:instancePath+"/newItem",schemaPath:"#/properties/newItem/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.itemLabel !== undefined){let data12 = data.itemLabel;if(typeof data12 === "string"){if(func2(data12) < 1){const err16 = {instancePath:instancePath+"/itemLabel",schemaPath:"#/properties/itemLabel/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}else {const err17 = {instancePath:instancePath+"/itemLabel",schemaPath:"#/properties/itemLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.emptyText !== undefined){if(typeof data.emptyText !== "string"){const err18 = {instancePath:instancePath+"/emptyText",schemaPath:"#/properties/emptyText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.pruneWhen !== undefined){let data14 = data.pruneWhen;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){for(const key1 in data14){if(!((key1 === "blank") || (key1 === "invalid"))){const err19 = {instancePath:instancePath+"/pruneWhen",schemaPath:"#/properties/pruneWhen/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data14.blank !== undefined){let data15 = data14.blank;if(Array.isArray(data15)){const len0 = data15.length;for(let i0=0; i0<len0; i0++){let data16 = data15[i0];if(typeof data16 === "string"){if(func2(data16) < 1){const err20 = {instancePath:instancePath+"/pruneWhen/blank/" + i0,schemaPath:"#/properties/pruneWhen/properties/blank/items/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}else {const err21 = {instancePath:instancePath+"/pruneWhen/blank/" + i0,schemaPath:"#/properties/pruneWhen/properties/blank/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}}else {const err22 = {instancePath:instancePath+"/pruneWhen/blank",schemaPath:"#/properties/pruneWhen/properties/blank/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data14.invalid !== undefined){if(typeof data14.invalid !== "boolean"){const err23 = {instancePath:instancePath+"/pruneWhen/invalid",schemaPath:"#/properties/pruneWhen/properties/invalid/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}else {const err24 = {instancePath:instancePath+"/pruneWhen",schemaPath:"#/properties/pruneWhen/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.placeholderFrom !== undefined){let data18 = data.placeholderFrom;if(typeof data18 === "string"){if(func2(data18) < 1){const err25 = {instancePath:instancePath+"/placeholderFrom",schemaPath:"#/properties/placeholderFrom/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/placeholderFrom",schemaPath:"#/properties/placeholderFrom/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data.requiredWhen !== undefined){let data19 = data.requiredWhen;if(!(data19 && typeof data19 == "object" && !Array.isArray(data19))){const err27 = {instancePath:instancePath+"/requiredWhen",schemaPath:"#/properties/requiredWhen/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data.warnWhen !== undefined){let data20 = data.warnWhen;const _errs46 = errors;let valid5 = false;const _errs47 = errors;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(data20.when === undefined){const err28 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/$defs/fieldWarning/required",keyword:"required",params:{missingProperty: "when"},message:"must have required property '"+"when"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data20.text === undefined){const err29 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/$defs/fieldWarning/required",keyword:"required",params:{missingProperty: "text"},message:"must have required property '"+"text"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key2 in data20){if(!((key2 === "when") || (key2 === "text"))){const err30 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/$defs/fieldWarning/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data20.when !== undefined){let data21 = data20.when;if(!(data21 && typeof data21 == "object" && !Array.isArray(data21))){const err31 = {instancePath:instancePath+"/warnWhen/when",schemaPath:"#/$defs/fieldWarning/properties/when/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}if(data20.text !== undefined){let data22 = data20.text;if(typeof data22 === "string"){if(func2(data22) < 1){const err32 = {instancePath:instancePath+"/warnWhen/text",schemaPath:"#/$defs/fieldWarning/properties/text/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/warnWhen/text",schemaPath:"#/$defs/fieldWarning/properties/text/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}else {const err34 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/$defs/fieldWarning/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}var _valid0 = _errs47 === errors;valid5 = valid5 || _valid0;const _errs55 = errors;if(Array.isArray(data20)){const len1 = data20.length;for(let i1=0; i1<len1; i1++){let data23 = data20[i1];if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(data23.when === undefined){const err35 = {instancePath:instancePath+"/warnWhen/" + i1,schemaPath:"#/$defs/fieldWarning/required",keyword:"required",params:{missingProperty: "when"},message:"must have required property '"+"when"+"'"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(data23.text === undefined){const err36 = {instancePath:instancePath+"/warnWhen/" + i1,schemaPath:"#/$defs/fieldWarning/required",keyword:"required",params:{missingProperty: "text"},message:"must have required property '"+"text"+"'"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key3 in data23){if(!((key3 === "when") || (key3 === "text"))){const err37 = {instancePath:instancePath+"/warnWhen/" + i1,schemaPath:"#/$defs/fieldWarning/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data23.when !== undefined){let data24 = data23.when;if(!(data24 && typeof data24 == "object" && !Array.isArray(data24))){const err38 = {instancePath:instancePath+"/warnWhen/" + i1+"/when",schemaPath:"#/$defs/fieldWarning/properties/when/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data23.text !== undefined){let data25 = data23.text;if(typeof data25 === "string"){if(func2(data25) < 1){const err39 = {instancePath:instancePath+"/warnWhen/" + i1+"/text",schemaPath:"#/$defs/fieldWarning/properties/text/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/warnWhen/" + i1+"/text",schemaPath:"#/$defs/fieldWarning/properties/text/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}}else {const err41 = {instancePath:instancePath+"/warnWhen/" + i1,schemaPath:"#/$defs/fieldWarning/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/properties/warnWhen/anyOf/1/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}var _valid0 = _errs55 === errors;valid5 = valid5 || _valid0;if(!valid5){const err43 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/properties/warnWhen/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}else {errors = _errs46;if(vErrors !== null){if(_errs46){vErrors.length = _errs46;}else {vErrors = null;}}}}if(data.clearWhenHidden !== undefined){if(typeof data.clearWhenHidden !== "boolean"){const err44 = {instancePath:instancePath+"/clearWhenHidden",schemaPath:"#/properties/clearWhenHidden/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data.stored !== undefined){if(typeof data.stored !== "boolean"){const err45 = {instancePath:instancePath+"/stored",schemaPath:"#/properties/stored/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}if(data.initialWhen !== undefined){let data28 = data.initialWhen;if(!(data28 && typeof data28 == "object" && !Array.isArray(data28))){const err46 = {instancePath:instancePath+"/initialWhen",schemaPath:"#/properties/initialWhen/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data.sectionCollapsed !== undefined){if(typeof data.sectionCollapsed !== "boolean"){const err47 = {instancePath:instancePath+"/sectionCollapsed",schemaPath:"#/properties/sectionCollapsed/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data.optionSwatches !== undefined){let data30 = data.optionSwatches;if(data30 && typeof data30 == "object" && !Array.isArray(data30)){for(const key4 in data30){if(typeof data30[key4] !== "string"){const err48 = {instancePath:instancePath+"/optionSwatches/" + key4.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/optionSwatches/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/optionSwatches",schemaPath:"#/properties/optionSwatches/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data.optionIcons !== undefined){let data32 = data.optionIcons;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){for(const key5 in data32){if(typeof data32[key5] !== "string"){const err50 = {instancePath:instancePath+"/optionIcons/" + key5.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/optionIcons/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}else {const err51 = {instancePath:instancePath+"/optionIcons",schemaPath:"#/properties/optionIcons/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data.unique !== undefined){let data34 = data.unique;const _errs84 = errors;let valid14 = false;const _errs85 = errors;if(typeof data34 !== "boolean"){const err52 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(true !== data34){const err53 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf/0/const",keyword:"const",params:{allowedValue: true},message:"must be equal to constant"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}var _valid1 = _errs85 === errors;valid14 = valid14 || _valid1;const _errs87 = errors;if(data34 && typeof data34 == "object" && !Array.isArray(data34)){for(const key6 in data34){if(!(key6 === "filter")){const err54 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data34.filter !== undefined){let data35 = data34.filter;if(!(data35 && typeof data35 == "object" && !Array.isArray(data35))){const err55 = {instancePath:instancePath+"/unique/filter",schemaPath:"#/properties/unique/anyOf/1/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}var _valid1 = _errs87 === errors;valid14 = valid14 || _valid1;if(!valid14){const err57 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}else {errors = _errs84;if(vErrors !== null){if(_errs84){vErrors.length = _errs84;}else {vErrors = null;}}}}if(data.defaults !== undefined){let data36 = data.defaults;if(!(data36 && typeof data36 == "object" && !Array.isArray(data36))){const err58 = {instancePath:instancePath+"/defaults",schemaPath:"#/properties/defaults/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data.optionsFrom !== undefined){let data37 = data.optionsFrom;const _errs96 = errors;let valid17 = false;let passing0 = null;const _errs97 = errors;if(data37 && typeof data37 == "object" && !Array.isArray(data37)){if(data37.url === undefined){const err59 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/0/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}for(const key7 in data37){if(!(func1.call(schema43.oneOf[0].properties, key7))){const err60 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/0/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}if(data37.url !== undefined){let data38 = data37.url;if(typeof data38 === "string"){if(func2(data38) < 1){const err61 = {instancePath:instancePath+"/optionsFrom/url",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/url/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}else {const err62 = {instancePath:instancePath+"/optionsFrom/url",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(data37.params !== undefined){let data39 = data37.params;if(!(data39 && typeof data39 == "object" && !Array.isArray(data39))){const err63 = {instancePath:instancePath+"/optionsFrom/params",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data37.responsePath !== undefined){let data40 = data37.responsePath;if(typeof data40 === "string"){if(func2(data40) < 1){const err64 = {instancePath:instancePath+"/optionsFrom/responsePath",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/responsePath/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}else {const err65 = {instancePath:instancePath+"/optionsFrom/responsePath",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/responsePath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data37.valueField !== undefined){let data41 = data37.valueField;if(typeof data41 === "string"){if(func2(data41) < 1){const err66 = {instancePath:instancePath+"/optionsFrom/valueField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/valueField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}else {const err67 = {instancePath:instancePath+"/optionsFrom/valueField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/valueField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}if(data37.labelField !== undefined){let data42 = data37.labelField;if(typeof data42 === "string"){if(func2(data42) < 1){const err68 = {instancePath:instancePath+"/optionsFrom/labelField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/labelField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}else {const err69 = {instancePath:instancePath+"/optionsFrom/labelField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/labelField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}if(data37.groupField !== undefined){let data43 = data37.groupField;if(typeof data43 === "string"){if(func2(data43) < 1){const err70 = {instancePath:instancePath+"/optionsFrom/groupField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/groupField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}else {const err71 = {instancePath:instancePath+"/optionsFrom/groupField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/groupField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}if(data37.where !== undefined){let data44 = data37.where;if(!(data44 && typeof data44 == "object" && !Array.isArray(data44))){const err72 = {instancePath:instancePath+"/optionsFrom/where",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/where/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data37.itemsPath !== undefined){let data45 = data37.itemsPath;if(typeof data45 === "string"){if(func2(data45) < 1){const err73 = {instancePath:instancePath+"/optionsFrom/itemsPath",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/itemsPath/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}else {const err74 = {instancePath:instancePath+"/optionsFrom/itemsPath",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/itemsPath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data37.hideWhenEmpty !== undefined){if(typeof data37.hideWhenEmpty !== "boolean"){const err75 = {instancePath:instancePath+"/optionsFrom/hideWhenEmpty",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/hideWhenEmpty/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}}else {const err76 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/0/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}var _valid2 = _errs97 === errors;if(_valid2){valid17 = true;passing0 = 0;}const _errs118 = errors;if(data37 && typeof data37 == "object" && !Array.isArray(data37)){if(data37.register === undefined){const err77 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/1/required",keyword:"required",params:{missingProperty: "register"},message:"must have required property '"+"register"+"'"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(data37.schema === undefined){const err78 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/1/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}for(const key8 in data37){if(!(func1.call(schema43.oneOf[1].properties, key8))){const err79 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}if(data37.register !== undefined){let data47 = data37.register;if(typeof data47 === "string"){if(func2(data47) < 1){const err80 = {instancePath:instancePath+"/optionsFrom/register",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/register/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}else {const err81 = {instancePath:instancePath+"/optionsFrom/register",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data37.schema !== undefined){let data48 = data37.schema;if(typeof data48 === "string"){if(func2(data48) < 1){const err82 = {instancePath:instancePath+"/optionsFrom/schema",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/schema/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}else {const err83 = {instancePath:instancePath+"/optionsFrom/schema",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}if(data37.filter !== undefined){let data49 = data37.filter;if(!(data49 && typeof data49 == "object" && !Array.isArray(data49))){const err84 = {instancePath:instancePath+"/optionsFrom/filter",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data37.valueField !== undefined){let data50 = data37.valueField;if(typeof data50 === "string"){if(func2(data50) < 1){const err85 = {instancePath:instancePath+"/optionsFrom/valueField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/valueField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}else {const err86 = {instancePath:instancePath+"/optionsFrom/valueField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/valueField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}if(data37.labelField !== undefined){let data51 = data37.labelField;if(typeof data51 === "string"){if(func2(data51) < 1){const err87 = {instancePath:instancePath+"/optionsFrom/labelField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/labelField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}else {const err88 = {instancePath:instancePath+"/optionsFrom/labelField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/labelField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data37.groupField !== undefined){let data52 = data37.groupField;if(typeof data52 === "string"){if(func2(data52) < 1){const err89 = {instancePath:instancePath+"/optionsFrom/groupField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/groupField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}else {const err90 = {instancePath:instancePath+"/optionsFrom/groupField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/groupField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}if(data37.where !== undefined){let data53 = data37.where;if(!(data53 && typeof data53 == "object" && !Array.isArray(data53))){const err91 = {instancePath:instancePath+"/optionsFrom/where",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/where/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}if(data37.itemsPath !== undefined){let data54 = data37.itemsPath;if(typeof data54 === "string"){if(func2(data54) < 1){const err92 = {instancePath:instancePath+"/optionsFrom/itemsPath",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/itemsPath/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}else {const err93 = {instancePath:instancePath+"/optionsFrom/itemsPath",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/itemsPath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}if(data37.hideWhenEmpty !== undefined){if(typeof data37.hideWhenEmpty !== "boolean"){const err94 = {instancePath:instancePath+"/optionsFrom/hideWhenEmpty",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/hideWhenEmpty/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}}else {const err95 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}var _valid2 = _errs118 === errors;if(_valid2 && valid17){valid17 = false;passing0 = [passing0, 1];}else {if(_valid2){valid17 = true;passing0 = 1;}}if(!valid17){const err96 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}else {errors = _errs96;if(vErrors !== null){if(_errs96){vErrors.length = _errs96;}else {vErrors = null;}}}}}else {const err97 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}validate24.errors = vErrors;return errors === 0;}validate24.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema45 = {"type":"object","additionalProperties":false,"description":"#91 Wave 3: the shared visibility predicate (the Wave-1 banner shape). THREE modes: `endpoint` (GET a JSON endpoint, `field` dot-paths into the body), `source` (an OpenRegister { register, schema, filter? } query — `field` into the first result, or omit / use @total for the collection total), or LOCAL (neither endpoint nor source — `field` dot-paths into the caller's object context, e.g. a detail-page record). `op` defaults to eq. Evaluation is FAIL-SAFE (any error → hidden).","properties":{"appInstalled":{"type":"string","minLength":1,"description":"Nextcloud app id that must be installed and enabled for the guarded element to show. A PRECONDITION rather than a fourth mode: it is checked before anything else, and on its own it is the whole condition. Combined with endpoint/source/field it gates that mode too, so both must hold. It exists because the menu's `visibleIf` already spoke this word and `visibleWhen` did not, so the same manifest vocabulary meant two different things in two places. Use it on an action that writes into a sibling app, so an install without that app shows no button rather than a form that saves nowhere."},"endpoint":{"type":"string","description":"A same-origin URL returning JSON; `field` is a dot-path into the body."},"source":{"type":"object","additionalProperties":true,"description":"An OpenRegister object query { register, schema, filter? } (filter supports the shared @-token grammar). `field` dot-paths into the first result; omit it (or use @total) to compare the collection total.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"filter":{"type":"object","additionalProperties":true}}},"workspace":{"type":"string","minLength":1,"description":"WORKSPACE mode (screens-page-lenses, 2.87.0): the key of the page workspace context to compare, read at once with no request. A page control (a `workspace-filter` with `writes`) sets the key, and a dashboard widget carrying this condition appears and disappears as it changes. Honoured on a dashboard widget or KPI-row tile only, and as a top-level condition (not inside `all` / `any`)."},"field":{"type":"string","description":"Dot-path to the compared value — into the endpoint body, the source's first result, or (LOCAL mode) the caller's object context."},"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte","empty","notEmpty","in","anyOf","contains"],"default":"eq","description":"Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `in` holds when the value is one of the listed `value` array (2.87.0). `anyOf` holds when the value, or any entry of a list value, is one of the listed `value` array: a record whose `dataCategories` holds \"health\" among others matches `anyOf [\"health\", \"ethnicity\"]` (2.126.0). `contains` holds when a list value holds the given `value`, e.g. a case type's overviewCards switching a card on (2.128.0). `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."},"value":{"description":"The right-hand side of the comparison, resolved through the shared @-token grammar so `@me` names the signed-in reader rather than a literal only the server knows. Omitted for `empty` / `notEmpty`."},"all":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/visibleWhen"},"description":"Every listed condition must hold. Use it where a gate is more than one fact — a Claim button that is both 'not closed' and 'nobody holds it'. Each entry is a full visibleWhen and may compose again."},"any":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/visibleWhen"},"description":"At least one listed condition must hold. Ignored when `all` is also present."}}};const wrapper1 = {validate: validate26};function validate26(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate26.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.op === undefined){data.op = "eq";}for(const key0 in data){if(!(func1.call(schema45.properties, key0))){const err0 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}if(data.appInstalled !== undefined){let data0 = data.appInstalled;if(typeof data0 === "string"){if(func2(data0) < 1){const err1 = {instancePath:instancePath+"/appInstalled",schemaPath:"#/properties/appInstalled/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}else {const err2 = {instancePath:instancePath+"/appInstalled",schemaPath:"#/properties/appInstalled/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.endpoint !== undefined){if(typeof data.endpoint !== "string"){const err3 = {instancePath:instancePath+"/endpoint",schemaPath:"#/properties/endpoint/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.source !== undefined){let data2 = data.source;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.register !== undefined){if(typeof data2.register !== "string"){const err4 = {instancePath:instancePath+"/source/register",schemaPath:"#/properties/source/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data2.schema !== undefined){if(typeof data2.schema !== "string"){const err5 = {instancePath:instancePath+"/source/schema",schemaPath:"#/properties/source/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data2.filter !== undefined){let data5 = data2.filter;if(data5 && typeof data5 == "object" && !Array.isArray(data5));else {const err6 = {instancePath:instancePath+"/source/filter",schemaPath:"#/properties/source/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}else {const err7 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.workspace !== undefined){let data6 = data.workspace;if(typeof data6 === "string"){if(func2(data6) < 1){const err8 = {instancePath:instancePath+"/workspace",schemaPath:"#/properties/workspace/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/workspace",schemaPath:"#/properties/workspace/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.field !== undefined){if(typeof data.field !== "string"){const err10 = {instancePath:instancePath+"/field",schemaPath:"#/properties/field/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}let data8 = data.op;if(typeof data8 !== "string"){const err11 = {instancePath:instancePath+"/op",schemaPath:"#/properties/op/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(!(((((((((((data8 === "eq") || (data8 === "neq")) || (data8 === "gt")) || (data8 === "gte")) || (data8 === "lt")) || (data8 === "lte")) || (data8 === "empty")) || (data8 === "notEmpty")) || (data8 === "in")) || (data8 === "anyOf")) || (data8 === "contains"))){const err12 = {instancePath:instancePath+"/op",schemaPath:"#/properties/op/enum",keyword:"enum",params:{allowedValues: schema45.properties.op.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(data.all !== undefined){let data9 = data.all;if(Array.isArray(data9)){if(data9.length < 1){const err13 = {instancePath:instancePath+"/all",schemaPath:"#/properties/all/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}const len0 = data9.length;for(let i0=0; i0<len0; i0++){if(!(wrapper1.validate(data9[i0], {instancePath:instancePath+"/all/" + i0,parentData:data9,parentDataProperty:i0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? wrapper1.validate.errors : vErrors.concat(wrapper1.validate.errors);errors = vErrors.length;}}}else {const err14 = {instancePath:instancePath+"/all",schemaPath:"#/properties/all/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.any !== undefined){let data11 = data.any;if(Array.isArray(data11)){if(data11.length < 1){const err15 = {instancePath:instancePath+"/any",schemaPath:"#/properties/any/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}const len1 = data11.length;for(let i1=0; i1<len1; i1++){if(!(wrapper1.validate(data11[i1], {instancePath:instancePath+"/any/" + i1,parentData:data11,parentDataProperty:i1,rootData,dynamicAnchors}))){vErrors = vErrors === null ? wrapper1.validate.errors : vErrors.concat(wrapper1.validate.errors);errors = vErrors.length;}}}else {const err16 = {instancePath:instancePath+"/any",schemaPath:"#/properties/any/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate26.errors = vErrors;return errors === 0;}validate26.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate23(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate23.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}else {if(data.type !== undefined){if("object-op" !== data.type){const err1 = {};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.op === undefined){const err2 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "op"},message:"must have required property '"+"op"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}var _valid0 = _errs5 === errors;valid1 = _valid0;}if(!valid1){const err3 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}const _errs7 = errors;let valid3 = true;const _errs8 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err4 = {};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}else {if(data.type !== undefined){if("api-call" !== data.type){const err5 = {};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}}}var _valid1 = _errs8 === errors;errors = _errs7;if(vErrors !== null){if(_errs7){vErrors.length = _errs7;}else {vErrors = null;}}if(_valid1){const _errs10 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.url === undefined){const err6 = {instancePath,schemaPath:"#/allOf/1/then/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}var _valid1 = _errs10 === errors;valid3 = _valid1;}if(!valid3){const err7 = {instancePath,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}const _errs12 = errors;let valid5 = true;const _errs13 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err8 = {};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}else {if(data.type !== undefined){if("open-form" !== data.type){const err9 = {};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}}}var _valid2 = _errs13 === errors;errors = _errs12;if(vErrors !== null){if(_errs12){vErrors.length = _errs12;}else {vErrors = null;}}if(_valid2){const _errs15 = errors;const _errs16 = errors;let valid7 = false;const _errs17 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err10 = {instancePath,schemaPath:"#/allOf/2/then/anyOf/0/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}var _valid3 = _errs17 === errors;valid7 = valid7 || _valid3;const _errs18 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.endpoint === undefined){const err11 = {instancePath,schemaPath:"#/allOf/2/then/anyOf/1/required",keyword:"required",params:{missingProperty: "endpoint"},message:"must have required property '"+"endpoint"+"'"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(data.formProperties === undefined){const err12 = {instancePath,schemaPath:"#/allOf/2/then/anyOf/1/required",keyword:"required",params:{missingProperty: "formProperties"},message:"must have required property '"+"formProperties"+"'"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}var _valid3 = _errs18 === errors;valid7 = valid7 || _valid3;if(!valid7){const err13 = {instancePath,schemaPath:"#/allOf/2/then/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}else {errors = _errs16;if(vErrors !== null){if(_errs16){vErrors.length = _errs16;}else {vErrors = null;}}}var _valid2 = _errs15 === errors;valid5 = _valid2;}if(!valid5){const err14 = {instancePath,schemaPath:"#/allOf/2/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}const _errs20 = errors;let valid8 = true;const _errs21 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err15 = {};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}else {if(data.type !== undefined){if("agent" !== data.type){const err16 = {};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}}var _valid4 = _errs21 === errors;errors = _errs20;if(vErrors !== null){if(_errs20){vErrors.length = _errs20;}else {vErrors = null;}}if(_valid4){const _errs23 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.agent === undefined){const err17 = {instancePath,schemaPath:"#/allOf/3/then/required",keyword:"required",params:{missingProperty: "agent"},message:"must have required property '"+"agent"+"'"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}var _valid4 = _errs23 === errors;valid8 = _valid4;}if(!valid8){const err18 = {instancePath,schemaPath:"#/allOf/3/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}const _errs25 = errors;let valid10 = true;const _errs26 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err19 = {};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}else {if(data.type !== undefined){if("toggle" !== data.type){const err20 = {};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}}var _valid5 = _errs26 === errors;errors = _errs25;if(vErrors !== null){if(_errs25){vErrors.length = _errs25;}else {vErrors = null;}}if(_valid5){const _errs28 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.writeUrl === undefined){const err21 = {instancePath,schemaPath:"#/allOf/4/then/required",keyword:"required",params:{missingProperty: "writeUrl"},message:"must have required property '"+"writeUrl"+"'"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}var _valid5 = _errs28 === errors;valid10 = _valid5;}if(!valid10){const err22 = {instancePath,schemaPath:"#/allOf/4/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}const _errs30 = errors;let valid12 = true;const _errs31 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err23 = {};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}else {if(data.type !== undefined){if("run-node" !== data.type){const err24 = {};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}}}var _valid6 = _errs31 === errors;errors = _errs30;if(vErrors !== null){if(_errs30){vErrors.length = _errs30;}else {vErrors = null;}}if(_valid6){const _errs33 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.flowId === undefined){const err25 = {instancePath,schemaPath:"#/allOf/5/then/required",keyword:"required",params:{missingProperty: "flowId"},message:"must have required property '"+"flowId"+"'"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(data.nodeId === undefined){const err26 = {instancePath,schemaPath:"#/allOf/5/then/required",keyword:"required",params:{missingProperty: "nodeId"},message:"must have required property '"+"nodeId"+"'"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}var _valid6 = _errs33 === errors;valid12 = _valid6;}if(!valid12){const err27 = {instancePath,schemaPath:"#/allOf/5/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.type === undefined){data.type = "handler";}if(data.confirm === undefined){data.confirm = false;}if(data.advanced === undefined){data.advanced = false;}if(data.size === undefined){data.size = "normal";}if(data.columns === undefined){data.columns = 1;}if(data.download === undefined){data.download = false;}if(data.refresh === undefined){data.refresh = true;}if(data.adminOnly === undefined){data.adminOnly = false;}if(data.id === undefined){const err28 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data.label === undefined){const err29 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(data.id !== undefined){if(typeof data.id !== "string"){const err30 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data.label !== undefined){if(typeof data.label !== "string"){const err31 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}let data8 = data.type;if(typeof data8 !== "string"){const err32 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(!((((((((((((data8 === "handler") || (data8 === "open-modal")) || (data8 === "open-page")) || (data8 === "navigate")) || (data8 === "object-op")) || (data8 === "export")) || (data8 === "open-form")) || (data8 === "refresh")) || (data8 === "api-call")) || (data8 === "agent")) || (data8 === "run-node")) || (data8 === "toggle"))){const err33 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/enum",keyword:"enum",params:{allowedValues: schema39.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(data.op !== undefined){let data9 = data.op;if(typeof data9 !== "string"){const err34 = {instancePath:instancePath+"/op",schemaPath:"#/properties/op/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(!(((data9 === "patch") || (data9 === "delete")) || (data9 === "create"))){const err35 = {instancePath:instancePath+"/op",schemaPath:"#/properties/op/enum",keyword:"enum",params:{allowedValues: schema39.properties.op.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data.values !== undefined){let data10 = data.values;if(data10 && typeof data10 == "object" && !Array.isArray(data10));else {const err36 = {instancePath:instancePath+"/values",schemaPath:"#/properties/values/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(typeof data.confirm !== "boolean"){const err37 = {instancePath:instancePath+"/confirm",schemaPath:"#/properties/confirm/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(data.confirmTitle !== undefined){if(typeof data.confirmTitle !== "string"){const err38 = {instancePath:instancePath+"/confirmTitle",schemaPath:"#/properties/confirmTitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data.confirmMessage !== undefined){if(typeof data.confirmMessage !== "string"){const err39 = {instancePath:instancePath+"/confirmMessage",schemaPath:"#/properties/confirmMessage/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data.confirmLabel !== undefined){if(typeof data.confirmLabel !== "string"){const err40 = {instancePath:instancePath+"/confirmLabel",schemaPath:"#/properties/confirmLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data.confirmIcon !== undefined){if(typeof data.confirmIcon !== "string"){const err41 = {instancePath:instancePath+"/confirmIcon",schemaPath:"#/properties/confirmIcon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}if(data.target !== undefined){if(typeof data.target !== "string"){const err42 = {instancePath:instancePath+"/target",schemaPath:"#/properties/target/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data.props !== undefined){let data17 = data.props;if(data17 && typeof data17 == "object" && !Array.isArray(data17));else {const err43 = {instancePath:instancePath+"/props",schemaPath:"#/properties/props/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data.createOverride !== undefined){if(typeof data.createOverride !== "string"){const err44 = {instancePath:instancePath+"/createOverride",schemaPath:"#/properties/createOverride/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data.includeFields !== undefined){let data19 = data.includeFields;if(Array.isArray(data19)){const len0 = data19.length;for(let i0=0; i0<len0; i0++){if(typeof data19[i0] !== "string"){const err45 = {instancePath:instancePath+"/includeFields/" + i0,schemaPath:"#/properties/includeFields/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}}else {const err46 = {instancePath:instancePath+"/includeFields",schemaPath:"#/properties/includeFields/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data.excludeFields !== undefined){let data21 = data.excludeFields;if(Array.isArray(data21)){const len1 = data21.length;for(let i1=0; i1<len1; i1++){if(typeof data21[i1] !== "string"){const err47 = {instancePath:instancePath+"/excludeFields/" + i1,schemaPath:"#/properties/excludeFields/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}}else {const err48 = {instancePath:instancePath+"/excludeFields",schemaPath:"#/properties/excludeFields/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data.fieldOverrides !== undefined){let data23 = data.fieldOverrides;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){for(const key0 in data23){if(!(validate24(data23[key0], {instancePath:instancePath+"/fieldOverrides/" + key0.replace(/~/g, "~0").replace(/\//g, "~1"),parentData:data23,parentDataProperty:key0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate24.errors : vErrors.concat(validate24.errors);errors = vErrors.length;}}}else {const err49 = {instancePath:instancePath+"/fieldOverrides",schemaPath:"#/properties/fieldOverrides/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(typeof data.advanced !== "boolean"){const err50 = {instancePath:instancePath+"/advanced",schemaPath:"#/properties/advanced/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}if(data.formTitle !== undefined){if(typeof data.formTitle !== "string"){const err51 = {instancePath:instancePath+"/formTitle",schemaPath:"#/properties/formTitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data.formEyebrow !== undefined){if(typeof data.formEyebrow !== "string"){const err52 = {instancePath:instancePath+"/formEyebrow",schemaPath:"#/properties/formEyebrow/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data.formSubtitle !== undefined){if(typeof data.formSubtitle !== "string"){const err53 = {instancePath:instancePath+"/formSubtitle",schemaPath:"#/properties/formSubtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data.formConfirmLabel !== undefined){if(typeof data.formConfirmLabel !== "string"){const err54 = {instancePath:instancePath+"/formConfirmLabel",schemaPath:"#/properties/formConfirmLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}let data30 = data.size;if(typeof data30 !== "string"){const err55 = {instancePath:instancePath+"/size",schemaPath:"#/properties/size/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}if(!((((data30 === "small") || (data30 === "normal")) || (data30 === "large")) || (data30 === "full"))){const err56 = {instancePath:instancePath+"/size",schemaPath:"#/properties/size/enum",keyword:"enum",params:{allowedValues: schema39.properties.size.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}let data31 = data.columns;if(!((typeof data31 == "number") && (!(data31 % 1) && !isNaN(data31)))){const err57 = {instancePath:instancePath+"/columns",schemaPath:"#/properties/columns/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}if(!((data31 === 1) || (data31 === 2))){const err58 = {instancePath:instancePath+"/columns",schemaPath:"#/properties/columns/enum",keyword:"enum",params:{allowedValues: schema39.properties.columns.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}if(data.endpoint !== undefined){let data32 = data.endpoint;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){if(data32.url === undefined){const err59 = {instancePath:instancePath+"/endpoint",schemaPath:"#/properties/endpoint/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}for(const key1 in data32){if(!((((key1 === "url") || (key1 === "method")) || (key1 === "loadUrl")) || (key1 === "responsePath"))){const err60 = {instancePath:instancePath+"/endpoint",schemaPath:"#/properties/endpoint/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}if(data32.url !== undefined){if(typeof data32.url !== "string"){const err61 = {instancePath:instancePath+"/endpoint/url",schemaPath:"#/properties/endpoint/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data32.method !== undefined){let data34 = data32.method;if(typeof data34 !== "string"){const err62 = {instancePath:instancePath+"/endpoint/method",schemaPath:"#/properties/endpoint/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(!(((data34 === "PATCH") || (data34 === "PUT")) || (data34 === "POST"))){const err63 = {instancePath:instancePath+"/endpoint/method",schemaPath:"#/properties/endpoint/properties/method/enum",keyword:"enum",params:{allowedValues: schema39.properties.endpoint.properties.method.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data32.loadUrl !== undefined){if(typeof data32.loadUrl !== "string"){const err64 = {instancePath:instancePath+"/endpoint/loadUrl",schemaPath:"#/properties/endpoint/properties/loadUrl/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}if(data32.responsePath !== undefined){if(typeof data32.responsePath !== "string"){const err65 = {instancePath:instancePath+"/endpoint/responsePath",schemaPath:"#/properties/endpoint/properties/responsePath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}}else {const err66 = {instancePath:instancePath+"/endpoint",schemaPath:"#/properties/endpoint/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}if(data.exportId !== undefined){let data37 = data.exportId;if(typeof data37 === "string"){if(func2(data37) < 1){const err67 = {instancePath:instancePath+"/exportId",schemaPath:"#/properties/exportId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}else {const err68 = {instancePath:instancePath+"/exportId",schemaPath:"#/properties/exportId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}if(data.formProperties !== undefined){let data38 = data.formProperties;if(data38 && typeof data38 == "object" && !Array.isArray(data38)){for(const key2 in data38){let data39 = data38[key2];if(!(data39 && typeof data39 == "object" && !Array.isArray(data39))){const err69 = {instancePath:instancePath+"/formProperties/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/formProperties/additionalProperties/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}else {const err70 = {instancePath:instancePath+"/formProperties",schemaPath:"#/properties/formProperties/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}if(data.handler !== undefined){if(typeof data.handler !== "string"){const err71 = {instancePath:instancePath+"/handler",schemaPath:"#/properties/handler/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}if(data.route !== undefined){if(typeof data.route !== "string"){const err72 = {instancePath:instancePath+"/route",schemaPath:"#/properties/route/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data.icon !== undefined){if(typeof data.icon !== "string"){const err73 = {instancePath:instancePath+"/icon",schemaPath:"#/properties/icon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}if(data.entities !== undefined){let data43 = data.entities;if(Array.isArray(data43)){const len2 = data43.length;for(let i2=0; i2<len2; i2++){let data44 = data43[i2];if(data44 && typeof data44 == "object" && !Array.isArray(data44)){if(data44.id === undefined){const err74 = {instancePath:instancePath+"/entities/" + i2,schemaPath:"#/properties/entities/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}if(data44.label === undefined){const err75 = {instancePath:instancePath+"/entities/" + i2,schemaPath:"#/properties/entities/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}for(const key3 in data44){if(!(((((key3 === "id") || (key3 === "label")) || (key3 === "register")) || (key3 === "schema")) || (key3 === "filter"))){const err76 = {instancePath:instancePath+"/entities/" + i2,schemaPath:"#/properties/entities/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}if(data44.id !== undefined){if(typeof data44.id !== "string"){const err77 = {instancePath:instancePath+"/entities/" + i2+"/id",schemaPath:"#/properties/entities/items/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}if(data44.label !== undefined){if(typeof data44.label !== "string"){const err78 = {instancePath:instancePath+"/entities/" + i2+"/label",schemaPath:"#/properties/entities/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}if(data44.register !== undefined){let data47 = data44.register;if(typeof data47 === "string"){if(func2(data47) < 1){const err79 = {instancePath:instancePath+"/entities/" + i2+"/register",schemaPath:"#/properties/entities/items/properties/register/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}else {const err80 = {instancePath:instancePath+"/entities/" + i2+"/register",schemaPath:"#/properties/entities/items/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data44.schema !== undefined){let data48 = data44.schema;if(typeof data48 === "string"){if(func2(data48) < 1){const err81 = {instancePath:instancePath+"/entities/" + i2+"/schema",schemaPath:"#/properties/entities/items/properties/schema/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}else {const err82 = {instancePath:instancePath+"/entities/" + i2+"/schema",schemaPath:"#/properties/entities/items/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}if(data44.filter !== undefined){let data49 = data44.filter;if(!(data49 && typeof data49 == "object" && !Array.isArray(data49))){const err83 = {instancePath:instancePath+"/entities/" + i2+"/filter",schemaPath:"#/properties/entities/items/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}}else {const err84 = {instancePath:instancePath+"/entities/" + i2,schemaPath:"#/properties/entities/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}}else {const err85 = {instancePath:instancePath+"/entities",schemaPath:"#/properties/entities/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}if(data.formats !== undefined){let data50 = data.formats;if(Array.isArray(data50)){const len3 = data50.length;for(let i3=0; i3<len3; i3++){let data51 = data50[i3];const _errs130 = errors;let valid27 = false;let passing0 = null;const _errs131 = errors;if(typeof data51 !== "string"){const err86 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}var _valid7 = _errs131 === errors;if(_valid7){valid27 = true;passing0 = 0;}const _errs133 = errors;if(data51 && typeof data51 == "object" && !Array.isArray(data51)){if(data51.id === undefined){const err87 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/1/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}if(data51.label === undefined){const err88 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/1/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}for(const key4 in data51){if(!((key4 === "id") || (key4 === "label"))){const err89 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}if(data51.id !== undefined){if(typeof data51.id !== "string"){const err90 = {instancePath:instancePath+"/formats/" + i3+"/id",schemaPath:"#/properties/formats/items/oneOf/1/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}if(data51.label !== undefined){if(typeof data51.label !== "string"){const err91 = {instancePath:instancePath+"/formats/" + i3+"/label",schemaPath:"#/properties/formats/items/oneOf/1/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}}else {const err92 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}var _valid7 = _errs133 === errors;if(_valid7 && valid27){valid27 = false;passing0 = [passing0, 1];}else {if(_valid7){valid27 = true;passing0 = 1;}}if(!valid27){const err93 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}else {errors = _errs130;if(vErrors !== null){if(_errs130){vErrors.length = _errs130;}else {vErrors = null;}}}}}else {const err94 = {instancePath:instancePath+"/formats",schemaPath:"#/properties/formats/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}if(data.description !== undefined){if(typeof data.description !== "string"){const err95 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}if(data.url !== undefined){if(typeof data.url !== "string"){const err96 = {instancePath:instancePath+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}}if(data.method !== undefined){let data56 = data.method;if(typeof data56 !== "string"){const err97 = {instancePath:instancePath+"/method",schemaPath:"#/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}if(!((((data56 === "POST") || (data56 === "PUT")) || (data56 === "PATCH")) || (data56 === "GET"))){const err98 = {instancePath:instancePath+"/method",schemaPath:"#/properties/method/enum",keyword:"enum",params:{allowedValues: schema39.properties.method.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}if(data.payload !== undefined){let data57 = data.payload;if(data57 && typeof data57 == "object" && !Array.isArray(data57));else {const err99 = {instancePath:instancePath+"/payload",schemaPath:"#/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data.params !== undefined){let data58 = data.params;if(data58 && typeof data58 == "object" && !Array.isArray(data58));else {const err100 = {instancePath:instancePath+"/params",schemaPath:"#/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}if(typeof data.download !== "boolean"){const err101 = {instancePath:instancePath+"/download",schemaPath:"#/properties/download/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}if(data.filename !== undefined){if(typeof data.filename !== "string"){const err102 = {instancePath:instancePath+"/filename",schemaPath:"#/properties/filename/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}if(data.successMessage !== undefined){if(typeof data.successMessage !== "string"){const err103 = {instancePath:instancePath+"/successMessage",schemaPath:"#/properties/successMessage/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data.errorMessage !== undefined){if(typeof data.errorMessage !== "string"){const err104 = {instancePath:instancePath+"/errorMessage",schemaPath:"#/properties/errorMessage/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}if(typeof data.refresh !== "boolean"){const err105 = {instancePath:instancePath+"/refresh",schemaPath:"#/properties/refresh/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if(data.flowId !== undefined){if(typeof data.flowId !== "string"){const err106 = {instancePath:instancePath+"/flowId",schemaPath:"#/properties/flowId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}if(data.nodeId !== undefined){if(typeof data.nodeId !== "string"){const err107 = {instancePath:instancePath+"/nodeId",schemaPath:"#/properties/nodeId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}if(data.subject !== undefined){if(typeof data.subject !== "string"){const err108 = {instancePath:instancePath+"/subject",schemaPath:"#/properties/subject/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data.agent !== undefined){if(typeof data.agent !== "string"){const err109 = {instancePath:instancePath+"/agent",schemaPath:"#/properties/agent/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}if(data.skill !== undefined){if(typeof data.skill !== "string"){const err110 = {instancePath:instancePath+"/skill",schemaPath:"#/properties/skill/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data.prompt !== undefined){if(typeof data.prompt !== "string"){const err111 = {instancePath:instancePath+"/prompt",schemaPath:"#/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}if(data.resultField !== undefined){if(typeof data.resultField !== "string"){const err112 = {instancePath:instancePath+"/resultField",schemaPath:"#/properties/resultField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}if(data.objectId !== undefined){if(typeof data.objectId !== "string"){const err113 = {instancePath:instancePath+"/objectId",schemaPath:"#/properties/objectId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}if(data.register !== undefined){if(typeof data.register !== "string"){const err114 = {instancePath:instancePath+"/register",schemaPath:"#/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}if(data.schema !== undefined){if(typeof data.schema !== "string"){const err115 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}if(data.onSuccessRoute !== undefined){let data74 = data.onSuccessRoute;const _errs183 = errors;let valid29 = false;let passing1 = null;const _errs184 = errors;if(typeof data74 !== "string"){const err116 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}var _valid8 = _errs184 === errors;if(_valid8){valid29 = true;passing1 = 0;}const _errs186 = errors;if(data74 && typeof data74 == "object" && !Array.isArray(data74)){if(data74.name === undefined){const err117 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf/1/required",keyword:"required",params:{missingProperty: "name"},message:"must have required property '"+"name"+"'"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}for(const key5 in data74){if(!(((key5 === "name") || (key5 === "paramField")) || (key5 === "objectParam"))){const err118 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}if(data74.name !== undefined){if(typeof data74.name !== "string"){const err119 = {instancePath:instancePath+"/onSuccessRoute/name",schemaPath:"#/properties/onSuccessRoute/oneOf/1/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}if(data74.paramField !== undefined){if(typeof data74.paramField !== "string"){const err120 = {instancePath:instancePath+"/onSuccessRoute/paramField",schemaPath:"#/properties/onSuccessRoute/oneOf/1/properties/paramField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}if(data74.objectParam !== undefined){if(typeof data74.objectParam !== "string"){const err121 = {instancePath:instancePath+"/onSuccessRoute/objectParam",schemaPath:"#/properties/onSuccessRoute/oneOf/1/properties/objectParam/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}}else {const err122 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}var _valid8 = _errs186 === errors;if(_valid8 && valid29){valid29 = false;passing1 = [passing1, 1];}else {if(_valid8){valid29 = true;passing1 = 1;}}if(!valid29){const err123 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf",keyword:"oneOf",params:{passingSchemas: passing1},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}else {errors = _errs183;if(vErrors !== null){if(_errs183){vErrors.length = _errs183;}else {vErrors = null;}}}}if(data.labelOn !== undefined){if(typeof data.labelOn !== "string"){const err124 = {instancePath:instancePath+"/labelOn",schemaPath:"#/properties/labelOn/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}if(data.labelOff !== undefined){if(typeof data.labelOff !== "string"){const err125 = {instancePath:instancePath+"/labelOff",schemaPath:"#/properties/labelOff/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}if(data.stateSource !== undefined){let data80 = data.stateSource;if(data80 && typeof data80 == "object" && !Array.isArray(data80)){if(data80.method === undefined){data80.method = "GET";}if(data80.loadOn === undefined){data80.loadOn = "mount";}if(data80.url === undefined){const err126 = {instancePath:instancePath+"/stateSource",schemaPath:"#/$defs/endpointSource/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}for(const key6 in data80){if(!((((((((key6 === "url") || (key6 === "method")) || (key6 === "params")) || (key6 === "loadOn")) || (key6 === "requestLabel")) || (key6 === "requestHint")) || (key6 === "truncatedText")) || (key6 === "responsePath"))){const err127 = {instancePath:instancePath+"/stateSource",schemaPath:"#/$defs/endpointSource/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}if(data80.url !== undefined){if(typeof data80.url !== "string"){const err128 = {instancePath:instancePath+"/stateSource/url",schemaPath:"#/$defs/endpointSource/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}let data82 = data80.method;if(typeof data82 !== "string"){const err129 = {instancePath:instancePath+"/stateSource/method",schemaPath:"#/$defs/endpointSource/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}if(!((data82 === "GET") || (data82 === "POST"))){const err130 = {instancePath:instancePath+"/stateSource/method",schemaPath:"#/$defs/endpointSource/properties/method/enum",keyword:"enum",params:{allowedValues: schema44.properties.method.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}if(data80.params !== undefined){let data83 = data80.params;if(data83 && typeof data83 == "object" && !Array.isArray(data83));else {const err131 = {instancePath:instancePath+"/stateSource/params",schemaPath:"#/$defs/endpointSource/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}let data84 = data80.loadOn;if(typeof data84 !== "string"){const err132 = {instancePath:instancePath+"/stateSource/loadOn",schemaPath:"#/$defs/endpointSource/properties/loadOn/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}if(!((data84 === "mount") || (data84 === "request"))){const err133 = {instancePath:instancePath+"/stateSource/loadOn",schemaPath:"#/$defs/endpointSource/properties/loadOn/enum",keyword:"enum",params:{allowedValues: schema44.properties.loadOn.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}if(data80.requestLabel !== undefined){if(typeof data80.requestLabel !== "string"){const err134 = {instancePath:instancePath+"/stateSource/requestLabel",schemaPath:"#/$defs/endpointSource/properties/requestLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}if(data80.requestHint !== undefined){if(typeof data80.requestHint !== "string"){const err135 = {instancePath:instancePath+"/stateSource/requestHint",schemaPath:"#/$defs/endpointSource/properties/requestHint/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}}if(data80.truncatedText !== undefined){if(typeof data80.truncatedText !== "string"){const err136 = {instancePath:instancePath+"/stateSource/truncatedText",schemaPath:"#/$defs/endpointSource/properties/truncatedText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}if(data80.responsePath !== undefined){if(typeof data80.responsePath !== "string"){const err137 = {instancePath:instancePath+"/stateSource/responsePath",schemaPath:"#/$defs/endpointSource/properties/responsePath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}}else {const err138 = {instancePath:instancePath+"/stateSource",schemaPath:"#/$defs/endpointSource/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}if(data.writeUrl !== undefined){if(typeof data.writeUrl !== "string"){const err139 = {instancePath:instancePath+"/writeUrl",schemaPath:"#/properties/writeUrl/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data.field !== undefined){if(typeof data.field !== "string"){const err140 = {instancePath:instancePath+"/field",schemaPath:"#/properties/field/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}if(data.variant !== undefined){if(typeof data.variant !== "string"){const err141 = {instancePath:instancePath+"/variant",schemaPath:"#/properties/variant/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}if(data.children !== undefined){if(!(Array.isArray(data.children))){const err142 = {instancePath:instancePath+"/children",schemaPath:"#/properties/children/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}if(data.childrenLabel !== undefined){if(typeof data.childrenLabel !== "string"){const err143 = {instancePath:instancePath+"/childrenLabel",schemaPath:"#/properties/childrenLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data.group !== undefined){let data94 = data.group;if(typeof data94 === "string"){if(func2(data94) < 1){const err144 = {instancePath:instancePath+"/group",schemaPath:"#/properties/group/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}else {const err145 = {instancePath:instancePath+"/group",schemaPath:"#/properties/group/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}if(typeof data.adminOnly !== "boolean"){const err146 = {instancePath:instancePath+"/adminOnly",schemaPath:"#/properties/adminOnly/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}if(data.visibleWhen !== undefined){if(!(validate26(data.visibleWhen, {instancePath:instancePath+"/visibleWhen",parentData:data,parentDataProperty:"visibleWhen",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);errors = vErrors.length;}}}else {const err147 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}validate23.errors = vErrors;return errors === 0;}validate23.evaluated = {"props":{"id":true,"label":true,"type":true,"op":true,"values":true,"confirm":true,"confirmTitle":true,"confirmMessage":true,"confirmLabel":true,"confirmIcon":true,"target":true,"props":true,"createOverride":true,"includeFields":true,"excludeFields":true,"fieldOverrides":true,"advanced":true,"formTitle":true,"formEyebrow":true,"formSubtitle":true,"formConfirmLabel":true,"size":true,"columns":true,"endpoint":true,"exportId":true,"formProperties":true,"handler":true,"route":true,"icon":true,"entities":true,"formats":true,"description":true,"url":true,"method":true,"payload":true,"params":true,"download":true,"filename":true,"successMessage":true,"errorMessage":true,"refresh":true,"flowId":true,"nodeId":true,"subject":true,"agent":true,"skill":true,"prompt":true,"resultField":true,"objectId":true,"register":true,"schema":true,"onSuccessRoute":true,"labelOn":true,"labelOff":true,"stateSource":true,"writeUrl":true,"field":true,"variant":true,"children":true,"childrenLabel":true,"group":true,"adminOnly":true,"visibleWhen":true},"dynamicProps":false,"dynamicItems":false};const wrapper3 = {validate: validate22};function validate22(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate22.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(!(validate23(data, {instancePath,parentData,parentDataProperty,rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate23.errors : vErrors.concat(validate23.errors);errors = vErrors.length;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.children !== undefined){let data0 = data.children;if(Array.isArray(data0)){const len0 = data0.length;for(let i0=0; i0<len0; i0++){if(!(wrapper3.validate(data0[i0], {instancePath:instancePath+"/children/" + i0,parentData:data0,parentDataProperty:i0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? wrapper3.validate.errors : vErrors.concat(wrapper3.validate.errors);errors = vErrors.length;}}}}for(const key0 in data){if(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((key0 !== "children") && (key0 !== "id")) && (key0 !== "label")) && (key0 !== "type")) && (key0 !== "op")) && (key0 !== "values")) && (key0 !== "confirm")) && (key0 !== "confirmTitle")) && (key0 !== "confirmMessage")) && (key0 !== "confirmLabel")) && (key0 !== "confirmIcon")) && (key0 !== "target")) && (key0 !== "props")) && (key0 !== "createOverride")) && (key0 !== "includeFields")) && (key0 !== "excludeFields")) && (key0 !== "fieldOverrides")) && (key0 !== "advanced")) && (key0 !== "formTitle")) && (key0 !== "formEyebrow")) && (key0 !== "formSubtitle")) && (key0 !== "formConfirmLabel")) && (key0 !== "size")) && (key0 !== "columns")) && (key0 !== "endpoint")) && (key0 !== "exportId")) && (key0 !== "formProperties")) && (key0 !== "handler")) && (key0 !== "route")) && (key0 !== "icon")) && (key0 !== "entities")) && (key0 !== "formats")) && (key0 !== "description")) && (key0 !== "url")) && (key0 !== "method")) && (key0 !== "payload")) && (key0 !== "params")) && (key0 !== "download")) && (key0 !== "filename")) && (key0 !== "successMessage")) && (key0 !== "errorMessage")) && (key0 !== "refresh")) && (key0 !== "flowId")) && (key0 !== "nodeId")) && (key0 !== "subject")) && (key0 !== "agent")) && (key0 !== "skill")) && (key0 !== "prompt")) && (key0 !== "resultField")) && (key0 !== "objectId")) && (key0 !== "register")) && (key0 !== "schema")) && (key0 !== "onSuccessRoute")) && (key0 !== "labelOn")) && (key0 !== "labelOff")) && (key0 !== "stateSource")) && (key0 !== "writeUrl")) && (key0 !== "field")) && (key0 !== "variant")) && (key0 !== "childrenLabel")) && (key0 !== "group")) && (key0 !== "adminOnly")) && (key0 !== "visibleWhen")){const err0 = {instancePath,schemaPath:"#/unevaluatedProperties",keyword:"unevaluatedProperties",params:{unevaluatedProperty: key0},message:"must NOT have unevaluated properties"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}}validate22.errors = vErrors;return errors === 0;}validate22.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate21(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate21.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.solid === undefined){data.solid = false;}if(data.label === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}for(const key0 in data){if(!(func1.call(schema37.properties, key0))){const err1 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}if(data.id !== undefined){if(typeof data.id !== "string"){const err2 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.label !== undefined){if(typeof data.label !== "string"){const err3 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.icon !== undefined){if(typeof data.icon !== "string"){const err4 = {instancePath:instancePath+"/icon",schemaPath:"#/properties/icon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.route !== undefined){if(typeof data.route !== "string"){const err5 = {instancePath:instancePath+"/route",schemaPath:"#/properties/route/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.query !== undefined){let data4 = data.query;if(data4 && typeof data4 == "object" && !Array.isArray(data4)){for(const key1 in data4){if(typeof data4[key1] !== "string"){const err6 = {instancePath:instancePath+"/query/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/query/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}else {const err7 = {instancePath:instancePath+"/query",schemaPath:"#/properties/query/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.href !== undefined){if(typeof data.href !== "string"){const err8 = {instancePath:instancePath+"/href",schemaPath:"#/properties/href/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.action !== undefined){if(!(validate22(data.action, {instancePath:instancePath+"/action",parentData:data,parentDataProperty:"action",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate22.errors : vErrors.concat(validate22.errors);errors = vErrors.length;}}if(typeof data.solid !== "boolean"){const err9 = {instancePath:instancePath+"/solid",schemaPath:"#/properties/solid/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.permission !== undefined){if(typeof data.permission !== "string"){const err10 = {instancePath:instancePath+"/permission",schemaPath:"#/properties/permission/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.visibleIf !== undefined){let data10 = data.visibleIf;if(data10 && typeof data10 == "object" && !Array.isArray(data10)){if(data10.appInstalled !== undefined){let data11 = data10.appInstalled;if(typeof data11 === "string"){if(func2(data11) < 1){const err11 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}else {const err13 = {instancePath:instancePath+"/visibleIf",schemaPath:"#/$defs/visibleIfCondition/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}}else {const err14 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}validate21.errors = vErrors;return errors === 0;}validate21.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema48 = {"type":"object","required":["id","label"],"additionalProperties":false,"description":"A top-level navigation entry. May contain one level of nested children.","properties":{"id":{"type":"string","description":"Unique identifier for this menu entry. Sentinels (@resolve:*) are NOT allowed — this is a router invariant."},"label":{"type":"string","description":"i18n translation key resolved by the consuming app's t() function at render time."},"translateLabel":{"type":"boolean","default":true,"description":"Whether CnAppNav runs `label` through the translate function. Set false for a user-authored title (e.g. a catalog name) that must render as written even when it equals a translation key such as 'Search'. Rendered as text either way, never as HTML."},"icon":{"type":"string","description":"CSS class for the icon (e.g. 'icon-checkmark') or MDI component name."},"route":{"type":"string","description":"Vue-router route name (matches a pages[].id) that this entry navigates to. Sentinels (@resolve:*) are NOT allowed — this is a router invariant."},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Optional vue-router query parameters merged into the target route link (e.g. a filter like { caseType: '<uuid>' }). Deep-links a nav entry to a pre-filtered index page."},"params":{"type":"object","additionalProperties":{"type":["string","number"],"minLength":1},"description":"Optional vue-router route params for a parameterised route (e.g. { catalogSlug: 'news' } for a page at '/catalogs/:catalogSlug'). String values must be non-empty. CnAppNav links to { name: route, params, query }. The entry is active only while every declared param equals the current route's param (compared as strings), so sibling entries on one route that differ in params are never marked together. On pages whose path sits below its page path with these params filled in, it is active too: by the same params check when those routes carry the same param names, otherwise as the one entry marked below its route."},"order":{"type":"integer","description":"Display order in the menu. Items without an order render last."},"permission":{"type":"string","description":"Permission string the user must hold for this entry to render."},"section":{"type":"string","enum":["main","footer","settings","integrations"],"default":"main","description":"Where the entry renders inside CnAppNav. 'main' (default) places it in the top list. 'footer' pins the entry to the bottom as a flat item above the settings foldout. 'settings' places it inside the NcAppNavigationSettings gear-icon foldout. 'integrations' renders the entry NOT in the navigation at all but in the Integrations section at the bottom of the per-user settings modal — the destination for links that leave this app for another (ADR-110). An 'integrations' entry MUST be clickable (href); a group with children is meaningless there."},"type":{"type":"string","enum":["item","caption"],"default":"item","description":"Render kind. 'item' (default) renders a clickable NcAppNavigationItem. 'caption' renders an NcAppNavigationCaption section divider — only label, id, order, and section are honoured."},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]},{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"A FILTERED count (zuiddrecht-pixel-gaps): the total of this register and schema with `filter` applied (filter-value tokens such as `@me` and `@today` resolve at fetch time), fetched by CnAppRoot under the entry's own key so an index page's whole-schema total is untouched. Renders \"My work 6\" beside \"All cases 48\".","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true}}}],"description":"Counter badge in the entry's #counter slot. Positive integer renders as-is; 'auto' resolves from cnMenuCounts (populated by CnAppRoot from useObjectStore totals) for the entry's resolved index-type page (register + schema); an object `{ register, schema, filter? }` is the total of that filtered list."},"counterVariant":{"type":"string","enum":["default","attention"],"default":"default","description":"screens-chrome-parity: how the entry's `count` reads. `attention` asks for the red attention pill under the board look (unread or overdue work) and maps to the counter bubble's highlighted state without it. `default` is the neutral pill."},"pinned":{"type":"boolean","default":false,"description":"Forwarded to NcAppNavigationItem's pinned prop. NC bottom-pins pinned items inside the parent list region."},"open":{"type":"boolean","default":false,"description":"Initial expansion state for a parent entry with children. When true and children[] is non-empty, the parent renders with :open=\"true\" so children are visible on mount."},"href":{"type":"string","description":"External URL. When set, the entry opens the URL in a new tab instead of dispatching a vue-router navigation."},"action":{"type":"string","enum":["user-settings","admin-settings","replay-walkthrough"],"description":"Built-in action to invoke when the entry is clicked. Closed enum. 'admin-settings' opens the app-level admin-settings dialog (distinct from the per-user settings dialog). 'replay-walkthrough' re-runs the product walkthrough (ADR-043), optionally for a specific tourId."},"tourId":{"type":"string","description":"Tour id for the 'replay-walkthrough' action (ADR-043); replays that tour from its first step. Omit to replay the default tour."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Optional display condition evaluated at render time."},"dynamicSource":{"$ref":"#/$defs/dynamicSource","description":"Optional dynamic source for populating nested menu items from a data endpoint. Carry-forward from v1.3.0."},"children":{"type":"array","items":{"$ref":"#/$defs/menuItemLeaf"},"description":"Nested entries. Only one level of nesting is supported."}}};const schema50 = {"type":"object","description":"Dynamic source configuration for populating menu items from a backend endpoint. Carry-forward from v1.3.0 — identical shape to v1.","additionalProperties":true,"properties":{"url":{"type":"string","description":"URL to fetch menu items from."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method for the fetch."},"labelField":{"type":"string","description":"Property name in the response items to use as the menu entry label."},"idField":{"type":"string","description":"Property name in the response items to use as the menu entry id."},"routeField":{"type":"string","description":"Property name in the response items to use as the menu entry route."}}};const schema51 = {"type":"object","required":["id","label"],"additionalProperties":false,"description":"A nested menu entry. Has no further children.","properties":{"id":{"type":"string"},"label":{"type":"string"},"translateLabel":{"type":"boolean","default":true,"description":"Same as menuItem.translateLabel: false renders `label` as written instead of translating it."},"icon":{"type":"string"},"route":{"type":"string"},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Optional vue-router query parameters merged into the target route link (e.g. a filter like { caseType: '<uuid>' }). Deep-links a nav entry to a pre-filtered index page."},"params":{"type":"object","additionalProperties":{"type":["string","number"],"minLength":1},"description":"Optional vue-router route params for a parameterised route; string values must be non-empty. Same contract as menuItem.params: part of the link, and the entry is active only while every declared param equals the current route's param."},"order":{"type":"integer"},"permission":{"type":"string"},"section":{"type":"string","enum":["main","footer","settings","integrations"],"default":"main"},"type":{"type":"string","enum":["item","caption"],"default":"item"},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]}]},"counterVariant":{"type":"string","enum":["default","attention"],"default":"default"},"pinned":{"type":"boolean","default":false},"href":{"type":"string"},"action":{"type":"string","enum":["user-settings","admin-settings","replay-walkthrough"]},"tourId":{"type":"string","description":"Tour id for the 'replay-walkthrough' action (ADR-043)."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition"}}};function validate32(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate32.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.translateLabel === undefined){data.translateLabel = true;}if(data.section === undefined){data.section = "main";}if(data.type === undefined){data.type = "item";}if(data.counterVariant === undefined){data.counterVariant = "default";}if(data.pinned === undefined){data.pinned = false;}if(data.id === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.label === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}for(const key0 in data){if(!(func1.call(schema51.properties, key0))){const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.id !== undefined){if(typeof data.id !== "string"){const err3 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.label !== undefined){if(typeof data.label !== "string"){const err4 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(typeof data.translateLabel !== "boolean"){const err5 = {instancePath:instancePath+"/translateLabel",schemaPath:"#/properties/translateLabel/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.icon !== undefined){if(typeof data.icon !== "string"){const err6 = {instancePath:instancePath+"/icon",schemaPath:"#/properties/icon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.route !== undefined){if(typeof data.route !== "string"){const err7 = {instancePath:instancePath+"/route",schemaPath:"#/properties/route/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.query !== undefined){let data5 = data.query;if(data5 && typeof data5 == "object" && !Array.isArray(data5)){for(const key1 in data5){let data6 = data5[key1];if(((typeof data6 !== "string") && (!(typeof data6 == "number"))) && (typeof data6 !== "boolean")){const err8 = {instancePath:instancePath+"/query/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/query/additionalProperties/type",keyword:"type",params:{type: schema51.properties.query.additionalProperties.type},message:"must be string,number,boolean"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}}else {const err9 = {instancePath:instancePath+"/query",schemaPath:"#/properties/query/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.params !== undefined){let data7 = data.params;if(data7 && typeof data7 == "object" && !Array.isArray(data7)){for(const key2 in data7){let data8 = data7[key2];if((typeof data8 !== "string") && (!(typeof data8 == "number"))){const err10 = {instancePath:instancePath+"/params/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/params/additionalProperties/type",keyword:"type",params:{type: schema51.properties.params.additionalProperties.type},message:"must be string,number"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(typeof data8 === "string"){if(func2(data8) < 1){const err11 = {instancePath:instancePath+"/params/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/params/additionalProperties/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}}}else {const err12 = {instancePath:instancePath+"/params",schemaPath:"#/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.order !== undefined){let data9 = data.order;if(!((typeof data9 == "number") && (!(data9 % 1) && !isNaN(data9)))){const err13 = {instancePath:instancePath+"/order",schemaPath:"#/properties/order/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.permission !== undefined){if(typeof data.permission !== "string"){const err14 = {instancePath:instancePath+"/permission",schemaPath:"#/properties/permission/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}let data11 = data.section;if(typeof data11 !== "string"){const err15 = {instancePath:instancePath+"/section",schemaPath:"#/properties/section/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(!((((data11 === "main") || (data11 === "footer")) || (data11 === "settings")) || (data11 === "integrations"))){const err16 = {instancePath:instancePath+"/section",schemaPath:"#/properties/section/enum",keyword:"enum",params:{allowedValues: schema51.properties.section.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}let data12 = data.type;if(typeof data12 !== "string"){const err17 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(!((data12 === "item") || (data12 === "caption"))){const err18 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/enum",keyword:"enum",params:{allowedValues: schema51.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(data.count !== undefined){let data13 = data.count;const _errs31 = errors;let valid3 = false;let passing0 = null;const _errs32 = errors;if(!((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13)))){const err19 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/0/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data13 == "number"){if(data13 < 0 || isNaN(data13)){const err20 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}var _valid0 = _errs32 === errors;if(_valid0){valid3 = true;passing0 = 0;}const _errs34 = errors;if(typeof data13 !== "string"){const err21 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(!(data13 === "auto")){const err22 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/1/enum",keyword:"enum",params:{allowedValues: schema51.properties.count.oneOf[1].enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}var _valid0 = _errs34 === errors;if(_valid0 && valid3){valid3 = false;passing0 = [passing0, 1];}else {if(_valid0){valid3 = true;passing0 = 1;}}if(!valid3){const err23 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}else {errors = _errs31;if(vErrors !== null){if(_errs31){vErrors.length = _errs31;}else {vErrors = null;}}}}let data14 = data.counterVariant;if(typeof data14 !== "string"){const err24 = {instancePath:instancePath+"/counterVariant",schemaPath:"#/properties/counterVariant/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(!((data14 === "default") || (data14 === "attention"))){const err25 = {instancePath:instancePath+"/counterVariant",schemaPath:"#/properties/counterVariant/enum",keyword:"enum",params:{allowedValues: schema51.properties.counterVariant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(typeof data.pinned !== "boolean"){const err26 = {instancePath:instancePath+"/pinned",schemaPath:"#/properties/pinned/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(data.href !== undefined){if(typeof data.href !== "string"){const err27 = {instancePath:instancePath+"/href",schemaPath:"#/properties/href/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data.action !== undefined){let data17 = data.action;if(typeof data17 !== "string"){const err28 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(!(((data17 === "user-settings") || (data17 === "admin-settings")) || (data17 === "replay-walkthrough"))){const err29 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema51.properties.action.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.tourId !== undefined){if(typeof data.tourId !== "string"){const err30 = {instancePath:instancePath+"/tourId",schemaPath:"#/properties/tourId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data.visibleIf !== undefined){let data19 = data.visibleIf;if(data19 && typeof data19 == "object" && !Array.isArray(data19)){if(data19.appInstalled !== undefined){let data20 = data19.appInstalled;if(typeof data20 === "string"){if(func2(data20) < 1){const err31 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}else {const err32 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}}else {const err33 = {instancePath:instancePath+"/visibleIf",schemaPath:"#/$defs/visibleIfCondition/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}else {const err34 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}validate32.errors = vErrors;return errors === 0;}validate32.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate31(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate31.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.translateLabel === undefined){data.translateLabel = true;}if(data.section === undefined){data.section = "main";}if(data.type === undefined){data.type = "item";}if(data.counterVariant === undefined){data.counterVariant = "default";}if(data.pinned === undefined){data.pinned = false;}if(data.open === undefined){data.open = false;}if(data.id === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.label === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}for(const key0 in data){if(!(func1.call(schema48.properties, key0))){const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.id !== undefined){if(typeof data.id !== "string"){const err3 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.label !== undefined){if(typeof data.label !== "string"){const err4 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(typeof data.translateLabel !== "boolean"){const err5 = {instancePath:instancePath+"/translateLabel",schemaPath:"#/properties/translateLabel/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.icon !== undefined){if(typeof data.icon !== "string"){const err6 = {instancePath:instancePath+"/icon",schemaPath:"#/properties/icon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.route !== undefined){if(typeof data.route !== "string"){const err7 = {instancePath:instancePath+"/route",schemaPath:"#/properties/route/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.query !== undefined){let data5 = data.query;if(data5 && typeof data5 == "object" && !Array.isArray(data5)){for(const key1 in data5){let data6 = data5[key1];if(((typeof data6 !== "string") && (!(typeof data6 == "number"))) && (typeof data6 !== "boolean")){const err8 = {instancePath:instancePath+"/query/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/query/additionalProperties/type",keyword:"type",params:{type: schema48.properties.query.additionalProperties.type},message:"must be string,number,boolean"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}}else {const err9 = {instancePath:instancePath+"/query",schemaPath:"#/properties/query/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.params !== undefined){let data7 = data.params;if(data7 && typeof data7 == "object" && !Array.isArray(data7)){for(const key2 in data7){let data8 = data7[key2];if((typeof data8 !== "string") && (!(typeof data8 == "number"))){const err10 = {instancePath:instancePath+"/params/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/params/additionalProperties/type",keyword:"type",params:{type: schema48.properties.params.additionalProperties.type},message:"must be string,number"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(typeof data8 === "string"){if(func2(data8) < 1){const err11 = {instancePath:instancePath+"/params/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/params/additionalProperties/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}}}else {const err12 = {instancePath:instancePath+"/params",schemaPath:"#/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.order !== undefined){let data9 = data.order;if(!((typeof data9 == "number") && (!(data9 % 1) && !isNaN(data9)))){const err13 = {instancePath:instancePath+"/order",schemaPath:"#/properties/order/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.permission !== undefined){if(typeof data.permission !== "string"){const err14 = {instancePath:instancePath+"/permission",schemaPath:"#/properties/permission/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}let data11 = data.section;if(typeof data11 !== "string"){const err15 = {instancePath:instancePath+"/section",schemaPath:"#/properties/section/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(!((((data11 === "main") || (data11 === "footer")) || (data11 === "settings")) || (data11 === "integrations"))){const err16 = {instancePath:instancePath+"/section",schemaPath:"#/properties/section/enum",keyword:"enum",params:{allowedValues: schema48.properties.section.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}let data12 = data.type;if(typeof data12 !== "string"){const err17 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(!((data12 === "item") || (data12 === "caption"))){const err18 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/enum",keyword:"enum",params:{allowedValues: schema48.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(data.count !== undefined){let data13 = data.count;const _errs31 = errors;let valid3 = false;let passing0 = null;const _errs32 = errors;if(!((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13)))){const err19 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/0/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data13 == "number"){if(data13 < 0 || isNaN(data13)){const err20 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}var _valid0 = _errs32 === errors;if(_valid0){valid3 = true;passing0 = 0;}const _errs34 = errors;if(typeof data13 !== "string"){const err21 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(!(data13 === "auto")){const err22 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/1/enum",keyword:"enum",params:{allowedValues: schema48.properties.count.oneOf[1].enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}var _valid0 = _errs34 === errors;if(_valid0 && valid3){valid3 = false;passing0 = [passing0, 1];}else {if(_valid0){valid3 = true;passing0 = 1;}const _errs36 = errors;if(data13 && typeof data13 == "object" && !Array.isArray(data13)){if(data13.register === undefined){const err23 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/2/required",keyword:"required",params:{missingProperty: "register"},message:"must have required property '"+"register"+"'"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(data13.schema === undefined){const err24 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/2/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}for(const key3 in data13){if(!(((key3 === "register") || (key3 === "schema")) || (key3 === "filter"))){const err25 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/2/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data13.register !== undefined){let data14 = data13.register;if(typeof data14 === "string"){if(func2(data14) < 1){const err26 = {instancePath:instancePath+"/count/register",schemaPath:"#/properties/count/oneOf/2/properties/register/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}else {const err27 = {instancePath:instancePath+"/count/register",schemaPath:"#/properties/count/oneOf/2/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data13.schema !== undefined){let data15 = data13.schema;if(typeof data15 === "string"){if(func2(data15) < 1){const err28 = {instancePath:instancePath+"/count/schema",schemaPath:"#/properties/count/oneOf/2/properties/schema/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}else {const err29 = {instancePath:instancePath+"/count/schema",schemaPath:"#/properties/count/oneOf/2/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data13.filter !== undefined){let data16 = data13.filter;if(data16 && typeof data16 == "object" && !Array.isArray(data16));else {const err30 = {instancePath:instancePath+"/count/filter",schemaPath:"#/properties/count/oneOf/2/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}}else {const err31 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/2/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}var _valid0 = _errs36 === errors;if(_valid0 && valid3){valid3 = false;passing0 = [passing0, 2];}else {if(_valid0){valid3 = true;passing0 = 2;}}}if(!valid3){const err32 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}else {errors = _errs31;if(vErrors !== null){if(_errs31){vErrors.length = _errs31;}else {vErrors = null;}}}}let data17 = data.counterVariant;if(typeof data17 !== "string"){const err33 = {instancePath:instancePath+"/counterVariant",schemaPath:"#/properties/counterVariant/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(!((data17 === "default") || (data17 === "attention"))){const err34 = {instancePath:instancePath+"/counterVariant",schemaPath:"#/properties/counterVariant/enum",keyword:"enum",params:{allowedValues: schema48.properties.counterVariant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(typeof data.pinned !== "boolean"){const err35 = {instancePath:instancePath+"/pinned",schemaPath:"#/properties/pinned/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(typeof data.open !== "boolean"){const err36 = {instancePath:instancePath+"/open",schemaPath:"#/properties/open/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}if(data.href !== undefined){if(typeof data.href !== "string"){const err37 = {instancePath:instancePath+"/href",schemaPath:"#/properties/href/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data.action !== undefined){let data21 = data.action;if(typeof data21 !== "string"){const err38 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(!(((data21 === "user-settings") || (data21 === "admin-settings")) || (data21 === "replay-walkthrough"))){const err39 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema48.properties.action.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data.tourId !== undefined){if(typeof data.tourId !== "string"){const err40 = {instancePath:instancePath+"/tourId",schemaPath:"#/properties/tourId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data.visibleIf !== undefined){let data23 = data.visibleIf;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(data23.appInstalled !== undefined){let data24 = data23.appInstalled;if(typeof data24 === "string"){if(func2(data24) < 1){const err41 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}else {const err42 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}else {const err43 = {instancePath:instancePath+"/visibleIf",schemaPath:"#/$defs/visibleIfCondition/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data.dynamicSource !== undefined){let data25 = data.dynamicSource;if(data25 && typeof data25 == "object" && !Array.isArray(data25)){if(data25.method === undefined){data25.method = "GET";}if(data25.url !== undefined){if(typeof data25.url !== "string"){const err44 = {instancePath:instancePath+"/dynamicSource/url",schemaPath:"#/$defs/dynamicSource/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}let data27 = data25.method;if(typeof data27 !== "string"){const err45 = {instancePath:instancePath+"/dynamicSource/method",schemaPath:"#/$defs/dynamicSource/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(!((data27 === "GET") || (data27 === "POST"))){const err46 = {instancePath:instancePath+"/dynamicSource/method",schemaPath:"#/$defs/dynamicSource/properties/method/enum",keyword:"enum",params:{allowedValues: schema50.properties.method.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}if(data25.labelField !== undefined){if(typeof data25.labelField !== "string"){const err47 = {instancePath:instancePath+"/dynamicSource/labelField",schemaPath:"#/$defs/dynamicSource/properties/labelField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data25.idField !== undefined){if(typeof data25.idField !== "string"){const err48 = {instancePath:instancePath+"/dynamicSource/idField",schemaPath:"#/$defs/dynamicSource/properties/idField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data25.routeField !== undefined){if(typeof data25.routeField !== "string"){const err49 = {instancePath:instancePath+"/dynamicSource/routeField",schemaPath:"#/$defs/dynamicSource/properties/routeField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}}else {const err50 = {instancePath:instancePath+"/dynamicSource",schemaPath:"#/$defs/dynamicSource/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}if(data.children !== undefined){let data31 = data.children;if(Array.isArray(data31)){const len0 = data31.length;for(let i0=0; i0<len0; i0++){if(!(validate32(data31[i0], {instancePath:instancePath+"/children/" + i0,parentData:data31,parentDataProperty:i0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate32.errors : vErrors.concat(validate32.errors);errors = vErrors.length;}}}else {const err51 = {instancePath:instancePath+"/children",schemaPath:"#/properties/children/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}}else {const err52 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}validate31.errors = vErrors;return errors === 0;}validate31.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema53 = {"type":"object","required":["id","route","type","title"],"additionalProperties":false,"description":"A v2 page definition. CnPageRenderer dispatches by type and matches by $route.name === page.id. All page types support a uniform widgets[] array for declarative slot placement. Sentinels (@resolve:*) are NOT allowed in id, route, component, headerComponent, actionsComponent, or slots.* — those are router and registry invariants.","properties":{"requiresApp":{"description":"Soft dependency gating this WHOLE page. When the named Nextcloud app is not installed, the page renders the missing-dependency screen instead of its body. This is for the DEEP LINK: an entry gated by visibleIf.appInstalled is already hidden from the menu, so the remaining ways in are a bookmark, a shared URL, a redirect or an e2e spec, and those otherwise land on an empty page where 'not installed' looks the same as 'no data'. Gate a single WIDGET with visibleIf.appInstalled instead: a detail page may carry many widgets where only one needs the app, and that widget should hide while the page renders normally.","oneOf":[{"type":"string","minLength":1},{"type":"object","required":["id"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"description":"Human-readable app name shown on the screen; defaults to the id."}}}]},"id":{"type":"string","description":"Vue-router route name. MUST be unique across pages[]. Sentinels (@resolve:*) are NOT allowed."},"route":{"type":"string","description":"Path pattern (e.g. '/decisions', '/decisions/:id'). Sentinels (@resolve:*) are NOT allowed."},"type":{"type":"string","enum":["index","detail","dashboard","logs","settings","chat","files","form","map","roadmap","reports","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","description":"i18n translation key for the page title."},"permission":{"type":"string","description":"Optional permission identifier required to access this page."},"component":{"type":"string","description":"For type='custom': name resolved against the app-provided customComponents registry. Sentinels (@resolve:*) are NOT allowed."},"headerComponent":{"type":"string","description":"Optional registry name for a component injected into the page's #header slot. Sentinels (@resolve:*) are NOT allowed."},"actionsComponent":{"type":"string","description":"Optional registry name for a component injected into the page's #actions slot. Sentinels (@resolve:*) are NOT allowed."},"sidebarComponent":{"type":"string","minLength":1,"description":"Optional registry name for a component mounted as the default sidebar content for this page."},"slots":{"type":"object","description":"Generic slot-override map. Each key is a slot name; each value is a registry component name. Sentinels (@resolve:*) are NOT allowed in values.","additionalProperties":{"type":"string"}},"primaryAction":{"$ref":"#/$defs/primaryAction","description":"Active-page-scoped primary action rendered as an NcAppNavigationNew button above the menu list when the current route resolves to this page. Page-scoped declarations win over nav.primaryAction. Also the target of the detail page's skip link: a keyboard user tabs in, follows the link and lands on this action."},"splitView":{"$ref":"#/$defs/splitView","description":"Index pages only. Opens a row beside the list rather than instead of it, at the sibling route <route>/split/:id that buildManifestRoutes() emits. Refused on any other page type."},"board":{"$ref":"#/$defs/boardView","description":"Index pages only. The board view's configuration, handed to CnIndexPage as the `board` prop. The segment appears only when `viewModes` also lists `board`. Refused on any other page type."},"dateAxis":{"$ref":"#/$defs/dateAxisView","description":"Index pages only. The date-axis view's configuration, handed to CnIndexPage as the `dateAxis` prop. The segment appears only when `viewModes` also lists `dateAxis`. Refused on any other page type."},"savedViewTree":{"$ref":"#/$defs/savedViewTree","description":"Index pages only. Renders this page's saved views as a tree with labels, and declares the views that ship with the app. Refused on any other page type: a page with no saved-view dropdown has nothing to put in a tree."},"manualOrder":{"type":"boolean","default":false,"description":"Index pages only. Lets a person drag the rows into an order of their own, held per user and per list in their Nextcloud preferences and never written onto the records. Refused on any other page type."},"tabInAddress":{"type":"boolean","default":false,"description":"Detail pages only. Puts the active tab in the address as ?_tab=<id> 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,"description":"Lets a reference on this page show a summary of the record it points at, on focus and on hover, without leaving the page. Rendered by CnReferencePreview."},"widgets":{"type":"array","items":{"$ref":"#/$defs/widgetEntry"},"description":"Uniform widget placement array. Available on ALL page types in v2 (not just dashboard). Each entry specifies a widget key, slot, and grid coordinates. The cross-field constraint gridX + gridWidth <= 12 is enforced by validateManifestV2() post-schema check. Single-widget dashboards: a type:'dashboard' page with exactly one body widget covering the full 12×12 grid whose widgetKey resolves to a custom (non-library-built-in) registry component is REJECTED — see ADR-036 Decision 1 (single-widget dashboard anti-pattern). Use type:'custom' with component:'<widgetKey>' or split into N>1 widgets instead. Library built-in widget keys are exempt from the rule; the exempt set is LIBRARY_WIDGET_KEYS in src/utils/libraryWidgetKeys.js, which is held to set equality against the BUILT_IN_WIDGETS registry and the dashboard widget catalog by tests/utils/libraryWidgetKeys.spec.js. It is not restated here, because an out-of-date second copy of that list is the defect this replaced. Enforced by validateManifestV2() post-schema check."},"actions":{"type":"array","items":{"$ref":"#/$defs/action"},"description":"Typed page-level actions with a discriminator. Each action has a type field (handler | open-modal | open-page | navigate) that defaults to 'handler' when omitted."},"config":{"type":"object","description":"Type-specific configuration. Shape varies by page type — see v1 schema for per-type config shapes (index, detail, dashboard, logs, settings, chat, files, form, map, custom). Config values under pages[].config may use the closed sentinel vocabulary (see $defs/sentinelTokenAny) — an out-of-vocabulary @-token FAILS validation via the sentinelGuardedValue allOf guard. Carry-forward from v1: register, schema, columns, actions, sidebar, sidebarProps, fields, sections, tabs, content, layout, widgets (dashboard only), cardComponent, columnGroups, etc.","additionalProperties":true,"allOf":[{"$ref":"#/$defs/sentinelGuardedValue"},{"$comment":"screens-chrome-parity: a page declares ONE place to save. `saveMode` excludes a section's own `save` (flat or inside a tab) and `autosave`.","if":{"required":["saveMode"]},"then":{"not":{"required":["autosave"],"properties":{"autosave":{"const":true}}},"properties":{"sections":{"type":"array","items":{"type":"object","not":{"required":["save"]}}},"tabs":{"type":"array","items":{"type":"object","properties":{"sections":{"type":"array","items":{"type":"object","not":{"required":["save"]}}}}}}}}}],"properties":{"favourite":{"type":"boolean","description":"Deprecated and ignored: there is no star any more, a favourite is a follow with notifications off. Kept so existing manifests stay valid; use follow."},"follow":{"type":"boolean","description":"Detail pages: the one Follow control beside the title, with a notifications on/off switch while the user follows and the follower count for a user who may update the record. Omitted means automatic: it shows when the object carries @self.watching. false removes it, and the object read then no longer asks for @self.can."},"showFollowColumn":{"type":"boolean","description":"Index pages: add a first column with a follow toggle per row, bound to each row's @self.watching. Clicking it does not open the row. Default false."},"showFavouriteColumn":{"type":"boolean","description":"Deprecated alias of showFollowColumn: the star column became the follow column. Default false."},"personalLenses":{"type":"array","description":"Index pages: personal lenses added to the quick filters, in order. watching lists the records the user follows (labelled Following), recent the records the user opened last (it owns the order, so column sorting is off), unread the records changed since the user last looked. They combine with every other filter. favourite is a deprecated alias of watching; asking for both gives one tab. Default none.","items":{"enum":["favourite","recent","watching","unread"]},"uniqueItems":true},"markRead":{"type":"boolean","description":"Detail pages: send PUT .../read-state once after the object has rendered, when it carries @self.unread, so opening a record marks it read. Default true; false keeps the page from marking anything read."},"lifecycleActions":{"description":"Detail pages: status-gated lifecycle transition buttons (CnLifecycleActions). `inputs` gives the transition dialog hints for server-declared transitions: a map of action name to a list of { field, picker?, fields? }, merged onto the server's declared input with the same `field`. `picker` { filter, excludeSelf, labelField } narrows the records a reference input offers; `fields` names the sub-properties an object input asks for. A hint for a field the server does not declare is ignored with a console warning and never adds an input.","if":{"type":"object"},"then":{"properties":{"inputs":{"type":"object","additionalProperties":{"type":"array","items":{"type":"object","required":["field"],"properties":{"field":{"type":"string","minLength":1},"picker":{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object"},"excludeSelf":{"type":"boolean"},"labelField":{"type":"string"}}},"fields":{"type":"array","items":{"type":"string"}}}}}}}}},"headerFields":{"type":"array","description":"Detail pages: fields shown as chips under the title (number, status, assignee, deadline). Each entry is a property key, or an object { key, format, labelField, colorField, warnWhenPast }. `format` is text (default), mono, badge, user or date. A `$ref` value shows `labelField` of the referenced object and falls back to the raw id in mono. An empty value shows no chip; a page without the key shows no row.","items":{"oneOf":[{"type":"string","minLength":1},{"type":"object","required":["key"],"additionalProperties":false,"properties":{"key":{"type":"string","minLength":1},"format":{"enum":["text","mono","badge","user","date"]},"labelField":{"type":"string"},"colorField":{"type":"string"},"warnWhenPast":{"type":"boolean"}}}]}},"searchInFiles":{"type":"boolean","description":"Index pages: show an \"Also search inside files\" switch beside the search box. On, the list query carries `_content_search=true` with a search term, and a row found through an attached file shows \"Found in {file}\". Default false."},"entitySource":{"type":"string","enum":["flows","flowRuns","tasks","endpoint","search","users","organisations"],"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","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`.","items":{"type":"object","additionalProperties":false,"required":["when","class"],"properties":{"when":{"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."}}}},"sourceConfig":{"type":"object","additionalProperties":true,"description":"type='index' with `entitySource`: what the named source loads with. `endpoint`: `url` (an app path, required) and `rowsPath` (default `results`); its other keys go along as query parameters. `search`: `kinds` (`{ id, label, register, schema, titleField?, subtitleField?, statusField?, href?, limit? }` or `{ id, label, files: true }`), `term` and `termQuery` (default `q`). `users`: `defaultGroup`, `functionField`, `rolesField`. `flows` / `tasks`: `app`, `scope`. Maps to CnIndexPage `sourceConfig`.","properties":{"url":{"type":"string","minLength":1},"rowsPath":{"type":"string","minLength":1},"kinds":{"type":"array","items":{"type":"object","required":["id"],"additionalProperties":true,"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string"},"register":{"type":"string"},"schema":{"type":"string"},"files":{"type":"boolean"}}}},"term":{"type":"string"},"termQuery":{"type":"string","minLength":1},"defaultGroup":{"type":"string","minLength":1},"limit":{"type":"integer","minimum":1,"description":"A paging source: rows per page (20 by default); the pager moves through the pages."}}},"app":{"type":"string","description":"Scopes a named `entitySource` to one app (e.g. entitySource='flows' with app='dossiq' lists that app's flows)."},"register":{"type":"string","description":"OpenRegister register slug."},"schema":{"type":"string","description":"OpenRegister schema slug."},"contentField":{"type":"string","description":"type='wiki': property on the article object holding the markdown body. Defaults to 'body'. Maps to CnWikiPage `contentField` prop."},"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`."},"idParam":{"type":"string","description":"type='wiki': $route.params key holding the article id. Defaults to 'id'. Maps to CnWikiPage `idParam` prop."},"treeField":{"type":"string","description":"type='wiki': property on each sidebar tree node holding its child array. Defaults to 'children'. Maps to CnWikiPage `treeField` prop."},"sidebarTitleField":{"type":"string","description":"type='wiki': property on each sidebar tree node holding its label. Defaults to `titleField`. Maps to CnWikiPage `sidebarTitleField` prop."},"sidebarRegister":{"type":"string","description":"type='wiki': register slug for the sidebar tree. Defaults to `register`. Maps to CnWikiPage `sidebarRegister` prop."},"sidebarSchema":{"type":"string","description":"type='wiki': schema slug providing the sidebar tree. When set AND `tree` is non-empty the sidebar renders. Maps to CnWikiPage `sidebarSchema` prop."},"emptyText":{"type":"string","description":"type='wiki': empty-state heading shown when the article is null. Maps to CnWikiPage `emptyText` prop."},"emptyDescription":{"type":"string","description":"type='wiki': empty-state description shown when the article is null. Maps to CnWikiPage `emptyDescription` prop."},"emptyBodyText":{"type":"string","description":"type='wiki': empty-body heading shown when the article has no content. Maps to CnWikiPage `emptyBodyText` prop."},"emptyBodyDescription":{"type":"string","description":"type='wiki': empty-body description shown when the article has no content. Maps to CnWikiPage `emptyBodyDescription` prop."},"cardComponent":{"type":"string","description":"Registry name for a custom card component on index pages. Carry-forward from v1.3.0."},"sidebar":{"description":"Sidebar configuration. For index: object with enabled, show, columnGroups[], facets, etc. For detail: boolean or object with show, enabled, register, schema, tabs[]. Carry-forward from v1.3.0.","oneOf":[{"type":"boolean"},{"type":"object","additionalProperties":true,"properties":{"show":{"type":"boolean","description":"Whether to show the sidebar on this page."},"enabled":{"type":"boolean","description":"Whether the sidebar is enabled."},"columnGroups":{"type":"array","description":"Index sidebar column-visibility groups. Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}}}}]},"tabs":{"type":"array","description":"Settings tabs (for type='settings'). Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}},"sidebarTabs":{"type":"array","description":"Sidebar tabs declaration for type='detail' pages. Each entry is the human-authored source of truth for one tab — `id` + `label` (required), optional `icon`, `order`, `component`. Widgets bound to a tab carry `tabGroup: \"<tab-id>\"` on `slot:\"sidebar\"` entries; cross-reference is validated. The CLI manifest-migrate transform lifts `config.sidebarTabs[].widgets[]` into top-level `widgets[]` with `slot:\"sidebar\"` + `tabGroup`; tab entries declaring only a `component` are carried forward as residual sidebarTabs[]. See src/cli/transforms/liftSidebarTabWidgets.js.","items":{"type":"object","additionalProperties":true,"required":["id","label"],"properties":{"id":{"type":"string","minLength":1,"description":"Unique tab identifier within the page. Referenced from widgets[].tabGroup."},"label":{"type":"string","minLength":1,"description":"i18n key (or literal) for the tab label."},"icon":{"type":"string","description":"Optional icon name (CSS class or MDI identifier) shown next to the label."},"order":{"type":"number","description":"Optional sort key for tab ordering; lower values render first."},"component":{"type":"string","description":"Optional registry name for a full-tab custom component. When set, the tab renders that component directly instead of the per-tab widgets[] grouping. Resolved against the customComponents registry."},"_note":{"type":"string","description":"Optional human note (e.g. lib-gap reason for an undecomposable tab)."}}}},"sections":{"type":"array","description":"Settings sections (for type='settings'). Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}},"fields":{"type":"array","description":"Form fields (for type='form'). Carry-forward from v1.3.0. manifest-form-logic adds two OPTIONAL typed properties — `visibleWhen` (reuses `$defs/visibleWhen`; LOCAL mode resolves `field` as a dot-path into the live form data) and `validation` (`$defs/fieldValidation`) — while every other carry-forward field shape (`key`, `label`, `type`, `enum`, `widget`, `help`, etc.) stays untyped via `additionalProperties: true` on the item.","items":{"type":"object","additionalProperties":true,"properties":{"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"manifest-form-logic: optional conditional-visibility predicate. LOCAL mode (neither `endpoint` nor `source`) resolves `field` as a dot-path into the live form data — the first dot-segment MUST match a declared `config.fields[].key` (validateManifestV2 post-schema check). `endpoint` / `source` modes keep their existing fail-safe, evaluated-once-at-mount semantics."},"validation":{"$ref":"#/$defs/fieldValidation","description":"manifest-form-logic: optional per-field validation rules, enforced client-side by CnFormPage before dispatch. See `$defs/fieldValidation` for the per-type semantics table."}}}},"steps":{"type":"array","minItems":1,"description":"manifest-form-logic: for type='form' pages, ordered multi-step wizard groups. Each entry is `{ id, title, description?, fields[] }` where `fields[]` is an ordered array of KEY REFERENCES into `config.fields[].key` — steps do not embed field objects (config.fields[] stays the single source of truth). Absent `steps` = single-step form, byte-for-byte the pre-change rendering. When present, validateManifestV2() post-schema requires: step ids unique within the page, every fields[] entry references a declared field key, and every declared field key appears in EXACTLY ONE step (complete partition — an unassigned field would silently never render; a doubly-assigned field would double-render).","items":{"type":"object","required":["id","title","fields"],"additionalProperties":false,"description":"One wizard step. `fields[]` order is the rendering order for that step's fields.","properties":{"id":{"type":"string","minLength":1,"description":"Unique step identifier within the page's `steps[]` array."},"title":{"type":"string","minLength":1,"description":"i18n key (or literal) for the step's title, shown in the step indicator."},"description":{"type":"string","description":"Optional i18n key (or literal) for a short step description/subtitle."},"fields":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Ordered field KEYS (referencing `config.fields[].key`) rendered on this step."}}}},"fieldWidgets":{"type":"array","description":"Declarative widget-component slots for type='form' / type='detail' pages. Each entry mounts a lib `Cn*` SFC inline (e.g. CnMarkdownEditor as a markdown field, CnSchemaSelect as a relation picker). Distinct from top-level pages[].widgets[] which uses widgetKey + grid coordinates against the host customComponents registry. Carries serialisable props only. See $defs/fieldWidget and ConductionNL/nextcloud-vue#314.","items":{"$ref":"#/$defs/fieldWidget"}},"columns":{"type":"array","description":"Table columns (for type='index' and type='logs'). Items may be strings (legacy shorthand) or column objects ({ key, label, sortable?, width?, formatter?, formatterOptions?, widget?, widgetProps?, format?, aggregate?, colorMap? }). `colorMap` maps an enum column's raw values to a pill tone (default, primary, success, warning, error, info, purple, teal) and wins over the schema property's own `colorMap` / `x-color-map`. `formatter` resolves against cnFormatters (built-ins incl. currency / conditionalPhrase — configured via `formatterOptions`); `widget` resolves against cnCellWidgets (built-ins incl. badge / fkResolve / link / avatar / date).","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]}},"folderSidebar":{"type":"object","additionalProperties":true,"description":"type='index': the opt-in folder navigation pane (CnFolderSidebar) rendered left of the list. Selecting a folder filters the list by `filterField` (or `field`); \"All\" clears it. Only the per-scope list-layout keys are typed here; the pane's own keys (source, register, schema, idField, nameField, folders, allLabel, title, allowCreate, ...) stay free-form. Maps to CnIndexPage `folderSidebar`.","properties":{"scopeViews":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"The view modes (`table`, `list`, `cards`, `board`, ...) a scope's look applies in; in the others the page keeps its generic look while the scope still filters (DqWerkbord, 2.129.0)."},"heading":{"type":"boolean","description":"true heads the list with the selected folder's name, its parent folder and its count (always on a `groups` pane, 2.127.0)."},"countText":{"type":"string","minLength":1,"description":"The count in that heading, `{count}` filled in, through the app's translate; a groups pane defaults to \"{count} members\" (2.127.0)."},"pane":{"type":"boolean","default":true,"description":"false keeps the folder scope (a deep link such as `?caseType=<uuid>` still applies the scope's look and filter) without drawing the pane (dossiq's simple profile, 2.126.0)."},"folders":{"type":"array","description":"Explicit folder list for source:'custom', and the place a folder declares its own list layout. Every other folder key stays free-form.","items":{"$ref":"#/$defs/folderSidebarScope"}}}},"layout":{"type":"array","description":"Dashboard layout entries (for type='dashboard', legacy). Carry-forward from v1.","items":{"type":"object","additionalProperties":true}},"content":{"type":"array","description":"Declarative content items for dashboard (widget-ref items). Carry-forward from v1.","items":{"type":"object","additionalProperties":true}},"widgets":{"type":"array","description":"Legacy dashboard widgets array in config (for type='dashboard'). In v2, prefer top-level pages[].widgets[] with the uniform widgetEntry shape. This legacy config.widgets key remains for back-compat.","items":{"type":"object","additionalProperties":true}},"stageField":{"type":"string","minLength":1,"default":"status","description":"type='detail': dot-path to the field holding the record's stage. Read by `primaryActionByStage` and `nextStep`."},"primaryActionByStage":{"type":"object","description":"type='detail': the page's primary button per stage. Keys are values of `stageField` (matched exactly first, then ignoring case). A value is an action in the header action shape, or the id of a `headerActions` entry, which then leaves the overflow menu. A stage without an entry falls back to the page's `primaryAction`.","additionalProperties":{"oneOf":[{"type":"string","minLength":1},{"$ref":"#/$defs/headerAction"}]}},"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. `{ 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"},{"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."}}}]}},"actionsMenu":{"type":"object","additionalProperties":false,"description":"type='detail': what the header's overflow menu itself shows. Omitted, the menu keeps Refresh and the three help links.","properties":{"showRefresh":{"type":"boolean","description":"false removes Refresh from the menu."},"showHelpLinks":{"type":"boolean","description":"false removes Request a feature, Report a bug and Documentation, for an app that offers them in its own help menu."},"showMarkUnread":{"type":"boolean","description":"false removes the built-in Mark as unread entry."},"showOpenSidebar":{"type":"boolean","description":"false removes the built-in Open sidebar entry of the board look."},"label":{"type":"string","minLength":1,"description":"i18n key / text naming the menu, for example 'More'. Defaults to 'Actions'."}}},"nextStep":{"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. `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,"description":"Dot-path to the stage, when it differs from `stageField`."},"stagesFrom":{"type":"string","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)."},"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."},"stages":{"type":"object","description":"Keys are stage values.","additionalProperties":{"type":"object","additionalProperties":false,"required":["checklist"],"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."},"checklist":{"type":"array","description":"What this stage still needs. An empty list shows no card.","items":{"type":"object","additionalProperties":false,"required":["label"],"properties":{"label":{"type":"string","minLength":1,"description":"i18n key / text of the item."},"hint":{"type":"string","description":"i18n key / short note after the label, for example '2 of 5 done'."},"doneField":{"type":"string","minLength":1,"description":"Dot-path on the record. The item is done when it holds a value; a list counts when it is not empty."},"doneWhen":{"type":"object","description":"A local visibleWhen condition ({ field, op, value } or { all | any }). The item is done when it is true. Wins over `doneField`."},"done":{"type":"boolean","description":"A fixed state, for an item the record cannot answer."}}}}}}}}},"typePill":{"$ref":"#/$defs/headerPill","description":"type='detail': a pill above the title saying what kind of record this is."},"statusPill":{"$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}}}]}},"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.","items":{"oneOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":true,"required":["type"],"properties":{"id":{"type":"string"},"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)."},"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}}}}}]}},"tabsFrom":{"type":"string","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`."},"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.","items":{"$ref":"#/$defs/headerAction"}},"bulkActions":{"type":"array","description":"Bulk actions rendered in the contextual selection strip that appears while rows are selected. Same shape as the row-level `actions[]` ($ref action), but the handler receives the SELECTION rather than a row: `{ actionId, selectedIds, count }`. For `type: \"open-modal\"` the selection is merged into the modal's props as `selectedIds` and `count`, so a manifest-declared modal component can act on the selection without the page hand-writing anything. Reserved ids (`copy`, `delete`) are dropped at render time: the strip already ships both as built-ins. Before this existed the strip had only a `#selection-actions` slot, which only a hand-written host component could fill — so a page declared in a manifest could carry row actions and header actions but never a bulk one.","items":{"$ref":"#/$defs/action"}},"actions":{"type":"array","items":{"anyOf":[{"type":"object","required":["label"],"additionalProperties":true,"properties":{"label":{"type":"string","minLength":1},"id":{"type":"string","not":{"pattern":"^builtin:"}},"builtin":false}},{"type":"string","enum":["builtin:view","builtin:edit","builtin:copy","builtin:delete"]}]},"allOf":[{"contains":{"const":"builtin:view"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:edit"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:copy"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:delete"},"minContains":0,"maxContains":1}],"description":"Declarative actions on a page. An object entry is governed ONLY by \"an object carrying a non-empty label\", deliberately: this key is polymorphic by page type (index rows dispatch $defs/action; detail pages also carry type='lifecycle-transition' entries), so one closed grammar here would reject working manifests. A label is what every dialect needs — CnRowActions renders an entry from its label and icon, so an entry without one became a full-height, clickable, EMPTY row in the overflow menu. An object is always an app action whatever its id: an id, when present, must be a string that does not start with 'builtin:', and the object may not set a 'builtin' key. On a type='index' page an entry may also be one of the placeholders \"builtin:view\", \"builtin:edit\", \"builtin:copy\" or \"builtin:delete\", each at most once, which puts that built-in row action at its position in the menu. A placeholder only places a built-in: config.actionToggles / the show*Action keys still decide whether it renders. Enabled built-ins the array does not place are appended after everything else in the order View, Edit, Copy, Delete. Any other string (a bare \"edit\", an unknown \"builtin:archive\") is refused, and placeholders are refused on every page type other than index."},"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, 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"}},"rowIndicatorCap":{"type":"integer","minimum":0,"default":3,"description":"type='index': how many declared indicators render on the row itself before the rest move into the row menu. Maps to CnIndexPage `rowIndicatorCap`."},"quickEditFields":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': the fields a quick edit asks for, opened from a row without leaving the list. The page names them; a record cannot open a form on a field the page did not name. A field this caller may not write (see `writableField`) renders read-only rather than missing. Maps to CnIndexPage `quickEditFields`."},"writableField":{"type":"string","default":"@self.writableFields","description":"type='index': the dotted path where a record lists the fields this caller may write. A row carrying nothing there leaves every field the quick edit named editable; an empty list is a refusal and locks them all. Maps to CnIndexPage `writableField`."},"viewTabs":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': saved view ids this page renders as tabs instead of as entries in the views control. A view appears in one place or the other, never both. An id naming a view that no longer exists produces no tab, rather than a tab that opens nothing. Maps to CnIndexPage `viewTabs`."},"viewCounts":{"description":"type='index': which saved views show how many records they match. `true` counts every saved view (tabs and the entries in the views control), an array counts only the view ids it lists. Off by default, so no count request is made. Quick filters opt in per entry with `showCount: true`. Maps to CnIndexPage `viewCounts`.","oneOf":[{"type":"boolean"},{"type":"array","items":{"type":"string","minLength":1}}],"default":false},"priorityField":{"type":"string","description":"type='index': the field carrying a record's DERIVED priority. The list reads the value and sorts on it; it never computes one. A record with no priority, or one outside `priorityLevels`, sorts after every ranked record in BOTH directions and stays in the list. Maps to CnIndexPage `priorityField`."},"priorityLevels":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': the priority values in rank order, lowest first (e.g. ['low', 'medium', 'high']). Needed because a priority does not sort alphabetically into the order a person means. Maps to CnIndexPage `priorityLevels`."},"listShortcuts":{"type":"boolean","default":false,"description":"type='index': offer the list's keyboard shortcuts. Every shortcut is also listed in the command palette and on the help key, from one catalogue, because a shortcut nobody can find does not count. Maps to CnIndexPage `listShortcuts`."},"rowActionField":{"type":"string","description":"type='index': the dotted path on each row where the server lists the actions this caller may run on that record (default '@self.actions'). A row's menu is then the intersection of what `config.actions` declares and what the server allows: an action the server allows but the page does not declare stays out, and an action the page declares but the server refuses stays out with its reason kept. A row carrying nothing at this path leaves the declaration standing. Maps to CnIndexPage `rowActionField`."},"editOpensDetail":{"type":"boolean","description":"type='index': send the Edit row action to the record's detail page instead of opening the edit modal. OPT-IN — CnPageRenderer used to set this wherever a type='detail' page or config.rowRoute existed, and it no longer does: @edit-open is bound to the same navigation as a row click, so a routed Edit just repeats the row click (the split pane, or the detail page) and the form is no longer reachable from the list. Declare true per page when the modal cannot express the record. Maps to CnIndexPage `editOpensDetail`."},"showEditAction":{"type":"boolean","description":"type='detail': show the header Edit button that opens the record's schema form. Set automatically by CnPageRenderer for every schema-bound detail page; declare false to make a detail page read-only. Maps to CnDetailPage `showEditAction`."},"formSize":{"type":"string","enum":["small","normal","large"],"default":"normal","description":"NcDialog size for the page's built-in Add/Edit form dialog (type='index' and type='detail'). A manifest `open-form` header action has always been able to ask for `large`; the built-in dialog could not, so the same app could have a roomy create form on one surface and a cramped one on another. Maps to CnIndexPage / CnDetailPage `formSize`."},"formColumns":{"type":"integer","enum":[1,2],"default":1,"description":"How many columns the page's built-in Add/Edit form flows its fields into. Pair 2 with formSize 'large', or the two columns are merely two narrow ones. CnFormDialog collapses back to one column below 700px, and a textarea or JSON editor spans both columns regardless. Maps to CnIndexPage / CnDetailPage `formColumns`."},"excludeFields":{"type":"array","items":{"type":"string"},"description":"Property names kept OUT of the page's built-in Add/Edit form dialog. Maps to CnIndexPage / CnDetailPage `excludeFields`."},"includeFields":{"type":"array","items":{"type":"string"},"description":"Property names the page's built-in Add/Edit form dialog shows, in this order — a whitelist, so anything absent is hidden regardless of excludeFields. Maps to CnIndexPage / CnDetailPage `includeFields`."},"fieldOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"Per-field overrides merged over the schema-derived field descriptors in the page's built-in Add/Edit form dialog (e.g. `{ \"description\": { \"widget\": \"textarea\" } }`). Which widget a field renders as belongs to the surface, not to the schema. Maps to CnIndexPage / CnDetailPage `fieldOverrides`."},"createDefaults":{"type":"object","additionalProperties":true,"description":"type='index': seed values for the page's built-in create dialog, never applied on edit. Resolves `@me`/`@now`/`@today` at any depth; `@object.*`/`@workspace.*`/`@config.*` need a context the page does not carry and pass through unchanged. Maps to CnIndexPage `createDefaults`."},"createSuccessRoute":{"oneOf":[{"type":"string"},{"type":"object","properties":{"name":{"type":"string"},"paramField":{"type":"string"},"objectParam":{"type":"string"}},"required":["name"],"additionalProperties":false}],"description":"type='index': route opened after a successful create from the page's built-in Add dialog; the created object's id is merged into the params. Maps to CnIndexPage `createSuccessRoute`."},"createSuccessMessage":{"type":"string","description":"type='index': toast shown after a successful create from the page's built-in Add dialog. Maps to CnIndexPage `createSuccessMessage`."},"savedViewsScope":{"type":"string","description":"type='index': which pages share this page's saved views. By default a view is shared by every page over the same register and schema and by no other, because a view is filters over one schema's fields. Set this to share views across pages over different sources, or to keep two pages over one source apart. Written into the saved view's `query.scope`; views saved before scoping stay visible everywhere. Maps to CnIndexPage `savedViewsScope`."},"defaultSavedViews":{"type":"array","description":"type='index': views the app ships for this page. Listed read-only above each user's own saved views, under Default views, and never stored: a new app version updates them for everybody and no user can delete one. A user may apply a default or save a copy, which they then own. `@`-tokens in `filters` (`@me`, `@today`, `@today+7d`) resolve when the default is applied; a copy keeps them. Maps to CnIndexPage `defaultSavedViews`.","items":{"type":"object","additionalProperties":false,"required":["slug","name","filters"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The stable name of the default. Lowercase and dash only."},"name":{"type":"string","minLength":1,"description":"What the filter is called in the control. Translatable."},"filters":{"type":"object","description":"The filters, keyed by property, in the shape a quick filter's `filter` takes."},"search":{"type":"string","description":"A search term the default applies."},"sort":{"type":"array","description":"Sort keys, in priority order.","items":{"type":"object","additionalProperties":false,"required":["key"],"properties":{"key":{"type":"string"},"order":{"type":"string","enum":["asc","desc"]}}}},"label":{"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,"description":"pill-color-field: a registered icon name the view's My views menu entry shows instead of the filter icon (a case type's own icon, say)."},"overrides":{"type":"object","additionalProperties":false,"description":"Page config this view lays over the page while it is active; leaving the view restores the page. Each key is optional and, apart from `header`, the aliases `tabs` and `views`, `sortOption`, `slots` and `badge`, takes the shape of the page config key of the same name. A saved view carries the same object in `query.overrides`, so a copy of a default keeps it (saved-view-overrides, 2.132.0).","properties":{"title":{"type":"string","minLength":1,"description":"The page title. Translatable."},"showTitle":{"type":"boolean","description":"Same as config.showTitle."},"breadcrumb":{"description":"Same as config.breadcrumb.","anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}},{"type":"null"}]},"headerMeta":{"type":"string","description":"Same as config.headerMeta."},"notice":{"type":["object","null"],"description":"Same as config.notice."},"pageTabs":{"type":["array","null"],"description":"Tabs to sibling pages or views, `countFrom` included. Same as config.pageTabs."},"headerButtons":{"type":"array","description":"Same as config.headerButtons."},"addLabel":{"type":"string","description":"Same as config.addLabel."},"searchPlaceholder":{"type":"string","description":"Same as config.searchPlaceholder."},"statRow":{"type":"array","description":"Same as config.statRow."},"quickFilters":{"type":"array","description":"Same as config.quickFilters."},"quickFiltersFrom":{"type":["object","null"],"description":"Same as config.quickFiltersFrom."},"quickFilterPlacement":{"type":"string","description":"Same as config.quickFilterPlacement."},"quickFilterMaxVisible":{"type":"integer","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."},"afterList":{"type":"array","description":"Same as config.afterList."},"columns":{"type":"array","description":"Same as config.columns."},"cardLayout":{"type":["object","null"],"description":"Same as config.cardLayout."},"cardFields":{"type":"array","items":{"type":"string"},"description":"Same as config.cardFields."},"cardComponent":{"type":"string","description":"Same as config.cardComponent."},"listConfig":{"type":"object","description":"Same as config.listConfig."},"viewModes":{"type":"array","items":{"type":"string","minLength":1},"description":"The view modes the toggle offers. Same as config.viewModes."},"viewMode":{"type":"string","minLength":1,"description":"The view mode the view opens in. Same as config.viewMode."},"viewSwitch":{"type":"array","items":{"type":"string"},"description":"Same as config.viewSwitch."},"countText":{"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."},"emptyText":{"type":"string","description":"Same as config.emptyText."},"folderSidebar":{"type":["object","null"],"description":"`null` hides the folder sidebar. Same as config.folderSidebar."},"sidebar":{"type":["object","null"],"description":"Same as config.sidebar."},"defaultSort":{"type":"array","description":"Same as config.defaultSort."},"rowClickToView":{"type":"boolean","description":"Same as config.rowClickToView."},"showViewAction":{"type":"boolean","description":"Same as config.showViewAction."},"actions":{"type":"array","description":"Same as config.actions."},"includeFields":{"type":"array","items":{"type":"string"},"description":"Same as config.includeFields."},"fieldOverrides":{"type":"object","description":"Same as config.fieldOverrides."},"tabs":{"type":["array","null"],"description":"Alias of pageTabs."},"views":{"type":"array","items":{"type":"string","minLength":1},"description":"Alias of viewModes."},"header":{"type":"object","additionalProperties":false,"description":"Groups header keys; a key spelled out beside it wins.","properties":{"title":{"type":"string","minLength":1},"showTitle":{"type":"boolean"},"breadcrumb":{"anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}},{"type":"null"}]},"notice":{"type":["object","null"]},"headerMeta":{"type":"string"},"headerButtons":{"type":"array"}}},"lockedFilters":{"type":"object","description":"Base filters added to the page's `filter` while the view is active, which the reader cannot clear, as the old Queue and Assigned to me pages had (2.132.0)."},"sortOption":{"type":"string","minLength":1,"description":"The `value` of a `sortSelectOptions` entry to select, an endpoint-sourced one included."},"slots":{"type":"object","description":"Named page slots (`below-header`, `before-collection`, ...) mapped to a registry component or a list of them drawn in order, laid over the page's `slots`; `null` or `[]` empties that slot.","additionalProperties":{"anyOf":[{"type":"string","minLength":1},{"type":"array","items":{"type":"string","minLength":1}},{"type":"null"}]}},"badge":{"description":"A count on the view's menu entry, in a menu entry's terms. `true`, or `count: true`, counts the view's own list: the page's register, schema and base filter with the view's filters. A `count` object may name another `register`, `schema` or `filter`. `counterVariant: attention` draws the attention pill.","anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"count":{"anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object"}}}]},"counterVariant":{"type":"string","enum":["default","attention"]}}}]}}}}}},"splitCloseButton":{"type":"boolean","default":true,"description":"type='index': whether the split pane draws its own close link. Set false when a `#split-pane` slot draws one from the slot's `close`. Maps to CnIndexPage `splitCloseButton`."},"splitCloseLabel":{"type":"string","description":"type='index': accessible name and tooltip for the split pane's close button. Defaults to \"Close\". Maps to CnIndexPage `splitCloseLabel`."},"look":{"type":"string","enum":["nextcloud","board"],"description":"screens-chrome-parity: draw this page in the `nextcloud` or the `board` look, over the manifest's root `look`. The nearest value wins: the page root carries `cn-look-board` or `cn-look-nextcloud`, and `cnLook` is re-provided to the page's descendants."},"saveMode":{"type":"string","enum":["section","page"],"description":"type='settings' (screens-chrome-parity): where the page saves. `section` ends each section that has fields in its own Save button and draws no page save bar. `page` draws one Save as the last header button and a Changes card listing the changed fields; no section draws a save button. Absent, the page keeps the save bar under the last section. Cannot be combined with `autosave` or with a section that declares its own `save`. Maps to CnSettingsPage `saveMode`."},"autosave":{"type":"boolean","default":false,"description":"type='settings' (screens-chrome-parity): the page saves on change. It draws no save button and its description ends with \"Changes are saved automatically.\" Cannot be combined with `saveMode`. Maps to CnSettingsPage `autosave`."},"showHeader":{"type":"boolean","default":true,"description":"type='dashboard': `false` drops the header row (title, description, header actions, edit toggle) for a page that opens with its own heading, such as a greeting widget; the title stays as a visually hidden heading (zuiddrecht-pixel-gaps). Maps to CnDashboardPage `showHeader`."},"views":{"type":"array","minItems":1,"description":"type='dashboard' | type='detail': views of the page, each its own widget grid behind one segmented control (\"My work | My team\") (view-switch-containers). The chosen view renders in a region below the page's own grid, drawn with the same grid and widgets; the page's own `widgets` and `layout` stay above it. The switch sits in the page header, or in a greeting header widget whose `content.views.options[]` carry `view` instead of `route`. The chosen view is in the address (`?view=<id>`) and in the user's preferences; the address wins. A view with nothing to draw shows `emptyText`, else a default sentence. On a detail page, views replace the automatic Data and Related body. Maps to CnDashboardPage / CnDetailPage `views`.","items":{"type":"object","required":["id","label"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"pattern":"^[A-Za-z0-9_-]+$","description":"The view id, written to the address as `?view=<id>`."},"label":{"type":"string","minLength":1,"description":"The option label and the region's accessible name (through the app's translations)."},"title":{"type":"string","minLength":1,"description":"type='dashboard': the page title while this view is open, e.g. \"My team\" (through the app's translations; view-title, 2.119.0). Without it the page keeps its own title."},"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}},"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}}}}}},"defaultView":{"type":"string","minLength":1,"description":"type='dashboard' | type='detail': the id of the view chosen when neither the address nor the user's stored choice names one; without it the first view (view-switch-containers). Maps to `defaultView`."},"viewsLabel":{"type":"string","minLength":1,"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":{"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:<name>}` in headerMeta (\"{total} contracten bij {count:suppliers} leveranciers\"); while a count loads or when it fails, the note is left out.","additionalProperties":{"type":"object","required":["register","schema"],"additionalProperties":false,"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object"}}}},"tabsLabel":{"type":"string","minLength":1,"description":"type='detail', board look: the accessible name of the tab list, for example \"Case parts\". Defaults to \"<type> parts\" from the page title (screens-detail-page-parity). Maps to CnDetailPage `tabsLabel`."},"identifierField":{"type":"string","minLength":1,"description":"type='detail', board look: the record field (a dotted path, for example `identifier`) that names the record in the last line of the side column's History card (screens-detail-page-parity). Maps to CnDetailPage `identifierField`."},"showWidgetActions":{"type":"boolean","default":true,"description":"type='dashboard' and type='detail': `false` drops the overflow Actions menu from every widget that does not set `showActions` itself, for a dashboard whose widget headers carry a header link and nothing else (zuiddrecht-pixel-gaps-2), or a record page whose data cards carry none (zuiddrecht-pixel-gaps-3; a card with an Add action keeps it). When the key is not set a detail page keeps the menu, except under the board look, where side cards carry none (screens-detail-page-parity). Maps to CnDashboardPage / CnDetailPage `showWidgetActions`."},"pageFilters":{"type":"array","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.<key>` / `@workspace.<key>` token (an endpoint widget also in its `responsePath`). Maps to the CnDashboardPage `pageFilters` prop.","items":{"type":"object","required":["key"],"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.<key>` / `@workspace.<key>` token name)."},"label":{"type":"string","description":"Control label (i18n key or plain text)."},"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":{"type":"object","additionalProperties":false,"description":"Read the options instead of listing them: from an app endpoint (`url`, `params`, `responsePath`) or from OpenRegister objects (`register`, `schema`, `filter`). `valueField` and `labelField` pick each option's value and label (default `id` and `title` or `name`); tokens resolve (2.130.0).","anyOf":[{"required":["url"]},{"required":["register","schema"]}],"properties":{"url":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"responsePath":{"type":"string","minLength":1},"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1}}},"placement":{"type":"string","enum":["row","description"],"default":"row","description":"Where the filter sits: in the header's filter row, or (a segmented group) on the description line after the period group (DqProcesanalyse)."},"default":{"type":["string","number"],"description":"Initial value seeded into the context on mount; the first option's value when omitted."},"options":{"type":"array","items":{"type":"object","required":["value","label"],"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)."},"months":{"type":"integer","minimum":1,"description":"A rolling window of this many months ending with the current one. A dashboard description's `{filter.<key>}` 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)."}}}}}}},"kpiRow":{"type":"array","items":{"type":"string"},"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."},"greeting":{"oneOf":[{"type":"boolean"},{"type":"string","enum":["first","full","date"]},{"type":"object","required":["label"],"additionalProperties":false,"properties":{"label":{"type":"string","minLength":1,"description":"i18n key drawn where the greeting would be."}}}],"default":false,"description":"type='dashboard': greet the reader in the header subtitle. `true` or \"first\" writes \"Good afternoon, Pieter · Monday 5 October 2026\", \"full\" uses the whole display name; the page `description` follows after another middle dot (screens-dashboard-greeting-header). Since 2.111.0 \"date\" leads with the date alone (\"Tuesday 7 October 2026 · ...\") and `{ \"label\": \"Customer contact\" }` puts the translated label where the greeting would be (dashboard-greeting-label). Maps to CnDashboardPage `greeting`."},"viewLinks":{"type":"array","default":[],"description":"type='dashboard': links drawn as small pills on the switch row under the header, right of the view switch (screens-dashboard-greeting-header). `route` is a route name or a `{ name, params, query }` location, `href` a plain URL; `label` is an i18n key. An entry without a label or a target is skipped. Maps to CnDashboardPage `viewLinks`.","items":{"type":"object","required":["label"],"additionalProperties":false,"properties":{"label":{"type":"string","minLength":1},"icon":{"type":"string","minLength":1},"route":{"oneOf":[{"type":"string","minLength":1},{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"params":{"type":"object"},"query":{"type":"object"}}}]},"href":{"type":"string","minLength":1}}}},"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`. 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`."},"showTitle":{"type":"boolean","default":false,"description":"type='index': show the page header (icon, title, description) inline. Default false: the title renders visually hidden and the sidebar names the page. Maps to CnIndexPage `showTitle`."},"countSubtitle":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["singular","plural"],"properties":{"singular":{"type":"string","minLength":1,"description":"i18n key of the line for a total of one, e.g. \"{total} invoice\"."},"plural":{"type":"string","minLength":1,"description":"i18n key of the line for any other total, e.g. \"{total} invoices\"."}}}],"description":"type='index': an i18n key for the description line with `{total}` replaced by the collection's total, e.g. \"{total} open cases\" (zuiddrecht-pixel-gaps). Shown in place of `description` once a total is known. `{ singular, plural }` picks the form by the total through the app's plural translation (`translatePlural`), so \"1 factuur\" and \"12 facturen\" each read right (index-count-plural). Maps to CnIndexPage `countSubtitle`."},"showTitleIcon":{"type":"boolean","default":true,"description":"type='index': `false` drops the icon before the page title, for a title that stands alone as the board draws it (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showTitleIcon`."},"showCount":{"type":"boolean","default":true,"description":"type='index': `false` drops the actions bar's \"Showing 20 of 258\" line, for a page whose title line already gives the total through `countSubtitle` (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showCount`."},"countText":{"type":"string","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`."},"footerNote":{"type":"string","minLength":1,"description":"type='index', board look: free text after the footer's count, e.g. \"click a column header to sort\" (screens-index-list-parity). Maps to CnIndexPage `footerNote`."},"queryPresets":{"type":"array","minItems":1,"description":"type='index': per-query overlays of this page (screens-index-query-presets). A menu entry that deep-links here with a query (`menu[].query`, e.g. { caseType: '<uuid>' }, the shape hydra gate-68 asks for instead of a second page) gets its own lenses, columns and copy: the first preset whose every `match` pair is in the route query replaces those keys of this config. Only the listed keys can be replaced; register, schema and permissions stay the page's. Switching preset remounts the list.","items":{"type":"object","required":["match"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"description":"A name for the preset, for people reading the manifest."},"match":{"type":"object","minProperties":1,"additionalProperties":{"type":["string","number","boolean"]},"description":"Route query pairs that must ALL be present, e.g. { \"caseType\": \"<uuid>\" }. A repeated parameter matches when it holds the value."},"title":{"type":"string","minLength":1,"description":"The page title while the preset applies."},"quickFilters":{"type":"array","description":"The lenses while the preset applies; same shape as config.quickFilters."},"columns":{"type":"array","description":"The columns while the preset applies; same shape as config.columns."},"cardFields":{"type":"array","items":{"type":"string"},"description":"The card facts while the preset applies."},"countText":{"type":"string","minLength":1,"description":"The count line while the preset applies."},"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","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)."},"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)."},"afterList":{"type":"array","description":"The cards after the list while the preset applies; same shape as config.afterList (index-scope-presentation).","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"}}}}}}},"viewSwitch":{"type":"array","minItems":1,"uniqueItems":true,"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 \"<mode>: 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","additionalProperties":false,"required":["field"],"description":"type='index': a second filter strip under the toolbar, on one field (board PqWachtrij: the kind in the quick filters, the channel in this strip). \"All\" and one pill per value (`options`, else the field's schema enum); picking one filters the list on that value, All clears it. With `showCount` each pill counts its rows within the active quick filter (screens-filter-strip, 2.92.0). Maps to CnIndexPage `filterStrip`.","properties":{"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}}}},"showCount":{"type":"boolean","default":false}}},"viewSwitchLinks":{"type":"object","additionalProperties":false,"minProperties":1,"description":"type='index', board look: view switch segments that open another PAGE instead of switching in place, by mode: `{ \"board\": { \"route\": \"CaseBoard\" } }` for a board or a map that is a page of its own. The segment sits in its place in the switch (table, cards, board, map) as a link: `route` with optional `params` and `query`, or `href`. A mode the page opens in place stays a button, and a route the router cannot resolve is left out. Ignored without the board look (screens-view-switch-links, 2.91.0). Maps to CnIndexPage `viewSwitchLinks`.","properties":{"table":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"cards":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"board":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"map":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}}}},"searchPlaceholder":{"type":"string","minLength":1,"description":"type='index': the placeholder (and accessible name) of the list's search field, e.g. \"Search by case, number or requester\". Run through the app's translate, so an English key with a Dutch catalogue entry reads Dutch. Absent: the library's \"Search…\" (screens-index-toolbar-parity). Maps to CnIndexPage `searchPlaceholder`."},"bulkHint":{"type":"string","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}},"cardLayout":{"type":"object","additionalProperties":false,"description":"type='index', board look: the record layout of each card in the cards view (board-record-card-layout): a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date. Each part names a field (a dot path) and is left out while it is empty; a pill maps values to a badge variant (`colorMap`, ignoring case) and to shown text (`labels`). Maps to CnIndexPage `cardLayout`.","properties":{"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"}}}]}},"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"]}}}]}}}},"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"}}}},"statRow":{"type":"array","default":[],"description":"type='index': KPI tiles above the list, between the header and the toolbar, in an auto-fit row (each at least 200px). Each entry is a widget definition, usually `type: \"stat\"`; the type resolves through the app's widget registry, then the dashboard catalog (screens-index-stat-row). Maps to CnIndexPage `statRow`.","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","minLength":1},"title":{"type":"string"},"content":{"type":"object"},"headerLink":{"type":"object"}}}},"sidePanel":{"type":"array","default":[],"description":"type='index': titled cards in a 300px column right of the toolbar and the list (above them below 1024px). Each entry is a widget definition with an optional `title` (i18n key) and `headerLink`; the type resolves as for `statRow` (screens-index-stat-row). Maps to CnIndexPage `sidePanel`.","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","minLength":1},"title":{"type":"string"},"content":{"type":"object"},"headerLink":{"type":"object"}}}},"headerButtons":{"type":"array","description":"type='index': buttons beside the page title, as the board's \"Export\" and \"New case\" (zuiddrecht-pixel-gaps-3). `action` is `add` (the Add flow; the label defaults to the Add label), `export` (the export leaf in `format`, else the export dialog; under the board look it reads \"Download\" by default), `actions-menu` (the page's header actions as one labelled menu button, absent when the page has none), `import`, `refresh`, or the id of a `headerActions` entry. Under the board look the buttons render in a fixed order whatever the manifest declares: export, actions-menu, other secondary buttons, the buildiq square, then the primary button. Shown only with `showTitle`; when they show, the actions bar drops its Views and Actions menus, and the Add button and Export menu when a button takes their action. Maps to CnIndexPage `headerButtons`. type='reports' (screens-reports-header-buttons, 2.96.0): the same buttons beside the reports page title, as board DcRapportages draws \"Downloaden\". There `label` is required and `action` is the id of a `headerActions` entry (same shape as on an index page), which says what the click does; every click also emits `header-action`. Under the board look the buildiq square sits between the secondary buttons and the primary one. Maps to CnReportsPage `headerButtons`.","items":{"type":"object","additionalProperties":false,"required":["action"],"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1,"description":"i18n key; optional for `add`."},"action":{"type":"string","minLength":1},"variant":{"type":"string","enum":["primary","secondary"],"default":"secondary"},"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)."},"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\": \"<i18n key>\" }` 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)."}}}},"search":{"type":"boolean","default":true,"description":"type='index': `false` draws no inline search in the actions bar, also in the board look, which otherwise draws one on every index page. Wins over `inlineSearch` (2.127.0)."},"searchRefusalHint":{"type":"string","description":"type='index': what to do after a refused search, through the app's translate; the default explains AND, OR, NOT and quotes (2.133.0)."},"searchPlacement":{"type":"string","enum":["","toolbar"],"default":"","description":"type='index', board look: where the search field sits. `toolbar` puts it on row 1 before the Filter button and the view switch instead of opening row 2 (DqTeamwachtrij, 2.127.0)."},"sortSelectOptions":{"type":"array","description":"type='index': the orders the index sidebar's Sort section offers in the list, card and map views, `{ value, label, order?, default?, source? }`. An option with a `source` takes its rows from an app endpoint, in that endpoint's order, for an order the server cannot sort by; the page's search, sidebar filters and pager run over those rows in the browser, and it is chosen by its value. `default: true` opens the page on it unless the address carries an order; a column header click in the table view leaves it. When the endpoint fails the list falls back to the option's `order` and shows `failedNote` (2.131.0).","items":{"type":"object","required":["value","label"],"properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"order":{"anyOf":[{"type":"string","minLength":1},{"type":"object"},{"type":"array"}]},"default":{"type":"boolean"},"source":{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","minLength":1,"description":"An app path, resolved with generateUrl and fetched with the session."},"params":{"type":"object","description":"Query parameters sent with the request."},"rowsPath":{"type":"string","minLength":1,"description":"Where the entries sit in the answer; `results` by default, a bare array as is."},"rowPath":{"type":"string","minLength":1,"description":"Where each entry carries its row; entries without one are left out."},"carry":{"type":"array","items":{"type":"string","minLength":1},"description":"Entry fields copied onto each row, such as a tier a card's head pill reads."},"idPath":{"type":"string","minLength":1,"description":"The row's identity for `annotate`; `id` by default."},"annotate":{"type":"boolean","description":"Copy the carried fields onto the rows of every other order too, matched on idPath."},"searchFields":{"type":"array","items":{"type":"string","minLength":1},"description":"The fields the search box matches; every top-level text by default."},"failedNote":{"type":"string","minLength":1,"description":"Shown above the list when the endpoint failed and the option's order stands in, through the app's translate."}}}}}},"defaultOrder":{"description":"type='index', self-fetch: the order the page opens on when neither the address nor sortKeys gives one: `\"-createdAt\"`, `\"priority:desc,title\"`, an `_order` map or a `[{ key, order }]` list (2.127.0).","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":{"type":"string","enum":["asc","desc","ASC","DESC"]}},{"type":"array","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","minLength":1},"order":{"type":"string","enum":["asc","desc"]}}}}]},"listConfig":{"type":"object","additionalProperties":true,"description":"type='index': the field mapping of the list view's rows (CnObjectRow): titleField, subtitleField, imageField, iconField, iconName, badgeField and the badge keys stay free-form. 2.127.0 types the row cells and the meta template (DqTeamwachtrij).","properties":{"subtitleTemplate":{"type":"string","minLength":1,"description":"The meta line: `{field}` tokens filled from the row; empty parts and their separators drop."},"leading":{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}},"description":"One cell before the title, drawn as a table column draws it (`{ key: \"createdAt\", widget: \"age\" }`)."},"trailing":{"description":"A cell, or a list of cells, after the badges, each drawn as a table column draws it.","anyOf":[{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}}},{"type":"array","items":{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}}}}]}}},"rowTitle":{"type":"string","enum":["link","plain"],"default":"link","description":"type='index' (screens-table-rows-parity): how the board look draws the table's title column. `link` (the default) is the 15px weight 600 underlined title of the DqZaken board; `plain` is the 15px bold title without an underline over a 13px muted secondary line, as the PqTickets and OcPublicaties boards draw it. The row stays clickable either way. The Nextcloud look ignores it. Maps to CnIndexPage `rowTitle`."},"showTypeEyebrow":{"type":"boolean","default":true,"description":"type='detail': `false` drops the type eyebrow (the type label above the record name) once the record resolves, for a header that says the type in a pill instead (zuiddrecht-pixel-gaps). Maps to CnDetailPage `showTypeEyebrow`."},"breadcrumb":{"description":"A breadcrumb line. type='detail' (zuiddrecht-pixel-gaps): one crumb naming the list the record belongs to (`route` + optional `params`, or `href`); the record's display name follows as the current crumb. Maps to CnDetailPage `breadcrumb`. type='index' | 'dashboard' | 'settings' (page-header-breadcrumb): one crumb or a list, drawn under the title in the same row style; the page title follows as the current crumb. Maps to the page's `breadcrumb`.","anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}}]},"headerCard":{"type":"boolean","default":false,"description":"type='detail': draw the header (pills, title, actions) as a bordered card on the surface colour, as the board's case header (zuiddrecht-pixel-gaps-2). Maps to CnDetailPage `headerCard`."},"headerWidget":{"type":"string","minLength":1,"description":"type='detail': the id of a widget in `widgets` to render inside the header on its own row under the title, without a card of its own, e.g. the stages bars (zuiddrecht-pixel-gaps-2). The widget leaves the body grid. Maps to CnDetailPage `headerWidget`."},"visibilityContext":{"type":"object","description":"type='detail': records the page reads beside the loaded one, so a widget's or a tab's `visibleWhen` can ask about them (detail-widgets-visible-when). `{ <name>: { field, register, schema } }`: `field` is the property of the loaded record holding the other record's id, and a condition reads that record as `@context.<name>.<path>`, e.g. `{ \"field\": \"@context.caseType.category\", \"op\": \"in\", \"value\": [\"Sociaal domein\"] }`. A record that cannot be read is absent, so its condition fails safe. Maps to CnDetailPage `visibilityContext`.","additionalProperties":{"type":"object","required":["field","register","schema"],"additionalProperties":false,"properties":{"field":{"type":"string","minLength":1,"description":"Dot-path on the loaded record to the id of the record to read."},"register":{"type":"string","minLength":1,"description":"Register slug or id of the record to read."},"schema":{"type":"string","minLength":1,"description":"Schema slug or id of the record to read."}}}},"accessPurpose":{"type":"object","required":["when","url"],"additionalProperties":false,"description":"type='detail': ask why the reader opens the record before the page shows it, and record the answer (detail-access-purpose). `when` is a condition in the visibleWhen grammar on the record and its visibilityContext records; `options` the purposes offered (\"Other\" with a written purpose is always added); `url` the app endpoint that records the purpose (POST { purpose, note }, {objectId} filled in). Nothing of the record shows until the endpoint accepts; the same record is not asked again in the same browser session. Maps to CnDetailPage `accessPurpose`.","properties":{"when":{"type":"object","description":"The condition under which the page asks, e.g. { \"field\": \"@context.caseType.specialDataCategories\", \"op\": \"notEmpty\" }."},"options":{"type":"array","items":{"type":"object","required":["value","label"],"additionalProperties":false,"properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1,"description":"i18n key."}}}},"url":{"type":"string","minLength":1,"description":"App endpoint that records the purpose; {objectId} is filled in."},"title":{"type":"string","description":"i18n key of the dialog's title."},"description":{"type":"string","description":"i18n key of the sentence under the title."}}},"mapConfig":{"type":"object","additionalProperties":true,"description":"type='index': how rows are plotted when the Map view mode is available. Non-empty (and not excluded by an explicit viewModes) adds a Map segment to the view toggle. Maps to CnIndexPage `mapConfig`.","properties":{"latField":{"type":"string","description":"Object (or `@self`) property path holding the marker's latitude. Dotted paths are supported, e.g. `@self.geo.lat`."},"lngField":{"type":"string","description":"Object (or `@self`) property path holding the marker's longitude."},"geoField":{"type":"string","description":"Alternative single property holding a GeoJSON Point geometry (`{ type: 'Point', coordinates: [lng, lat] }`). Takes precedence over latField/lngField when present and resolvable."},"popupField":{"type":"string","description":"Object property rendered in the marker popup."},"center":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"Fallback centre `[lat, lng]` when the filtered set has no plottable rows."},"clustering":{"type":"boolean","default":true,"description":"Whether coincident markers are grouped into a cluster that spiderfies on click. ON by default here — the opposite of CnMapWidget's own default — because an index map plots whatever the filter returns, and several rows at one address is the normal case: unclustered they land on identical pixels, the topmost takes every click and the rest are unreachable with nothing on screen saying they exist. Non-point geometry skips the cluster group, so areas are unaffected. Set false for the old behaviour."},"markerColor":{"type":"object","required":["field"],"additionalProperties":false,"description":"Colour each marker by a field of its row. Without `source` the value is looked up in `colorMap`; with `source` the field is a reference and the referenced row's `colorField` is the colour, or the key `colorMap` maps to one. A value nothing maps to gets `default`, else the map's own colour.","properties":{"field":{"type":"string","description":"The row field (dotted path, also under `@self`)."},"colorMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Value (or source colour key) to CSS colour; `var(--x, #fallback)` is resolved for the SVG marker."},"source":{"type":"object","required":["colorField"],"properties":{"register":{"type":"string"},"schema":{"type":"string"},"colorField":{"type":"string"},"filter":{"type":"object"}}},"default":{"type":"string"}}},"plotAll":{"oneOf":[{"type":"boolean"},{"type":"integer","minimum":1}],"description":"Plot every row that matches the filters instead of the current page: `true` for up to 500, or a number. When more rows match, a note under the map says the first N are shown."},"count":{"oneOf":[{"type":"boolean"},{"type":"string"}],"description":"A count line under the map: `true` for \"Showing {located} of {total} located\", or a template with `{located}` and `{total}` through the app's translate."}}},"quickFilterMaxVisible":{"type":"integer","minimum":0,"default":0,"description":"type='index', chips mode: how many quickFilters render as pills before the rest move behind one more chip — a `⋯` pill that opens a panel of the hidden lenses, and that wears the label and the active fill of whichever one is on. 0 (the default) renders every lens, which wraps the actions bar onto a second line once a page declares more than a handful. The visible set is the FIRST n entries of quickFilters, so reorder that array to change which lenses stay out. Maps to CnIndexPage `quickFilterMaxVisible`."},"actionToggles":{"type":"object","description":"Built-in action toggles for type='index' pages. Each key is sugar for the matching CnIndexPage prop — manifest authors can hide built-in mass actions / row actions / the Add button without writing a wrapper. Mirrors the CnIndexPage prop names. Explicit config.<key> wins over config.actionToggles.<key> (matches the readOnly precedence rule). Distinct from config.actions[] which is the typed row-actions array.","additionalProperties":true,"properties":{"showAdd":{"type":"boolean","description":"Whether to show the Add button in the actions bar. Maps to CnIndexPage `showAdd`."},"showFormDialog":{"type":"boolean","description":"Whether the built-in create / edit form dialog is enabled. Maps to CnIndexPage `showFormDialog`."},"showViewAction":{"type":"boolean","description":"Whether to add a View action to row actions. Maps to CnIndexPage `showViewAction`."},"showEditAction":{"type":"boolean","description":"Whether to add an Edit action to row actions. Maps to CnIndexPage `showEditAction`."},"showCopyAction":{"type":"boolean","description":"Whether to add a Copy action to row actions. Maps to CnIndexPage `showCopyAction`."},"showDeleteAction":{"type":"boolean","description":"Whether to add a Delete action to row actions. Maps to CnIndexPage `showDeleteAction`."},"showMassImport":{"type":"boolean","description":"Whether to show the built-in mass Import action. Maps to CnIndexPage `showMassImport`."},"showMassExport":{"type":"boolean","description":"Whether to show the built-in mass Export action. Maps to CnIndexPage `showMassExport`."},"showMassCopy":{"type":"boolean","description":"Whether to show the built-in mass Copy button. Maps to CnIndexPage `showMassCopy`."},"showMassDelete":{"type":"boolean","description":"Whether to show the built-in mass Delete button. Maps to CnIndexPage `showMassDelete`."},"showViewToggle":{"type":"boolean","description":"Whether to show the table/cards view-mode toggle. Maps to CnIndexPage `showViewToggle`."},"selectable":{"type":"boolean","description":"Whether rows are selectable (driving the mass-action surface). Maps to CnIndexPage `selectable`."}}},"mode":{"type":"string","enum":["edit","create","public"],"description":"Authoring / access mode. type='form': 'edit' loads from `data`, 'create' starts blank, 'public' shows the public-form success banner and skips auth-state requirements. type='detail': 'public' marks the route as unauthenticated (token-scoped reader pages — pair with a route-param token via the @route.<param> sentinel). Maps to CnFormPage / CnDetailPage `mode` prop."},"subscribe":{"type":"boolean","default":true,"description":"Collaborative-editing default for type='detail'. When true (the default) CnDetailPage calls `objectStore.subscribe(objectType, objectId)` on mount via `useObjectSubscription` and unsubscribes on unmount. Set to false for read-only or archive views. Mirrors the `subscribe` Boolean prop on CnDetailPage / CnObjectSidebar. See the `collaborative-editing-defaults` capability."},"lock":{"type":"boolean","default":true,"description":"Collaborative-editing default for type='detail'. When true (the default) the page wires `useObjectLock` so the locked-by-other banner renders when a remote lock is active. Explicit lock acquire/release on the edit toggle is the v2 follow-up; at v1 this flag controls whether read-side `lockState` is wired. Mirrors the `lock` Boolean prop on CnDetailPage / CnObjectSidebar."}}},"sidebar":{"type":"object","description":"Per-page top-level sidebar visibility gate. Sibling of config, applies to every page type. show: boolean gates CnPageRenderer's #sidebar slot.","additionalProperties":true,"properties":{"show":{"type":"boolean","description":"Whether the host App's #sidebar slot renders for this page. Defaults to true."}}},"_note":{"type":"string","description":"Human-readable note. REQUIRED when type is 'custom' — must document why a standard page type was not feasible."}},"allOf":[{"description":"map-image-layer: on a map page an `image` layer is a picture in flat pixel coordinates and needs a url and its width and height in pixels; markers on such a map read xField and yField.","if":{"properties":{"type":{"const":"map"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"layers":{"items":{"if":{"properties":{"type":{"const":"image"}},"required":["type"]},"then":{"required":["url","width","height"],"properties":{"url":{"type":"string","minLength":1},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}}}},"markers":{"properties":{"xField":{"type":"string","minLength":1,"description":"Image maps: the row property holding x, in pixels from the picture's left. Default x."},"yField":{"type":"string","minLength":1,"description":"Image maps: the row property holding y, in pixels from the picture's top. Default y."}}}}}}}},{"description":"screens-board-reference-columns (2.97.0): config.board on an index page, the block CnPageRenderer hands to CnIndexPage's `board` prop, is typed by boardView like pages[].board.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"board":{"$ref":"#/$defs/boardView"}}}}}},{"description":"index-calendar-view-mode: config.calendar on an index page is typed by calendarView.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"calendar":{"$ref":"#/$defs/calendarView"}}}}}},{"description":"index-copy-with-relations: config.copy on an index page is typed by copySettings.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"copy":{"$ref":"#/$defs/copySettings"}}}}}},{"description":"index-column-order-and-pinning: config.personalColumns on an index page lets people order and pin columns and keeps the layout per user; false keeps show and hide only.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"personalColumns":{"type":"boolean","default":true}}}}}},{"description":"form-live-values: config.fields[] on a form page may carry assign, a token default and calculate.","if":{"properties":{"type":{"const":"form"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"fields":{"type":"array","items":{"$ref":"#/$defs/formFieldLiveValues"}}}}}}},{"description":"form-smart-paste: config.smartPaste on a form page is typed by formSmartPaste.","if":{"properties":{"type":{"const":"form"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"smartPaste":{"$ref":"#/$defs/formSmartPaste"}}}}}},{"description":"splitView, manualOrder, savedViewTree, board and dateAxis are index-page mechanics. splitView emits a second route beside an index route and mounts the detail pane inside CnIndexPage; manualOrder holds a per-user row order for a list. Declared on a detail, dashboard or custom page they would validate and then do nothing, which is the failure this branch refuses out loud (case-page-and-list-as-a-place).","if":{"not":{"properties":{"type":{"const":"index"}},"required":["type"]}},"then":{"not":{"anyOf":[{"required":["splitView"]},{"required":["manualOrder"]},{"required":["savedViewTree"]},{"required":["board"]},{"required":["dateAxis"]}]}}},{"description":"The \"builtin:<id>\" placeholders in config.actions place CnIndexPage's built-in row actions, so only a type='index' page may use them. On every other page type config.actions holds objects only; a placeholder there would validate and then render nothing (row-action-builtin-placement).","if":{"not":{"properties":{"type":{"const":"index"}},"required":["type"]}},"then":{"properties":{"config":{"properties":{"actions":{"items":{"type":"object"}}}}}}},{"description":"tabInAddress puts the active tab of a record in the address. Only a detail page has a record and a tab strip, so the key is refused elsewhere rather than validating into a no-op (case-page-and-list-as-a-place).","if":{"not":{"properties":{"type":{"const":"detail"}},"required":["type"]}},"then":{"not":{"required":["tabInAddress"]}}},{"description":"type='wiki' (CnWikiPage) renders one manifest-declared markdown article. It MUST declare both config.register and config.schema as non-empty strings so the manifest stays the source of truth for which OpenRegister register/schema the article body is read from (manifest-wiki-page-type).","if":{"properties":{"type":{"const":"wiki"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","required":["register","schema"],"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1}}}},"required":["config"]}},{"description":"type='links' (CnLinkCardsPage) renders config.cards as link cards grouped by config.categories. It MUST declare a non-empty cards list, and every card is a $defs/linkCard, so a card that can go nowhere is refused here and not dropped in silence at render time (link-cards-page).","if":{"properties":{"type":{"const":"links"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","required":["cards"],"properties":{"description":{"type":"string"},"emptyLabel":{"type":"string"},"categories":{"type":"object","additionalProperties":{"type":"string","minLength":1}},"cards":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/linkCard"}},"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)."}}}},"required":["config"]}},{"description":"type='custom' requires _note documenting why decomposition into a standard page type was not feasible. Relaxed by ConductionNL/nextcloud-vue#315: when `component` matches the lib `Cn[A-Z]\\w+` pattern, the component name already documents the choice and _note is optional. When `component` is absent or non-Cn (host-app SFC), _note remains required.","if":{"properties":{"type":{"const":"custom"}},"required":["type"]},"then":{"if":{"properties":{"component":{"type":"string","pattern":"^Cn[A-Z]\\w+$"}},"required":["component"]},"then":{"description":"Lib-SFC custom page: component is self-documenting, _note is optional."},"else":{"description":"Host-app SFC custom page (or no component): _note is required to document why a standard page type was not feasible.","required":["_note"]}}}]};const schema59 = {"type":"object","additionalProperties":false,"description":"index-copy-with-relations: what a copy on this index page may take along. `include` lists the link kinds: relationRows (the source's relation rows are re-created on the copy), incoming (the copy is added next to the source in every array-valued reference that points at it; a single-valued reference is never moved) and files (the attached files). The copy dialogs list the included kinds, ticked, and the copy is one request to OpenRegister's copy endpoint. Without `include` a copy carries the fields only.","properties":{"include":{"type":"array","uniqueItems":true,"items":{"enum":["relationRows","incoming","files"]}}}};const schema70 = {"type":"object","additionalProperties":false,"description":"Saved views render as a tree rather than a flat list, carry labels, and may ship with the app. Two hundred personal views in one dropdown is the problem this solves. Index pages only, and only meaningful alongside allowSavedViews: without the dropdown there is no view to put in a tree. Absent, the page renders the flat dropdown it renders today. The view entity itself, its parent, its labels and its slug are OpenRegister's (saved-search-views); this key declares how this page presents them.","properties":{"enabled":{"type":"boolean","default":false,"description":"Whether this page renders its saved views as a tree. False reads as a page that considered it and declined, and renders exactly as a page that never named the key."},"maxDepth":{"type":"integer","minimum":1,"maximum":5,"default":3,"description":"How deep a parent chain may go. A bound rather than a taste: inheritance resolves by walking the chain, so an unbounded depth turns one render into an unbounded walk, and a person who can nest for ever will. Past the bound a save is refused naming the view, which is louder than a tree nobody can read."},"templates":{"type":"array","description":"What a new view may start from. A template presets the columns, the sort and the export field set, and the person may change each. Declare none and Save current view means what it has always meant: the list as it stands.","items":{"type":"object","additionalProperties":false,"required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"name":{"type":"string"},"columns":{"type":"array","items":{"type":"string"}},"defaultSort":{"type":"string"},"exportFields":{"type":"array","items":{"type":"string"}}}}},"landingView":{"type":"object","additionalProperties":false,"description":"Which view a role opens this page on, as { role: slug }. A personal choice always wins and Reset returns here, so this is where somebody starts rather than where they are kept. A role's landing view changing mid-session applies on the next arrival: moving a reader's list out from under them is worse than a stale default.","patternProperties":{"^[A-Za-z0-9_.-]+$":{"type":"string"}}},"columnsPerRole":{"type":"object","additionalProperties":false,"description":"Which columns a role sees, as { role: [column] }. NARROWS index-columns-per-scope rather than replacing it: a role naming a column the scope does not offer gets nothing extra, because a per-role list that could add one would be a second, quieter way to put a field on screen the scope deliberately left off.","patternProperties":{"^[A-Za-z0-9_.-]+$":{"type":"array","items":{"type":"string"}}}},"groupBy":{"type":"string","description":"A field the list groups on, with a count per group. The counts are of the rows the list HOLDS, so on a paged list they are of the page and the surface says so; a second fetch per group is the cost this grouping exists to avoid."},"seeded":{"type":"array","deprecated":true,"description":"DEPRECATED since 2.129.0: declare default views with the index page's `config.defaultSavedViews`, which carries the criteria this key never had and is what the page actually lists. Nothing ever read this key, so moving off it loses nothing. Views that ship with the app. They render as their own group above the user's own, and a user may copy one but not delete it: a view the product promises is not a view one person can remove from everybody's install.","items":{"type":"object","additionalProperties":false,"required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The stable name a dashboard widget, an export action or an API caller cites this view by. Lowercase and dash only, because it appears in an address and in a manifest somebody else writes. Required: a seeded view with no slug is a view nothing can call, which is most of the reason to seed it."},"name":{"type":"string","description":"What the view is called in the control. Translatable."},"label":{"type":"string","description":"One label this view carries, for the label filter. A view may carry none and stays reachable when no label filter is active."},"parent":{"type":"string","description":"The slug of the view this one hangs under. Omitted, it sits at the root of the seeded group."},"actions":{"type":"array","description":"The action ids this view offers, so a triage view offers three and not thirty. INTERSECTED with what the reader may run and never added to: a view declaring an action the reader has no right to does not grant it. Declare none and the view offers the page's actions, as today.","items":{"type":"string"},"uniqueItems":true},"inherits":{"type":"array","description":"Which parts this view takes from its parent. Each part resolves independently, so a child may narrow the criteria and keep the parent's columns. Declared on a view with no parent it is a contradiction and the validator refuses it: there is nothing to inherit from.","items":{"type":"string","enum":["criteria","columns","sorting","defaultSort","exportFields"]},"uniqueItems":true}},"allOf":[{"description":"inherits without a parent is a declaration that reads as configured and resolves to nothing. Refused here rather than silently ignored at render, which is the shape this whole change exists to stop.","if":{"required":["inherits"]},"then":{"required":["parent"]}}]}}}};const schema92 = {"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."}}};const schema112 = {"type":"object","additionalProperties":false,"anyOf":[{"required":["field"]},{"required":["text"]},{"required":["countFrom"]}],"description":"A pill in a detail page header, rendered through CnStatusBadge: from one field of the record (`field`; nothing renders while it is empty), a constant (`text`), or a count of a related list (`countFrom` with `countLabel`; nothing renders at 0, while counting or after a failed count). screens-relation-pages, 2.85.0.","properties":{"field":{"type":"string","minLength":1,"description":"Dot-path to the value on the record."},"values":{"type":"array","items":{"type":"string"},"description":"With `field`: draw the pill only when the raw value is one of these (a priority pill for high and urgent only)."},"text":{"type":"string","minLength":1,"description":"A constant label (an i18n key), such as \"Inwoner\" or \"Klant\". Used when `field` is not set."},"countFrom":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"The list the pill counts with one list request (`_limit=1`, the response's `total`). `filter` values take the `@objectId` and `@object.<field>` tokens. Used when `field` is not set.","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true}}},"resolve":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"detail-pill-resolve: the field holds the id of a record of this register and schema; the pill shows that record's `labelField` (else its title or name) and keys `colorMap` by it. The raw value shows while the lookup runs and when it finds nothing.","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"colorField":{"type":"string","minLength":1,"description":"pill-color-field: the referenced record's property holding a colour name (an NL Design System hue such as `red` or `blue-light`, or a badge role); the pill takes the tone it maps onto."},"iconField":{"type":"string","minLength":1,"description":"pill-color-field: the referenced record's property holding a registered icon name, shown before the label."}}},"countLabel":{"type":"object","additionalProperties":false,"description":"The count pill's label for one and for more, i18n keys with `{count}`: `{ \"one\": \"{count} lopende zaak\", \"other\": \"{count} lopende zaken\" }`.","properties":{"one":{"type":"string","minLength":1},"other":{"type":"string","minLength":1}}},"colorMap":{"type":"object","description":"Raw value to badge variant. Keys are matched ignoring case.","additionalProperties":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"]}},"labels":{"type":"object","description":"Raw value to the i18n key / text to show. A value without an entry is shown as is.","additionalProperties":{"type":"string"}},"variant":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"],"default":"default","description":"Badge variant for a value `colorMap` does not name."}}};const schema114 = {"type":"object","additionalProperties":false,"required":["field","text"],"description":"One declared state indicator on an index page's rows. Renders as an icon with a text alternative and a tooltip, never as colour alone.","properties":{"id":{"type":"string","minLength":1,"description":"Stable id for this indicator, used as its test id and its render key. Defaults to `field`."},"field":{"type":"string","minLength":1,"description":"Dotted path on the row the condition reads."},"equals":{"description":"The indicator applies when the field equals this value exactly."},"in":{"type":"array","description":"The indicator applies when the field's value is one of these."},"after":{"type":"string","minLength":1,"description":"The indicator applies while the field holds a readable date after this moment: `now` or an ISO date (\"held until a later date\")."},"before":{"type":"string","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."},"text":{"type":"string","minLength":1,"description":"The text alternative, read out beside the icon. Required: an indicator without one does not render."},"tooltip":{"type":"string","description":"Hover text. Defaults to `text`."}}};const func0 = (function equal(a, b) {
23
+ };})();const pattern4 = new RegExp("^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$", "u");const pattern5 = new RegExp("^[a-z0-9][a-z0-9-]*$", "u");const pattern6 = new RegExp("^[a-z0-9]+(?:-[a-z0-9]+)*$", "u");const pattern38 = new RegExp("^[a-z0-9]+:[a-z0-9-]+$", "u");const pattern39 = new RegExp("^[a-z0-9_-]+\\.[a-zA-Z0-9_-]+\\.(search|get|create|update|delete)$", "u");const schema37 = {"type":"object","required":["label"],"additionalProperties":false,"description":"A primary action declared on either a pages[] entry (active-page scoped) or nav.primaryAction (app-wide default). Rendered above the menu list as an NcAppNavigationNew button.","properties":{"id":{"type":"string","description":"Stable identifier for this action."},"label":{"type":"string","description":"i18n translation key / text for the button."},"icon":{"type":"string","description":"MDI icon name (e.g. 'Plus'). Defaults to 'Plus' when omitted."},"route":{"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. On a detail page it may name the record's fields as @object.<field> tokens."},"payload":{"description":"Free-form payload echoed back to the host inside the @primary-action-click event."},"action":{"$ref":"#/$defs/action","description":"A page action to run on click (zuiddrecht-pixel-gaps): CnAppNav then renders the button through CnActionButtons, so `type: \"open-form\"` (with `register` + `schema`) opens the create dialog without a create page, `navigate` pushes a route, `api-call` calls an endpoint. Drawn as one solid full-width primary button. `route` / `href` on the primary action itself are ignored while `action` is set."},"solid":{"type":"boolean","default":false,"description":"Draw the primary action as a solid, full-width primary button whatever its form (zuiddrecht-pixel-gaps). Default: a link action is already a primary button; a plain one is NcAppNavigationNew."},"permission":{"type":"string","description":"Permission the reader must hold for the action to render, checked like `menuItem.permission` (zuiddrecht-pixel-gaps)."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Display condition evaluated at render time, like `menuItem.visibleIf` (zuiddrecht-pixel-gaps). A gated-out page-scoped action falls back to `nav.primaryAction`."}}};const schema39 = {"type":"object","required":["id","label"],"description":"The fields of an action, shared by $defs/action and the non-link $defs/headerAction, which close it and constrain `children`. A typed page action with a discriminator field. The type field determines behaviour: handler (registry function), open-modal (modal by id), open-page (named route), navigate (URL), object-op (declarative OpenRegister mutation via useObjectStore), export (opens the shared CnMassExportDialog export launcher), open-form (schema-driven create dialog — CnActionButtons), refresh (bumps the cn:page:refresh signal), api-call (POST/PUT an app endpoint + toast + refresh — CnActionButtons/dispatchAction), agent (run a governed hermiq agent), run-node (invoke one OpenRegister flow node against the page object, config collected via a dialog sourced from the node's own declared form — CnActionButtons/dispatchAction, or-flow-run-node; distinct from the UNRELATED setup-wizard step type of a similar name, 'run-action'), toggle (two-way state button — CnActionButtons only, NOT dispatchable). When type is omitted, it defaults to 'handler' (back-compatibility with v1.3.0 action declarations). The Ajv instance is compiled with useDefaults: true so the default is applied during validation.","properties":{"id":{"type":"string","description":"Unique action identifier within the page's actions array."},"label":{"type":"string","description":"i18n key resolved by the consuming app's t() function at render time."},"type":{"type":"string","enum":["handler","open-modal","open-page","navigate","object-op","export","open-form","refresh","api-call","agent","run-node","toggle"],"default":"handler","description":"Action behaviour discriminator. Defaults to 'handler' when omitted (back-compat with v1.3.0). handler: calls a registry function; open-modal: opens a modal by target id; open-page: navigates to a named route; navigate: navigates to a URL; object-op: dispatches a declarative mutation (patch | delete | create) of an OpenRegister object via the shared object store; export: opens the shared CnMassExportDialog export launcher configured via entities[]/formats[] — the confirm payload ({format, entity?}) routes to the action's `handler` in the manifest actions map (the app's export service does the download). open-form (#91 Wave 3): CnActionButtons opens a schema-driven create dialog (CnAdvancedFormDialog) for the action's register/schema, saves + toasts + refreshes + optionally navigates to onSuccessRoute. refresh (#91 Wave 3): bumps the page-level cn:page:refresh event-bus signal so every endpoint-bound widget refetches. api-call (#91 Wave 3): POST/PUT the token-interpolated url with payload/params (JSON body, payload resolves @-tokens recursively at any depth) + success/error toast + auto page-refresh (unless refresh:false); confirm gating is surface-consumed intent (object-op precedent); download:true requests a binary blob and triggers a browser file download instead (auto-refresh defaults to OFF in that mode). agent (hermiq#41): run a governed hermiq agent against the page object — POSTs { register, schema, objectId, resultField?, skill?, prompt? } to /apps/hermiq/api/agents/{agent}/run-on-object (object-RBAC-scoped; dispatches the governed AgentRunRequestedEvent). register/schema/objectId default to the page's @register/@schema/@objectId object context. A first-class companion to api-call (which remains the fallback for a bespoke body); hermiq is NOT hard-required — an app-level 404 surfaces a graceful 'agent runtime unavailable' toast. toggle (#91 Wave 3): a stateful two-way state button rendered by CnActionButtons (GET stateSource on mount, optimistic write on click) — NOT dispatchable through dispatchAction. object-op declares mutation INTENT only — authority is enforced server-side by OpenRegister RBAC (ADR-022/ADR-023); authorization-shaped fields have no client-side effect and are not accepted here."},"op":{"type":"string","enum":["patch","delete","create"],"description":"object-op only: the mutation verb. patch merges `values` into the row's object (row-scoped); delete removes the row's object (row-scoped, ALWAYS confirm-gated); create adds a new object built from `values` against the widget's source register/schema (widget-scoped footer/header affordance). Required when type is 'object-op'."},"values":{"type":"object","additionalProperties":true,"description":"object-op only: the verb's payload. For patch: the partial object merged over the row's object. For create: the new object's properties. Omitted for delete."},"confirm":{"type":"boolean","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."},"props":{"type":"object","description":"Additional props forwarded to the handler or modal.","additionalProperties":true},"createOverride":{"type":"string","description":"Registry handler name that owns the persist for an `open-form` action, replacing the default objectStore.saveObject. Resolved exactly as CnIndexPage resolves its own `createOverride` prop (a kind:'create-override' entry's `.handler`, a function-valued registry entry, or a function in the legacy customComponents map). Needed when the schema requires a field the create form cannot supply on its own — a server-minted foreign key, say — where a straight save would 400."},"includeFields":{"type":"array","items":{"type":"string"},"description":"open-form: the only fields the create dialog asks for. One schema often backs two surfaces, a detail page that edits all of it and a header button that collects what someone filing a new one types, and this is how the button says which it is. Omit for the whole schema."},"excludeFields":{"type":"array","items":{"type":"string"},"description":"open-form: fields the create dialog leaves out. Use it to drop a handful; use includeFields when it is easier to name what stays."},"fieldOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"open-form: per-field overrides passed to fieldsFromSchema, keyed by field. Recognised keys include label, order, hidden, readOnly and widget."},"advanced":{"type":"boolean","default":false,"description":"open-form: open the properties-and-JSON table (CnAdvancedFormDialog) instead of the plain form. The plain form is the default because a header button is aimed at someone filing a record, not at inspecting its schema."},"formTitle":{"type":"string","description":"open-form: overrides the create dialog's heading. Defaults to 'Create {schema title}'."},"formEyebrow":{"type":"string","description":"open-form: the context line above the create dialog's heading (board look), e.g. the list the record joins. Through the host translate. Absent: no line."},"formSubtitle":{"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"],"default":"normal","description":"open-form: how wide the create dialog is, passed straight to NcDialog. Widen a form that asks enough questions to need scrolling; pair it with columns: 2."},"columns":{"type":"integer","enum":[1,2],"default":1,"description":"open-form: how many columns the fields flow into. 2 pairs them up, which halves the scrolling on a longer form. Textareas and JSON editors still span the full width, and the layout collapses to one column below 700px. Widen the dialog with size: 'large' as well, or two columns are merely two narrow ones."},"endpoint":{"type":"object","description":"open-form only: edit a record that is not a register object through an endpoint (an OpenRegister organisation). `url` is where the save goes (token-interpolated, app-relative or absolute), `method` is PATCH (default) or PUT, or POST to create a record with an empty form, `loadUrl` reads the record when it differs from `url`, `responsePath` plucks it from the answer. The fields are `formProperties`. Without `endpoint` the action is the schema-driven create form.","properties":{"url":{"type":"string"},"method":{"type":"string","enum":["PATCH","PUT","POST"]},"loadUrl":{"type":"string"},"responsePath":{"type":"string"}},"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\" } }`).","additionalProperties":{"type":"object"}},"handler":{"type":"string","description":"Registry key for type: 'handler'. Resolved across both registries passed to CnAppRoot: a v2 `registry` entry of `kind: 'handler'` exposing the function as `.handler` (or `.fn`), a directly function-valued registry entry, or a function-valued entry in the deprecated `customComponents` map. The same resolution serves actions[], bulkActions[] and headerActions[]. Also the built-in dispatch verb when paired with `route` (handler: 'navigate' → $router.push to `route`)."},"route":{"type":"string","description":"Destination route name for handler: 'navigate'. The dispatcher (manifestActionDispatch) calls $router.push({ name: route, params }). For row-level actions params include { id: row[rowKey] } automatically — do NOT restate it as params: { id: \"{id}\" }, though that placeholder does resolve (see `params`). Page-level header actions navigate without row context (e.g. a 'New X' action to a detail route)."},"icon":{"type":"string","description":"Optional icon for the action, rendered by CnActionsBar. Either an MDI icon name (e.g. 'TextBoxOutline') or a CSS icon class (e.g. 'icon-file')."},"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 + 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":"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."}}}},"formats":{"type":"array","description":"export only: the offered export formats — bare ids ('excel', 'csv', 'json' — lifted to {id, label} with an upper-cased label) or full {id, label} entries. Optional — the dialog's built-in Excel/CSV defaults apply when absent.","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["id","label"],"properties":{"id":{"type":"string","description":"Format id handed to the confirm handler."},"label":{"type":"string","description":"Pre-translated display label."}}}]}},"description":{"type":"string","description":"export only: pre-translated description shown above the export launcher's pickers."},"url":{"type":"string","description":"api-call / toggle only: the app endpoint (POST/PUT). App-relative (routed through generateUrl) or absolute (http/https). May interpolate @objectId / @object.<field> / @workspace.<key> / @config.<key> tokens inline, as well as the literal {objectId} brace placeholder (unresolved tokens collapse to empty)."},"method":{"type":"string","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","additionalProperties":true,"description":"api-call only: the JSON request body — PREFERRED over `params`, and used instead of it when both are set. Values run the SAME token grammar as `params` (@me, @now, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId, @object.<field>, @workspace.<key>/@config.<key> with a trailing `?` marking a value OPTIONAL — dropped when unresolved) but resolve RECURSIVELY at ANY nesting depth, including arrays of objects — e.g. a Filinq document-generation body: { \"dataRefs\": [{ \"register\": \"crm\", \"schema\": \"lead\", \"id\": \"@objectId\" }] }. A required (non-`?`) token left unresolved anywhere in the tree BLOCKS the call (error toast) instead of sending the literal token string to the server."},"params":{"type":"object","additionalProperties":true,"description":"Two distinct uses depending on the action's dispatch. (1) api-call / toggle: the JSON request body (legacy — prefer `payload` for anything with nested objects/arrays; ignored when `payload` is set). Values run the shared filter-token grammar ONE level deep (@me, @today±Nd, @workspace.<key>/@config.<key> with a trailing `?` for optional values dropped when unresolved; a required unresolved token skips the call). (2) handler: 'navigate' (index-page row actions, manifestActionDispatch): vue-router params merged OVER the default { id: row[rowKey] }. OMIT it for the ordinary 'open this row's detail page' action — the row id is already injected. String values run the `{field}` ROW-token grammar (NOT the @-token grammar): \"{id}\" resolves to row.id with its type preserved, \"run-{id}\" interpolates as text, and a brace-less \"new\" stays literal (that is what makes a 'New X' action navigate with { id: 'new' }). A token naming a field the row does not carry is dropped with a console.warn — so `id` falls back to the row id — instead of being pushed as a literal %7Bid%7D path segment."},"download":{"type":"boolean","default":false,"description":"api-call only: request the response as a binary blob (axios responseType 'blob') and trigger a browser file download instead of a JSON toast+refresh cycle. The filename is taken from the response's Content-Disposition header, else the token-resolved `filename`, else 'download.pdf'. The success toast still shows; unlike a normal api-call the page does NOT auto-refresh afterwards unless `refresh: true` is explicitly set."},"filename":{"type":"string","description":"api-call `download: true` only: fallback filename used when the response carries no Content-Disposition header (falls back further to 'download.pdf'). Interpolates the same token grammar as `url` (@objectId, @object.<field>, @workspace.<key>, @config.<key>, {objectId})."},"successMessage":{"type":"string","description":"api-call / open-form / agent / run-node / toggle: pre-translated success toast text (a library default applies when absent; agent default 'Run queued')."},"errorMessage":{"type":"string","description":"api-call / agent / run-node / toggle: pre-translated error toast text (falls back to the server's error/message, then a library default; an agent 404 with no structured body toasts 'agent runtime unavailable' instead)."},"refresh":{"type":"boolean","default":true,"description":"api-call / agent / run-node / toggle: bump the page-level cn:page:refresh signal after a successful call (default true; set false to skip). EXCEPTION: for an api-call with download:true the default flips to false — a file download normally shouldn't also force-refetch every widget — set refresh:true explicitly to opt back in."},"flowId":{"type":"string","description":"run-node only (REQUIRED): the published flow's uuid whose node this action invokes. A node's config and eligibility are properties of the flow it lives in (RN-3, OpenRegister's or-flow-run-node), so the manifest names which flow's instance to call rather than a bare node type — two flows could each carry the same node TYPE wired differently."},"nodeId":{"type":"string","description":"run-node only (REQUIRED): the id of the node, within flowId's published graph, to invoke directly. POSTed to /api/flows/{flowId}/nodes/{nodeId}/run — refused (404) unless that node type has opted in to direct invocation (IFlowDirectlyInvokable)."},"subject":{"type":"string","description":"run-node only: the subject object the node acts on and is authorized against (OpenRegister object-RBAC — the caller needs update permission on it; the flow's own flow.run right is deliberately NOT what gates this). Typically @objectId; may interpolate the shared @-token grammar. An unresolved required token BLOCKS the call, matching agent's objectId."},"agent":{"type":"string","description":"agent only (REQUIRED): the hermiq agent uuid to run against the page object. POSTed to /apps/hermiq/api/agents/{agent}/run-on-object (hermiq#41) — object-RBAC-scoped, dispatches the governed AgentRunRequestedEvent. May interpolate the shared @-token grammar (@objectId, @object.<field>, @workspace.<key>, @config.<key>)."},"skill":{"type":"string","description":"agent only: optional skill id folded into the governed run (interpolates the same @-token grammar as `agent`)."},"prompt":{"type":"string","description":"agent only: optional prompt handed to the governed run. Interpolates the shared @-token grammar inline (@objectId, @object.<field>, @workspace.<key>, @config.<key>) so it can be grounded on the page object — e.g. 'Summarise @object.title'."},"resultField":{"type":"string","description":"agent only: the object field the agent's result is written back to (falls back to hermiq's default result field when absent)."},"objectId":{"type":"string","description":"agent only: the target object id. Defaults to the page object context's @objectId; an unresolved required @objectId BLOCKS the call (fail-closed) rather than sending a literal token. May be an explicit id or an @-token string."},"register":{"type":"string","description":"open-form / agent / run-node: OpenRegister register slug. open-form: the create dialog's schema register. agent / run-node: the register the subject object belongs to — falls back to the page object context's @register."},"schema":{"type":"string","description":"open-form / agent / run-node: OpenRegister schema slug. open-form: the create dialog fetches this schema and saves the new object against it. agent / run-node: the schema the subject object belongs to — falls back to the page object context's @schema."},"onSuccessRoute":{"description":"open-form only: optional post-save navigation target (#91). A bare route NAME (string) navigates with the saved object's id merged into the route params under `id` (harmless when the route has no `:id` segment — backward compatible). The object form { name, paramField?, objectParam? } names the id param (`paramField`, default `id`) and, when `objectParam` is set, also passes the whole saved object under that param key (for a props:true detail route to render without a refetch). The id is read from saved.id, then saved.uuid, then saved['@self'].id.","oneOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","description":"The vue-router route NAME pushed after a successful save."},"paramField":{"type":"string","default":"id","description":"Route param name populated from the saved object's id (default `id`)."},"objectParam":{"type":"string","description":"When set, the whole saved object is passed under this route param (props:true detail routes render without a refetch)."}}}]},"labelOn":{"type":"string","description":"toggle only: button label rendered while the state is ON (true). Falls back to `label`."},"labelOff":{"type":"string","description":"toggle only: button label rendered while the state is OFF (false). Falls back to `label`."},"stateSource":{"$ref":"#/$defs/endpointSource","description":"toggle only: the endpoint the initial state is read from on mount (the shared endpointSource shape — url + token-resolved params + responsePath). The `field` dot-path picks the boolean off the payload."},"writeUrl":{"type":"string","description":"toggle only: the endpoint the flipped state is written to on click (interpolates the same tokens as `url`)."},"field":{"type":"string","description":"toggle only: the state field — read off the stateSource payload on mount AND sent (as the flipped boolean) in the write body under this key."},"variant":{"type":"string","description":"CnActionButtons button variant (e.g. 'primary', 'secondary', 'error'). 'error' also styles the confirm dialog as destructive. A 'primary' action is never collapsed into CnActionButtons' overflow menu and consumes none of its `inline` slots: NcActions paints all of its inline actions with one shared variant, so a lone primary among them is not expressible."},"children":{"type":"array","description":"Sub-actions, rendered by CnActionButtons as this action's OWN chevron dropdown beside its button — deliberately not folded into the page's single overflow menu, because NcActions cannot nest and a list that grows per record (app versions, say) would swamp it. Children take the same shape as any action, `visibleWhen` included, and an action carrying them consumes none of the `inline` slots. The parent itself still renders its own button beside the chevron (`type` defaults to 'handler' when omitted), so a parent meant only to group its children needs a `handler` that does something meaningful — an untyped, handler-less parent renders a button that dispatches to nothing. Each child is an action; a header action's child is a header action, so it may be a link with `href`."},"childrenLabel":{"type":"string","description":"i18n key naming the `children` dropdown. Falls back to the parent action's own label, so the chevron is never announced unlabelled."},"group":{"type":"string","minLength":1,"description":"Detail page header actions only. i18n key / text naming the group this action sits in inside the overflow menu. Grouped actions render under a caption with this name, in first appearance order, after the ungrouped ones. Ignored on other surfaces."},"adminOnly":{"type":"boolean","default":false,"description":"Detail page header actions only. Shows the action to instance admins alone, in the last group of the overflow menu. Hides a control and nothing more: the endpoint behind the action still decides who may call it. Ignored on other surfaces."},"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"#91 Wave 3: an optional predicate gating whether the action renders — the shared banner shape ({ endpoint | source | field, op, value }). The `field`-only LOCAL form evaluates against the page object context (a detail action shown only in the right lifecycle state); endpoint / source forms fetch. Fail-safe: a broken predicate hides the action."}},"allOf":[{"description":"object-op actions MUST declare their mutation verb via `op`.","if":{"properties":{"type":{"const":"object-op"}},"required":["type"]},"then":{"required":["op"]}},{"description":"api-call actions MUST declare their endpoint via `url`.","if":{"properties":{"type":{"const":"api-call"}},"required":["type"]},"then":{"required":["url"]}},{"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":{"anyOf":[{"required":["schema"]},{"required":["endpoint","formProperties"]}]}},{"description":"agent actions MUST declare the target agent uuid via `agent`.","if":{"properties":{"type":{"const":"agent"}},"required":["type"]},"then":{"required":["agent"]}},{"description":"toggle actions MUST declare their write endpoint via `writeUrl`.","if":{"properties":{"type":{"const":"toggle"}},"required":["type"]},"then":{"required":["writeUrl"]}},{"description":"run-node actions MUST declare which flow and which node within it (RN-3, or-flow-run-node) — a node's config and eligibility are properties of the flow it lives in, so neither can be inferred.","if":{"properties":{"type":{"const":"run-node"}},"required":["type"]},"then":{"required":["flowId","nodeId"]}}]};const schema44 = {"type":"object","required":["url"],"additionalProperties":false,"description":"Wave 2 (#91): ONE coherent endpoint data binding shared by the stat / delta / object-table widgets (the chart uses the extended $defs/chartEndpointSource). Reads an arbitrary app REST endpoint through the library's useEndpointSource engine: params pass the SAME @-token grammar widget filters use (@me, @today±Nd, @monthStart, @workspace.<key>/@config.<key> with a trailing `?` for optional values that drop when unresolved — a REQUIRED token that stays unresolved blocks the fetch), requests dedupe + short-TTL cache per (method, url, resolved params), and the widget refetches on cn:page:refresh / a matching cn:widget:refresh. Exactly ONE of the widget's OpenRegister source | endpointSource may be configured — a cross-field rule enforced by validateManifestV2() as a post-schema check (clear error message), matching the stats-block precedent.","properties":{"url":{"type":"string","description":"Endpoint URL — app-relative (routed through generateUrl, e.g. '/apps/pipelinq/api/analytics/overview') or absolute (http/https, left untouched). May interpolate @page.<key> / @workspace.<key> / @config.<key> / @objectId / @object.<field> tokens inline; unresolved URL tokens collapse to an empty string."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method. GET (default) sends the resolved params as query parameters; POST sends them as the JSON body."},"params":{"type":"object","additionalProperties":true,"description":"Request parameters. Values pass the shared filter-token grammar (resolveFilterTokens): @me, @now, @today, @today±Nd, @monthStart/@quarterStart/@yearStart, @objectId/@object.<field> (detail context), @workspace.<key>/@config.<key> 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."}}};const schema40 = {"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.<key>` 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.<key>` 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."},"optionIcon":{"type":"string","minLength":1,"description":"look 'search': one icon for every row."},"itemOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"widget 'sub-objects': field overrides for the row form (sub-objects-editor)."},"newItem":{"type":"object","description":"widget 'sub-objects': the values a new row starts with."},"itemLabel":{"type":"string","minLength":1,"description":"widget 'sub-objects': what one row is called (\"Checklist item\"), for its accessible name and dialog title."},"emptyText":{"type":"string","description":"widget 'sub-objects': shown while there are no rows."},"pruneWhen":{"type":"object","additionalProperties":false,"description":"widget 'sub-objects': tidy the rows on save: trim strings, omit empty optional keys, drop rows with a `blank` key, and with `invalid: true` rows missing a required key.","properties":{"blank":{"type":"array","items":{"type":"string","minLength":1}},"invalid":{"type":"boolean"}}},"placeholderFrom":{"type":"string","minLength":1,"description":"A text field's placeholder is this other field's value (form-small-features)."},"requiredWhen":{"type":"object","description":"Required while this condition holds: a `visibleWhen` predicate, or `{ all }` / `{ any }` of them (form-small-features)."},"warnWhen":{"description":"A note under the field that does not block the save, shown while `when` holds (form-small-features).","anyOf":[{"$ref":"#/$defs/fieldWarning"},{"type":"array","items":{"$ref":"#/$defs/fieldWarning"}}]},"clearWhenHidden":{"type":"boolean","description":"Write the field empty when its visibleWhen hides it (form-small-features)."},"stored":{"type":"boolean","description":"`false`: the field steers the form only and is not saved; it may be a key the schema does not have, with `type` and `label` (form-small-features)."},"initialWhen":{"type":"object","description":"A field that is not stored starts true when this condition holds on the loaded values (form-small-features)."},"sectionCollapsed":{"type":"boolean","description":"Makes the field's section foldable from its heading; true opens it folded (form-small-features)."},"optionSwatches":{"type":"object","additionalProperties":{"type":"string"},"description":"A select option's colour by value, drawn before its label (form-small-features)."},"optionIcons":{"type":"object","additionalProperties":{"type":"string"},"description":"A select option's icon by value, drawn before its label (form-small-features)."},"unique":{"description":"Refuse a value another stored object of this schema already has, checked before the save: `true`, or `{ filter }` to compare only among the objects the filter matches (`{ \"caseType\": \"@objectId\" }`) (list-reorder-order-field).","anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object"}}}]},"defaults":{"type":"object","description":"widget 'object-switch': the object the switch fills in when turned on; off writes null. Its keys are edited as fields at dotted paths."},"optionsFrom":{"$ref":"#/$defs/optionsFrom","description":"Draw the field as a select (a multiselect on an array field) over options read from a source; the option's value is stored and its label shown. A register schema property can say the same with `x-options-from` (form-field-options-from, 2.133.0)."}}};const schema43 = {"description":"Select options read from a source: rows from an app endpoint (`url`, `params`, `responsePath`) or OpenRegister objects (`register`, `schema`, `filter`; tokens resolve). `valueField` and `labelField` are dot-paths into a row: by default the value is `id` (else the object's uuid) and the label `title` (else `name`, else the value).","oneOf":[{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","minLength":1,"description":"An app path, resolved with generateUrl."},"params":{"type":"object","description":"Query parameters sent with the request."},"responsePath":{"type":"string","minLength":1,"description":"Where the rows sit in the answer."},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"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.<key>`, 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)."}}},{"type":"object","additionalProperties":false,"required":["register","schema"],"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","description":"Filters on the objects, as a list filter takes them."},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"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.<key>`, 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)."}}}]};const wrapper0 = {validate: validate24};function validate24(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate24.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.widget !== undefined){let data0 = data.widget;if(typeof data0 === "string"){if(func2(data0) < 1){const err0 = {instancePath:instancePath+"/widget",schemaPath:"#/properties/widget/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}else {const err1 = {instancePath:instancePath+"/widget",schemaPath:"#/properties/widget/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}if(data.createModal !== undefined){let data1 = data.createModal;if(typeof data1 === "string"){if(func2(data1) < 1){const err2 = {instancePath:instancePath+"/createModal",schemaPath:"#/properties/createModal/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}else {const err3 = {instancePath:instancePath+"/createModal",schemaPath:"#/properties/createModal/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.createModalProps !== undefined){let data2 = data.createModalProps;if(data2 && typeof data2 == "object" && !Array.isArray(data2));else {const err4 = {instancePath:instancePath+"/createModalProps",schemaPath:"#/properties/createModalProps/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.pickerFilter !== undefined){let data3 = data.pickerFilter;if(data3 && typeof data3 == "object" && !Array.isArray(data3));else {const err5 = {instancePath:instancePath+"/pickerFilter",schemaPath:"#/properties/pickerFilter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.hintWidget !== undefined){let data4 = data.hintWidget;if(typeof data4 === "string"){if(func2(data4) < 1){const err6 = {instancePath:instancePath+"/hintWidget",schemaPath:"#/properties/hintWidget/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}else {const err7 = {instancePath:instancePath+"/hintWidget",schemaPath:"#/properties/hintWidget/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.look !== undefined){let data5 = data.look;if(typeof data5 !== "string"){const err8 = {instancePath:instancePath+"/look",schemaPath:"#/properties/look/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}if(!(data5 === "search")){const err9 = {instancePath:instancePath+"/look",schemaPath:"#/properties/look/enum",keyword:"enum",params:{allowedValues: schema40.properties.look.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.optionSecondary !== undefined){if(typeof data.optionSecondary !== "string"){const err10 = {instancePath:instancePath+"/optionSecondary",schemaPath:"#/properties/optionSecondary/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.optionAvatar !== undefined){if(typeof data.optionAvatar !== "boolean"){const err11 = {instancePath:instancePath+"/optionAvatar",schemaPath:"#/properties/optionAvatar/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.optionIcon !== undefined){let data8 = data.optionIcon;if(typeof data8 === "string"){if(func2(data8) < 1){const err12 = {instancePath:instancePath+"/optionIcon",schemaPath:"#/properties/optionIcon/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}else {const err13 = {instancePath:instancePath+"/optionIcon",schemaPath:"#/properties/optionIcon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.itemOverrides !== undefined){let data9 = data.itemOverrides;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){for(const key0 in data9){if(!(wrapper0.validate(data9[key0], {instancePath:instancePath+"/itemOverrides/" + key0.replace(/~/g, "~0").replace(/\//g, "~1"),parentData:data9,parentDataProperty:key0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? wrapper0.validate.errors : vErrors.concat(wrapper0.validate.errors);errors = vErrors.length;}}}else {const err14 = {instancePath:instancePath+"/itemOverrides",schemaPath:"#/properties/itemOverrides/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.newItem !== undefined){let data11 = data.newItem;if(!(data11 && typeof data11 == "object" && !Array.isArray(data11))){const err15 = {instancePath:instancePath+"/newItem",schemaPath:"#/properties/newItem/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.itemLabel !== undefined){let data12 = data.itemLabel;if(typeof data12 === "string"){if(func2(data12) < 1){const err16 = {instancePath:instancePath+"/itemLabel",schemaPath:"#/properties/itemLabel/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}else {const err17 = {instancePath:instancePath+"/itemLabel",schemaPath:"#/properties/itemLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data.emptyText !== undefined){if(typeof data.emptyText !== "string"){const err18 = {instancePath:instancePath+"/emptyText",schemaPath:"#/properties/emptyText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data.pruneWhen !== undefined){let data14 = data.pruneWhen;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){for(const key1 in data14){if(!((key1 === "blank") || (key1 === "invalid"))){const err19 = {instancePath:instancePath+"/pruneWhen",schemaPath:"#/properties/pruneWhen/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data14.blank !== undefined){let data15 = data14.blank;if(Array.isArray(data15)){const len0 = data15.length;for(let i0=0; i0<len0; i0++){let data16 = data15[i0];if(typeof data16 === "string"){if(func2(data16) < 1){const err20 = {instancePath:instancePath+"/pruneWhen/blank/" + i0,schemaPath:"#/properties/pruneWhen/properties/blank/items/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}else {const err21 = {instancePath:instancePath+"/pruneWhen/blank/" + i0,schemaPath:"#/properties/pruneWhen/properties/blank/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}}else {const err22 = {instancePath:instancePath+"/pruneWhen/blank",schemaPath:"#/properties/pruneWhen/properties/blank/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data14.invalid !== undefined){if(typeof data14.invalid !== "boolean"){const err23 = {instancePath:instancePath+"/pruneWhen/invalid",schemaPath:"#/properties/pruneWhen/properties/invalid/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}else {const err24 = {instancePath:instancePath+"/pruneWhen",schemaPath:"#/properties/pruneWhen/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.placeholderFrom !== undefined){let data18 = data.placeholderFrom;if(typeof data18 === "string"){if(func2(data18) < 1){const err25 = {instancePath:instancePath+"/placeholderFrom",schemaPath:"#/properties/placeholderFrom/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/placeholderFrom",schemaPath:"#/properties/placeholderFrom/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data.requiredWhen !== undefined){let data19 = data.requiredWhen;if(!(data19 && typeof data19 == "object" && !Array.isArray(data19))){const err27 = {instancePath:instancePath+"/requiredWhen",schemaPath:"#/properties/requiredWhen/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data.warnWhen !== undefined){let data20 = data.warnWhen;const _errs46 = errors;let valid5 = false;const _errs47 = errors;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(data20.when === undefined){const err28 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/$defs/fieldWarning/required",keyword:"required",params:{missingProperty: "when"},message:"must have required property '"+"when"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data20.text === undefined){const err29 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/$defs/fieldWarning/required",keyword:"required",params:{missingProperty: "text"},message:"must have required property '"+"text"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key2 in data20){if(!((key2 === "when") || (key2 === "text"))){const err30 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/$defs/fieldWarning/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data20.when !== undefined){let data21 = data20.when;if(!(data21 && typeof data21 == "object" && !Array.isArray(data21))){const err31 = {instancePath:instancePath+"/warnWhen/when",schemaPath:"#/$defs/fieldWarning/properties/when/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}if(data20.text !== undefined){let data22 = data20.text;if(typeof data22 === "string"){if(func2(data22) < 1){const err32 = {instancePath:instancePath+"/warnWhen/text",schemaPath:"#/$defs/fieldWarning/properties/text/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/warnWhen/text",schemaPath:"#/$defs/fieldWarning/properties/text/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}else {const err34 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/$defs/fieldWarning/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}var _valid0 = _errs47 === errors;valid5 = valid5 || _valid0;const _errs55 = errors;if(Array.isArray(data20)){const len1 = data20.length;for(let i1=0; i1<len1; i1++){let data23 = data20[i1];if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(data23.when === undefined){const err35 = {instancePath:instancePath+"/warnWhen/" + i1,schemaPath:"#/$defs/fieldWarning/required",keyword:"required",params:{missingProperty: "when"},message:"must have required property '"+"when"+"'"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(data23.text === undefined){const err36 = {instancePath:instancePath+"/warnWhen/" + i1,schemaPath:"#/$defs/fieldWarning/required",keyword:"required",params:{missingProperty: "text"},message:"must have required property '"+"text"+"'"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key3 in data23){if(!((key3 === "when") || (key3 === "text"))){const err37 = {instancePath:instancePath+"/warnWhen/" + i1,schemaPath:"#/$defs/fieldWarning/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data23.when !== undefined){let data24 = data23.when;if(!(data24 && typeof data24 == "object" && !Array.isArray(data24))){const err38 = {instancePath:instancePath+"/warnWhen/" + i1+"/when",schemaPath:"#/$defs/fieldWarning/properties/when/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data23.text !== undefined){let data25 = data23.text;if(typeof data25 === "string"){if(func2(data25) < 1){const err39 = {instancePath:instancePath+"/warnWhen/" + i1+"/text",schemaPath:"#/$defs/fieldWarning/properties/text/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/warnWhen/" + i1+"/text",schemaPath:"#/$defs/fieldWarning/properties/text/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}}else {const err41 = {instancePath:instancePath+"/warnWhen/" + i1,schemaPath:"#/$defs/fieldWarning/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/properties/warnWhen/anyOf/1/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}var _valid0 = _errs55 === errors;valid5 = valid5 || _valid0;if(!valid5){const err43 = {instancePath:instancePath+"/warnWhen",schemaPath:"#/properties/warnWhen/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}else {errors = _errs46;if(vErrors !== null){if(_errs46){vErrors.length = _errs46;}else {vErrors = null;}}}}if(data.clearWhenHidden !== undefined){if(typeof data.clearWhenHidden !== "boolean"){const err44 = {instancePath:instancePath+"/clearWhenHidden",schemaPath:"#/properties/clearWhenHidden/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data.stored !== undefined){if(typeof data.stored !== "boolean"){const err45 = {instancePath:instancePath+"/stored",schemaPath:"#/properties/stored/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}if(data.initialWhen !== undefined){let data28 = data.initialWhen;if(!(data28 && typeof data28 == "object" && !Array.isArray(data28))){const err46 = {instancePath:instancePath+"/initialWhen",schemaPath:"#/properties/initialWhen/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data.sectionCollapsed !== undefined){if(typeof data.sectionCollapsed !== "boolean"){const err47 = {instancePath:instancePath+"/sectionCollapsed",schemaPath:"#/properties/sectionCollapsed/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data.optionSwatches !== undefined){let data30 = data.optionSwatches;if(data30 && typeof data30 == "object" && !Array.isArray(data30)){for(const key4 in data30){if(typeof data30[key4] !== "string"){const err48 = {instancePath:instancePath+"/optionSwatches/" + key4.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/optionSwatches/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/optionSwatches",schemaPath:"#/properties/optionSwatches/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data.optionIcons !== undefined){let data32 = data.optionIcons;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){for(const key5 in data32){if(typeof data32[key5] !== "string"){const err50 = {instancePath:instancePath+"/optionIcons/" + key5.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/optionIcons/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}else {const err51 = {instancePath:instancePath+"/optionIcons",schemaPath:"#/properties/optionIcons/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data.unique !== undefined){let data34 = data.unique;const _errs84 = errors;let valid14 = false;const _errs85 = errors;if(typeof data34 !== "boolean"){const err52 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(true !== data34){const err53 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf/0/const",keyword:"const",params:{allowedValue: true},message:"must be equal to constant"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}var _valid1 = _errs85 === errors;valid14 = valid14 || _valid1;const _errs87 = errors;if(data34 && typeof data34 == "object" && !Array.isArray(data34)){for(const key6 in data34){if(!(key6 === "filter")){const err54 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data34.filter !== undefined){let data35 = data34.filter;if(!(data35 && typeof data35 == "object" && !Array.isArray(data35))){const err55 = {instancePath:instancePath+"/unique/filter",schemaPath:"#/properties/unique/anyOf/1/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}var _valid1 = _errs87 === errors;valid14 = valid14 || _valid1;if(!valid14){const err57 = {instancePath:instancePath+"/unique",schemaPath:"#/properties/unique/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}else {errors = _errs84;if(vErrors !== null){if(_errs84){vErrors.length = _errs84;}else {vErrors = null;}}}}if(data.defaults !== undefined){let data36 = data.defaults;if(!(data36 && typeof data36 == "object" && !Array.isArray(data36))){const err58 = {instancePath:instancePath+"/defaults",schemaPath:"#/properties/defaults/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data.optionsFrom !== undefined){let data37 = data.optionsFrom;const _errs96 = errors;let valid17 = false;let passing0 = null;const _errs97 = errors;if(data37 && typeof data37 == "object" && !Array.isArray(data37)){if(data37.url === undefined){const err59 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/0/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}for(const key7 in data37){if(!(func1.call(schema43.oneOf[0].properties, key7))){const err60 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/0/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}if(data37.url !== undefined){let data38 = data37.url;if(typeof data38 === "string"){if(func2(data38) < 1){const err61 = {instancePath:instancePath+"/optionsFrom/url",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/url/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}else {const err62 = {instancePath:instancePath+"/optionsFrom/url",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(data37.params !== undefined){let data39 = data37.params;if(!(data39 && typeof data39 == "object" && !Array.isArray(data39))){const err63 = {instancePath:instancePath+"/optionsFrom/params",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data37.responsePath !== undefined){let data40 = data37.responsePath;if(typeof data40 === "string"){if(func2(data40) < 1){const err64 = {instancePath:instancePath+"/optionsFrom/responsePath",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/responsePath/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}else {const err65 = {instancePath:instancePath+"/optionsFrom/responsePath",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/responsePath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data37.valueField !== undefined){let data41 = data37.valueField;if(typeof data41 === "string"){if(func2(data41) < 1){const err66 = {instancePath:instancePath+"/optionsFrom/valueField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/valueField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}else {const err67 = {instancePath:instancePath+"/optionsFrom/valueField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/valueField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}if(data37.labelField !== undefined){let data42 = data37.labelField;if(typeof data42 === "string"){if(func2(data42) < 1){const err68 = {instancePath:instancePath+"/optionsFrom/labelField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/labelField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}else {const err69 = {instancePath:instancePath+"/optionsFrom/labelField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/labelField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}if(data37.groupField !== undefined){let data43 = data37.groupField;if(typeof data43 === "string"){if(func2(data43) < 1){const err70 = {instancePath:instancePath+"/optionsFrom/groupField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/groupField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}else {const err71 = {instancePath:instancePath+"/optionsFrom/groupField",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/groupField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}if(data37.where !== undefined){let data44 = data37.where;if(!(data44 && typeof data44 == "object" && !Array.isArray(data44))){const err72 = {instancePath:instancePath+"/optionsFrom/where",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/where/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data37.itemsPath !== undefined){let data45 = data37.itemsPath;if(typeof data45 === "string"){if(func2(data45) < 1){const err73 = {instancePath:instancePath+"/optionsFrom/itemsPath",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/itemsPath/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}else {const err74 = {instancePath:instancePath+"/optionsFrom/itemsPath",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/itemsPath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data37.hideWhenEmpty !== undefined){if(typeof data37.hideWhenEmpty !== "boolean"){const err75 = {instancePath:instancePath+"/optionsFrom/hideWhenEmpty",schemaPath:"#/$defs/optionsFrom/oneOf/0/properties/hideWhenEmpty/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}}else {const err76 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/0/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}var _valid2 = _errs97 === errors;if(_valid2){valid17 = true;passing0 = 0;}const _errs118 = errors;if(data37 && typeof data37 == "object" && !Array.isArray(data37)){if(data37.register === undefined){const err77 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/1/required",keyword:"required",params:{missingProperty: "register"},message:"must have required property '"+"register"+"'"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(data37.schema === undefined){const err78 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/1/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}for(const key8 in data37){if(!(func1.call(schema43.oneOf[1].properties, key8))){const err79 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}if(data37.register !== undefined){let data47 = data37.register;if(typeof data47 === "string"){if(func2(data47) < 1){const err80 = {instancePath:instancePath+"/optionsFrom/register",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/register/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}else {const err81 = {instancePath:instancePath+"/optionsFrom/register",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data37.schema !== undefined){let data48 = data37.schema;if(typeof data48 === "string"){if(func2(data48) < 1){const err82 = {instancePath:instancePath+"/optionsFrom/schema",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/schema/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}else {const err83 = {instancePath:instancePath+"/optionsFrom/schema",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}if(data37.filter !== undefined){let data49 = data37.filter;if(!(data49 && typeof data49 == "object" && !Array.isArray(data49))){const err84 = {instancePath:instancePath+"/optionsFrom/filter",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data37.valueField !== undefined){let data50 = data37.valueField;if(typeof data50 === "string"){if(func2(data50) < 1){const err85 = {instancePath:instancePath+"/optionsFrom/valueField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/valueField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}else {const err86 = {instancePath:instancePath+"/optionsFrom/valueField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/valueField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}if(data37.labelField !== undefined){let data51 = data37.labelField;if(typeof data51 === "string"){if(func2(data51) < 1){const err87 = {instancePath:instancePath+"/optionsFrom/labelField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/labelField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}else {const err88 = {instancePath:instancePath+"/optionsFrom/labelField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/labelField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data37.groupField !== undefined){let data52 = data37.groupField;if(typeof data52 === "string"){if(func2(data52) < 1){const err89 = {instancePath:instancePath+"/optionsFrom/groupField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/groupField/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}else {const err90 = {instancePath:instancePath+"/optionsFrom/groupField",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/groupField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}if(data37.where !== undefined){let data53 = data37.where;if(!(data53 && typeof data53 == "object" && !Array.isArray(data53))){const err91 = {instancePath:instancePath+"/optionsFrom/where",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/where/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}if(data37.itemsPath !== undefined){let data54 = data37.itemsPath;if(typeof data54 === "string"){if(func2(data54) < 1){const err92 = {instancePath:instancePath+"/optionsFrom/itemsPath",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/itemsPath/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}else {const err93 = {instancePath:instancePath+"/optionsFrom/itemsPath",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/itemsPath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}if(data37.hideWhenEmpty !== undefined){if(typeof data37.hideWhenEmpty !== "boolean"){const err94 = {instancePath:instancePath+"/optionsFrom/hideWhenEmpty",schemaPath:"#/$defs/optionsFrom/oneOf/1/properties/hideWhenEmpty/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}}else {const err95 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}var _valid2 = _errs118 === errors;if(_valid2 && valid17){valid17 = false;passing0 = [passing0, 1];}else {if(_valid2){valid17 = true;passing0 = 1;}}if(!valid17){const err96 = {instancePath:instancePath+"/optionsFrom",schemaPath:"#/$defs/optionsFrom/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}else {errors = _errs96;if(vErrors !== null){if(_errs96){vErrors.length = _errs96;}else {vErrors = null;}}}}}else {const err97 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}validate24.errors = vErrors;return errors === 0;}validate24.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema45 = {"type":"object","additionalProperties":false,"description":"#91 Wave 3: the shared visibility predicate (the Wave-1 banner shape). THREE modes: `endpoint` (GET a JSON endpoint, `field` dot-paths into the body), `source` (an OpenRegister { register, schema, filter? } query — `field` into the first result, or omit / use @total for the collection total), or LOCAL (neither endpoint nor source — `field` dot-paths into the caller's object context, e.g. a detail-page record). `op` defaults to eq. Evaluation is FAIL-SAFE (any error → hidden).","properties":{"appInstalled":{"type":"string","minLength":1,"description":"Nextcloud app id that must be installed and enabled for the guarded element to show. A PRECONDITION rather than a fourth mode: it is checked before anything else, and on its own it is the whole condition. Combined with endpoint/source/field it gates that mode too, so both must hold. It exists because the menu's `visibleIf` already spoke this word and `visibleWhen` did not, so the same manifest vocabulary meant two different things in two places. Use it on an action that writes into a sibling app, so an install without that app shows no button rather than a form that saves nowhere."},"endpoint":{"type":"string","description":"A same-origin URL returning JSON; `field` is a dot-path into the body."},"source":{"type":"object","additionalProperties":true,"description":"An OpenRegister object query { register, schema, filter? } (filter supports the shared @-token grammar). `field` dot-paths into the first result; omit it (or use @total) to compare the collection total.","properties":{"register":{"type":"string"},"schema":{"type":"string"},"filter":{"type":"object","additionalProperties":true}}},"workspace":{"type":"string","minLength":1,"description":"WORKSPACE mode (screens-page-lenses, 2.87.0): the key of the page workspace context to compare, read at once with no request. A page control (a `workspace-filter` with `writes`) sets the key, and a dashboard widget carrying this condition appears and disappears as it changes. Honoured on a dashboard widget or KPI-row tile only, and as a top-level condition (not inside `all` / `any`)."},"field":{"type":"string","description":"Dot-path to the compared value — into the endpoint body, the source's first result, or (LOCAL mode) the caller's object context."},"op":{"type":"string","enum":["eq","neq","gt","gte","lt","lte","empty","notEmpty","in","anyOf","contains"],"default":"eq","description":"Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `in` holds when the value is one of the listed `value` array (2.87.0). `anyOf` holds when the value, or any entry of a list value, is one of the listed `value` array: a record whose `dataCategories` holds \"health\" among others matches `anyOf [\"health\", \"ethnicity\"]` (2.126.0). `contains` holds when a list value holds the given `value`, e.g. a case type's overviewCards switching a card on (2.128.0). `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."},"value":{"description":"The right-hand side of the comparison, resolved through the shared @-token grammar so `@me` names the signed-in reader rather than a literal only the server knows. Omitted for `empty` / `notEmpty`."},"all":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/visibleWhen"},"description":"Every listed condition must hold. Use it where a gate is more than one fact — a Claim button that is both 'not closed' and 'nobody holds it'. Each entry is a full visibleWhen and may compose again."},"any":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/visibleWhen"},"description":"At least one listed condition must hold. Ignored when `all` is also present."}}};const wrapper1 = {validate: validate26};function validate26(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate26.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.op === undefined){data.op = "eq";}for(const key0 in data){if(!(func1.call(schema45.properties, key0))){const err0 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}if(data.appInstalled !== undefined){let data0 = data.appInstalled;if(typeof data0 === "string"){if(func2(data0) < 1){const err1 = {instancePath:instancePath+"/appInstalled",schemaPath:"#/properties/appInstalled/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}else {const err2 = {instancePath:instancePath+"/appInstalled",schemaPath:"#/properties/appInstalled/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.endpoint !== undefined){if(typeof data.endpoint !== "string"){const err3 = {instancePath:instancePath+"/endpoint",schemaPath:"#/properties/endpoint/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.source !== undefined){let data2 = data.source;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.register !== undefined){if(typeof data2.register !== "string"){const err4 = {instancePath:instancePath+"/source/register",schemaPath:"#/properties/source/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data2.schema !== undefined){if(typeof data2.schema !== "string"){const err5 = {instancePath:instancePath+"/source/schema",schemaPath:"#/properties/source/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data2.filter !== undefined){let data5 = data2.filter;if(data5 && typeof data5 == "object" && !Array.isArray(data5));else {const err6 = {instancePath:instancePath+"/source/filter",schemaPath:"#/properties/source/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}else {const err7 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.workspace !== undefined){let data6 = data.workspace;if(typeof data6 === "string"){if(func2(data6) < 1){const err8 = {instancePath:instancePath+"/workspace",schemaPath:"#/properties/workspace/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/workspace",schemaPath:"#/properties/workspace/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.field !== undefined){if(typeof data.field !== "string"){const err10 = {instancePath:instancePath+"/field",schemaPath:"#/properties/field/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}let data8 = data.op;if(typeof data8 !== "string"){const err11 = {instancePath:instancePath+"/op",schemaPath:"#/properties/op/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(!(((((((((((data8 === "eq") || (data8 === "neq")) || (data8 === "gt")) || (data8 === "gte")) || (data8 === "lt")) || (data8 === "lte")) || (data8 === "empty")) || (data8 === "notEmpty")) || (data8 === "in")) || (data8 === "anyOf")) || (data8 === "contains"))){const err12 = {instancePath:instancePath+"/op",schemaPath:"#/properties/op/enum",keyword:"enum",params:{allowedValues: schema45.properties.op.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(data.all !== undefined){let data9 = data.all;if(Array.isArray(data9)){if(data9.length < 1){const err13 = {instancePath:instancePath+"/all",schemaPath:"#/properties/all/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}const len0 = data9.length;for(let i0=0; i0<len0; i0++){if(!(wrapper1.validate(data9[i0], {instancePath:instancePath+"/all/" + i0,parentData:data9,parentDataProperty:i0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? wrapper1.validate.errors : vErrors.concat(wrapper1.validate.errors);errors = vErrors.length;}}}else {const err14 = {instancePath:instancePath+"/all",schemaPath:"#/properties/all/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data.any !== undefined){let data11 = data.any;if(Array.isArray(data11)){if(data11.length < 1){const err15 = {instancePath:instancePath+"/any",schemaPath:"#/properties/any/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}const len1 = data11.length;for(let i1=0; i1<len1; i1++){if(!(wrapper1.validate(data11[i1], {instancePath:instancePath+"/any/" + i1,parentData:data11,parentDataProperty:i1,rootData,dynamicAnchors}))){vErrors = vErrors === null ? wrapper1.validate.errors : vErrors.concat(wrapper1.validate.errors);errors = vErrors.length;}}}else {const err16 = {instancePath:instancePath+"/any",schemaPath:"#/properties/any/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate26.errors = vErrors;return errors === 0;}validate26.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate23(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate23.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}const _errs2 = errors;let valid1 = true;const _errs3 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err0 = {};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}else {if(data.type !== undefined){if("object-op" !== data.type){const err1 = {};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}}}var _valid0 = _errs3 === errors;errors = _errs2;if(vErrors !== null){if(_errs2){vErrors.length = _errs2;}else {vErrors = null;}}if(_valid0){const _errs5 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.op === undefined){const err2 = {instancePath,schemaPath:"#/allOf/0/then/required",keyword:"required",params:{missingProperty: "op"},message:"must have required property '"+"op"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}var _valid0 = _errs5 === errors;valid1 = _valid0;}if(!valid1){const err3 = {instancePath,schemaPath:"#/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}const _errs7 = errors;let valid3 = true;const _errs8 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err4 = {};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}else {if(data.type !== undefined){if("api-call" !== data.type){const err5 = {};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}}}var _valid1 = _errs8 === errors;errors = _errs7;if(vErrors !== null){if(_errs7){vErrors.length = _errs7;}else {vErrors = null;}}if(_valid1){const _errs10 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.url === undefined){const err6 = {instancePath,schemaPath:"#/allOf/1/then/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}var _valid1 = _errs10 === errors;valid3 = _valid1;}if(!valid3){const err7 = {instancePath,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}const _errs12 = errors;let valid5 = true;const _errs13 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err8 = {};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}else {if(data.type !== undefined){if("open-form" !== data.type){const err9 = {};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}}}var _valid2 = _errs13 === errors;errors = _errs12;if(vErrors !== null){if(_errs12){vErrors.length = _errs12;}else {vErrors = null;}}if(_valid2){const _errs15 = errors;const _errs16 = errors;let valid7 = false;const _errs17 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema === undefined){const err10 = {instancePath,schemaPath:"#/allOf/2/then/anyOf/0/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}var _valid3 = _errs17 === errors;valid7 = valid7 || _valid3;const _errs18 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.endpoint === undefined){const err11 = {instancePath,schemaPath:"#/allOf/2/then/anyOf/1/required",keyword:"required",params:{missingProperty: "endpoint"},message:"must have required property '"+"endpoint"+"'"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(data.formProperties === undefined){const err12 = {instancePath,schemaPath:"#/allOf/2/then/anyOf/1/required",keyword:"required",params:{missingProperty: "formProperties"},message:"must have required property '"+"formProperties"+"'"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}var _valid3 = _errs18 === errors;valid7 = valid7 || _valid3;if(!valid7){const err13 = {instancePath,schemaPath:"#/allOf/2/then/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}else {errors = _errs16;if(vErrors !== null){if(_errs16){vErrors.length = _errs16;}else {vErrors = null;}}}var _valid2 = _errs15 === errors;valid5 = _valid2;}if(!valid5){const err14 = {instancePath,schemaPath:"#/allOf/2/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}const _errs20 = errors;let valid8 = true;const _errs21 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err15 = {};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}else {if(data.type !== undefined){if("agent" !== data.type){const err16 = {};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}}var _valid4 = _errs21 === errors;errors = _errs20;if(vErrors !== null){if(_errs20){vErrors.length = _errs20;}else {vErrors = null;}}if(_valid4){const _errs23 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.agent === undefined){const err17 = {instancePath,schemaPath:"#/allOf/3/then/required",keyword:"required",params:{missingProperty: "agent"},message:"must have required property '"+"agent"+"'"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}var _valid4 = _errs23 === errors;valid8 = _valid4;}if(!valid8){const err18 = {instancePath,schemaPath:"#/allOf/3/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}const _errs25 = errors;let valid10 = true;const _errs26 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err19 = {};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}else {if(data.type !== undefined){if("toggle" !== data.type){const err20 = {};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}}var _valid5 = _errs26 === errors;errors = _errs25;if(vErrors !== null){if(_errs25){vErrors.length = _errs25;}else {vErrors = null;}}if(_valid5){const _errs28 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.writeUrl === undefined){const err21 = {instancePath,schemaPath:"#/allOf/4/then/required",keyword:"required",params:{missingProperty: "writeUrl"},message:"must have required property '"+"writeUrl"+"'"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}var _valid5 = _errs28 === errors;valid10 = _valid5;}if(!valid10){const err22 = {instancePath,schemaPath:"#/allOf/4/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}const _errs30 = errors;let valid12 = true;const _errs31 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if((data.type === undefined) && ("type")){const err23 = {};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}else {if(data.type !== undefined){if("run-node" !== data.type){const err24 = {};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}}}var _valid6 = _errs31 === errors;errors = _errs30;if(vErrors !== null){if(_errs30){vErrors.length = _errs30;}else {vErrors = null;}}if(_valid6){const _errs33 = errors;if(data && typeof data == "object" && !Array.isArray(data)){if(data.flowId === undefined){const err25 = {instancePath,schemaPath:"#/allOf/5/then/required",keyword:"required",params:{missingProperty: "flowId"},message:"must have required property '"+"flowId"+"'"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(data.nodeId === undefined){const err26 = {instancePath,schemaPath:"#/allOf/5/then/required",keyword:"required",params:{missingProperty: "nodeId"},message:"must have required property '"+"nodeId"+"'"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}var _valid6 = _errs33 === errors;valid12 = _valid6;}if(!valid12){const err27 = {instancePath,schemaPath:"#/allOf/5/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.type === undefined){data.type = "handler";}if(data.confirm === undefined){data.confirm = false;}if(data.advanced === undefined){data.advanced = false;}if(data.size === undefined){data.size = "normal";}if(data.columns === undefined){data.columns = 1;}if(data.download === undefined){data.download = false;}if(data.refresh === undefined){data.refresh = true;}if(data.adminOnly === undefined){data.adminOnly = false;}if(data.id === undefined){const err28 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data.label === undefined){const err29 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(data.id !== undefined){if(typeof data.id !== "string"){const err30 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data.label !== undefined){if(typeof data.label !== "string"){const err31 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}let data8 = data.type;if(typeof data8 !== "string"){const err32 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if(!((((((((((((data8 === "handler") || (data8 === "open-modal")) || (data8 === "open-page")) || (data8 === "navigate")) || (data8 === "object-op")) || (data8 === "export")) || (data8 === "open-form")) || (data8 === "refresh")) || (data8 === "api-call")) || (data8 === "agent")) || (data8 === "run-node")) || (data8 === "toggle"))){const err33 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/enum",keyword:"enum",params:{allowedValues: schema39.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(data.op !== undefined){let data9 = data.op;if(typeof data9 !== "string"){const err34 = {instancePath:instancePath+"/op",schemaPath:"#/properties/op/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(!(((data9 === "patch") || (data9 === "delete")) || (data9 === "create"))){const err35 = {instancePath:instancePath+"/op",schemaPath:"#/properties/op/enum",keyword:"enum",params:{allowedValues: schema39.properties.op.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data.values !== undefined){let data10 = data.values;if(data10 && typeof data10 == "object" && !Array.isArray(data10));else {const err36 = {instancePath:instancePath+"/values",schemaPath:"#/properties/values/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(typeof data.confirm !== "boolean"){const err37 = {instancePath:instancePath+"/confirm",schemaPath:"#/properties/confirm/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}if(data.confirmTitle !== undefined){if(typeof data.confirmTitle !== "string"){const err38 = {instancePath:instancePath+"/confirmTitle",schemaPath:"#/properties/confirmTitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data.confirmMessage !== undefined){if(typeof data.confirmMessage !== "string"){const err39 = {instancePath:instancePath+"/confirmMessage",schemaPath:"#/properties/confirmMessage/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data.confirmLabel !== undefined){if(typeof data.confirmLabel !== "string"){const err40 = {instancePath:instancePath+"/confirmLabel",schemaPath:"#/properties/confirmLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data.confirmIcon !== undefined){if(typeof data.confirmIcon !== "string"){const err41 = {instancePath:instancePath+"/confirmIcon",schemaPath:"#/properties/confirmIcon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}if(data.target !== undefined){if(typeof data.target !== "string"){const err42 = {instancePath:instancePath+"/target",schemaPath:"#/properties/target/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data.props !== undefined){let data17 = data.props;if(data17 && typeof data17 == "object" && !Array.isArray(data17));else {const err43 = {instancePath:instancePath+"/props",schemaPath:"#/properties/props/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data.createOverride !== undefined){if(typeof data.createOverride !== "string"){const err44 = {instancePath:instancePath+"/createOverride",schemaPath:"#/properties/createOverride/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data.includeFields !== undefined){let data19 = data.includeFields;if(Array.isArray(data19)){const len0 = data19.length;for(let i0=0; i0<len0; i0++){if(typeof data19[i0] !== "string"){const err45 = {instancePath:instancePath+"/includeFields/" + i0,schemaPath:"#/properties/includeFields/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}}else {const err46 = {instancePath:instancePath+"/includeFields",schemaPath:"#/properties/includeFields/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data.excludeFields !== undefined){let data21 = data.excludeFields;if(Array.isArray(data21)){const len1 = data21.length;for(let i1=0; i1<len1; i1++){if(typeof data21[i1] !== "string"){const err47 = {instancePath:instancePath+"/excludeFields/" + i1,schemaPath:"#/properties/excludeFields/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}}else {const err48 = {instancePath:instancePath+"/excludeFields",schemaPath:"#/properties/excludeFields/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data.fieldOverrides !== undefined){let data23 = data.fieldOverrides;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){for(const key0 in data23){if(!(validate24(data23[key0], {instancePath:instancePath+"/fieldOverrides/" + key0.replace(/~/g, "~0").replace(/\//g, "~1"),parentData:data23,parentDataProperty:key0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate24.errors : vErrors.concat(validate24.errors);errors = vErrors.length;}}}else {const err49 = {instancePath:instancePath+"/fieldOverrides",schemaPath:"#/properties/fieldOverrides/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(typeof data.advanced !== "boolean"){const err50 = {instancePath:instancePath+"/advanced",schemaPath:"#/properties/advanced/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}if(data.formTitle !== undefined){if(typeof data.formTitle !== "string"){const err51 = {instancePath:instancePath+"/formTitle",schemaPath:"#/properties/formTitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data.formEyebrow !== undefined){if(typeof data.formEyebrow !== "string"){const err52 = {instancePath:instancePath+"/formEyebrow",schemaPath:"#/properties/formEyebrow/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data.formSubtitle !== undefined){if(typeof data.formSubtitle !== "string"){const err53 = {instancePath:instancePath+"/formSubtitle",schemaPath:"#/properties/formSubtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data.formConfirmLabel !== undefined){if(typeof data.formConfirmLabel !== "string"){const err54 = {instancePath:instancePath+"/formConfirmLabel",schemaPath:"#/properties/formConfirmLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}let data30 = data.size;if(typeof data30 !== "string"){const err55 = {instancePath:instancePath+"/size",schemaPath:"#/properties/size/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}if(!((((data30 === "small") || (data30 === "normal")) || (data30 === "large")) || (data30 === "full"))){const err56 = {instancePath:instancePath+"/size",schemaPath:"#/properties/size/enum",keyword:"enum",params:{allowedValues: schema39.properties.size.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}let data31 = data.columns;if(!((typeof data31 == "number") && (!(data31 % 1) && !isNaN(data31)))){const err57 = {instancePath:instancePath+"/columns",schemaPath:"#/properties/columns/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}if(!((data31 === 1) || (data31 === 2))){const err58 = {instancePath:instancePath+"/columns",schemaPath:"#/properties/columns/enum",keyword:"enum",params:{allowedValues: schema39.properties.columns.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}if(data.endpoint !== undefined){let data32 = data.endpoint;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){if(data32.url === undefined){const err59 = {instancePath:instancePath+"/endpoint",schemaPath:"#/properties/endpoint/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}for(const key1 in data32){if(!((((key1 === "url") || (key1 === "method")) || (key1 === "loadUrl")) || (key1 === "responsePath"))){const err60 = {instancePath:instancePath+"/endpoint",schemaPath:"#/properties/endpoint/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}if(data32.url !== undefined){if(typeof data32.url !== "string"){const err61 = {instancePath:instancePath+"/endpoint/url",schemaPath:"#/properties/endpoint/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data32.method !== undefined){let data34 = data32.method;if(typeof data34 !== "string"){const err62 = {instancePath:instancePath+"/endpoint/method",schemaPath:"#/properties/endpoint/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}if(!(((data34 === "PATCH") || (data34 === "PUT")) || (data34 === "POST"))){const err63 = {instancePath:instancePath+"/endpoint/method",schemaPath:"#/properties/endpoint/properties/method/enum",keyword:"enum",params:{allowedValues: schema39.properties.endpoint.properties.method.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data32.loadUrl !== undefined){if(typeof data32.loadUrl !== "string"){const err64 = {instancePath:instancePath+"/endpoint/loadUrl",schemaPath:"#/properties/endpoint/properties/loadUrl/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}if(data32.responsePath !== undefined){if(typeof data32.responsePath !== "string"){const err65 = {instancePath:instancePath+"/endpoint/responsePath",schemaPath:"#/properties/endpoint/properties/responsePath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}}else {const err66 = {instancePath:instancePath+"/endpoint",schemaPath:"#/properties/endpoint/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}if(data.exportId !== undefined){let data37 = data.exportId;if(typeof data37 === "string"){if(func2(data37) < 1){const err67 = {instancePath:instancePath+"/exportId",schemaPath:"#/properties/exportId/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}else {const err68 = {instancePath:instancePath+"/exportId",schemaPath:"#/properties/exportId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}if(data.formProperties !== undefined){let data38 = data.formProperties;if(data38 && typeof data38 == "object" && !Array.isArray(data38)){for(const key2 in data38){let data39 = data38[key2];if(!(data39 && typeof data39 == "object" && !Array.isArray(data39))){const err69 = {instancePath:instancePath+"/formProperties/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/formProperties/additionalProperties/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}else {const err70 = {instancePath:instancePath+"/formProperties",schemaPath:"#/properties/formProperties/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}if(data.handler !== undefined){if(typeof data.handler !== "string"){const err71 = {instancePath:instancePath+"/handler",schemaPath:"#/properties/handler/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}if(data.route !== undefined){if(typeof data.route !== "string"){const err72 = {instancePath:instancePath+"/route",schemaPath:"#/properties/route/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data.icon !== undefined){if(typeof data.icon !== "string"){const err73 = {instancePath:instancePath+"/icon",schemaPath:"#/properties/icon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}if(data.entities !== undefined){let data43 = data.entities;if(Array.isArray(data43)){const len2 = data43.length;for(let i2=0; i2<len2; i2++){let data44 = data43[i2];if(data44 && typeof data44 == "object" && !Array.isArray(data44)){if(data44.id === undefined){const err74 = {instancePath:instancePath+"/entities/" + i2,schemaPath:"#/properties/entities/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}if(data44.label === undefined){const err75 = {instancePath:instancePath+"/entities/" + i2,schemaPath:"#/properties/entities/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}for(const key3 in data44){if(!(((((key3 === "id") || (key3 === "label")) || (key3 === "register")) || (key3 === "schema")) || (key3 === "filter"))){const err76 = {instancePath:instancePath+"/entities/" + i2,schemaPath:"#/properties/entities/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}if(data44.id !== undefined){if(typeof data44.id !== "string"){const err77 = {instancePath:instancePath+"/entities/" + i2+"/id",schemaPath:"#/properties/entities/items/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}if(data44.label !== undefined){if(typeof data44.label !== "string"){const err78 = {instancePath:instancePath+"/entities/" + i2+"/label",schemaPath:"#/properties/entities/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}if(data44.register !== undefined){let data47 = data44.register;if(typeof data47 === "string"){if(func2(data47) < 1){const err79 = {instancePath:instancePath+"/entities/" + i2+"/register",schemaPath:"#/properties/entities/items/properties/register/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}else {const err80 = {instancePath:instancePath+"/entities/" + i2+"/register",schemaPath:"#/properties/entities/items/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data44.schema !== undefined){let data48 = data44.schema;if(typeof data48 === "string"){if(func2(data48) < 1){const err81 = {instancePath:instancePath+"/entities/" + i2+"/schema",schemaPath:"#/properties/entities/items/properties/schema/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}else {const err82 = {instancePath:instancePath+"/entities/" + i2+"/schema",schemaPath:"#/properties/entities/items/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}if(data44.filter !== undefined){let data49 = data44.filter;if(!(data49 && typeof data49 == "object" && !Array.isArray(data49))){const err83 = {instancePath:instancePath+"/entities/" + i2+"/filter",schemaPath:"#/properties/entities/items/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}}else {const err84 = {instancePath:instancePath+"/entities/" + i2,schemaPath:"#/properties/entities/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}}else {const err85 = {instancePath:instancePath+"/entities",schemaPath:"#/properties/entities/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}if(data.formats !== undefined){let data50 = data.formats;if(Array.isArray(data50)){const len3 = data50.length;for(let i3=0; i3<len3; i3++){let data51 = data50[i3];const _errs130 = errors;let valid27 = false;let passing0 = null;const _errs131 = errors;if(typeof data51 !== "string"){const err86 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}var _valid7 = _errs131 === errors;if(_valid7){valid27 = true;passing0 = 0;}const _errs133 = errors;if(data51 && typeof data51 == "object" && !Array.isArray(data51)){if(data51.id === undefined){const err87 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/1/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}if(data51.label === undefined){const err88 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/1/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}for(const key4 in data51){if(!((key4 === "id") || (key4 === "label"))){const err89 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}if(data51.id !== undefined){if(typeof data51.id !== "string"){const err90 = {instancePath:instancePath+"/formats/" + i3+"/id",schemaPath:"#/properties/formats/items/oneOf/1/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}if(data51.label !== undefined){if(typeof data51.label !== "string"){const err91 = {instancePath:instancePath+"/formats/" + i3+"/label",schemaPath:"#/properties/formats/items/oneOf/1/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}}else {const err92 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}var _valid7 = _errs133 === errors;if(_valid7 && valid27){valid27 = false;passing0 = [passing0, 1];}else {if(_valid7){valid27 = true;passing0 = 1;}}if(!valid27){const err93 = {instancePath:instancePath+"/formats/" + i3,schemaPath:"#/properties/formats/items/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}else {errors = _errs130;if(vErrors !== null){if(_errs130){vErrors.length = _errs130;}else {vErrors = null;}}}}}else {const err94 = {instancePath:instancePath+"/formats",schemaPath:"#/properties/formats/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}if(data.description !== undefined){if(typeof data.description !== "string"){const err95 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}if(data.url !== undefined){if(typeof data.url !== "string"){const err96 = {instancePath:instancePath+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}}if(data.method !== undefined){let data56 = data.method;if(typeof data56 !== "string"){const err97 = {instancePath:instancePath+"/method",schemaPath:"#/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}if(!((((data56 === "POST") || (data56 === "PUT")) || (data56 === "PATCH")) || (data56 === "GET"))){const err98 = {instancePath:instancePath+"/method",schemaPath:"#/properties/method/enum",keyword:"enum",params:{allowedValues: schema39.properties.method.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}if(data.payload !== undefined){let data57 = data.payload;if(data57 && typeof data57 == "object" && !Array.isArray(data57));else {const err99 = {instancePath:instancePath+"/payload",schemaPath:"#/properties/payload/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data.params !== undefined){let data58 = data.params;if(data58 && typeof data58 == "object" && !Array.isArray(data58));else {const err100 = {instancePath:instancePath+"/params",schemaPath:"#/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}if(typeof data.download !== "boolean"){const err101 = {instancePath:instancePath+"/download",schemaPath:"#/properties/download/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}if(data.filename !== undefined){if(typeof data.filename !== "string"){const err102 = {instancePath:instancePath+"/filename",schemaPath:"#/properties/filename/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}if(data.successMessage !== undefined){if(typeof data.successMessage !== "string"){const err103 = {instancePath:instancePath+"/successMessage",schemaPath:"#/properties/successMessage/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data.errorMessage !== undefined){if(typeof data.errorMessage !== "string"){const err104 = {instancePath:instancePath+"/errorMessage",schemaPath:"#/properties/errorMessage/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}if(typeof data.refresh !== "boolean"){const err105 = {instancePath:instancePath+"/refresh",schemaPath:"#/properties/refresh/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if(data.flowId !== undefined){if(typeof data.flowId !== "string"){const err106 = {instancePath:instancePath+"/flowId",schemaPath:"#/properties/flowId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}if(data.nodeId !== undefined){if(typeof data.nodeId !== "string"){const err107 = {instancePath:instancePath+"/nodeId",schemaPath:"#/properties/nodeId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}if(data.subject !== undefined){if(typeof data.subject !== "string"){const err108 = {instancePath:instancePath+"/subject",schemaPath:"#/properties/subject/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data.agent !== undefined){if(typeof data.agent !== "string"){const err109 = {instancePath:instancePath+"/agent",schemaPath:"#/properties/agent/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}if(data.skill !== undefined){if(typeof data.skill !== "string"){const err110 = {instancePath:instancePath+"/skill",schemaPath:"#/properties/skill/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data.prompt !== undefined){if(typeof data.prompt !== "string"){const err111 = {instancePath:instancePath+"/prompt",schemaPath:"#/properties/prompt/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}if(data.resultField !== undefined){if(typeof data.resultField !== "string"){const err112 = {instancePath:instancePath+"/resultField",schemaPath:"#/properties/resultField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}if(data.objectId !== undefined){if(typeof data.objectId !== "string"){const err113 = {instancePath:instancePath+"/objectId",schemaPath:"#/properties/objectId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}if(data.register !== undefined){if(typeof data.register !== "string"){const err114 = {instancePath:instancePath+"/register",schemaPath:"#/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}if(data.schema !== undefined){if(typeof data.schema !== "string"){const err115 = {instancePath:instancePath+"/schema",schemaPath:"#/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}if(data.onSuccessRoute !== undefined){let data74 = data.onSuccessRoute;const _errs183 = errors;let valid29 = false;let passing1 = null;const _errs184 = errors;if(typeof data74 !== "string"){const err116 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}var _valid8 = _errs184 === errors;if(_valid8){valid29 = true;passing1 = 0;}const _errs186 = errors;if(data74 && typeof data74 == "object" && !Array.isArray(data74)){if(data74.name === undefined){const err117 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf/1/required",keyword:"required",params:{missingProperty: "name"},message:"must have required property '"+"name"+"'"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}for(const key5 in data74){if(!(((key5 === "name") || (key5 === "paramField")) || (key5 === "objectParam"))){const err118 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}if(data74.name !== undefined){if(typeof data74.name !== "string"){const err119 = {instancePath:instancePath+"/onSuccessRoute/name",schemaPath:"#/properties/onSuccessRoute/oneOf/1/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}if(data74.paramField !== undefined){if(typeof data74.paramField !== "string"){const err120 = {instancePath:instancePath+"/onSuccessRoute/paramField",schemaPath:"#/properties/onSuccessRoute/oneOf/1/properties/paramField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}if(data74.objectParam !== undefined){if(typeof data74.objectParam !== "string"){const err121 = {instancePath:instancePath+"/onSuccessRoute/objectParam",schemaPath:"#/properties/onSuccessRoute/oneOf/1/properties/objectParam/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}}else {const err122 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}var _valid8 = _errs186 === errors;if(_valid8 && valid29){valid29 = false;passing1 = [passing1, 1];}else {if(_valid8){valid29 = true;passing1 = 1;}}if(!valid29){const err123 = {instancePath:instancePath+"/onSuccessRoute",schemaPath:"#/properties/onSuccessRoute/oneOf",keyword:"oneOf",params:{passingSchemas: passing1},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}else {errors = _errs183;if(vErrors !== null){if(_errs183){vErrors.length = _errs183;}else {vErrors = null;}}}}if(data.labelOn !== undefined){if(typeof data.labelOn !== "string"){const err124 = {instancePath:instancePath+"/labelOn",schemaPath:"#/properties/labelOn/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}if(data.labelOff !== undefined){if(typeof data.labelOff !== "string"){const err125 = {instancePath:instancePath+"/labelOff",schemaPath:"#/properties/labelOff/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}if(data.stateSource !== undefined){let data80 = data.stateSource;if(data80 && typeof data80 == "object" && !Array.isArray(data80)){if(data80.method === undefined){data80.method = "GET";}if(data80.loadOn === undefined){data80.loadOn = "mount";}if(data80.url === undefined){const err126 = {instancePath:instancePath+"/stateSource",schemaPath:"#/$defs/endpointSource/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}for(const key6 in data80){if(!((((((((key6 === "url") || (key6 === "method")) || (key6 === "params")) || (key6 === "loadOn")) || (key6 === "requestLabel")) || (key6 === "requestHint")) || (key6 === "truncatedText")) || (key6 === "responsePath"))){const err127 = {instancePath:instancePath+"/stateSource",schemaPath:"#/$defs/endpointSource/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}if(data80.url !== undefined){if(typeof data80.url !== "string"){const err128 = {instancePath:instancePath+"/stateSource/url",schemaPath:"#/$defs/endpointSource/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}let data82 = data80.method;if(typeof data82 !== "string"){const err129 = {instancePath:instancePath+"/stateSource/method",schemaPath:"#/$defs/endpointSource/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}if(!((data82 === "GET") || (data82 === "POST"))){const err130 = {instancePath:instancePath+"/stateSource/method",schemaPath:"#/$defs/endpointSource/properties/method/enum",keyword:"enum",params:{allowedValues: schema44.properties.method.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}if(data80.params !== undefined){let data83 = data80.params;if(data83 && typeof data83 == "object" && !Array.isArray(data83));else {const err131 = {instancePath:instancePath+"/stateSource/params",schemaPath:"#/$defs/endpointSource/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}let data84 = data80.loadOn;if(typeof data84 !== "string"){const err132 = {instancePath:instancePath+"/stateSource/loadOn",schemaPath:"#/$defs/endpointSource/properties/loadOn/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}if(!((data84 === "mount") || (data84 === "request"))){const err133 = {instancePath:instancePath+"/stateSource/loadOn",schemaPath:"#/$defs/endpointSource/properties/loadOn/enum",keyword:"enum",params:{allowedValues: schema44.properties.loadOn.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}if(data80.requestLabel !== undefined){if(typeof data80.requestLabel !== "string"){const err134 = {instancePath:instancePath+"/stateSource/requestLabel",schemaPath:"#/$defs/endpointSource/properties/requestLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}if(data80.requestHint !== undefined){if(typeof data80.requestHint !== "string"){const err135 = {instancePath:instancePath+"/stateSource/requestHint",schemaPath:"#/$defs/endpointSource/properties/requestHint/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}}if(data80.truncatedText !== undefined){if(typeof data80.truncatedText !== "string"){const err136 = {instancePath:instancePath+"/stateSource/truncatedText",schemaPath:"#/$defs/endpointSource/properties/truncatedText/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}if(data80.responsePath !== undefined){if(typeof data80.responsePath !== "string"){const err137 = {instancePath:instancePath+"/stateSource/responsePath",schemaPath:"#/$defs/endpointSource/properties/responsePath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}}else {const err138 = {instancePath:instancePath+"/stateSource",schemaPath:"#/$defs/endpointSource/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}if(data.writeUrl !== undefined){if(typeof data.writeUrl !== "string"){const err139 = {instancePath:instancePath+"/writeUrl",schemaPath:"#/properties/writeUrl/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data.field !== undefined){if(typeof data.field !== "string"){const err140 = {instancePath:instancePath+"/field",schemaPath:"#/properties/field/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}if(data.variant !== undefined){if(typeof data.variant !== "string"){const err141 = {instancePath:instancePath+"/variant",schemaPath:"#/properties/variant/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}if(data.children !== undefined){if(!(Array.isArray(data.children))){const err142 = {instancePath:instancePath+"/children",schemaPath:"#/properties/children/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}if(data.childrenLabel !== undefined){if(typeof data.childrenLabel !== "string"){const err143 = {instancePath:instancePath+"/childrenLabel",schemaPath:"#/properties/childrenLabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data.group !== undefined){let data94 = data.group;if(typeof data94 === "string"){if(func2(data94) < 1){const err144 = {instancePath:instancePath+"/group",schemaPath:"#/properties/group/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}else {const err145 = {instancePath:instancePath+"/group",schemaPath:"#/properties/group/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}if(typeof data.adminOnly !== "boolean"){const err146 = {instancePath:instancePath+"/adminOnly",schemaPath:"#/properties/adminOnly/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}if(data.visibleWhen !== undefined){if(!(validate26(data.visibleWhen, {instancePath:instancePath+"/visibleWhen",parentData:data,parentDataProperty:"visibleWhen",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate26.errors : vErrors.concat(validate26.errors);errors = vErrors.length;}}}else {const err147 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}validate23.errors = vErrors;return errors === 0;}validate23.evaluated = {"props":{"id":true,"label":true,"type":true,"op":true,"values":true,"confirm":true,"confirmTitle":true,"confirmMessage":true,"confirmLabel":true,"confirmIcon":true,"target":true,"props":true,"createOverride":true,"includeFields":true,"excludeFields":true,"fieldOverrides":true,"advanced":true,"formTitle":true,"formEyebrow":true,"formSubtitle":true,"formConfirmLabel":true,"size":true,"columns":true,"endpoint":true,"exportId":true,"formProperties":true,"handler":true,"route":true,"icon":true,"entities":true,"formats":true,"description":true,"url":true,"method":true,"payload":true,"params":true,"download":true,"filename":true,"successMessage":true,"errorMessage":true,"refresh":true,"flowId":true,"nodeId":true,"subject":true,"agent":true,"skill":true,"prompt":true,"resultField":true,"objectId":true,"register":true,"schema":true,"onSuccessRoute":true,"labelOn":true,"labelOff":true,"stateSource":true,"writeUrl":true,"field":true,"variant":true,"children":true,"childrenLabel":true,"group":true,"adminOnly":true,"visibleWhen":true},"dynamicProps":false,"dynamicItems":false};const wrapper3 = {validate: validate22};function validate22(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate22.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(!(validate23(data, {instancePath,parentData,parentDataProperty,rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate23.errors : vErrors.concat(validate23.errors);errors = vErrors.length;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.children !== undefined){let data0 = data.children;if(Array.isArray(data0)){const len0 = data0.length;for(let i0=0; i0<len0; i0++){if(!(wrapper3.validate(data0[i0], {instancePath:instancePath+"/children/" + i0,parentData:data0,parentDataProperty:i0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? wrapper3.validate.errors : vErrors.concat(wrapper3.validate.errors);errors = vErrors.length;}}}}for(const key0 in data){if(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((key0 !== "children") && (key0 !== "id")) && (key0 !== "label")) && (key0 !== "type")) && (key0 !== "op")) && (key0 !== "values")) && (key0 !== "confirm")) && (key0 !== "confirmTitle")) && (key0 !== "confirmMessage")) && (key0 !== "confirmLabel")) && (key0 !== "confirmIcon")) && (key0 !== "target")) && (key0 !== "props")) && (key0 !== "createOverride")) && (key0 !== "includeFields")) && (key0 !== "excludeFields")) && (key0 !== "fieldOverrides")) && (key0 !== "advanced")) && (key0 !== "formTitle")) && (key0 !== "formEyebrow")) && (key0 !== "formSubtitle")) && (key0 !== "formConfirmLabel")) && (key0 !== "size")) && (key0 !== "columns")) && (key0 !== "endpoint")) && (key0 !== "exportId")) && (key0 !== "formProperties")) && (key0 !== "handler")) && (key0 !== "route")) && (key0 !== "icon")) && (key0 !== "entities")) && (key0 !== "formats")) && (key0 !== "description")) && (key0 !== "url")) && (key0 !== "method")) && (key0 !== "payload")) && (key0 !== "params")) && (key0 !== "download")) && (key0 !== "filename")) && (key0 !== "successMessage")) && (key0 !== "errorMessage")) && (key0 !== "refresh")) && (key0 !== "flowId")) && (key0 !== "nodeId")) && (key0 !== "subject")) && (key0 !== "agent")) && (key0 !== "skill")) && (key0 !== "prompt")) && (key0 !== "resultField")) && (key0 !== "objectId")) && (key0 !== "register")) && (key0 !== "schema")) && (key0 !== "onSuccessRoute")) && (key0 !== "labelOn")) && (key0 !== "labelOff")) && (key0 !== "stateSource")) && (key0 !== "writeUrl")) && (key0 !== "field")) && (key0 !== "variant")) && (key0 !== "childrenLabel")) && (key0 !== "group")) && (key0 !== "adminOnly")) && (key0 !== "visibleWhen")){const err0 = {instancePath,schemaPath:"#/unevaluatedProperties",keyword:"unevaluatedProperties",params:{unevaluatedProperty: key0},message:"must NOT have unevaluated properties"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}}}validate22.errors = vErrors;return errors === 0;}validate22.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate21(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate21.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.solid === undefined){data.solid = false;}if(data.label === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}for(const key0 in data){if(!(func1.call(schema37.properties, key0))){const err1 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}}if(data.id !== undefined){if(typeof data.id !== "string"){const err2 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.label !== undefined){if(typeof data.label !== "string"){const err3 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.icon !== undefined){if(typeof data.icon !== "string"){const err4 = {instancePath:instancePath+"/icon",schemaPath:"#/properties/icon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.route !== undefined){if(typeof data.route !== "string"){const err5 = {instancePath:instancePath+"/route",schemaPath:"#/properties/route/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.query !== undefined){let data4 = data.query;if(data4 && typeof data4 == "object" && !Array.isArray(data4)){for(const key1 in data4){if(typeof data4[key1] !== "string"){const err6 = {instancePath:instancePath+"/query/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/query/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}else {const err7 = {instancePath:instancePath+"/query",schemaPath:"#/properties/query/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.href !== undefined){if(typeof data.href !== "string"){const err8 = {instancePath:instancePath+"/href",schemaPath:"#/properties/href/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.action !== undefined){if(!(validate22(data.action, {instancePath:instancePath+"/action",parentData:data,parentDataProperty:"action",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate22.errors : vErrors.concat(validate22.errors);errors = vErrors.length;}}if(typeof data.solid !== "boolean"){const err9 = {instancePath:instancePath+"/solid",schemaPath:"#/properties/solid/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(data.permission !== undefined){if(typeof data.permission !== "string"){const err10 = {instancePath:instancePath+"/permission",schemaPath:"#/properties/permission/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.visibleIf !== undefined){let data10 = data.visibleIf;if(data10 && typeof data10 == "object" && !Array.isArray(data10)){if(data10.appInstalled !== undefined){let data11 = data10.appInstalled;if(typeof data11 === "string"){if(func2(data11) < 1){const err11 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}else {const err13 = {instancePath:instancePath+"/visibleIf",schemaPath:"#/$defs/visibleIfCondition/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}}else {const err14 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}validate21.errors = vErrors;return errors === 0;}validate21.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema48 = {"type":"object","required":["id","label"],"additionalProperties":false,"description":"A top-level navigation entry. May contain one level of nested children.","properties":{"id":{"type":"string","description":"Unique identifier for this menu entry. Sentinels (@resolve:*) are NOT allowed — this is a router invariant."},"label":{"type":"string","description":"i18n translation key resolved by the consuming app's t() function at render time."},"translateLabel":{"type":"boolean","default":true,"description":"Whether CnAppNav runs `label` through the translate function. Set false for a user-authored title (e.g. a catalog name) that must render as written even when it equals a translation key such as 'Search'. Rendered as text either way, never as HTML."},"icon":{"type":"string","description":"CSS class for the icon (e.g. 'icon-checkmark') or MDI component name."},"route":{"type":"string","description":"Vue-router route name (matches a pages[].id) that this entry navigates to. Sentinels (@resolve:*) are NOT allowed — this is a router invariant."},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Optional vue-router query parameters merged into the target route link (e.g. a filter like { caseType: '<uuid>' }). Deep-links a nav entry to a pre-filtered index page."},"params":{"type":"object","additionalProperties":{"type":["string","number"],"minLength":1},"description":"Optional vue-router route params for a parameterised route (e.g. { catalogSlug: 'news' } for a page at '/catalogs/:catalogSlug'). String values must be non-empty. CnAppNav links to { name: route, params, query }. The entry is active only while every declared param equals the current route's param (compared as strings), so sibling entries on one route that differ in params are never marked together. On pages whose path sits below its page path with these params filled in, it is active too: by the same params check when those routes carry the same param names, otherwise as the one entry marked below its route."},"order":{"type":"integer","description":"Display order in the menu. Items without an order render last."},"permission":{"type":"string","description":"Permission string the user must hold for this entry to render."},"section":{"type":"string","enum":["main","footer","settings","integrations"],"default":"main","description":"Where the entry renders inside CnAppNav. 'main' (default) places it in the top list. 'footer' pins the entry to the bottom as a flat item above the settings foldout. 'settings' places it inside the NcAppNavigationSettings gear-icon foldout. 'integrations' renders the entry NOT in the navigation at all but in the Integrations section at the bottom of the per-user settings modal — the destination for links that leave this app for another (ADR-110). An 'integrations' entry MUST be clickable (href); a group with children is meaningless there."},"type":{"type":"string","enum":["item","caption"],"default":"item","description":"Render kind. 'item' (default) renders a clickable NcAppNavigationItem. 'caption' renders an NcAppNavigationCaption section divider — only label, id, order, and section are honoured."},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]},{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"A FILTERED count (zuiddrecht-pixel-gaps): the total of this register and schema with `filter` applied (filter-value tokens such as `@me` and `@today` resolve at fetch time), fetched by CnAppRoot under the entry's own key so an index page's whole-schema total is untouched. Renders \"My work 6\" beside \"All cases 48\".","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true}}}],"description":"Counter badge in the entry's #counter slot. Positive integer renders as-is; 'auto' resolves from cnMenuCounts (populated by CnAppRoot from useObjectStore totals) for the entry's resolved index-type page (register + schema); an object `{ register, schema, filter? }` is the total of that filtered list."},"counterVariant":{"type":"string","enum":["default","attention"],"default":"default","description":"screens-chrome-parity: how the entry's `count` reads. `attention` asks for the red attention pill under the board look (unread or overdue work) and maps to the counter bubble's highlighted state without it. `default` is the neutral pill."},"pinned":{"type":"boolean","default":false,"description":"Forwarded to NcAppNavigationItem's pinned prop. NC bottom-pins pinned items inside the parent list region."},"open":{"type":"boolean","default":false,"description":"Initial expansion state for a parent entry with children. When true and children[] is non-empty, the parent renders with :open=\"true\" so children are visible on mount."},"href":{"type":"string","description":"External URL. When set, the entry opens the URL in a new tab instead of dispatching a vue-router navigation."},"action":{"type":"string","enum":["user-settings","admin-settings","replay-walkthrough"],"description":"Built-in action to invoke when the entry is clicked. Closed enum. 'admin-settings' opens the app-level admin-settings dialog (distinct from the per-user settings dialog). 'replay-walkthrough' re-runs the product walkthrough (ADR-043), optionally for a specific tourId."},"tourId":{"type":"string","description":"Tour id for the 'replay-walkthrough' action (ADR-043); replays that tour from its first step. Omit to replay the default tour."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition","description":"Optional display condition evaluated at render time."},"dynamicSource":{"$ref":"#/$defs/dynamicSource","description":"Optional dynamic source for populating nested menu items from a data endpoint. Carry-forward from v1.3.0."},"children":{"type":"array","items":{"$ref":"#/$defs/menuItemLeaf"},"description":"Nested entries. Only one level of nesting is supported."}}};const schema50 = {"type":"object","description":"Dynamic source configuration for populating menu items from a backend endpoint. Carry-forward from v1.3.0 — identical shape to v1.","additionalProperties":true,"properties":{"url":{"type":"string","description":"URL to fetch menu items from."},"method":{"type":"string","enum":["GET","POST"],"default":"GET","description":"HTTP method for the fetch."},"labelField":{"type":"string","description":"Property name in the response items to use as the menu entry label."},"idField":{"type":"string","description":"Property name in the response items to use as the menu entry id."},"routeField":{"type":"string","description":"Property name in the response items to use as the menu entry route."}}};const schema51 = {"type":"object","required":["id","label"],"additionalProperties":false,"description":"A nested menu entry. Has no further children.","properties":{"id":{"type":"string"},"label":{"type":"string"},"translateLabel":{"type":"boolean","default":true,"description":"Same as menuItem.translateLabel: false renders `label` as written instead of translating it."},"icon":{"type":"string"},"route":{"type":"string"},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Optional vue-router query parameters merged into the target route link (e.g. a filter like { caseType: '<uuid>' }). Deep-links a nav entry to a pre-filtered index page."},"params":{"type":"object","additionalProperties":{"type":["string","number"],"minLength":1},"description":"Optional vue-router route params for a parameterised route; string values must be non-empty. Same contract as menuItem.params: part of the link, and the entry is active only while every declared param equals the current route's param."},"order":{"type":"integer"},"permission":{"type":"string"},"section":{"type":"string","enum":["main","footer","settings","integrations"],"default":"main"},"type":{"type":"string","enum":["item","caption"],"default":"item"},"count":{"oneOf":[{"type":"integer","minimum":0},{"type":"string","enum":["auto"]}]},"counterVariant":{"type":"string","enum":["default","attention"],"default":"default"},"pinned":{"type":"boolean","default":false},"href":{"type":"string"},"action":{"type":"string","enum":["user-settings","admin-settings","replay-walkthrough"]},"tourId":{"type":"string","description":"Tour id for the 'replay-walkthrough' action (ADR-043)."},"visibleIf":{"$ref":"#/$defs/visibleIfCondition"}}};function validate32(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate32.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.translateLabel === undefined){data.translateLabel = true;}if(data.section === undefined){data.section = "main";}if(data.type === undefined){data.type = "item";}if(data.counterVariant === undefined){data.counterVariant = "default";}if(data.pinned === undefined){data.pinned = false;}if(data.id === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.label === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}for(const key0 in data){if(!(func1.call(schema51.properties, key0))){const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.id !== undefined){if(typeof data.id !== "string"){const err3 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.label !== undefined){if(typeof data.label !== "string"){const err4 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(typeof data.translateLabel !== "boolean"){const err5 = {instancePath:instancePath+"/translateLabel",schemaPath:"#/properties/translateLabel/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.icon !== undefined){if(typeof data.icon !== "string"){const err6 = {instancePath:instancePath+"/icon",schemaPath:"#/properties/icon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.route !== undefined){if(typeof data.route !== "string"){const err7 = {instancePath:instancePath+"/route",schemaPath:"#/properties/route/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.query !== undefined){let data5 = data.query;if(data5 && typeof data5 == "object" && !Array.isArray(data5)){for(const key1 in data5){let data6 = data5[key1];if(((typeof data6 !== "string") && (!(typeof data6 == "number"))) && (typeof data6 !== "boolean")){const err8 = {instancePath:instancePath+"/query/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/query/additionalProperties/type",keyword:"type",params:{type: schema51.properties.query.additionalProperties.type},message:"must be string,number,boolean"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}}else {const err9 = {instancePath:instancePath+"/query",schemaPath:"#/properties/query/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.params !== undefined){let data7 = data.params;if(data7 && typeof data7 == "object" && !Array.isArray(data7)){for(const key2 in data7){let data8 = data7[key2];if((typeof data8 !== "string") && (!(typeof data8 == "number"))){const err10 = {instancePath:instancePath+"/params/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/params/additionalProperties/type",keyword:"type",params:{type: schema51.properties.params.additionalProperties.type},message:"must be string,number"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(typeof data8 === "string"){if(func2(data8) < 1){const err11 = {instancePath:instancePath+"/params/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/params/additionalProperties/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}}}else {const err12 = {instancePath:instancePath+"/params",schemaPath:"#/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.order !== undefined){let data9 = data.order;if(!((typeof data9 == "number") && (!(data9 % 1) && !isNaN(data9)))){const err13 = {instancePath:instancePath+"/order",schemaPath:"#/properties/order/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.permission !== undefined){if(typeof data.permission !== "string"){const err14 = {instancePath:instancePath+"/permission",schemaPath:"#/properties/permission/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}let data11 = data.section;if(typeof data11 !== "string"){const err15 = {instancePath:instancePath+"/section",schemaPath:"#/properties/section/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(!((((data11 === "main") || (data11 === "footer")) || (data11 === "settings")) || (data11 === "integrations"))){const err16 = {instancePath:instancePath+"/section",schemaPath:"#/properties/section/enum",keyword:"enum",params:{allowedValues: schema51.properties.section.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}let data12 = data.type;if(typeof data12 !== "string"){const err17 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(!((data12 === "item") || (data12 === "caption"))){const err18 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/enum",keyword:"enum",params:{allowedValues: schema51.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(data.count !== undefined){let data13 = data.count;const _errs31 = errors;let valid3 = false;let passing0 = null;const _errs32 = errors;if(!((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13)))){const err19 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/0/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data13 == "number"){if(data13 < 0 || isNaN(data13)){const err20 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}var _valid0 = _errs32 === errors;if(_valid0){valid3 = true;passing0 = 0;}const _errs34 = errors;if(typeof data13 !== "string"){const err21 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(!(data13 === "auto")){const err22 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/1/enum",keyword:"enum",params:{allowedValues: schema51.properties.count.oneOf[1].enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}var _valid0 = _errs34 === errors;if(_valid0 && valid3){valid3 = false;passing0 = [passing0, 1];}else {if(_valid0){valid3 = true;passing0 = 1;}}if(!valid3){const err23 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}else {errors = _errs31;if(vErrors !== null){if(_errs31){vErrors.length = _errs31;}else {vErrors = null;}}}}let data14 = data.counterVariant;if(typeof data14 !== "string"){const err24 = {instancePath:instancePath+"/counterVariant",schemaPath:"#/properties/counterVariant/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(!((data14 === "default") || (data14 === "attention"))){const err25 = {instancePath:instancePath+"/counterVariant",schemaPath:"#/properties/counterVariant/enum",keyword:"enum",params:{allowedValues: schema51.properties.counterVariant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(typeof data.pinned !== "boolean"){const err26 = {instancePath:instancePath+"/pinned",schemaPath:"#/properties/pinned/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(data.href !== undefined){if(typeof data.href !== "string"){const err27 = {instancePath:instancePath+"/href",schemaPath:"#/properties/href/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data.action !== undefined){let data17 = data.action;if(typeof data17 !== "string"){const err28 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(!(((data17 === "user-settings") || (data17 === "admin-settings")) || (data17 === "replay-walkthrough"))){const err29 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema51.properties.action.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data.tourId !== undefined){if(typeof data.tourId !== "string"){const err30 = {instancePath:instancePath+"/tourId",schemaPath:"#/properties/tourId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data.visibleIf !== undefined){let data19 = data.visibleIf;if(data19 && typeof data19 == "object" && !Array.isArray(data19)){if(data19.appInstalled !== undefined){let data20 = data19.appInstalled;if(typeof data20 === "string"){if(func2(data20) < 1){const err31 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}else {const err32 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}}else {const err33 = {instancePath:instancePath+"/visibleIf",schemaPath:"#/$defs/visibleIfCondition/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}}else {const err34 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}validate32.errors = vErrors;return errors === 0;}validate32.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};function validate31(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate31.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.translateLabel === undefined){data.translateLabel = true;}if(data.section === undefined){data.section = "main";}if(data.type === undefined){data.type = "item";}if(data.counterVariant === undefined){data.counterVariant = "default";}if(data.pinned === undefined){data.pinned = false;}if(data.open === undefined){data.open = false;}if(data.id === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.label === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}for(const key0 in data){if(!(func1.call(schema48.properties, key0))){const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.id !== undefined){if(typeof data.id !== "string"){const err3 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.label !== undefined){if(typeof data.label !== "string"){const err4 = {instancePath:instancePath+"/label",schemaPath:"#/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(typeof data.translateLabel !== "boolean"){const err5 = {instancePath:instancePath+"/translateLabel",schemaPath:"#/properties/translateLabel/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.icon !== undefined){if(typeof data.icon !== "string"){const err6 = {instancePath:instancePath+"/icon",schemaPath:"#/properties/icon/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.route !== undefined){if(typeof data.route !== "string"){const err7 = {instancePath:instancePath+"/route",schemaPath:"#/properties/route/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.query !== undefined){let data5 = data.query;if(data5 && typeof data5 == "object" && !Array.isArray(data5)){for(const key1 in data5){let data6 = data5[key1];if(((typeof data6 !== "string") && (!(typeof data6 == "number"))) && (typeof data6 !== "boolean")){const err8 = {instancePath:instancePath+"/query/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/query/additionalProperties/type",keyword:"type",params:{type: schema48.properties.query.additionalProperties.type},message:"must be string,number,boolean"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}}else {const err9 = {instancePath:instancePath+"/query",schemaPath:"#/properties/query/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.params !== undefined){let data7 = data.params;if(data7 && typeof data7 == "object" && !Array.isArray(data7)){for(const key2 in data7){let data8 = data7[key2];if((typeof data8 !== "string") && (!(typeof data8 == "number"))){const err10 = {instancePath:instancePath+"/params/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/params/additionalProperties/type",keyword:"type",params:{type: schema48.properties.params.additionalProperties.type},message:"must be string,number"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}if(typeof data8 === "string"){if(func2(data8) < 1){const err11 = {instancePath:instancePath+"/params/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/params/additionalProperties/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}}}else {const err12 = {instancePath:instancePath+"/params",schemaPath:"#/properties/params/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.order !== undefined){let data9 = data.order;if(!((typeof data9 == "number") && (!(data9 % 1) && !isNaN(data9)))){const err13 = {instancePath:instancePath+"/order",schemaPath:"#/properties/order/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.permission !== undefined){if(typeof data.permission !== "string"){const err14 = {instancePath:instancePath+"/permission",schemaPath:"#/properties/permission/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}let data11 = data.section;if(typeof data11 !== "string"){const err15 = {instancePath:instancePath+"/section",schemaPath:"#/properties/section/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(!((((data11 === "main") || (data11 === "footer")) || (data11 === "settings")) || (data11 === "integrations"))){const err16 = {instancePath:instancePath+"/section",schemaPath:"#/properties/section/enum",keyword:"enum",params:{allowedValues: schema48.properties.section.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}let data12 = data.type;if(typeof data12 !== "string"){const err17 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(!((data12 === "item") || (data12 === "caption"))){const err18 = {instancePath:instancePath+"/type",schemaPath:"#/properties/type/enum",keyword:"enum",params:{allowedValues: schema48.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(data.count !== undefined){let data13 = data.count;const _errs31 = errors;let valid3 = false;let passing0 = null;const _errs32 = errors;if(!((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13)))){const err19 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/0/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data13 == "number"){if(data13 < 0 || isNaN(data13)){const err20 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}var _valid0 = _errs32 === errors;if(_valid0){valid3 = true;passing0 = 0;}const _errs34 = errors;if(typeof data13 !== "string"){const err21 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}if(!(data13 === "auto")){const err22 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/1/enum",keyword:"enum",params:{allowedValues: schema48.properties.count.oneOf[1].enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}var _valid0 = _errs34 === errors;if(_valid0 && valid3){valid3 = false;passing0 = [passing0, 1];}else {if(_valid0){valid3 = true;passing0 = 1;}const _errs36 = errors;if(data13 && typeof data13 == "object" && !Array.isArray(data13)){if(data13.register === undefined){const err23 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/2/required",keyword:"required",params:{missingProperty: "register"},message:"must have required property '"+"register"+"'"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}if(data13.schema === undefined){const err24 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/2/required",keyword:"required",params:{missingProperty: "schema"},message:"must have required property '"+"schema"+"'"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}for(const key3 in data13){if(!(((key3 === "register") || (key3 === "schema")) || (key3 === "filter"))){const err25 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/2/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data13.register !== undefined){let data14 = data13.register;if(typeof data14 === "string"){if(func2(data14) < 1){const err26 = {instancePath:instancePath+"/count/register",schemaPath:"#/properties/count/oneOf/2/properties/register/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}else {const err27 = {instancePath:instancePath+"/count/register",schemaPath:"#/properties/count/oneOf/2/properties/register/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data13.schema !== undefined){let data15 = data13.schema;if(typeof data15 === "string"){if(func2(data15) < 1){const err28 = {instancePath:instancePath+"/count/schema",schemaPath:"#/properties/count/oneOf/2/properties/schema/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}else {const err29 = {instancePath:instancePath+"/count/schema",schemaPath:"#/properties/count/oneOf/2/properties/schema/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data13.filter !== undefined){let data16 = data13.filter;if(data16 && typeof data16 == "object" && !Array.isArray(data16));else {const err30 = {instancePath:instancePath+"/count/filter",schemaPath:"#/properties/count/oneOf/2/properties/filter/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}}else {const err31 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf/2/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}var _valid0 = _errs36 === errors;if(_valid0 && valid3){valid3 = false;passing0 = [passing0, 2];}else {if(_valid0){valid3 = true;passing0 = 2;}}}if(!valid3){const err32 = {instancePath:instancePath+"/count",schemaPath:"#/properties/count/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}else {errors = _errs31;if(vErrors !== null){if(_errs31){vErrors.length = _errs31;}else {vErrors = null;}}}}let data17 = data.counterVariant;if(typeof data17 !== "string"){const err33 = {instancePath:instancePath+"/counterVariant",schemaPath:"#/properties/counterVariant/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(!((data17 === "default") || (data17 === "attention"))){const err34 = {instancePath:instancePath+"/counterVariant",schemaPath:"#/properties/counterVariant/enum",keyword:"enum",params:{allowedValues: schema48.properties.counterVariant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}if(typeof data.pinned !== "boolean"){const err35 = {instancePath:instancePath+"/pinned",schemaPath:"#/properties/pinned/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(typeof data.open !== "boolean"){const err36 = {instancePath:instancePath+"/open",schemaPath:"#/properties/open/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}if(data.href !== undefined){if(typeof data.href !== "string"){const err37 = {instancePath:instancePath+"/href",schemaPath:"#/properties/href/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data.action !== undefined){let data21 = data.action;if(typeof data21 !== "string"){const err38 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(!(((data21 === "user-settings") || (data21 === "admin-settings")) || (data21 === "replay-walkthrough"))){const err39 = {instancePath:instancePath+"/action",schemaPath:"#/properties/action/enum",keyword:"enum",params:{allowedValues: schema48.properties.action.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data.tourId !== undefined){if(typeof data.tourId !== "string"){const err40 = {instancePath:instancePath+"/tourId",schemaPath:"#/properties/tourId/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data.visibleIf !== undefined){let data23 = data.visibleIf;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(data23.appInstalled !== undefined){let data24 = data23.appInstalled;if(typeof data24 === "string"){if(func2(data24) < 1){const err41 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}else {const err42 = {instancePath:instancePath+"/visibleIf/appInstalled",schemaPath:"#/$defs/visibleIfCondition/properties/appInstalled/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}else {const err43 = {instancePath:instancePath+"/visibleIf",schemaPath:"#/$defs/visibleIfCondition/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data.dynamicSource !== undefined){let data25 = data.dynamicSource;if(data25 && typeof data25 == "object" && !Array.isArray(data25)){if(data25.method === undefined){data25.method = "GET";}if(data25.url !== undefined){if(typeof data25.url !== "string"){const err44 = {instancePath:instancePath+"/dynamicSource/url",schemaPath:"#/$defs/dynamicSource/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}let data27 = data25.method;if(typeof data27 !== "string"){const err45 = {instancePath:instancePath+"/dynamicSource/method",schemaPath:"#/$defs/dynamicSource/properties/method/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(!((data27 === "GET") || (data27 === "POST"))){const err46 = {instancePath:instancePath+"/dynamicSource/method",schemaPath:"#/$defs/dynamicSource/properties/method/enum",keyword:"enum",params:{allowedValues: schema50.properties.method.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}if(data25.labelField !== undefined){if(typeof data25.labelField !== "string"){const err47 = {instancePath:instancePath+"/dynamicSource/labelField",schemaPath:"#/$defs/dynamicSource/properties/labelField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data25.idField !== undefined){if(typeof data25.idField !== "string"){const err48 = {instancePath:instancePath+"/dynamicSource/idField",schemaPath:"#/$defs/dynamicSource/properties/idField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}if(data25.routeField !== undefined){if(typeof data25.routeField !== "string"){const err49 = {instancePath:instancePath+"/dynamicSource/routeField",schemaPath:"#/$defs/dynamicSource/properties/routeField/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}}else {const err50 = {instancePath:instancePath+"/dynamicSource",schemaPath:"#/$defs/dynamicSource/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}if(data.children !== undefined){let data31 = data.children;if(Array.isArray(data31)){const len0 = data31.length;for(let i0=0; i0<len0; i0++){if(!(validate32(data31[i0], {instancePath:instancePath+"/children/" + i0,parentData:data31,parentDataProperty:i0,rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate32.errors : vErrors.concat(validate32.errors);errors = vErrors.length;}}}else {const err51 = {instancePath:instancePath+"/children",schemaPath:"#/properties/children/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}}else {const err52 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}validate31.errors = vErrors;return errors === 0;}validate31.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const schema53 = {"type":"object","required":["id","route","type","title"],"additionalProperties":false,"description":"A v2 page definition. CnPageRenderer dispatches by type and matches by $route.name === page.id. All page types support a uniform widgets[] array for declarative slot placement. Sentinels (@resolve:*) are NOT allowed in id, route, component, headerComponent, actionsComponent, or slots.* — those are router and registry invariants.","properties":{"requiresApp":{"description":"Soft dependency gating this WHOLE page. When the named Nextcloud app is not installed, the page renders the missing-dependency screen instead of its body. This is for the DEEP LINK: an entry gated by visibleIf.appInstalled is already hidden from the menu, so the remaining ways in are a bookmark, a shared URL, a redirect or an e2e spec, and those otherwise land on an empty page where 'not installed' looks the same as 'no data'. Gate a single WIDGET with visibleIf.appInstalled instead: a detail page may carry many widgets where only one needs the app, and that widget should hide while the page renders normally.","oneOf":[{"type":"string","minLength":1},{"type":"object","required":["id"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"description":"Human-readable app name shown on the screen; defaults to the id."}}}]},"id":{"type":"string","description":"Vue-router route name. MUST be unique across pages[]. Sentinels (@resolve:*) are NOT allowed."},"route":{"type":"string","description":"Path pattern (e.g. '/decisions', '/decisions/:id'). Sentinels (@resolve:*) are NOT allowed."},"type":{"type":"string","enum":["index","detail","dashboard","logs","settings","chat","files","form","map","roadmap","reports","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","description":"i18n translation key for the page title."},"permission":{"type":"string","description":"Optional permission identifier required to access this page."},"component":{"type":"string","description":"For type='custom': name resolved against the app-provided customComponents registry. Sentinels (@resolve:*) are NOT allowed."},"headerComponent":{"type":"string","description":"Optional registry name for a component injected into the page's #header slot. Sentinels (@resolve:*) are NOT allowed."},"actionsComponent":{"type":"string","description":"Optional registry name for a component injected into the page's #actions slot. Sentinels (@resolve:*) are NOT allowed."},"sidebarComponent":{"type":"string","minLength":1,"description":"Optional registry name for a component mounted as the default sidebar content for this page."},"slots":{"type":"object","description":"Generic slot-override map. Each key is a slot name; each value is a registry component name. Sentinels (@resolve:*) are NOT allowed in values.","additionalProperties":{"type":"string"}},"primaryAction":{"$ref":"#/$defs/primaryAction","description":"Active-page-scoped primary action rendered as an NcAppNavigationNew button above the menu list when the current route resolves to this page. Page-scoped declarations win over nav.primaryAction. Also the target of the detail page's skip link: a keyboard user tabs in, follows the link and lands on this action."},"splitView":{"$ref":"#/$defs/splitView","description":"Index pages only. Opens a row beside the list rather than instead of it, at the sibling route <route>/split/:id that buildManifestRoutes() emits. Refused on any other page type."},"board":{"$ref":"#/$defs/boardView","description":"Index pages only. The board view's configuration, handed to CnIndexPage as the `board` prop. The segment appears only when `viewModes` also lists `board`. Refused on any other page type."},"dateAxis":{"$ref":"#/$defs/dateAxisView","description":"Index pages only. The date-axis view's configuration, handed to CnIndexPage as the `dateAxis` prop. The segment appears only when `viewModes` also lists `dateAxis`. Refused on any other page type."},"savedViewTree":{"$ref":"#/$defs/savedViewTree","description":"Index pages only. Renders this page's saved views as a tree with labels, and declares the views that ship with the app. Refused on any other page type: a page with no saved-view dropdown has nothing to put in a tree."},"manualOrder":{"type":"boolean","default":false,"description":"Index pages only. Lets a person drag the rows into an order of their own, held per user and per list in their Nextcloud preferences and never written onto the records. Refused on any other page type."},"tabInAddress":{"type":"boolean","default":false,"description":"Detail pages only. Puts the active tab in the address as ?_tab=<id> 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,"description":"Lets a reference on this page show a summary of the record it points at, on focus and on hover, without leaving the page. Rendered by CnReferencePreview."},"widgets":{"type":"array","items":{"$ref":"#/$defs/widgetEntry"},"description":"Uniform widget placement array. Available on ALL page types in v2 (not just dashboard). Each entry specifies a widget key, slot, and grid coordinates. The cross-field constraint gridX + gridWidth <= 12 is enforced by validateManifestV2() post-schema check. Single-widget dashboards: a type:'dashboard' page with exactly one body widget covering the full 12×12 grid whose widgetKey resolves to a custom (non-library-built-in) registry component is REJECTED — see ADR-036 Decision 1 (single-widget dashboard anti-pattern). Use type:'custom' with component:'<widgetKey>' or split into N>1 widgets instead. Library built-in widget keys are exempt from the rule; the exempt set is LIBRARY_WIDGET_KEYS in src/utils/libraryWidgetKeys.js, which is held to set equality against the BUILT_IN_WIDGETS registry and the dashboard widget catalog by tests/utils/libraryWidgetKeys.spec.js. It is not restated here, because an out-of-date second copy of that list is the defect this replaced. Enforced by validateManifestV2() post-schema check."},"actions":{"type":"array","items":{"$ref":"#/$defs/action"},"description":"Typed page-level actions with a discriminator. Each action has a type field (handler | open-modal | open-page | navigate) that defaults to 'handler' when omitted."},"config":{"type":"object","description":"Type-specific configuration. Shape varies by page type — see v1 schema for per-type config shapes (index, detail, dashboard, logs, settings, chat, files, form, map, custom). Config values under pages[].config may use the closed sentinel vocabulary (see $defs/sentinelTokenAny) — an out-of-vocabulary @-token FAILS validation via the sentinelGuardedValue allOf guard. Carry-forward from v1: register, schema, columns, actions, sidebar, sidebarProps, fields, sections, tabs, content, layout, widgets (dashboard only), cardComponent, columnGroups, etc.","additionalProperties":true,"allOf":[{"$ref":"#/$defs/sentinelGuardedValue"},{"$comment":"screens-chrome-parity: a page declares ONE place to save. `saveMode` excludes a section's own `save` (flat or inside a tab) and `autosave`.","if":{"required":["saveMode"]},"then":{"not":{"required":["autosave"],"properties":{"autosave":{"const":true}}},"properties":{"sections":{"type":"array","items":{"type":"object","not":{"required":["save"]}}},"tabs":{"type":"array","items":{"type":"object","properties":{"sections":{"type":"array","items":{"type":"object","not":{"required":["save"]}}}}}}}}}],"properties":{"favourite":{"type":"boolean","description":"Deprecated and ignored: there is no star any more, a favourite is a follow with notifications off. Kept so existing manifests stay valid; use follow."},"follow":{"type":"boolean","description":"Detail pages: the one Follow control beside the title, with a notifications on/off switch while the user follows and the follower count for a user who may update the record. Omitted means automatic: it shows when the object carries @self.watching. false removes it, and the object read then no longer asks for @self.can."},"showFollowColumn":{"type":"boolean","description":"Index pages: add a first column with a follow toggle per row, bound to each row's @self.watching. Clicking it does not open the row. Default false."},"showFavouriteColumn":{"type":"boolean","description":"Deprecated alias of showFollowColumn: the star column became the follow column. Default false."},"personalLenses":{"type":"array","description":"Index pages: personal lenses added to the quick filters, in order. watching lists the records the user follows (labelled Following), recent the records the user opened last (it owns the order, so column sorting is off), unread the records changed since the user last looked. They combine with every other filter. favourite is a deprecated alias of watching; asking for both gives one tab. Default none.","items":{"enum":["favourite","recent","watching","unread"]},"uniqueItems":true},"markRead":{"type":"boolean","description":"Detail pages: send PUT .../read-state once after the object has rendered, when it carries @self.unread, so opening a record marks it read. Default true; false keeps the page from marking anything read."},"lifecycleActions":{"description":"Detail pages: status-gated lifecycle transition buttons (CnLifecycleActions). `inputs` gives the transition dialog hints for server-declared transitions: a map of action name to a list of { field, picker?, fields? }, merged onto the server's declared input with the same `field`. `picker` { filter, excludeSelf, labelField } narrows the records a reference input offers; `fields` names the sub-properties an object input asks for. A hint for a field the server does not declare is ignored with a console warning and never adds an input.","if":{"type":"object"},"then":{"properties":{"inputs":{"type":"object","additionalProperties":{"type":"array","items":{"type":"object","required":["field"],"properties":{"field":{"type":"string","minLength":1},"picker":{"type":"object","additionalProperties":false,"properties":{"filter":{"type":"object"},"excludeSelf":{"type":"boolean"},"labelField":{"type":"string"}}},"fields":{"type":"array","items":{"type":"string"}}}}}}}}},"headerFields":{"type":"array","description":"Detail pages: fields shown as chips under the title (number, status, assignee, deadline). Each entry is a property key, or an object { key, format, labelField, colorField, warnWhenPast }. `format` is text (default), mono, badge, user or date. A `$ref` value shows `labelField` of the referenced object and falls back to the raw id in mono. An empty value shows no chip; a page without the key shows no row.","items":{"oneOf":[{"type":"string","minLength":1},{"type":"object","required":["key"],"additionalProperties":false,"properties":{"key":{"type":"string","minLength":1},"format":{"enum":["text","mono","badge","user","date"]},"labelField":{"type":"string"},"colorField":{"type":"string"},"warnWhenPast":{"type":"boolean"}}}]}},"searchInFiles":{"type":"boolean","description":"Index pages: show an \"Also search inside files\" switch beside the search box. On, the list query carries `_content_search=true` with a search term, and a row found through an attached file shows \"Found in {file}\". Default false."},"entitySource":{"type":"string","enum":["flows","flowRuns","tasks","endpoint","search","users","organisations"],"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","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`.","items":{"type":"object","additionalProperties":false,"required":["when","class"],"properties":{"when":{"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."}}}},"sourceConfig":{"type":"object","additionalProperties":true,"description":"type='index' with `entitySource`: what the named source loads with. `endpoint`: `url` (an app path, required) and `rowsPath` (default `results`); its other keys go along as query parameters. `search`: `kinds` (`{ id, label, register, schema, titleField?, subtitleField?, statusField?, href?, limit? }` or `{ id, label, files: true }`), `term` and `termQuery` (default `q`). `users`: `defaultGroup`, `functionField`, `rolesField`. `flows` / `tasks`: `app`, `scope`. Maps to CnIndexPage `sourceConfig`.","properties":{"url":{"type":"string","minLength":1},"rowsPath":{"type":"string","minLength":1},"kinds":{"type":"array","items":{"type":"object","required":["id"],"additionalProperties":true,"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string"},"register":{"type":"string"},"schema":{"type":"string"},"files":{"type":"boolean"}}}},"term":{"type":"string"},"termQuery":{"type":"string","minLength":1},"defaultGroup":{"type":"string","minLength":1},"limit":{"type":"integer","minimum":1,"description":"A paging source: rows per page (20 by default); the pager moves through the pages."}}},"app":{"type":"string","description":"Scopes a named `entitySource` to one app (e.g. entitySource='flows' with app='dossiq' lists that app's flows)."},"register":{"type":"string","description":"OpenRegister register slug."},"schema":{"type":"string","description":"OpenRegister schema slug."},"contentField":{"type":"string","description":"type='wiki': property on the article object holding the markdown body. Defaults to 'body'. Maps to CnWikiPage `contentField` prop."},"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`."},"idParam":{"type":"string","description":"type='wiki': $route.params key holding the article id. Defaults to 'id'. Maps to CnWikiPage `idParam` prop."},"treeField":{"type":"string","description":"type='wiki': property on each sidebar tree node holding its child array. Defaults to 'children'. Maps to CnWikiPage `treeField` prop."},"sidebarTitleField":{"type":"string","description":"type='wiki': property on each sidebar tree node holding its label. Defaults to `titleField`. Maps to CnWikiPage `sidebarTitleField` prop."},"sidebarRegister":{"type":"string","description":"type='wiki': register slug for the sidebar tree. Defaults to `register`. Maps to CnWikiPage `sidebarRegister` prop."},"sidebarSchema":{"type":"string","description":"type='wiki': schema slug providing the sidebar tree. When set AND `tree` is non-empty the sidebar renders. Maps to CnWikiPage `sidebarSchema` prop."},"emptyText":{"type":"string","description":"type='wiki': empty-state heading shown when the article is null. Maps to CnWikiPage `emptyText` prop."},"emptyDescription":{"type":"string","description":"type='wiki': empty-state description shown when the article is null. Maps to CnWikiPage `emptyDescription` prop."},"emptyBodyText":{"type":"string","description":"type='wiki': empty-body heading shown when the article has no content. Maps to CnWikiPage `emptyBodyText` prop."},"emptyBodyDescription":{"type":"string","description":"type='wiki': empty-body description shown when the article has no content. Maps to CnWikiPage `emptyBodyDescription` prop."},"cardComponent":{"type":"string","description":"Registry name for a custom card component on index pages. Carry-forward from v1.3.0."},"sidebar":{"description":"Sidebar configuration. For index: object with enabled, show, columnGroups[], facets, etc. For detail: boolean or object with show, enabled, register, schema, tabs[]. Carry-forward from v1.3.0.","oneOf":[{"type":"boolean"},{"type":"object","additionalProperties":true,"properties":{"show":{"type":"boolean","description":"Whether to show the sidebar on this page."},"enabled":{"type":"boolean","description":"Whether the sidebar is enabled."},"columnGroups":{"type":"array","description":"Index sidebar column-visibility groups. Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}}}}]},"tabs":{"type":"array","description":"Settings tabs (for type='settings'). Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}},"sidebarTabs":{"type":"array","description":"Sidebar tabs declaration for type='detail' pages. Each entry is the human-authored source of truth for one tab — `id` + `label` (required), optional `icon`, `order`, `component`. Widgets bound to a tab carry `tabGroup: \"<tab-id>\"` on `slot:\"sidebar\"` entries; cross-reference is validated. The CLI manifest-migrate transform lifts `config.sidebarTabs[].widgets[]` into top-level `widgets[]` with `slot:\"sidebar\"` + `tabGroup`; tab entries declaring only a `component` are carried forward as residual sidebarTabs[]. See src/cli/transforms/liftSidebarTabWidgets.js.","items":{"type":"object","additionalProperties":true,"required":["id","label"],"properties":{"id":{"type":"string","minLength":1,"description":"Unique tab identifier within the page. Referenced from widgets[].tabGroup."},"label":{"type":"string","minLength":1,"description":"i18n key (or literal) for the tab label."},"icon":{"type":"string","description":"Optional icon name (CSS class or MDI identifier) shown next to the label."},"order":{"type":"number","description":"Optional sort key for tab ordering; lower values render first."},"component":{"type":"string","description":"Optional registry name for a full-tab custom component. When set, the tab renders that component directly instead of the per-tab widgets[] grouping. Resolved against the customComponents registry."},"_note":{"type":"string","description":"Optional human note (e.g. lib-gap reason for an undecomposable tab)."}}}},"sections":{"type":"array","description":"Settings sections (for type='settings'). Carry-forward from v1.3.0.","items":{"type":"object","additionalProperties":true}},"fields":{"type":"array","description":"Form fields (for type='form'). Carry-forward from v1.3.0. manifest-form-logic adds two OPTIONAL typed properties — `visibleWhen` (reuses `$defs/visibleWhen`; LOCAL mode resolves `field` as a dot-path into the live form data) and `validation` (`$defs/fieldValidation`) — while every other carry-forward field shape (`key`, `label`, `type`, `enum`, `widget`, `help`, etc.) stays untyped via `additionalProperties: true` on the item.","items":{"type":"object","additionalProperties":true,"properties":{"visibleWhen":{"$ref":"#/$defs/visibleWhen","description":"manifest-form-logic: optional conditional-visibility predicate. LOCAL mode (neither `endpoint` nor `source`) resolves `field` as a dot-path into the live form data — the first dot-segment MUST match a declared `config.fields[].key` (validateManifestV2 post-schema check). `endpoint` / `source` modes keep their existing fail-safe, evaluated-once-at-mount semantics."},"validation":{"$ref":"#/$defs/fieldValidation","description":"manifest-form-logic: optional per-field validation rules, enforced client-side by CnFormPage before dispatch. See `$defs/fieldValidation` for the per-type semantics table."}}}},"steps":{"type":"array","minItems":1,"description":"manifest-form-logic: for type='form' pages, ordered multi-step wizard groups. Each entry is `{ id, title, description?, fields[] }` where `fields[]` is an ordered array of KEY REFERENCES into `config.fields[].key` — steps do not embed field objects (config.fields[] stays the single source of truth). Absent `steps` = single-step form, byte-for-byte the pre-change rendering. When present, validateManifestV2() post-schema requires: step ids unique within the page, every fields[] entry references a declared field key, and every declared field key appears in EXACTLY ONE step (complete partition — an unassigned field would silently never render; a doubly-assigned field would double-render).","items":{"type":"object","required":["id","title","fields"],"additionalProperties":false,"description":"One wizard step. `fields[]` order is the rendering order for that step's fields.","properties":{"id":{"type":"string","minLength":1,"description":"Unique step identifier within the page's `steps[]` array."},"title":{"type":"string","minLength":1,"description":"i18n key (or literal) for the step's title, shown in the step indicator."},"description":{"type":"string","description":"Optional i18n key (or literal) for a short step description/subtitle."},"fields":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Ordered field KEYS (referencing `config.fields[].key`) rendered on this step."}}}},"fieldWidgets":{"type":"array","description":"Declarative widget-component slots for type='form' / type='detail' pages. Each entry mounts a lib `Cn*` SFC inline (e.g. CnMarkdownEditor as a markdown field, CnSchemaSelect as a relation picker). Distinct from top-level pages[].widgets[] which uses widgetKey + grid coordinates against the host customComponents registry. Carries serialisable props only. See $defs/fieldWidget and ConductionNL/nextcloud-vue#314.","items":{"$ref":"#/$defs/fieldWidget"}},"columns":{"type":"array","description":"Table columns (for type='index' and type='logs'). Items may be strings (legacy shorthand) or column objects ({ key, label, sortable?, width?, formatter?, formatterOptions?, widget?, widgetProps?, format?, aggregate?, colorMap? }). `colorMap` maps an enum column's raw values to a pill tone (default, primary, success, warning, error, info, purple, teal) and wins over the schema property's own `colorMap` / `x-color-map`. `formatter` resolves against cnFormatters (built-ins incl. currency / conditionalPhrase — configured via `formatterOptions`); `widget` resolves against cnCellWidgets (built-ins incl. badge / fkResolve / link / avatar / date).","items":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":true}]}},"folderSidebar":{"type":"object","additionalProperties":true,"description":"type='index': the opt-in folder navigation pane (CnFolderSidebar) rendered left of the list. Selecting a folder filters the list by `filterField` (or `field`); \"All\" clears it. Only the per-scope list-layout keys are typed here; the pane's own keys (source, register, schema, idField, nameField, folders, allLabel, title, allowCreate, ...) stay free-form. Maps to CnIndexPage `folderSidebar`.","properties":{"scopeViews":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"The view modes (`table`, `list`, `cards`, `board`, ...) a scope's look applies in; in the others the page keeps its generic look while the scope still filters (DqWerkbord, 2.129.0)."},"heading":{"type":"boolean","description":"true heads the list with the selected folder's name, its parent folder and its count (always on a `groups` pane, 2.127.0)."},"countText":{"type":"string","minLength":1,"description":"The count in that heading, `{count}` filled in, through the app's translate; a groups pane defaults to \"{count} members\" (2.127.0)."},"pane":{"type":"boolean","default":true,"description":"false keeps the folder scope (a deep link such as `?caseType=<uuid>` still applies the scope's look and filter) without drawing the pane (dossiq's simple profile, 2.126.0)."},"folders":{"type":"array","description":"Explicit folder list for source:'custom', and the place a folder declares its own list layout. Every other folder key stays free-form.","items":{"$ref":"#/$defs/folderSidebarScope"}}}},"layout":{"type":"array","description":"Dashboard layout entries (for type='dashboard', legacy). Carry-forward from v1.","items":{"type":"object","additionalProperties":true}},"content":{"type":"array","description":"Declarative content items for dashboard (widget-ref items). Carry-forward from v1.","items":{"type":"object","additionalProperties":true}},"widgets":{"type":"array","description":"Legacy dashboard widgets array in config (for type='dashboard'). In v2, prefer top-level pages[].widgets[] with the uniform widgetEntry shape. This legacy config.widgets key remains for back-compat.","items":{"type":"object","additionalProperties":true}},"stageField":{"type":"string","minLength":1,"default":"status","description":"type='detail': dot-path to the field holding the record's stage. Read by `primaryActionByStage` and `nextStep`."},"primaryActionByStage":{"type":"object","description":"type='detail': the page's primary button per stage. Keys are values of `stageField` (matched exactly first, then ignoring case). A value is an action in the header action shape, or the id of a `headerActions` entry, which then leaves the overflow menu. A stage without an entry falls back to the page's `primaryAction`.","additionalProperties":{"oneOf":[{"type":"string","minLength":1},{"$ref":"#/$defs/headerAction"}]}},"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. `{ 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"},{"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."}}}]}},"actionsMenu":{"type":"object","additionalProperties":false,"description":"type='detail': what the header's overflow menu itself shows. Omitted, the menu keeps Refresh and the three help links.","properties":{"showRefresh":{"type":"boolean","description":"false removes Refresh from the menu."},"showHelpLinks":{"type":"boolean","description":"false removes Request a feature, Report a bug and Documentation, for an app that offers them in its own help menu."},"showMarkUnread":{"type":"boolean","description":"false removes the built-in Mark as unread entry."},"showOpenSidebar":{"type":"boolean","description":"false removes the built-in Open sidebar entry of the board look."},"label":{"type":"string","minLength":1,"description":"i18n key / text naming the menu, for example 'More'. Defaults to 'Actions'."}}},"nextStep":{"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. `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,"description":"Dot-path to the stage, when it differs from `stageField`."},"stagesFrom":{"type":"string","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)."},"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."},"stages":{"type":"object","description":"Keys are stage values.","additionalProperties":{"type":"object","additionalProperties":false,"required":["checklist"],"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."},"checklist":{"type":"array","description":"What this stage still needs. An empty list shows no card.","items":{"type":"object","additionalProperties":false,"required":["label"],"properties":{"label":{"type":"string","minLength":1,"description":"i18n key / text of the item."},"hint":{"type":"string","description":"i18n key / short note after the label, for example '2 of 5 done'."},"doneField":{"type":"string","minLength":1,"description":"Dot-path on the record. The item is done when it holds a value; a list counts when it is not empty."},"doneWhen":{"type":"object","description":"A local visibleWhen condition ({ field, op, value } or { all | any }). The item is done when it is true. Wins over `doneField`."},"done":{"type":"boolean","description":"A fixed state, for an item the record cannot answer."}}}}}}}}},"typePill":{"$ref":"#/$defs/headerPill","description":"type='detail': a pill above the title saying what kind of record this is."},"statusPill":{"$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}}}]}},"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.","items":{"oneOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":true,"required":["type"],"properties":{"id":{"type":"string"},"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)."},"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}}}}}]}},"tabsFrom":{"type":"string","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`."},"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.","items":{"$ref":"#/$defs/headerAction"}},"bulkActions":{"type":"array","description":"Bulk actions rendered in the contextual selection strip that appears while rows are selected. Same shape as the row-level `actions[]` ($ref action), but the handler receives the SELECTION rather than a row: `{ actionId, selectedIds, count }`. For `type: \"open-modal\"` the selection is merged into the modal's props as `selectedIds` and `count`, so a manifest-declared modal component can act on the selection without the page hand-writing anything. Reserved ids (`copy`, `delete`) are dropped at render time: the strip already ships both as built-ins. Before this existed the strip had only a `#selection-actions` slot, which only a hand-written host component could fill — so a page declared in a manifest could carry row actions and header actions but never a bulk one.","items":{"$ref":"#/$defs/action"}},"actions":{"type":"array","items":{"anyOf":[{"type":"object","required":["label"],"additionalProperties":true,"properties":{"label":{"type":"string","minLength":1},"id":{"type":"string","not":{"pattern":"^builtin:"}},"builtin":false}},{"type":"string","enum":["builtin:view","builtin:edit","builtin:copy","builtin:delete"]}]},"allOf":[{"contains":{"const":"builtin:view"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:edit"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:copy"},"minContains":0,"maxContains":1},{"contains":{"const":"builtin:delete"},"minContains":0,"maxContains":1}],"description":"Declarative actions on a page. An object entry is governed ONLY by \"an object carrying a non-empty label\", deliberately: this key is polymorphic by page type (index rows dispatch $defs/action; detail pages also carry type='lifecycle-transition' entries), so one closed grammar here would reject working manifests. A label is what every dialect needs — CnRowActions renders an entry from its label and icon, so an entry without one became a full-height, clickable, EMPTY row in the overflow menu. An object is always an app action whatever its id: an id, when present, must be a string that does not start with 'builtin:', and the object may not set a 'builtin' key. On a type='index' page an entry may also be one of the placeholders \"builtin:view\", \"builtin:edit\", \"builtin:copy\" or \"builtin:delete\", each at most once, which puts that built-in row action at its position in the menu. A placeholder only places a built-in: config.actionToggles / the show*Action keys still decide whether it renders. Enabled built-ins the array does not place are appended after everything else in the order View, Edit, Copy, Delete. Any other string (a bare \"edit\", an unknown \"builtin:archive\") is refused, and placeholders are refused on every page type other than index."},"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, 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"}},"rowIndicatorCap":{"type":"integer","minimum":0,"default":3,"description":"type='index': how many declared indicators render on the row itself before the rest move into the row menu. Maps to CnIndexPage `rowIndicatorCap`."},"quickEditFields":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': the fields a quick edit asks for, opened from a row without leaving the list. The page names them; a record cannot open a form on a field the page did not name. A field this caller may not write (see `writableField`) renders read-only rather than missing. Maps to CnIndexPage `quickEditFields`."},"writableField":{"type":"string","default":"@self.writableFields","description":"type='index': the dotted path where a record lists the fields this caller may write. A row carrying nothing there leaves every field the quick edit named editable; an empty list is a refusal and locks them all. Maps to CnIndexPage `writableField`."},"viewTabs":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': saved view ids this page renders as tabs instead of as entries in the views control. A view appears in one place or the other, never both. An id naming a view that no longer exists produces no tab, rather than a tab that opens nothing. Maps to CnIndexPage `viewTabs`."},"viewCounts":{"description":"type='index': which saved views show how many records they match. `true` counts every saved view (tabs and the entries in the views control), an array counts only the view ids it lists. Off by default, so no count request is made. Quick filters opt in per entry with `showCount: true`. Maps to CnIndexPage `viewCounts`.","oneOf":[{"type":"boolean"},{"type":"array","items":{"type":"string","minLength":1}}],"default":false},"priorityField":{"type":"string","description":"type='index': the field carrying a record's DERIVED priority. The list reads the value and sorts on it; it never computes one. A record with no priority, or one outside `priorityLevels`, sorts after every ranked record in BOTH directions and stays in the list. Maps to CnIndexPage `priorityField`."},"priorityLevels":{"type":"array","items":{"type":"string","minLength":1},"description":"type='index': the priority values in rank order, lowest first (e.g. ['low', 'medium', 'high']). Needed because a priority does not sort alphabetically into the order a person means. Maps to CnIndexPage `priorityLevels`."},"listShortcuts":{"type":"boolean","default":false,"description":"type='index': offer the list's keyboard shortcuts. Every shortcut is also listed in the command palette and on the help key, from one catalogue, because a shortcut nobody can find does not count. Maps to CnIndexPage `listShortcuts`."},"rowActionField":{"type":"string","description":"type='index': the dotted path on each row where the server lists the actions this caller may run on that record (default '@self.actions'). A row's menu is then the intersection of what `config.actions` declares and what the server allows: an action the server allows but the page does not declare stays out, and an action the page declares but the server refuses stays out with its reason kept. A row carrying nothing at this path leaves the declaration standing. Maps to CnIndexPage `rowActionField`."},"editOpensDetail":{"type":"boolean","description":"type='index': send the Edit row action to the record's detail page instead of opening the edit modal. OPT-IN — CnPageRenderer used to set this wherever a type='detail' page or config.rowRoute existed, and it no longer does: @edit-open is bound to the same navigation as a row click, so a routed Edit just repeats the row click (the split pane, or the detail page) and the form is no longer reachable from the list. Declare true per page when the modal cannot express the record. Maps to CnIndexPage `editOpensDetail`."},"showEditAction":{"type":"boolean","description":"type='detail': show the header Edit button that opens the record's schema form. Set automatically by CnPageRenderer for every schema-bound detail page; declare false to make a detail page read-only. Maps to CnDetailPage `showEditAction`."},"formSize":{"type":"string","enum":["small","normal","large"],"default":"normal","description":"NcDialog size for the page's built-in Add/Edit form dialog (type='index' and type='detail'). A manifest `open-form` header action has always been able to ask for `large`; the built-in dialog could not, so the same app could have a roomy create form on one surface and a cramped one on another. Maps to CnIndexPage / CnDetailPage `formSize`."},"formColumns":{"type":"integer","enum":[1,2],"default":1,"description":"How many columns the page's built-in Add/Edit form flows its fields into. Pair 2 with formSize 'large', or the two columns are merely two narrow ones. CnFormDialog collapses back to one column below 700px, and a textarea or JSON editor spans both columns regardless. Maps to CnIndexPage / CnDetailPage `formColumns`."},"excludeFields":{"type":"array","items":{"type":"string"},"description":"Property names kept OUT of the page's built-in Add/Edit form dialog. Maps to CnIndexPage / CnDetailPage `excludeFields`."},"includeFields":{"type":"array","items":{"type":"string"},"description":"Property names the page's built-in Add/Edit form dialog shows, in this order — a whitelist, so anything absent is hidden regardless of excludeFields. Maps to CnIndexPage / CnDetailPage `includeFields`."},"fieldOverrides":{"type":"object","additionalProperties":{"$ref":"#/$defs/fieldOverride"},"description":"Per-field overrides merged over the schema-derived field descriptors in the page's built-in Add/Edit form dialog (e.g. `{ \"description\": { \"widget\": \"textarea\" } }`). Which widget a field renders as belongs to the surface, not to the schema. Maps to CnIndexPage / CnDetailPage `fieldOverrides`."},"createDefaults":{"type":"object","additionalProperties":true,"description":"type='index': seed values for the page's built-in create dialog, never applied on edit. Resolves `@me`/`@now`/`@today` at any depth; `@object.*`/`@workspace.*`/`@config.*` need a context the page does not carry and pass through unchanged. Maps to CnIndexPage `createDefaults`."},"createSuccessRoute":{"oneOf":[{"type":"string"},{"type":"object","properties":{"name":{"type":"string"},"paramField":{"type":"string"},"objectParam":{"type":"string"}},"required":["name"],"additionalProperties":false}],"description":"type='index': route opened after a successful create from the page's built-in Add dialog; the created object's id is merged into the params. Maps to CnIndexPage `createSuccessRoute`."},"createSuccessMessage":{"type":"string","description":"type='index': toast shown after a successful create from the page's built-in Add dialog. Maps to CnIndexPage `createSuccessMessage`."},"savedViewsScope":{"type":"string","description":"type='index': which pages share this page's saved views. By default a view is shared by every page over the same register and schema and by no other, because a view is filters over one schema's fields. Set this to share views across pages over different sources, or to keep two pages over one source apart. Written into the saved view's `query.scope`; views saved before scoping stay visible everywhere. Maps to CnIndexPage `savedViewsScope`."},"defaultSavedViews":{"type":"array","description":"type='index': views the app ships for this page. Listed read-only above each user's own saved views, under Default views, and never stored: a new app version updates them for everybody and no user can delete one. A user may apply a default or save a copy, which they then own. `@`-tokens in `filters` (`@me`, `@today`, `@today+7d`) resolve when the default is applied; a copy keeps them. Maps to CnIndexPage `defaultSavedViews`.","items":{"type":"object","additionalProperties":false,"required":["slug","name","filters"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The stable name of the default. Lowercase and dash only."},"name":{"type":"string","minLength":1,"description":"What the filter is called in the control. Translatable."},"filters":{"type":"object","description":"The filters, keyed by property, in the shape a quick filter's `filter` takes."},"search":{"type":"string","description":"A search term the default applies."},"sort":{"type":"array","description":"Sort keys, in priority order.","items":{"type":"object","additionalProperties":false,"required":["key"],"properties":{"key":{"type":"string"},"order":{"type":"string","enum":["asc","desc"]}}}},"label":{"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,"description":"pill-color-field: a registered icon name the view's My views menu entry shows instead of the filter icon (a case type's own icon, say)."},"overrides":{"type":"object","additionalProperties":false,"description":"Page config this view lays over the page while it is active; leaving the view restores the page. Each key is optional and, apart from `header`, the aliases `tabs` and `views`, `sortOption`, `slots` and `badge`, takes the shape of the page config key of the same name. A saved view carries the same object in `query.overrides`, so a copy of a default keeps it (saved-view-overrides, 2.132.0).","properties":{"title":{"type":"string","minLength":1,"description":"The page title. Translatable."},"showTitle":{"type":"boolean","description":"Same as config.showTitle."},"breadcrumb":{"description":"Same as config.breadcrumb.","anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}},{"type":"null"}]},"headerMeta":{"type":"string","description":"Same as config.headerMeta."},"notice":{"type":["object","null"],"description":"Same as config.notice."},"pageTabs":{"type":["array","null"],"description":"Tabs to sibling pages or views, `countFrom` included. Same as config.pageTabs."},"headerButtons":{"type":"array","description":"Same as config.headerButtons."},"addLabel":{"type":"string","description":"Same as config.addLabel."},"searchPlaceholder":{"type":"string","description":"Same as config.searchPlaceholder."},"statRow":{"type":"array","description":"Same as config.statRow."},"quickFilters":{"type":"array","description":"Same as config.quickFilters."},"quickFiltersFrom":{"type":["object","null"],"description":"Same as config.quickFiltersFrom."},"quickFilterPlacement":{"type":"string","description":"Same as config.quickFilterPlacement."},"quickFilterMaxVisible":{"type":"integer","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."},"afterList":{"type":"array","description":"Same as config.afterList."},"columns":{"type":"array","description":"Same as config.columns."},"cardLayout":{"type":["object","null"],"description":"Same as config.cardLayout."},"cardFields":{"type":"array","items":{"type":"string"},"description":"Same as config.cardFields."},"cardComponent":{"type":"string","description":"Same as config.cardComponent."},"listConfig":{"type":"object","description":"Same as config.listConfig."},"viewModes":{"type":"array","items":{"type":"string","minLength":1},"description":"The view modes the toggle offers. Same as config.viewModes."},"viewMode":{"type":"string","minLength":1,"description":"The view mode the view opens in. Same as config.viewMode."},"viewSwitch":{"type":"array","items":{"type":"string"},"description":"Same as config.viewSwitch."},"countText":{"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."},"emptyText":{"type":"string","description":"Same as config.emptyText."},"folderSidebar":{"type":["object","null"],"description":"`null` hides the folder sidebar. Same as config.folderSidebar."},"sidebar":{"type":["object","null"],"description":"Same as config.sidebar."},"defaultSort":{"type":"array","description":"Same as config.defaultSort."},"rowClickToView":{"type":"boolean","description":"Same as config.rowClickToView."},"showViewAction":{"type":"boolean","description":"Same as config.showViewAction."},"actions":{"type":"array","description":"Same as config.actions."},"includeFields":{"type":"array","items":{"type":"string"},"description":"Same as config.includeFields."},"fieldOverrides":{"type":"object","description":"Same as config.fieldOverrides."},"tabs":{"type":["array","null"],"description":"Alias of pageTabs."},"views":{"type":"array","items":{"type":"string","minLength":1},"description":"Alias of viewModes."},"header":{"type":"object","additionalProperties":false,"description":"Groups header keys; a key spelled out beside it wins.","properties":{"title":{"type":"string","minLength":1},"showTitle":{"type":"boolean"},"breadcrumb":{"anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}},{"type":"null"}]},"notice":{"type":["object","null"]},"headerMeta":{"type":"string"},"headerButtons":{"type":"array"}}},"lockedFilters":{"type":"object","description":"Base filters added to the page's `filter` while the view is active, which the reader cannot clear, as the old Queue and Assigned to me pages had (2.132.0)."},"sortOption":{"type":"string","minLength":1,"description":"The `value` of a `sortSelectOptions` entry to select, an endpoint-sourced one included."},"slots":{"type":"object","description":"Named page slots (`below-header`, `before-collection`, ...) mapped to a registry component or a list of them drawn in order, laid over the page's `slots`; `null` or `[]` empties that slot.","additionalProperties":{"anyOf":[{"type":"string","minLength":1},{"type":"array","items":{"type":"string","minLength":1}},{"type":"null"}]}},"badge":{"description":"A count on the view's menu entry, in a menu entry's terms. `true`, or `count: true`, counts the view's own list: the page's register, schema and base filter with the view's filters. A `count` object may name another `register`, `schema` or `filter`. `counterVariant: attention` draws the attention pill.","anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"count":{"anyOf":[{"type":"boolean","const":true},{"type":"object","additionalProperties":false,"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object"}}}]},"counterVariant":{"type":"string","enum":["default","attention"]}}}]}}}}}},"splitCloseButton":{"type":"boolean","default":true,"description":"type='index': whether the split pane draws its own close link. Set false when a `#split-pane` slot draws one from the slot's `close`. Maps to CnIndexPage `splitCloseButton`."},"splitCloseLabel":{"type":"string","description":"type='index': accessible name and tooltip for the split pane's close button. Defaults to \"Close\". Maps to CnIndexPage `splitCloseLabel`."},"look":{"type":"string","enum":["nextcloud","board"],"description":"screens-chrome-parity: draw this page in the `nextcloud` or the `board` look, over the manifest's root `look`. The nearest value wins: the page root carries `cn-look-board` or `cn-look-nextcloud`, and `cnLook` is re-provided to the page's descendants."},"saveMode":{"type":"string","enum":["section","page"],"description":"type='settings' (screens-chrome-parity): where the page saves. `section` ends each section that has fields in its own Save button and draws no page save bar. `page` draws one Save as the last header button and a Changes card listing the changed fields; no section draws a save button. Absent, the page keeps the save bar under the last section. Cannot be combined with `autosave` or with a section that declares its own `save`. Maps to CnSettingsPage `saveMode`."},"autosave":{"type":"boolean","default":false,"description":"type='settings' (screens-chrome-parity): the page saves on change. It draws no save button and its description ends with \"Changes are saved automatically.\" Cannot be combined with `saveMode`. Maps to CnSettingsPage `autosave`."},"showHeader":{"type":"boolean","default":true,"description":"type='dashboard': `false` drops the header row (title, description, header actions, edit toggle) for a page that opens with its own heading, such as a greeting widget; the title stays as a visually hidden heading (zuiddrecht-pixel-gaps). Maps to CnDashboardPage `showHeader`."},"views":{"type":"array","minItems":1,"description":"type='dashboard' | type='detail': views of the page, each its own widget grid behind one segmented control (\"My work | My team\") (view-switch-containers). The chosen view renders in a region below the page's own grid, drawn with the same grid and widgets; the page's own `widgets` and `layout` stay above it. The switch sits in the page header, or in a greeting header widget whose `content.views.options[]` carry `view` instead of `route`. The chosen view is in the address (`?view=<id>`) and in the user's preferences; the address wins. A view with nothing to draw shows `emptyText`, else a default sentence. On a detail page, views replace the automatic Data and Related body. Maps to CnDashboardPage / CnDetailPage `views`.","items":{"type":"object","required":["id","label"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"pattern":"^[A-Za-z0-9_-]+$","description":"The view id, written to the address as `?view=<id>`."},"label":{"type":"string","minLength":1,"description":"The option label and the region's accessible name (through the app's translations)."},"title":{"type":"string","minLength":1,"description":"type='dashboard': the page title while this view is open, e.g. \"My team\" (through the app's translations; view-title, 2.119.0). Without it the page keeps its own title."},"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}},"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}}}}}},"defaultView":{"type":"string","minLength":1,"description":"type='dashboard' | type='detail': the id of the view chosen when neither the address nor the user's stored choice names one; without it the first view (view-switch-containers). Maps to `defaultView`."},"viewsLabel":{"type":"string","minLength":1,"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":{"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:<name>}` in headerMeta (\"{total} contracten bij {count:suppliers} leveranciers\"); while a count loads or when it fails, the note is left out.","additionalProperties":{"type":"object","required":["register","schema"],"additionalProperties":false,"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object"}}}},"tabsLabel":{"type":"string","minLength":1,"description":"type='detail', board look: the accessible name of the tab list, for example \"Case parts\". Defaults to \"<type> parts\" from the page title (screens-detail-page-parity). Maps to CnDetailPage `tabsLabel`."},"identifierField":{"type":"string","minLength":1,"description":"type='detail', board look: the record field (a dotted path, for example `identifier`) that names the record in the last line of the side column's History card (screens-detail-page-parity). Maps to CnDetailPage `identifierField`."},"showWidgetActions":{"type":"boolean","default":true,"description":"type='dashboard' and type='detail': `false` drops the overflow Actions menu from every widget that does not set `showActions` itself, for a dashboard whose widget headers carry a header link and nothing else (zuiddrecht-pixel-gaps-2), or a record page whose data cards carry none (zuiddrecht-pixel-gaps-3; a card with an Add action keeps it). When the key is not set a detail page keeps the menu, except under the board look, where side cards carry none (screens-detail-page-parity). Maps to CnDashboardPage / CnDetailPage `showWidgetActions`."},"pageFilters":{"type":"array","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.<key>` / `@workspace.<key>` token (an endpoint widget also in its `responsePath`). Maps to the CnDashboardPage `pageFilters` prop.","items":{"type":"object","required":["key"],"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.<key>` / `@workspace.<key>` token name)."},"label":{"type":"string","description":"Control label (i18n key or plain text)."},"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":{"type":"object","additionalProperties":false,"description":"Read the options instead of listing them: from an app endpoint (`url`, `params`, `responsePath`) or from OpenRegister objects (`register`, `schema`, `filter`). `valueField` and `labelField` pick each option's value and label (default `id` and `title` or `name`); tokens resolve (2.130.0).","anyOf":[{"required":["url"]},{"required":["register","schema"]}],"properties":{"url":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"responsePath":{"type":"string","minLength":1},"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true},"valueField":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1}}},"placement":{"type":"string","enum":["row","description"],"default":"row","description":"Where the filter sits: in the header's filter row, or (a segmented group) on the description line after the period group (DqProcesanalyse)."},"default":{"type":["string","number"],"description":"Initial value seeded into the context on mount; the first option's value when omitted."},"options":{"type":"array","items":{"type":"object","required":["value","label"],"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)."},"months":{"type":"integer","minimum":1,"description":"A rolling window of this many months ending with the current one. A dashboard description's `{filter.<key>}` 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)."}}}}}}},"kpiRow":{"type":"array","items":{"type":"string"},"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."},"greeting":{"oneOf":[{"type":"boolean"},{"type":"string","enum":["first","full","date"]},{"type":"object","required":["label"],"additionalProperties":false,"properties":{"label":{"type":"string","minLength":1,"description":"i18n key drawn where the greeting would be."}}}],"default":false,"description":"type='dashboard': greet the reader in the header subtitle. `true` or \"first\" writes \"Good afternoon, Pieter · Monday 5 October 2026\", \"full\" uses the whole display name; the page `description` follows after another middle dot (screens-dashboard-greeting-header). Since 2.111.0 \"date\" leads with the date alone (\"Tuesday 7 October 2026 · ...\") and `{ \"label\": \"Customer contact\" }` puts the translated label where the greeting would be (dashboard-greeting-label). Maps to CnDashboardPage `greeting`."},"viewLinks":{"type":"array","default":[],"description":"type='dashboard': links drawn as small pills on the switch row under the header, right of the view switch (screens-dashboard-greeting-header). `route` is a route name or a `{ name, params, query }` location, `href` a plain URL; `label` is an i18n key. An entry without a label or a target is skipped. Maps to CnDashboardPage `viewLinks`.","items":{"type":"object","required":["label"],"additionalProperties":false,"properties":{"label":{"type":"string","minLength":1},"icon":{"type":"string","minLength":1},"route":{"oneOf":[{"type":"string","minLength":1},{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"params":{"type":"object"},"query":{"type":"object"}}}]},"href":{"type":"string","minLength":1}}}},"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`. 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' 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","default":false,"description":"type='index': show the page header (icon, title, description) inline. Default false: the title renders visually hidden and the sidebar names the page. Maps to CnIndexPage `showTitle`."},"countSubtitle":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":false,"required":["singular","plural"],"properties":{"singular":{"type":"string","minLength":1,"description":"i18n key of the line for a total of one, e.g. \"{total} invoice\"."},"plural":{"type":"string","minLength":1,"description":"i18n key of the line for any other total, e.g. \"{total} invoices\"."}}}],"description":"type='index': an i18n key for the description line with `{total}` replaced by the collection's total, e.g. \"{total} open cases\" (zuiddrecht-pixel-gaps). Shown in place of `description` once a total is known. `{ singular, plural }` picks the form by the total through the app's plural translation (`translatePlural`), so \"1 factuur\" and \"12 facturen\" each read right (index-count-plural). Maps to CnIndexPage `countSubtitle`."},"showTitleIcon":{"type":"boolean","default":true,"description":"type='index': `false` drops the icon before the page title, for a title that stands alone as the board draws it (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showTitleIcon`."},"showCount":{"type":"boolean","default":true,"description":"type='index': `false` drops the actions bar's \"Showing 20 of 258\" line, for a page whose title line already gives the total through `countSubtitle` (zuiddrecht-pixel-gaps-3). Maps to CnIndexPage `showCount`."},"countText":{"type":"string","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`."},"footerNote":{"type":"string","minLength":1,"description":"type='index', board look: free text after the footer's count, e.g. \"click a column header to sort\" (screens-index-list-parity). Maps to CnIndexPage `footerNote`."},"queryPresets":{"type":"array","minItems":1,"description":"type='index': per-query overlays of this page (screens-index-query-presets). A menu entry that deep-links here with a query (`menu[].query`, e.g. { caseType: '<uuid>' }, the shape hydra gate-68 asks for instead of a second page) gets its own lenses, columns and copy: the first preset whose every `match` pair is in the route query replaces those keys of this config. Only the listed keys can be replaced; register, schema and permissions stay the page's. Switching preset remounts the list.","items":{"type":"object","required":["match"],"additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"description":"A name for the preset, for people reading the manifest."},"match":{"type":"object","minProperties":1,"additionalProperties":{"type":["string","number","boolean"]},"description":"Route query pairs that must ALL be present, e.g. { \"caseType\": \"<uuid>\" }. A repeated parameter matches when it holds the value."},"title":{"type":"string","minLength":1,"description":"The page title while the preset applies."},"quickFilters":{"type":"array","description":"The lenses while the preset applies; same shape as config.quickFilters."},"columns":{"type":"array","description":"The columns while the preset applies; same shape as config.columns."},"cardFields":{"type":"array","items":{"type":"string"},"description":"The card facts while the preset applies."},"countText":{"type":"string","minLength":1,"description":"The count line while the preset applies."},"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","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)."},"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)."},"afterList":{"type":"array","description":"The cards after the list while the preset applies; same shape as config.afterList (index-scope-presentation).","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"}}}}}}},"viewSwitch":{"type":"array","minItems":1,"uniqueItems":true,"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 \"<mode>: 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","additionalProperties":false,"required":["field"],"description":"type='index': a second filter strip under the toolbar, on one field (board PqWachtrij: the kind in the quick filters, the channel in this strip). \"All\" and one pill per value (`options`, else the field's schema enum); picking one filters the list on that value, All clears it. With `showCount` each pill counts its rows within the active quick filter (screens-filter-strip, 2.92.0). Maps to CnIndexPage `filterStrip`.","properties":{"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}}}},"showCount":{"type":"boolean","default":false}}},"viewSwitchLinks":{"type":"object","additionalProperties":false,"minProperties":1,"description":"type='index', board look: view switch segments that open another PAGE instead of switching in place, by mode: `{ \"board\": { \"route\": \"CaseBoard\" } }` for a board or a map that is a page of its own. The segment sits in its place in the switch (table, cards, board, map) as a link: `route` with optional `params` and `query`, or `href`. A mode the page opens in place stays a button, and a route the router cannot resolve is left out. Ignored without the board look (screens-view-switch-links, 2.91.0). Maps to CnIndexPage `viewSwitchLinks`.","properties":{"table":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"cards":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"board":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}},"map":{"type":"object","additionalProperties":false,"anyOf":[{"required":["route"]},{"required":["href"]}],"properties":{"route":{"type":"string","minLength":1},"params":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":true},"href":{"type":"string","minLength":1}}}}},"searchPlaceholder":{"type":"string","minLength":1,"description":"type='index': the placeholder (and accessible name) of the list's search field, e.g. \"Search by case, number or requester\". Run through the app's translate, so an English key with a Dutch catalogue entry reads Dutch. Absent: the library's \"Search…\" (screens-index-toolbar-parity). Maps to CnIndexPage `searchPlaceholder`."},"bulkHint":{"type":"string","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}},"cardLayout":{"type":"object","additionalProperties":false,"description":"type='index', board look: the record layout of each card in the cards view (board-record-card-layout): a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date. Each part names a field (a dot path) and is left out while it is empty; a pill maps values to a badge variant (`colorMap`, ignoring case) and to shown text (`labels`). Maps to CnIndexPage `cardLayout`.","properties":{"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"}}}]}},"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"]}}}]}}}},"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"}}}},"statRow":{"type":"array","default":[],"description":"type='index': KPI tiles above the list, between the header and the toolbar, in an auto-fit row (each at least 200px). Each entry is a widget definition, usually `type: \"stat\"`; the type resolves through the app's widget registry, then the dashboard catalog (screens-index-stat-row). Maps to CnIndexPage `statRow`.","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","minLength":1},"title":{"type":"string"},"content":{"type":"object"},"headerLink":{"type":"object"}}}},"sidePanel":{"type":"array","default":[],"description":"type='index': titled cards in a 300px column right of the toolbar and the list (above them below 1024px). Each entry is a widget definition with an optional `title` (i18n key) and `headerLink`; the type resolves as for `statRow` (screens-index-stat-row). Maps to CnIndexPage `sidePanel`.","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","minLength":1},"title":{"type":"string"},"content":{"type":"object"},"headerLink":{"type":"object"}}}},"headerButtons":{"type":"array","description":"type='index': buttons beside the page title, as the board's \"Export\" and \"New case\" (zuiddrecht-pixel-gaps-3). `action` is `add` (the Add flow; the label defaults to the Add label), `export` (the export leaf in `format`, else the export dialog; under the board look it reads \"Download\" by default), `actions-menu` (the page's header actions as one labelled menu button, absent when the page has none), `import`, `refresh`, or the id of a `headerActions` entry. Under the board look the buttons render in a fixed order whatever the manifest declares: export, actions-menu, other secondary buttons, the buildiq square, then the primary button. Shown only with `showTitle`; when they show, the actions bar drops its Views and Actions menus, and the Add button and Export menu when a button takes their action. Maps to CnIndexPage `headerButtons`. type='reports' (screens-reports-header-buttons, 2.96.0): the same buttons beside the reports page title, as board DcRapportages draws \"Downloaden\". There `label` is required and `action` is the id of a `headerActions` entry (same shape as on an index page), which says what the click does; every click also emits `header-action`. Under the board look the buildiq square sits between the secondary buttons and the primary one. Maps to CnReportsPage `headerButtons`.","items":{"type":"object","additionalProperties":false,"required":["action"],"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1,"description":"i18n key; optional for `add`."},"action":{"type":"string","minLength":1},"variant":{"type":"string","enum":["primary","secondary"],"default":"secondary"},"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)."},"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\": \"<i18n key>\" }` 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)."}}}},"search":{"type":"boolean","default":true,"description":"type='index': `false` draws no inline search in the actions bar, also in the board look, which otherwise draws one on every index page. Wins over `inlineSearch` (2.127.0)."},"searchRefusalHint":{"type":"string","description":"type='index': what to do after a refused search, through the app's translate; the default explains AND, OR, NOT and quotes (2.133.0)."},"searchPlacement":{"type":"string","enum":["","toolbar"],"default":"","description":"type='index', board look: where the search field sits. `toolbar` puts it on row 1 before the Filter button and the view switch instead of opening row 2 (DqTeamwachtrij, 2.127.0)."},"sortSelectOptions":{"type":"array","description":"type='index': the orders the index sidebar's Sort section offers in the list, card and map views, `{ value, label, order?, default?, source? }`. An option with a `source` takes its rows from an app endpoint, in that endpoint's order, for an order the server cannot sort by; the page's search, sidebar filters and pager run over those rows in the browser, and it is chosen by its value. `default: true` opens the page on it unless the address carries an order; a column header click in the table view leaves it. When the endpoint fails the list falls back to the option's `order` and shows `failedNote` (2.131.0).","items":{"type":"object","required":["value","label"],"properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"order":{"anyOf":[{"type":"string","minLength":1},{"type":"object"},{"type":"array"}]},"default":{"type":"boolean"},"source":{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","minLength":1,"description":"An app path, resolved with generateUrl and fetched with the session."},"params":{"type":"object","description":"Query parameters sent with the request."},"rowsPath":{"type":"string","minLength":1,"description":"Where the entries sit in the answer; `results` by default, a bare array as is."},"rowPath":{"type":"string","minLength":1,"description":"Where each entry carries its row; entries without one are left out."},"carry":{"type":"array","items":{"type":"string","minLength":1},"description":"Entry fields copied onto each row, such as a tier a card's head pill reads."},"idPath":{"type":"string","minLength":1,"description":"The row's identity for `annotate`; `id` by default."},"annotate":{"type":"boolean","description":"Copy the carried fields onto the rows of every other order too, matched on idPath."},"searchFields":{"type":"array","items":{"type":"string","minLength":1},"description":"The fields the search box matches; every top-level text by default."},"failedNote":{"type":"string","minLength":1,"description":"Shown above the list when the endpoint failed and the option's order stands in, through the app's translate."}}}}}},"defaultOrder":{"description":"type='index', self-fetch: the order the page opens on when neither the address nor sortKeys gives one: `\"-createdAt\"`, `\"priority:desc,title\"`, an `_order` map or a `[{ key, order }]` list (2.127.0).","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":{"type":"string","enum":["asc","desc","ASC","DESC"]}},{"type":"array","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","minLength":1},"order":{"type":"string","enum":["asc","desc"]}}}}]},"listConfig":{"type":"object","additionalProperties":true,"description":"type='index': the field mapping of the list view's rows (CnObjectRow): titleField, subtitleField, imageField, iconField, iconName, badgeField and the badge keys stay free-form. 2.127.0 types the row cells and the meta template (DqTeamwachtrij).","properties":{"subtitleTemplate":{"type":"string","minLength":1,"description":"The meta line: `{field}` tokens filled from the row; empty parts and their separators drop."},"leading":{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}},"description":"One cell before the title, drawn as a table column draws it (`{ key: \"createdAt\", widget: \"age\" }`)."},"trailing":{"description":"A cell, or a list of cells, after the badges, each drawn as a table column draws it.","anyOf":[{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}}},{"type":"array","items":{"type":"object","required":["key"],"additionalProperties":true,"properties":{"key":{"type":"string","minLength":1},"widget":{"type":"string","minLength":1},"widgetProps":{"type":"object","additionalProperties":true},"formatter":{"type":"string","minLength":1},"width":{"type":"string","minLength":1,"description":"CSS width, lining the column up across rows."}}}}]}}},"rowTitle":{"type":"string","enum":["link","plain"],"default":"link","description":"type='index' (screens-table-rows-parity): how the board look draws the table's title column. `link` (the default) is the 15px weight 600 underlined title of the DqZaken board; `plain` is the 15px bold title without an underline over a 13px muted secondary line, as the PqTickets and OcPublicaties boards draw it. The row stays clickable either way. The Nextcloud look ignores it. Maps to CnIndexPage `rowTitle`."},"showTypeEyebrow":{"type":"boolean","default":true,"description":"type='detail': `false` drops the type eyebrow (the type label above the record name) once the record resolves, for a header that says the type in a pill instead (zuiddrecht-pixel-gaps). Maps to CnDetailPage `showTypeEyebrow`."},"breadcrumb":{"description":"A breadcrumb line. type='detail' (zuiddrecht-pixel-gaps): one crumb naming the list the record belongs to (`route` + optional `params`, or `href`); the record's display name follows as the current crumb. Maps to CnDetailPage `breadcrumb`. type='index' | 'dashboard' | 'settings' (page-header-breadcrumb): one crumb or a list, drawn under the title in the same row style; the page title follows as the current crumb. Maps to the page's `breadcrumb`.","anyOf":[{"$ref":"#/$defs/breadcrumbCrumb"},{"type":"array","minItems":1,"items":{"$ref":"#/$defs/breadcrumbCrumb"}}]},"headerCard":{"type":"boolean","default":false,"description":"type='detail': draw the header (pills, title, actions) as a bordered card on the surface colour, as the board's case header (zuiddrecht-pixel-gaps-2). Maps to CnDetailPage `headerCard`."},"headerWidget":{"type":"string","minLength":1,"description":"type='detail': the id of a widget in `widgets` to render inside the header on its own row under the title, without a card of its own, e.g. the stages bars (zuiddrecht-pixel-gaps-2). The widget leaves the body grid. Maps to CnDetailPage `headerWidget`."},"visibilityContext":{"type":"object","description":"type='detail': records the page reads beside the loaded one, so a widget's or a tab's `visibleWhen` can ask about them (detail-widgets-visible-when). `{ <name>: { field, register, schema } }`: `field` is the property of the loaded record holding the other record's id, and a condition reads that record as `@context.<name>.<path>`, e.g. `{ \"field\": \"@context.caseType.category\", \"op\": \"in\", \"value\": [\"Sociaal domein\"] }`. A record that cannot be read is absent, so its condition fails safe. Maps to CnDetailPage `visibilityContext`.","additionalProperties":{"type":"object","required":["field","register","schema"],"additionalProperties":false,"properties":{"field":{"type":"string","minLength":1,"description":"Dot-path on the loaded record to the id of the record to read."},"register":{"type":"string","minLength":1,"description":"Register slug or id of the record to read."},"schema":{"type":"string","minLength":1,"description":"Schema slug or id of the record to read."}}}},"accessPurpose":{"type":"object","required":["when","url"],"additionalProperties":false,"description":"type='detail': ask why the reader opens the record before the page shows it, and record the answer (detail-access-purpose). `when` is a condition in the visibleWhen grammar on the record and its visibilityContext records; `options` the purposes offered (\"Other\" with a written purpose is always added); `url` the app endpoint that records the purpose (POST { purpose, note }, {objectId} filled in). Nothing of the record shows until the endpoint accepts; the same record is not asked again in the same browser session. Maps to CnDetailPage `accessPurpose`.","properties":{"when":{"type":"object","description":"The condition under which the page asks, e.g. { \"field\": \"@context.caseType.specialDataCategories\", \"op\": \"notEmpty\" }."},"options":{"type":"array","items":{"type":"object","required":["value","label"],"additionalProperties":false,"properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1,"description":"i18n key."}}}},"url":{"type":"string","minLength":1,"description":"App endpoint that records the purpose; {objectId} is filled in."},"title":{"type":"string","description":"i18n key of the dialog's title."},"description":{"type":"string","description":"i18n key of the sentence under the title."}}},"mapConfig":{"type":"object","additionalProperties":true,"description":"type='index': how rows are plotted when the Map view mode is available. Non-empty (and not excluded by an explicit viewModes) adds a Map segment to the view toggle. Maps to CnIndexPage `mapConfig`.","properties":{"latField":{"type":"string","description":"Object (or `@self`) property path holding the marker's latitude. Dotted paths are supported, e.g. `@self.geo.lat`."},"lngField":{"type":"string","description":"Object (or `@self`) property path holding the marker's longitude."},"geoField":{"type":"string","description":"Alternative single property holding a GeoJSON Point geometry (`{ type: 'Point', coordinates: [lng, lat] }`). Takes precedence over latField/lngField when present and resolvable."},"popupField":{"type":"string","description":"Object property rendered in the marker popup."},"center":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"Fallback centre `[lat, lng]` when the filtered set has no plottable rows."},"clustering":{"type":"boolean","default":true,"description":"Whether coincident markers are grouped into a cluster that spiderfies on click. ON by default here — the opposite of CnMapWidget's own default — because an index map plots whatever the filter returns, and several rows at one address is the normal case: unclustered they land on identical pixels, the topmost takes every click and the rest are unreachable with nothing on screen saying they exist. Non-point geometry skips the cluster group, so areas are unaffected. Set false for the old behaviour."},"markerColor":{"type":"object","required":["field"],"additionalProperties":false,"description":"Colour each marker by a field of its row. Without `source` the value is looked up in `colorMap`; with `source` the field is a reference and the referenced row's `colorField` is the colour, or the key `colorMap` maps to one. A value nothing maps to gets `default`, else the map's own colour.","properties":{"field":{"type":"string","description":"The row field (dotted path, also under `@self`)."},"colorMap":{"type":"object","additionalProperties":{"type":"string"},"description":"Value (or source colour key) to CSS colour; `var(--x, #fallback)` is resolved for the SVG marker."},"source":{"type":"object","required":["colorField"],"properties":{"register":{"type":"string"},"schema":{"type":"string"},"colorField":{"type":"string"},"filter":{"type":"object"}}},"default":{"type":"string"}}},"plotAll":{"oneOf":[{"type":"boolean"},{"type":"integer","minimum":1}],"description":"Plot every row that matches the filters instead of the current page: `true` for up to 500, or a number. When more rows match, a note under the map says the first N are shown."},"count":{"oneOf":[{"type":"boolean"},{"type":"string"}],"description":"A count line under the map: `true` for \"Showing {located} of {total} located\", or a template with `{located}` and `{total}` through the app's translate."}}},"quickFilterMaxVisible":{"type":"integer","minimum":0,"default":0,"description":"type='index', chips mode: how many quickFilters render as pills before the rest move behind one more chip — a `⋯` pill that opens a panel of the hidden lenses, and that wears the label and the active fill of whichever one is on. 0 (the default) renders every lens, which wraps the actions bar onto a second line once a page declares more than a handful. The visible set is the FIRST n entries of quickFilters, so reorder that array to change which lenses stay out. Maps to CnIndexPage `quickFilterMaxVisible`."},"actionToggles":{"type":"object","description":"Built-in action toggles for type='index' pages. Each key is sugar for the matching CnIndexPage prop — manifest authors can hide built-in mass actions / row actions / the Add button without writing a wrapper. Mirrors the CnIndexPage prop names. Explicit config.<key> wins over config.actionToggles.<key> (matches the readOnly precedence rule). Distinct from config.actions[] which is the typed row-actions array.","additionalProperties":true,"properties":{"showAdd":{"type":"boolean","description":"Whether to show the Add button in the actions bar. Maps to CnIndexPage `showAdd`."},"showFormDialog":{"type":"boolean","description":"Whether the built-in create / edit form dialog is enabled. Maps to CnIndexPage `showFormDialog`."},"showViewAction":{"type":"boolean","description":"Whether to add a View action to row actions. Maps to CnIndexPage `showViewAction`."},"showEditAction":{"type":"boolean","description":"Whether to add an Edit action to row actions. Maps to CnIndexPage `showEditAction`."},"showCopyAction":{"type":"boolean","description":"Whether to add a Copy action to row actions. Maps to CnIndexPage `showCopyAction`."},"showDeleteAction":{"type":"boolean","description":"Whether to add a Delete action to row actions. Maps to CnIndexPage `showDeleteAction`."},"showMassImport":{"type":"boolean","description":"Whether to show the built-in mass Import action. Maps to CnIndexPage `showMassImport`."},"showMassExport":{"type":"boolean","description":"Whether to show the built-in mass Export action. Maps to CnIndexPage `showMassExport`."},"showMassCopy":{"type":"boolean","description":"Whether to show the built-in mass Copy button. Maps to CnIndexPage `showMassCopy`."},"showMassDelete":{"type":"boolean","description":"Whether to show the built-in mass Delete button. Maps to CnIndexPage `showMassDelete`."},"showViewToggle":{"type":"boolean","description":"Whether to show the table/cards view-mode toggle. Maps to CnIndexPage `showViewToggle`."},"selectable":{"type":"boolean","description":"Whether rows are selectable (driving the mass-action surface). Maps to CnIndexPage `selectable`."}}},"mode":{"type":"string","enum":["edit","create","public"],"description":"Authoring / access mode. type='form': 'edit' loads from `data`, 'create' starts blank, 'public' shows the public-form success banner and skips auth-state requirements. type='detail': 'public' marks the route as unauthenticated (token-scoped reader pages — pair with a route-param token via the @route.<param> sentinel). Maps to CnFormPage / CnDetailPage `mode` prop."},"subscribe":{"type":"boolean","default":true,"description":"Collaborative-editing default for type='detail'. When true (the default) CnDetailPage calls `objectStore.subscribe(objectType, objectId)` on mount via `useObjectSubscription` and unsubscribes on unmount. Set to false for read-only or archive views. Mirrors the `subscribe` Boolean prop on CnDetailPage / CnObjectSidebar. See the `collaborative-editing-defaults` capability."},"lock":{"type":"boolean","default":true,"description":"Collaborative-editing default for type='detail'. When true (the default) the page wires `useObjectLock` so the locked-by-other banner renders when a remote lock is active. Explicit lock acquire/release on the edit toggle is the v2 follow-up; at v1 this flag controls whether read-side `lockState` is wired. Mirrors the `lock` Boolean prop on CnDetailPage / CnObjectSidebar."}}},"sidebar":{"type":"object","description":"Per-page top-level sidebar visibility gate. Sibling of config, applies to every page type. show: boolean gates CnPageRenderer's #sidebar slot.","additionalProperties":true,"properties":{"show":{"type":"boolean","description":"Whether the host App's #sidebar slot renders for this page. Defaults to true."}}},"_note":{"type":"string","description":"Human-readable note. REQUIRED when type is 'custom' — must document why a standard page type was not feasible."}},"allOf":[{"description":"map-image-layer: on a map page an `image` layer is a picture in flat pixel coordinates and needs a url and its width and height in pixels; markers on such a map read xField and yField.","if":{"properties":{"type":{"const":"map"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"layers":{"items":{"if":{"properties":{"type":{"const":"image"}},"required":["type"]},"then":{"required":["url","width","height"],"properties":{"url":{"type":"string","minLength":1},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}}}},"markers":{"properties":{"xField":{"type":"string","minLength":1,"description":"Image maps: the row property holding x, in pixels from the picture's left. Default x."},"yField":{"type":"string","minLength":1,"description":"Image maps: the row property holding y, in pixels from the picture's top. Default y."}}}}}}}},{"description":"screens-board-reference-columns (2.97.0): config.board on an index page, the block CnPageRenderer hands to CnIndexPage's `board` prop, is typed by boardView like pages[].board.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"board":{"$ref":"#/$defs/boardView"}}}}}},{"description":"index-calendar-view-mode: config.calendar on an index page is typed by calendarView.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"calendar":{"$ref":"#/$defs/calendarView"}}}}}},{"description":"index-copy-with-relations: config.copy on an index page is typed by copySettings.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"copy":{"$ref":"#/$defs/copySettings"}}}}}},{"description":"index-column-order-and-pinning: config.personalColumns on an index page lets people order and pin columns and keeps the layout per user; false keeps show and hide only.","if":{"properties":{"type":{"const":"index"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"personalColumns":{"type":"boolean","default":true}}}}}},{"description":"form-live-values: config.fields[] on a form page may carry assign, a token default and calculate.","if":{"properties":{"type":{"const":"form"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"fields":{"type":"array","items":{"$ref":"#/$defs/formFieldLiveValues"}}}}}}},{"description":"form-smart-paste: config.smartPaste on a form page is typed by formSmartPaste.","if":{"properties":{"type":{"const":"form"}},"required":["type"]},"then":{"properties":{"config":{"properties":{"smartPaste":{"$ref":"#/$defs/formSmartPaste"}}}}}},{"description":"splitView, manualOrder, savedViewTree, board and dateAxis are index-page mechanics. splitView emits a second route beside an index route and mounts the detail pane inside CnIndexPage; manualOrder holds a per-user row order for a list. Declared on a detail, dashboard or custom page they would validate and then do nothing, which is the failure this branch refuses out loud (case-page-and-list-as-a-place).","if":{"not":{"properties":{"type":{"const":"index"}},"required":["type"]}},"then":{"not":{"anyOf":[{"required":["splitView"]},{"required":["manualOrder"]},{"required":["savedViewTree"]},{"required":["board"]},{"required":["dateAxis"]}]}}},{"description":"The \"builtin:<id>\" placeholders in config.actions place CnIndexPage's built-in row actions, so only a type='index' page may use them. On every other page type config.actions holds objects only; a placeholder there would validate and then render nothing (row-action-builtin-placement).","if":{"not":{"properties":{"type":{"const":"index"}},"required":["type"]}},"then":{"properties":{"config":{"properties":{"actions":{"items":{"type":"object"}}}}}}},{"description":"tabInAddress puts the active tab of a record in the address. Only a detail page has a record and a tab strip, so the key is refused elsewhere rather than validating into a no-op (case-page-and-list-as-a-place).","if":{"not":{"properties":{"type":{"const":"detail"}},"required":["type"]}},"then":{"not":{"required":["tabInAddress"]}}},{"description":"type='wiki' (CnWikiPage) renders one manifest-declared markdown article. It MUST declare both config.register and config.schema as non-empty strings so the manifest stays the source of truth for which OpenRegister register/schema the article body is read from (manifest-wiki-page-type).","if":{"properties":{"type":{"const":"wiki"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","required":["register","schema"],"properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1}}}},"required":["config"]}},{"description":"type='links' (CnLinkCardsPage) renders config.cards as link cards grouped by config.categories. It MUST declare a non-empty cards list, and every card is a $defs/linkCard, so a card that can go nowhere is refused here and not dropped in silence at render time (link-cards-page).","if":{"properties":{"type":{"const":"links"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","required":["cards"],"properties":{"description":{"type":"string"},"emptyLabel":{"type":"string"},"categories":{"type":"object","additionalProperties":{"type":"string","minLength":1}},"cards":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/linkCard"}},"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)."}}}},"required":["config"]}},{"description":"type='custom' requires _note documenting why decomposition into a standard page type was not feasible. Relaxed by ConductionNL/nextcloud-vue#315: when `component` matches the lib `Cn[A-Z]\\w+` pattern, the component name already documents the choice and _note is optional. When `component` is absent or non-Cn (host-app SFC), _note remains required.","if":{"properties":{"type":{"const":"custom"}},"required":["type"]},"then":{"if":{"properties":{"component":{"type":"string","pattern":"^Cn[A-Z]\\w+$"}},"required":["component"]},"then":{"description":"Lib-SFC custom page: component is self-documenting, _note is optional."},"else":{"description":"Host-app SFC custom page (or no component): _note is required to document why a standard page type was not feasible.","required":["_note"]}}}]};const schema59 = {"type":"object","additionalProperties":false,"description":"index-copy-with-relations: what a copy on this index page may take along. `include` lists the link kinds: relationRows (the source's relation rows are re-created on the copy), incoming (the copy is added next to the source in every array-valued reference that points at it; a single-valued reference is never moved) and files (the attached files). The copy dialogs list the included kinds, ticked, and the copy is one request to OpenRegister's copy endpoint. Without `include` a copy carries the fields only.","properties":{"include":{"type":"array","uniqueItems":true,"items":{"enum":["relationRows","incoming","files"]}}}};const schema70 = {"type":"object","additionalProperties":false,"description":"Saved views render as a tree rather than a flat list, carry labels, and may ship with the app. Two hundred personal views in one dropdown is the problem this solves. Index pages only, and only meaningful alongside allowSavedViews: without the dropdown there is no view to put in a tree. Absent, the page renders the flat dropdown it renders today. The view entity itself, its parent, its labels and its slug are OpenRegister's (saved-search-views); this key declares how this page presents them.","properties":{"enabled":{"type":"boolean","default":false,"description":"Whether this page renders its saved views as a tree. False reads as a page that considered it and declined, and renders exactly as a page that never named the key."},"maxDepth":{"type":"integer","minimum":1,"maximum":5,"default":3,"description":"How deep a parent chain may go. A bound rather than a taste: inheritance resolves by walking the chain, so an unbounded depth turns one render into an unbounded walk, and a person who can nest for ever will. Past the bound a save is refused naming the view, which is louder than a tree nobody can read."},"templates":{"type":"array","description":"What a new view may start from. A template presets the columns, the sort and the export field set, and the person may change each. Declare none and Save current view means what it has always meant: the list as it stands.","items":{"type":"object","additionalProperties":false,"required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"name":{"type":"string"},"columns":{"type":"array","items":{"type":"string"}},"defaultSort":{"type":"string"},"exportFields":{"type":"array","items":{"type":"string"}}}}},"landingView":{"type":"object","additionalProperties":false,"description":"Which view a role opens this page on, as { role: slug }. A personal choice always wins and Reset returns here, so this is where somebody starts rather than where they are kept. A role's landing view changing mid-session applies on the next arrival: moving a reader's list out from under them is worse than a stale default.","patternProperties":{"^[A-Za-z0-9_.-]+$":{"type":"string"}}},"columnsPerRole":{"type":"object","additionalProperties":false,"description":"Which columns a role sees, as { role: [column] }. NARROWS index-columns-per-scope rather than replacing it: a role naming a column the scope does not offer gets nothing extra, because a per-role list that could add one would be a second, quieter way to put a field on screen the scope deliberately left off.","patternProperties":{"^[A-Za-z0-9_.-]+$":{"type":"array","items":{"type":"string"}}}},"groupBy":{"type":"string","description":"A field the list groups on, with a count per group. The counts are of the rows the list HOLDS, so on a paged list they are of the page and the surface says so; a second fetch per group is the cost this grouping exists to avoid."},"seeded":{"type":"array","deprecated":true,"description":"DEPRECATED since 2.129.0: declare default views with the index page's `config.defaultSavedViews`, which carries the criteria this key never had and is what the page actually lists. Nothing ever read this key, so moving off it loses nothing. Views that ship with the app. They render as their own group above the user's own, and a user may copy one but not delete it: a view the product promises is not a view one person can remove from everybody's install.","items":{"type":"object","additionalProperties":false,"required":["slug","name"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The stable name a dashboard widget, an export action or an API caller cites this view by. Lowercase and dash only, because it appears in an address and in a manifest somebody else writes. Required: a seeded view with no slug is a view nothing can call, which is most of the reason to seed it."},"name":{"type":"string","description":"What the view is called in the control. Translatable."},"label":{"type":"string","description":"One label this view carries, for the label filter. A view may carry none and stays reachable when no label filter is active."},"parent":{"type":"string","description":"The slug of the view this one hangs under. Omitted, it sits at the root of the seeded group."},"actions":{"type":"array","description":"The action ids this view offers, so a triage view offers three and not thirty. INTERSECTED with what the reader may run and never added to: a view declaring an action the reader has no right to does not grant it. Declare none and the view offers the page's actions, as today.","items":{"type":"string"},"uniqueItems":true},"inherits":{"type":"array","description":"Which parts this view takes from its parent. Each part resolves independently, so a child may narrow the criteria and keep the parent's columns. Declared on a view with no parent it is a contradiction and the validator refuses it: there is nothing to inherit from.","items":{"type":"string","enum":["criteria","columns","sorting","defaultSort","exportFields"]},"uniqueItems":true}},"allOf":[{"description":"inherits without a parent is a declaration that reads as configured and resolves to nothing. Refused here rather than silently ignored at render, which is the shape this whole change exists to stop.","if":{"required":["inherits"]},"then":{"required":["parent"]}}]}}}};const schema92 = {"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."}}};const schema112 = {"type":"object","additionalProperties":false,"anyOf":[{"required":["field"]},{"required":["text"]},{"required":["countFrom"]}],"description":"A pill in a detail page header, rendered through CnStatusBadge: from one field of the record (`field`; nothing renders while it is empty), a constant (`text`), or a count of a related list (`countFrom` with `countLabel`; nothing renders at 0, while counting or after a failed count). screens-relation-pages, 2.85.0.","properties":{"field":{"type":"string","minLength":1,"description":"Dot-path to the value on the record."},"values":{"type":"array","items":{"type":"string"},"description":"With `field`: draw the pill only when the raw value is one of these (a priority pill for high and urgent only)."},"text":{"type":"string","minLength":1,"description":"A constant label (an i18n key), such as \"Inwoner\" or \"Klant\". Used when `field` is not set."},"countFrom":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"The list the pill counts with one list request (`_limit=1`, the response's `total`). `filter` values take the `@objectId` and `@object.<field>` tokens. Used when `field` is not set.","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"filter":{"type":"object","additionalProperties":true}}},"resolve":{"type":"object","additionalProperties":false,"required":["register","schema"],"description":"detail-pill-resolve: the field holds the id of a record of this register and schema; the pill shows that record's `labelField` (else its title or name) and keys `colorMap` by it. The raw value shows while the lookup runs and when it finds nothing.","properties":{"register":{"type":"string","minLength":1},"schema":{"type":"string","minLength":1},"labelField":{"type":"string","minLength":1},"colorField":{"type":"string","minLength":1,"description":"pill-color-field: the referenced record's property holding a colour name (an NL Design System hue such as `red` or `blue-light`, or a badge role); the pill takes the tone it maps onto."},"iconField":{"type":"string","minLength":1,"description":"pill-color-field: the referenced record's property holding a registered icon name, shown before the label."}}},"countLabel":{"type":"object","additionalProperties":false,"description":"The count pill's label for one and for more, i18n keys with `{count}`: `{ \"one\": \"{count} lopende zaak\", \"other\": \"{count} lopende zaken\" }`.","properties":{"one":{"type":"string","minLength":1},"other":{"type":"string","minLength":1}}},"colorMap":{"type":"object","description":"Raw value to badge variant. Keys are matched ignoring case.","additionalProperties":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"]}},"labels":{"type":"object","description":"Raw value to the i18n key / text to show. A value without an entry is shown as is.","additionalProperties":{"type":"string"}},"variant":{"type":"string","enum":["default","primary","success","warning","error","info","purple","teal"],"default":"default","description":"Badge variant for a value `colorMap` does not name."}}};const schema114 = {"type":"object","additionalProperties":false,"required":["field","text"],"description":"One declared state indicator on an index page's rows. Renders as an icon with a text alternative and a tooltip, never as colour alone.","properties":{"id":{"type":"string","minLength":1,"description":"Stable id for this indicator, used as its test id and its render key. Defaults to `field`."},"field":{"type":"string","minLength":1,"description":"Dotted path on the row the condition reads."},"equals":{"description":"The indicator applies when the field equals this value exactly."},"in":{"type":"array","description":"The indicator applies when the field's value is one of these."},"after":{"type":"string","minLength":1,"description":"The indicator applies while the field holds a readable date after this moment: `now` or an ISO date (\"held until a later date\")."},"before":{"type":"string","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."},"text":{"type":"string","minLength":1,"description":"The text alternative, read out beside the icon. Required: an indicator without one does not render."},"tooltip":{"type":"string","description":"Hover text. Defaults to `text`."}}};const func0 = (function equal(a, b) {
24
24
  if (a === b) return true;
25
25
 
26
26
  if (a && b && typeof a == 'object' && typeof b == 'object') {